diff options
243 files changed, 2931 insertions, 2176 deletions
diff --git a/Android.mk b/Android.mk index 9d113d954..78ea02a26 100644 --- a/Android.mk +++ b/Android.mk @@ -23,12 +23,7 @@ include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true LOCAL_AAPT2_ONLY := true LOCAL_MODULE_TAGS := optional - -ifneq (,$(wildcard frameworks/base)) - LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib -else - LOCAL_STATIC_JAVA_LIBRARIES:= libPluginCore -endif +LOCAL_STATIC_JAVA_LIBRARIES:= PluginCoreLib LOCAL_SRC_FILES := \ $(call all-java-files-under, src_plugins) @@ -151,11 +146,10 @@ LOCAL_USE_AAPT2 := true LOCAL_AAPT2_ONLY := true LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano ifneq (,$(wildcard frameworks/base)) - LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano LOCAL_PRIVATE_PLATFORM_APIS := true else - LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos LOCAL_SDK_VERSION := system_current LOCAL_MIN_SDK_VERSION := 26 endif @@ -224,11 +218,10 @@ include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano ifneq (,$(wildcard frameworks/base)) - LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano LOCAL_PRIVATE_PLATFORM_APIS := true else - LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos LOCAL_SDK_VERSION := system_current LOCAL_MIN_SDK_VERSION := 26 endif @@ -271,11 +264,10 @@ include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true LOCAL_MODULE_TAGS := optional +LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano ifneq (,$(wildcard frameworks/base)) - LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLib launcherprotosnano LOCAL_PRIVATE_PLATFORM_APIS := true else - LOCAL_STATIC_JAVA_LIBRARIES := libSharedSystemUI libLauncherProtos LOCAL_SDK_VERSION := system_current LOCAL_MIN_SDK_VERSION := 26 endif diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg new file mode 100644 index 000000000..f3db20e3b --- /dev/null +++ b/PREUPLOAD.cfg @@ -0,0 +1,2 @@ +[Hook Scripts] +checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} diff --git a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 0fa3d866f..216972cba 100644 --- a/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/go/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -15,8 +15,8 @@ */ package com.android.quickstep; -import static com.android.systemui.shared.system.ActivityManagerWrapper - .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.annotation.TargetApi; import android.content.Context; @@ -25,7 +25,6 @@ import android.os.SystemClock; import android.view.ViewConfiguration; import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; @@ -43,7 +42,6 @@ public class OverviewCommandHelper { private final Context mContext; private final ActivityManagerWrapper mAM; private final RecentsModel mRecentsModel; - private final MainThreadExecutor mMainThreadExecutor; private final OverviewComponentObserver mOverviewComponentObserver; private long mLastToggleTime; @@ -51,7 +49,6 @@ public class OverviewCommandHelper { public OverviewCommandHelper(Context context, OverviewComponentObserver observer) { mContext = context; mAM = ActivityManagerWrapper.getInstance(); - mMainThreadExecutor = new MainThreadExecutor(); mRecentsModel = RecentsModel.INSTANCE.get(mContext); mOverviewComponentObserver = observer; } @@ -63,19 +60,19 @@ public class OverviewCommandHelper { } mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); - mMainThreadExecutor.execute(new RecentsActivityCommand<>()); + MAIN_EXECUTOR.execute(new RecentsActivityCommand<>()); } public void onOverviewShown(boolean triggeredFromAltTab) { - mMainThreadExecutor.execute(new ShowRecentsCommand()); + MAIN_EXECUTOR.execute(new ShowRecentsCommand()); } public void onOverviewHidden() { - mMainThreadExecutor.execute(new HideRecentsCommand()); + MAIN_EXECUTOR.execute(new HideRecentsCommand()); } public void onTip(int actionType, int viewType) { - mMainThreadExecutor.execute(() -> + MAIN_EXECUTOR.execute(() -> UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType)); } @@ -161,7 +158,7 @@ public class OverviewCommandHelper { // Otherwise, start overview. mListener = mHelper.createActivityInitListener(provider::onActivityReady); mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(), - provider, mContext, mMainThreadExecutor.getHandler(), + provider, mContext, MAIN_EXECUTOR.getHandler(), provider.getRecentsLaunchDuration()); } diff --git a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java index 577b17566..19dd82f7f 100644 --- a/go/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/go/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -34,8 +34,6 @@ import android.view.MotionEvent; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.util.LooperExecutor; -import com.android.launcher3.util.UiThreadHelper; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; @@ -139,9 +137,6 @@ public class TouchInteractionService extends Service { return sConnected; } - public static final LooperExecutor BACKGROUND_EXECUTOR = - new LooperExecutor(UiThreadHelper.getBackgroundLooper()); - private RecentsModel mRecentsModel; private OverviewComponentObserver mOverviewComponentObserver; private OverviewCommandHelper mOverviewCommandHelper; diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java index b82f36249..26c331318 100644 --- a/go/src/com/android/launcher3/model/LoaderResults.java +++ b/go/src/com/android/launcher3/model/LoaderResults.java @@ -16,9 +16,8 @@ package com.android.launcher3.model; -import com.android.launcher3.AllAppsList; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.model.BgDataModel.Callbacks; import java.lang.ref.WeakReference; diff --git a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java index ee113dfeb..42b119429 100644 --- a/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java +++ b/go/src/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -27,34 +27,23 @@ import android.os.UserHandle; import com.android.launcher3.ItemInfo; import com.android.launcher3.notification.NotificationKeyData; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; /** * Performs operations related to deep shortcuts, such as querying for them, pinning them, etc. */ public class DeepShortcutManager { - private static DeepShortcutManager sInstance; - private static final Object sInstanceLock = new Object(); - public static DeepShortcutManager getInstance(Context context) { - synchronized (sInstanceLock) { - if (sInstance == null) { - sInstance = new DeepShortcutManager(context.getApplicationContext()); - } - return sInstance; - } - } + private static final DeepShortcutManager sInstance = new DeepShortcutManager(); - private DeepShortcutManager(Context context) { + public static DeepShortcutManager getInstance(Context context) { + return sInstance; } - public boolean wasLastCallSuccess() { - return false; - } + private final QueryResult mFailure = new QueryResult(); - public void onShortcutsChanged(List<ShortcutInfo> shortcuts) { - } + private DeepShortcutManager() { } /** * Queries for the shortcuts with the package name and provided ids. @@ -62,18 +51,18 @@ public class DeepShortcutManager { * This method is intended to get the full details for shortcuts when they are added or updated, * because we only get "key" fields in onShortcutsChanged(). */ - public List<ShortcutInfo> queryForFullDetails(String packageName, + public QueryResult queryForFullDetails(String packageName, List<String> shortcutIds, UserHandle user) { - return Collections.emptyList(); + return mFailure; } /** * Gets all the manifest and dynamic shortcuts associated with the given package and user, * to be displayed in the shortcuts container on long press. */ - public List<ShortcutInfo> queryForShortcutsContainer(ComponentName activity, + public QueryResult queryForShortcutsContainer(ComponentName activity, UserHandle user) { - return Collections.emptyList(); + return mFailure; } /** @@ -103,20 +92,28 @@ public class DeepShortcutManager { * * If packageName is null, returns all pinned shortcuts regardless of package. */ - public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, UserHandle user) { - return Collections.emptyList(); + public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) { + return mFailure; } - public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, + public QueryResult queryForPinnedShortcuts(String packageName, List<String> shortcutIds, UserHandle user) { - return Collections.emptyList(); + return mFailure; } - public List<ShortcutInfo> queryForAllShortcuts(UserHandle user) { - return Collections.emptyList(); + public QueryResult queryForAllShortcuts(UserHandle user) { + return mFailure; } public boolean hasHostPermission() { return false; } + + + public static class QueryResult extends ArrayList<ShortcutInfo> { + + public boolean wasSuccess() { + return true; + } + } } diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java index 36d1c3ecc..a886c0aa0 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java @@ -267,9 +267,13 @@ public abstract class BaseIconCache { entry = new CacheEntry(); cachingLogic.loadIcon(mContext, object, entry); } + // Icon can't be loaded from cachingLogic, which implies alternative icon was loaded + // (e.g. fallback icon, default icon). So we drop here since there's no point in caching + // an empty entry. + if (entry.icon == null) return; entry.title = cachingLogic.getLabel(object); entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user); - mCache.put(key, entry); + if (cachingLogic.addToMemCache()) mCache.put(key, entry); ContentValues values = newContentValues(entry, entry.title.toString(), componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList)); @@ -308,20 +312,12 @@ public abstract class BaseIconCache { @NonNull ComponentName componentName, @NonNull UserHandle user, @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic, boolean usePackageIcon, boolean useLowResIcon) { - return cacheLocked(componentName, user, infoProvider, cachingLogic, usePackageIcon, - useLowResIcon, true); - } - - protected <T> CacheEntry cacheLocked( - @NonNull ComponentName componentName, @NonNull UserHandle user, - @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic, - boolean usePackageIcon, boolean useLowResIcon, boolean addToMemCache) { assertWorkerThread(); ComponentKey cacheKey = new ComponentKey(componentName, user); CacheEntry entry = mCache.get(cacheKey); if (entry == null || (entry.isLowRes() && !useLowResIcon)) { entry = new CacheEntry(); - if (addToMemCache) { + if (cachingLogic.addToMemCache()) { mCache.put(cacheKey, entry); } diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java index 09f59b84c..3aa783a14 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java @@ -17,6 +17,7 @@ package com.android.launcher3.icons.cache; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageInfo; import android.os.LocaleList; import android.os.UserHandle; @@ -41,4 +42,18 @@ public interface CachingLogic<T> { default String getKeywords(T object, LocaleList localeList) { return null; } + + /** + * Returns the timestamp the entry was last updated in cache. + */ + default long getLastUpdatedTime(T object, PackageInfo info) { + return info.lastUpdateTime; + } + + /** + * Returns true the object should be added to mem cache; otherwise returns false. + */ + default boolean addToMemCache() { + return true; + } } diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java index 3c71bd027..bcdbce5e2 100644 --- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java +++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java @@ -171,8 +171,9 @@ public class IconCacheUpdateHandler { long updateTime = c.getLong(indexLastUpdate); int version = c.getInt(indexVersion); T app = componentMap.remove(component); - if (version == info.versionCode && updateTime == info.lastUpdateTime && - TextUtils.equals(c.getString(systemStateIndex), + if (version == info.versionCode + && updateTime == cachingLogic.getLastUpdatedTime(app, info) + && TextUtils.equals(c.getString(systemStateIndex), mIconCache.getIconSystemState(info.packageName))) { if (mFilterMode == MODE_CLEAR_VALID_ITEMS) { @@ -231,7 +232,6 @@ public class IconCacheUpdateHandler { } } - /** * A runnable that updates invalid icons and adds missing icons in the DB for the provided * LauncherActivityInfo list. Items are updated/added one at a time, so that the diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto index 49fd43617..055ade58e 100644 --- a/protos/launcher_log.proto +++ b/protos/launcher_log.proto @@ -118,6 +118,7 @@ enum ControlType { APP_USAGE_SETTINGS = 18; BACK_GESTURE = 19; UNDO = 20; + DISMISS_PREDICTION = 21; } enum TipType { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java index 4ecc39cf6..65e69b604 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java @@ -17,6 +17,8 @@ package com.android.launcher3.appprediction; import static android.content.pm.PackageManager.MATCH_INSTANT; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -30,8 +32,12 @@ import android.os.Message; import android.util.ArrayMap; import android.util.Log; +import androidx.annotation.MainThread; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; + import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; @@ -45,11 +51,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import androidx.annotation.MainThread; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; - /** * Utility class which loads and caches predicted items like instant apps and shortcuts, before * they can be displayed on the UI @@ -77,7 +78,7 @@ public class DynamicItemCache { public DynamicItemCache(Context context, Runnable onUpdateCallback) { mContext = context; - mWorker = new Handler(LauncherModel.getWorkerLooper(), this::handleWorkerMessage); + mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage); mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage); mInstantAppResolver = InstantAppResolver.newInstance(context); mOnUpdateCallback = onUpdateCallback; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java index 24fc61bef..4c7943b10 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +16,7 @@ package com.android.launcher3.appprediction; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.annotation.TargetApi; import android.app.prediction.AppPredictionContext; @@ -34,19 +35,25 @@ import android.os.UserHandle; import android.util.Log; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; + import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.appprediction.PredictionUiStateManager.Client; import com.android.launcher3.model.AppLaunchTracker; -import com.android.launcher3.util.UiThreadHelper; +import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; +import com.android.systemui.plugins.AppLaunchEventsPlugin; +import com.android.systemui.plugins.PluginListener; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; +import java.util.ArrayList; +import java.util.List; /** * Subclass of app tracker which publishes the data to the prediction engine and gets back results. */ @TargetApi(Build.VERSION_CODES.Q) -public class PredictionAppTracker extends AppLaunchTracker { +public class PredictionAppTracker extends AppLaunchTracker + implements PluginListener<AppLaunchEventsPlugin> { private static final String TAG = "PredictionAppTracker"; private static final boolean DBG = false; @@ -58,6 +65,7 @@ public class PredictionAppTracker extends AppLaunchTracker { protected final Context mContext; private final Handler mMessageHandler; + private final List<AppLaunchEventsPlugin> mAppLaunchEventsPluginsList; // Accessed only on worker thread private AppPredictor mHomeAppPredictor; @@ -65,10 +73,14 @@ public class PredictionAppTracker extends AppLaunchTracker { public PredictionAppTracker(Context context) { mContext = context; - mMessageHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleMessage); + mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage); InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged); mMessageHandler.sendEmptyMessage(MSG_INIT); + + mAppLaunchEventsPluginsList = new ArrayList<>(); + PluginManagerWrapper.INSTANCE.get(context) + .addPluginListener(this, AppLaunchEventsPlugin.class, true); } @UiThread @@ -96,7 +108,7 @@ public class PredictionAppTracker extends AppLaunchTracker { AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class); if (apm == null) { - return null; + return null; } AppPredictor predictor = apm.createAppPredictionSession( @@ -116,7 +128,7 @@ public class PredictionAppTracker extends AppLaunchTracker { */ @WorkerThread @Nullable - public Bundle getAppPredictionContextExtras(Client client){ + public Bundle getAppPredictionContextExtras(Client client) { return null; } @@ -128,7 +140,7 @@ public class PredictionAppTracker extends AppLaunchTracker { destroy(); // Initialize the clients - int count = InvariantDeviceProfile.INSTANCE.get(mContext).numColumns; + int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns; mHomeAppPredictor = createPredictor(Client.HOME, count); mRecentsOverviewPredictor = createPredictor(Client.OVERVIEW, count); return true; @@ -167,37 +179,98 @@ public class PredictionAppTracker extends AppLaunchTracker { if (DBG) { Log.d(TAG, String.format("Sent immediate message to update %s", client)); } + + // Relay onReturnedToHome to every plugin. + mAppLaunchEventsPluginsList.forEach(AppLaunchEventsPlugin::onReturnedToHome); } @Override @UiThread public void onStartShortcut(String packageName, String shortcutId, UserHandle user, - String container) { + String container) { // TODO: Use the full shortcut info - AppTarget target = new AppTarget - .Builder(new AppTargetId("shortcut:" + shortcutId), packageName, user) - .setClassName(shortcutId) - .build(); + AppTarget target = new AppTarget.Builder( + new AppTargetId("shortcut:" + shortcutId), packageName, user) + .setClassName(shortcutId) + .build(); + sendLaunch(target, container); + + // Relay onStartShortcut info to every connected plugin. + mAppLaunchEventsPluginsList + .forEach(plugin -> plugin.onStartShortcut( + packageName, + shortcutId, + user, + container != null ? container : CONTAINER_DEFAULT) + ); + } @Override @UiThread public void onStartApp(ComponentName cn, UserHandle user, String container) { if (cn != null) { - AppTarget target = new AppTarget - .Builder(new AppTargetId("app:" + cn), cn.getPackageName(), user) - .setClassName(cn.getClassName()) - .build(); + AppTarget target = new AppTarget.Builder( + new AppTargetId("app:" + cn), cn.getPackageName(), user) + .setClassName(cn.getClassName()) + .build(); sendLaunch(target, container); + + // Relay onStartApp to every connected plugin. + mAppLaunchEventsPluginsList + .forEach(plugin -> plugin.onStartApp( + cn, + user, + container != null ? container : CONTAINER_DEFAULT) + ); } } + @Override @UiThread - private void sendLaunch(AppTarget target, String container) { - AppTargetEvent event = new AppTargetEvent.Builder(target, AppTargetEvent.ACTION_LAUNCH) + public void onDismissApp(ComponentName cn, UserHandle user, String container) { + if (cn == null) return; + AppTarget target = new AppTarget.Builder( + new AppTargetId("app: " + cn), cn.getPackageName(), user) + .setClassName(cn.getClassName()) + .build(); + sendDismiss(target, container); + + // Relay onDismissApp to every connected plugin. + mAppLaunchEventsPluginsList + .forEach(plugin -> plugin.onDismissApp( + cn, + user, + container != null ? container : CONTAINER_DEFAULT) + ); + } + + @UiThread + private void sendEvent(AppTarget target, String container, int eventId) { + AppTargetEvent event = new AppTargetEvent.Builder(target, eventId) .setLaunchLocation(container == null ? CONTAINER_DEFAULT : container) .build(); Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget(); } + + @UiThread + private void sendLaunch(AppTarget target, String container) { + sendEvent(target, container, AppTargetEvent.ACTION_LAUNCH); + } + + @UiThread + private void sendDismiss(AppTarget target, String container) { + sendEvent(target, container, AppTargetEvent.ACTION_DISMISS); + } + + @Override + public void onPluginConnected(AppLaunchEventsPlugin appLaunchEventsPlugin, Context context) { + mAppLaunchEventsPluginsList.add(appLaunchEventsPlugin); + } + + @Override + public void onPluginDisconnected(AppLaunchEventsPlugin appLaunchEventsPlugin) { + mAppLaunchEventsPluginsList.remove(appLaunchEventsPlugin); + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index 0c7ba9c95..23db5df2e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -43,6 +43,7 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.allapps.AllAppsStore; @@ -92,7 +93,7 @@ public class PredictionRowView extends LinearLayout implements private final Launcher mLauncher; private final PredictionUiStateManager mPredictionUiStateManager; - private final int mNumPredictedAppsPerRow; + private int mNumPredictedAppsPerRow; // The set of predicted app component names private final List<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>(); @@ -128,7 +129,7 @@ public class PredictionRowView extends LinearLayout implements mFocusHelper = new SimpleFocusIndicatorHelper(this); - mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numColumns; + mNumPredictedAppsPerRow = LauncherAppState.getIDP(context).numAllAppsColumns; mLauncher = Launcher.getLauncher(context); mLauncher.addOnDeviceProfileChangeListener(this); @@ -226,6 +227,7 @@ public class PredictionRowView extends LinearLayout implements @Override public void onDeviceProfileChanged(DeviceProfile dp) { + mNumPredictedAppsPerRow = dp.inv.numAllAppsColumns; removeAllViews(); applyPredictionApps(); } @@ -281,7 +283,7 @@ public class PredictionRowView extends LinearLayout implements } private List<ItemInfoWithIcon> processPredictedAppComponents(List<ComponentKeyMapper> components) { - if (getAppsStore().getApps().isEmpty()) { + if (getAppsStore().getApps().length == 0) { // Apps have not been bound yet. return Collections.emptyList(); } @@ -290,7 +292,9 @@ public class PredictionRowView extends LinearLayout implements for (ComponentKeyMapper mapper : components) { ItemInfoWithIcon info = mapper.getApp(getAppsStore()); if (info != null) { - predictedApps.add(info); + ItemInfoWithIcon predictedApp = info.clone(); + predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION; + predictedApps.add(predictedApp); } else { if (FeatureFlags.IS_DOGFOOD_BUILD) { Log.e(TAG, "Predicted app not found: " + mapper); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java index 50cfac8e4..468b8afc9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java @@ -19,7 +19,6 @@ import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; -import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.util.LayoutUtils; @@ -69,8 +68,16 @@ public class BackgroundAppState extends OverviewState { if (taskCount == 0) { return super.getOverviewScaleAndTranslation(launcher); } - TaskView dummyTask = recentsView.getTaskViewAt(Utilities.boundToRange( - recentsView.getCurrentPage(), 0, taskCount - 1)); + TaskView dummyTask; + if (recentsView.getCurrentPage() >= 0) { + if (recentsView.getCurrentPage() <= taskCount - 1) { + dummyTask = recentsView.getCurrentPageTaskView(); + } else { + dummyTask = recentsView.getTaskViewAt(taskCount - 1); + } + } else { + dummyTask = recentsView.getTaskViewAt(0); + } return recentsView.getTempClipAnimationHelper().updateForFullscreenOverview(dummyTask) .getScaleAndTranslation(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index ab346c059..4a3ad1d72 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -42,6 +42,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; +import android.view.View; import android.view.ViewConfiguration; import com.android.launcher3.Launcher; @@ -179,6 +180,11 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { } else { super.onDragEnd(velocity, fling); } + + View searchView = mLauncher.getAppsView().getSearchView(); + if (searchView instanceof FeedbackHandler) { + ((FeedbackHandler) searchView).resetFeedback(); + } mMotionPauseDetector.clear(); } @@ -205,4 +211,16 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW); } } + + /** + * Interface for views with feedback animation requiring reset + */ + public interface FeedbackHandler { + + /** + * reset searchWidget feedback + */ + void resetFeedback(); + } + } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java index 20a248734..03862db1d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java @@ -47,9 +47,9 @@ public final class PortraitOverviewStateTouchHelper { * @return true if we should intercept the motion event */ boolean canInterceptTouch(MotionEvent ev) { - if (mRecentsView.getChildCount() > 0) { + if (mRecentsView.getTaskViewCount() > 0) { // Allow swiping up in the gap between the hotseat and overview. - return ev.getY() >= mRecentsView.getChildAt(0).getBottom(); + return ev.getY() >= mRecentsView.getTaskViewAt(0).getBottom(); } else { // If there are no tasks, we only intercept if we're below the hotseat height. return isTouchOverHotseat(mLauncher, ev); @@ -63,7 +63,7 @@ public final class PortraitOverviewStateTouchHelper { * @return true if going back should take the user to the currently running task */ boolean shouldSwipeDownReturnToApp() { - TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()); + TaskView taskView = mRecentsView.getNextPageTaskView(); return taskView != null && mRecentsView.shouldSwipeDownLaunchApp(); } @@ -76,7 +76,7 @@ public final class PortraitOverviewStateTouchHelper { */ PendingAnimation createSwipeDownToTaskAppAnimation(long duration) { mRecentsView.setCurrentPage(mRecentsView.getPageNearestToCenterOfScreen()); - TaskView taskView = mRecentsView.getTaskViewAt(mRecentsView.getCurrentPage()); + TaskView taskView = mRecentsView.getCurrentPageTaskView(); if (taskView == null) { throw new IllegalStateException("There is no task view to animate to."); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index eb571f607..6576ec573 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -50,7 +50,6 @@ import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.system.QuickStepContract; /** * Handles quick switching to a recent task from the home screen. diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index d627a7f14..7196f7c6f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -22,9 +22,9 @@ import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; -import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; -import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR; import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG; import android.animation.Animator; @@ -44,9 +44,10 @@ import android.os.Vibrator; import android.provider.Settings; import android.view.MotionEvent; import android.view.View; -import android.view.WindowManager; import android.view.animation.Interpolator; +import androidx.annotation.UiThread; + import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; @@ -54,7 +55,6 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; @@ -75,8 +75,6 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import java.util.function.Consumer; -import androidx.annotation.UiThread; - /** * Base class for swipe up handler with some utility methods */ @@ -126,7 +124,7 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten protected Runnable mGestureEndCallback; - protected final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler(); + protected final Handler mMainThreadHandler = MAIN_EXECUTOR.getHandler(); protected MultiStateCallback mStateCallback; protected boolean mCanceled; @@ -174,7 +172,7 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten if (effect == null) { return; } - BACKGROUND_EXECUTOR.execute(() -> mVibrator.vibrate(effect)); + UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(effect)); } public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) { @@ -229,10 +227,10 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten // Launch the task user scrolled to (mRecentsView.getNextPage()). if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { // We finish recents animation inside launchTask() when live tile is enabled. - mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */, + mRecentsView.getNextPageTaskView().launchTask(false /* animate */, true /* freezeTaskList */); } else { - int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id; + int taskId = mRecentsView.getNextPageTaskView().getTask().key.id; mFinishingRecentsAnimationForNewTaskId = taskId; mRecentsAnimationWrapper.finish(true /* toRecents */, () -> { if (!mCanceled) { @@ -275,7 +273,7 @@ public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q exten overviewStackBounds = getStackBounds(dp); } dp.updateInsets(targetSet.homeContentInsets); - dp.updateIsSeascape(mContext.getSystemService(WindowManager.class)); + dp.updateIsSeascape(mContext); if (runningTaskTarget != null) { mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index 36eb8a13b..0a45ccabe 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -268,7 +268,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0); int runningTaskIndex = recentsView.getRunningTaskIndex(); - if (runningTaskIndex == 0) { + if (runningTaskIndex == recentsView.getTaskViewStartIndex()) { // If we are on the first task (we haven't quick switched), translate recents in // from the side. Calculate the start translation based on current scale/scroll. float currScale = recentsView.getScaleX(); @@ -351,8 +351,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe private void playScaleDownAnim(AnimatorSet anim, Launcher launcher, LauncherState fromState, LauncherState endState) { RecentsView recentsView = launcher.getOverviewPanel(); - TaskView v = recentsView.getTaskViewAt(recentsView.getCurrentPage()); - if (v == null) { + if (recentsView.getCurrentPageTaskView() == null) { return; } @@ -380,7 +379,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe // recents as a whole needs to translate further to keep up with the app window. TaskView runningTaskView = recentsView.getRunningTaskView(); if (runningTaskView == null) { - runningTaskView = recentsView.getTaskViewAt(recentsView.getCurrentPage()); + runningTaskView = recentsView.getCurrentPageTaskView(); if (runningTaskView == null) { // There are no task views in LockTask mode when Overview is enabled. return; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java index 222ef97d9..a8d402e56 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java @@ -15,8 +15,8 @@ */ package com.android.quickstep; -import static com.android.systemui.shared.system.ActivityManagerWrapper - .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -29,7 +29,6 @@ import android.util.Log; import android.view.ViewConfiguration; import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -49,7 +48,6 @@ public class OverviewCommandHelper { private final Context mContext; private final ActivityManagerWrapper mAM; private final RecentsModel mRecentsModel; - private final MainThreadExecutor mMainThreadExecutor; private final OverviewComponentObserver mOverviewComponentObserver; private long mLastToggleTime; @@ -57,7 +55,6 @@ public class OverviewCommandHelper { public OverviewCommandHelper(Context context, OverviewComponentObserver observer) { mContext = context; mAM = ActivityManagerWrapper.getInstance(); - mMainThreadExecutor = new MainThreadExecutor(); mRecentsModel = RecentsModel.INSTANCE.get(mContext); mOverviewComponentObserver = observer; } @@ -69,19 +66,19 @@ public class OverviewCommandHelper { } mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); - mMainThreadExecutor.execute(new RecentsActivityCommand<>()); + MAIN_EXECUTOR.execute(new RecentsActivityCommand<>()); } public void onOverviewShown(boolean triggeredFromAltTab) { - mMainThreadExecutor.execute(new ShowRecentsCommand(triggeredFromAltTab)); + MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab)); } public void onOverviewHidden() { - mMainThreadExecutor.execute(new HideRecentsCommand()); + MAIN_EXECUTOR.execute(new HideRecentsCommand()); } public void onTip(int actionType, int viewType) { - mMainThreadExecutor.execute(() -> + MAIN_EXECUTOR.execute(() -> UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType)); } @@ -112,7 +109,7 @@ public class OverviewCommandHelper { TaskView taskView = rv.getNextTaskView(); if (taskView == null) { if (rv.getTaskViewCount() > 0) { - taskView = (TaskView) rv.getPageAt(0); + taskView = rv.getTaskViewAt(0); taskView.requestFocus(); } else { rv.requestFocus(); @@ -180,7 +177,7 @@ public class OverviewCommandHelper { // Otherwise, start overview. mListener = mHelper.createActivityInitListener(this::onActivityReady); mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(), - this::createWindowAnimation, mContext, mMainThreadExecutor.getHandler(), + this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(), mAnimationProvider.getRecentsLaunchDuration()); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java index da4642636..72a14b58e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -1,9 +1,10 @@ package com.android.quickstep; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.Context; import android.os.Bundle; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.testing.TestInformationHandler; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.uioverrides.states.OverviewState; @@ -52,7 +53,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: { try { - final int leftMargin = new MainThreadExecutor().submit(() -> + final int leftMargin = MAIN_EXECUTOR.submit(() -> mLauncher.<RecentsView>getOverviewPanel().getLeftGestureMargin()).get(); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin); } catch (ExecutionException e) { @@ -65,7 +66,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: { try { - final int rightMargin = new MainThreadExecutor().submit(() -> + final int rightMargin = MAIN_EXECUTOR.submit(() -> mLauncher.<RecentsView>getOverviewPanel().getRightGestureMargin()). get(); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index f08ae4a82..9bdc98bf8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -136,6 +136,12 @@ public final class RecentsActivity extends BaseRecentsActivity { } @Override + public void returnToHomescreen() { + super.returnToHomescreen(); + // TODO(b/137318995) This should go home, but doing so removes freeform windows + } + + @Override public ActivityOptions getActivityLaunchOptions(final View v) { if (!(v instanceof TaskView)) { return null; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java index c55f656df..8783ee3cc 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeSharedState.java @@ -15,7 +15,7 @@ */ package com.android.quickstep; -import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.util.Log; @@ -25,6 +25,7 @@ import com.android.launcher3.util.Preconditions; import com.android.quickstep.util.RecentsAnimationListenerSet; import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener; + import java.io.PrintWriter; /** @@ -44,6 +45,7 @@ public class SwipeSharedState implements SwipeAnimationListener { public boolean goingToLauncher; public boolean recentsAnimationFinishInterrupted; public int nextRunningTaskId = -1; + private int mLogId; public void setOverviewComponentObserver(OverviewComponentObserver observer) { mOverviewComponentObserver = observer; @@ -77,7 +79,7 @@ public class SwipeSharedState implements SwipeAnimationListener { mRecentsAnimationListener.removeListener(this); mRecentsAnimationListener.cancelListener(); if (mLastAnimationRunning && mLastAnimationTarget != null) { - Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), + Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), finishAnimation ? mLastAnimationTarget::finishAnimation : mLastAnimationTarget::cancelAnimation); @@ -155,5 +157,10 @@ public class SwipeSharedState implements SwipeAnimationListener { pw.println(prefix + "nextRunningTaskId=" + nextRunningTaskId); pw.println(prefix + "lastAnimationCancelled=" + mLastAnimationCancelled); pw.println(prefix + "lastAnimationRunning=" + mLastAnimationRunning); + pw.println(prefix + "logTraceId=" + mLogId); + } + + public void setLogTraceId(int logId) { + this.mLogId = logId; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java index fd4592307..1af0db07d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java @@ -36,8 +36,6 @@ import android.view.View; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.ItemInfo; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; @@ -267,12 +265,16 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut @Override protected ActivityOptions makeLaunchOptions(Activity activity) { - return ActivityOptionsCompat.makeFreeformOptions(); + ActivityOptions activityOptions = ActivityOptionsCompat.makeFreeformOptions(); + // Arbitrary bounds only because freeform is in dev mode right now + Rect r = new Rect(50, 50, 200, 200); + activityOptions.setLaunchBounds(r); + return activityOptions; } @Override protected boolean onActivityStarted(BaseDraggingActivity activity) { - Launcher.getLauncher(activity).getStateManager().goToState(LauncherState.NORMAL); + activity.returnToHomescreen(); return true; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 86ba85578..729287c82 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -23,6 +23,8 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_HINTS_IN_OVERVIEW import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.config.FeatureFlags.FAKE_LANDSCAPE_UI; import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; @@ -50,8 +52,6 @@ import android.content.res.Configuration; import android.graphics.Point; import android.graphics.RectF; import android.graphics.Region; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; import android.os.Build; import android.os.Bundle; import android.os.IBinder; @@ -61,18 +61,15 @@ import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import android.view.Choreographer; -import android.view.Display; import android.view.InputEvent; import android.view.MotionEvent; import android.view.Surface; -import android.view.WindowManager; import androidx.annotation.BinderThread; import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.ResourceUtils; import com.android.launcher3.Utilities; @@ -82,8 +79,7 @@ import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.provider.RestoreDbTask; import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.util.LooperExecutor; -import com.android.launcher3.util.UiThreadHelper; +import com.android.launcher3.util.DefaultDisplay; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.inputconsumers.AccessibilityInputConsumer; @@ -106,8 +102,8 @@ import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import com.android.systemui.shared.system.RecentsAnimationListener; import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat; - import com.android.systemui.shared.system.TaskInfoCompat; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; @@ -136,11 +132,14 @@ class ArgList extends LinkedList<String> { */ @TargetApi(Build.VERSION_CODES.Q) public class TouchInteractionService extends Service implements - NavigationModeChangeListener, DisplayListener { + NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener { + + /** + * NOTE: This value should be kept same as + * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform + */ + public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID"; - public static final MainThreadExecutor MAIN_THREAD_EXECUTOR = new MainThreadExecutor(); - public static final LooperExecutor BACKGROUND_EXECUTOR = - new LooperExecutor(UiThreadHelper.getBackgroundLooper()); public static final EventLogArray TOUCH_INTERACTION_LOG = new EventLogArray("touch_interaction_log", 40); @@ -161,9 +160,9 @@ public class TouchInteractionService extends Service implements public void onInitialize(Bundle bundle) { mISystemUiProxy = ISystemUiProxy.Stub .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY)); - MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor); - MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet); - MAIN_THREAD_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */)); + MAIN_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor); + MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet); + MAIN_EXECUTOR.execute(() -> preloadOverview(true /* fromInit */)); sIsInitialized = true; } @@ -198,7 +197,7 @@ public class TouchInteractionService extends Service implements @Override public void onAssistantVisibilityChanged(float visibility) { mLastAssistantVisibility = visibility; - MAIN_THREAD_EXECUTOR.execute( + MAIN_EXECUTOR.execute( TouchInteractionService.this::onAssistantVisibilityChanged); } @@ -214,13 +213,13 @@ public class TouchInteractionService extends Service implements isButton, gestureSwipeLeft, activityControl.getContainerType()); if (completed && !isButton && shouldNotifyBackGesture()) { - BACKGROUND_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture); + UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture); } } public void onSystemUiStateChanged(int stateFlags) { mSystemUiStateFlags = stateFlags; - MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged); + MAIN_EXECUTOR.execute(TouchInteractionService.this::onSystemUiFlagsChanged); } /** Deprecated methods **/ @@ -244,6 +243,7 @@ public class TouchInteractionService extends Service implements private static boolean sConnected = false; private static boolean sIsInitialized = false; private static final SwipeSharedState sSwipeSharedState = new SwipeSharedState(); + private int mLogId; public static boolean isConnected() { return sConnected; @@ -323,8 +323,7 @@ public class TouchInteractionService extends Service implements registerReceiver(mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED)); } - mDefaultDisplayId = getSystemService(WindowManager.class).getDefaultDisplay() - .getDisplayId(); + mDefaultDisplayId = DefaultDisplay.INSTANCE.get(this).getInfo().id; String blockingActivity = getString(R.string.gesture_blocking_activity); mGestureBlockingActivity = TextUtils.isEmpty(blockingActivity) ? null : ComponentName.unflattenFromString(blockingActivity); @@ -391,9 +390,8 @@ public class TouchInteractionService extends Service implements return; } - Display defaultDisplay = getSystemService(WindowManager.class).getDefaultDisplay(); - Point realSize = new Point(); - defaultDisplay.getRealSize(realSize); + DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(this).getInfo(); + Point realSize = new Point(displayInfo.realSize); mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y); if (mMode == Mode.NO_BUTTON) { int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); @@ -415,7 +413,7 @@ public class TouchInteractionService extends Service implements } else { mAssistantLeftRegion.setEmpty(); mAssistantRightRegion.setEmpty(); - switch (defaultDisplay.getRotation()) { + switch (displayInfo.rotation) { case Surface.ROTATION_90: mSwipeTouchRegion.left = mSwipeTouchRegion.right - getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE); @@ -438,10 +436,9 @@ public class TouchInteractionService extends Service implements } if (mMode.hasGestures != newMode.hasGestures) { if (newMode.hasGestures) { - getSystemService(DisplayManager.class).registerDisplayListener( - this, MAIN_THREAD_EXECUTOR.getHandler()); + DefaultDisplay.INSTANCE.get(this).addChangeListener(this); } else { - getSystemService(DisplayManager.class).unregisterDisplayListener(this); + DefaultDisplay.INSTANCE.get(this).removeChangeListener(this); } } mMode = newMode; @@ -457,14 +454,8 @@ public class TouchInteractionService extends Service implements } @Override - public void onDisplayAdded(int i) { } - - @Override - public void onDisplayRemoved(int i) { } - - @Override - public void onDisplayChanged(int displayId) { - if (displayId != mDefaultDisplayId) { + public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) { + if (info.id != mDefaultDisplayId) { return; } @@ -529,7 +520,7 @@ public class TouchInteractionService extends Service implements } disposeEventHandlers(); if (mMode.hasGestures) { - getSystemService(DisplayManager.class).unregisterDisplayListener(this); + DefaultDisplay.INSTANCE.get(this).removeChangeListener(this); } sConnected = false; @@ -554,9 +545,12 @@ public class TouchInteractionService extends Service implements Log.e(TAG, "Unknown event " + ev); return; } + MotionEvent event = (MotionEvent) ev; - TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked()); if (event.getAction() == ACTION_DOWN) { + mLogId = TOUCH_INTERACTION_LOG.generateAndSetLogId(); + sSwipeSharedState.setLogTraceId(mLogId); + if (mSwipeTouchRegion.contains(event.getX(), event.getY())) { boolean useSharedState = mConsumer.useSharedSwipeState(); mConsumer.onConsumerAboutToBeSwitched(); @@ -576,6 +570,8 @@ public class TouchInteractionService extends Service implements mUncheckedConsumer = InputConsumer.NO_OP; } } + + TOUCH_INTERACTION_LOG.addLog("onMotionEvent", event.getActionMasked()); mUncheckedConsumer.onMotionEvent(event); } @@ -661,7 +657,7 @@ public class TouchInteractionService extends Service implements final ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent(); forceOverviewInputConsumer = - runningTaskInfo.baseIntent.getComponent().equals(homeComponent); + runningTaskInfo.baseIntent.getComponent(). equals(homeComponent); } } @@ -718,13 +714,13 @@ public class TouchInteractionService extends Service implements return new OtherActivityInputConsumer(this, runningTaskInfo, shouldDefer, mOverviewCallbacks, this::onConsumerInactive, sSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion, - disableHorizontalSwipe(event), factory); + disableHorizontalSwipe(event), factory, mLogId); } private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) { if (mMode == Mode.NO_BUTTON && taskInfo != null) { return new DeviceLockedInputConsumer(this, sSwipeSharedState, mInputMonitorCompat, - mSwipeTouchRegion, taskInfo.taskId); + mSwipeTouchRegion, taskInfo.taskId, mLogId); } else { return mResetGestureInputConsumer; } @@ -788,7 +784,8 @@ public class TouchInteractionService extends Service implements } // Pass null animation handler to indicate this start is preload. - startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(), null); + startRecentsActivityAsync(mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState(), + null); } @Override @@ -897,7 +894,7 @@ public class TouchInteractionService extends Service implements } public static void startRecentsActivityAsync(Intent intent, RecentsAnimationListener listener) { - BACKGROUND_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() + UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance() .startRecentsActivity(intent, null, listener, null, null)); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index a3bd348e2..411a8088c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -479,8 +479,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask; if (animate) { // Only animate if an adjacent task view is visible on screen. - TaskView adjacentTask1 = mRecentsView.getTaskViewAt(runningTaskIndex + 1); - TaskView adjacentTask2 = mRecentsView.getTaskViewAt(runningTaskIndex - 1); + TaskView adjacentTask1 = mRecentsView.getNextTaskView(); + TaskView adjacentTask2 = mRecentsView.getPreviousTaskView(); float prevTranslationX = mRecentsView.getTranslationX(); mRecentsView.setTranslationX(0); animate = (adjacentTask1 != null && adjacentTask1.getGlobalVisibleRect(TEMP_RECT)) @@ -590,8 +590,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> */ private void updateSysUiFlags(float windowProgress) { if (mRecentsView != null) { - TaskView centermostTask = mRecentsView.getTaskViewAt(mRecentsView - .getPageNearestToCenterOfScreen()); + TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen(); int centermostTaskFlags = centermostTask == null ? 0 : centermostTask.getThumbnail().getSysUiStatusNavFlags(); boolean useHomeScreenFlags = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java index 2e9c0a3c4..7f1aae5e2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -176,7 +176,7 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> { protected void applyLoadPlan(ArrayList<Task> tasks) { // When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to - // track the index of the next task appropriately, as it we are switching on any other app. + // track the index of the next task appropriately, as if we are switching on any other app. if (mRunningTaskInfo != null && mRunningTaskInfo.taskId == mRunningTaskId) { // Check if the task list has running task boolean found = false; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index 3d763ab52..b24c78810 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -22,8 +22,9 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; -import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; +import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID; import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync; +import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; import android.content.ComponentName; import android.content.Context; @@ -35,10 +36,10 @@ import android.graphics.RectF; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; -import android.view.WindowManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.util.DefaultDisplay; import com.android.quickstep.LockScreenRecentsActivity; import com.android.quickstep.MultiStateCallback; import com.android.quickstep.SwipeSharedState; @@ -76,6 +77,7 @@ public class DeviceLockedInputConsumer implements InputConsumer, private final PointF mTouchDown = new PointF(); private final ClipAnimationHelper mClipAnimationHelper; + private int mLogId; private final ClipAnimationHelper.TransformParams mTransformParams; private final Point mDisplaySize; private final MultiStateCallback mStateCallback; @@ -90,19 +92,20 @@ public class DeviceLockedInputConsumer implements InputConsumer, private SwipeAnimationTargetSet mTargetSet; public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState, - InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId) { + InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId, + int logId) { mContext = context; mTouchSlopSquared = squaredTouchSlop(context); mSwipeSharedState = swipeSharedState; mClipAnimationHelper = new ClipAnimationHelper(context); + mLogId = logId; mTransformParams = new ClipAnimationHelper.TransformParams(); mInputMonitorCompat = inputMonitorCompat; mSwipeTouchRegion = swipeTouchRegion; mRunningTaskId = runningTaskId; // Do not use DeviceProfile as the user data might be locked - mDisplaySize = new Point(); - context.getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(mDisplaySize); + mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize; // Init states mStateCallback = new MultiStateCallback(STATE_NAMES); @@ -205,7 +208,8 @@ public class DeviceLockedInputConsumer implements InputConsumer, Intent intent = new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_DEFAULT) .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) + .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId); mInputMonitorCompat.pilferPointers(); startRecentsActivityAsync(intent, newListenerSet); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java index 6ec1da0c4..e0ff8afe6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java @@ -50,7 +50,6 @@ import com.android.quickstep.fallback.FallbackRecentsView; import com.android.quickstep.util.ObjectWrapper; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.SwipeAnimationTargetSet; -import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 86766d99f..e41880df5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -22,11 +22,11 @@ import static android.view.MotionEvent.ACTION_POINTER_DOWN; import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static android.view.MotionEvent.INVALID_POINTER_ID; - import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.util.RaceConditionTracker.ENTER; import static com.android.launcher3.util.RaceConditionTracker.EXIT; +import static com.android.quickstep.TouchInteractionService.INTENT_EXTRA_LOG_TRACE_ID; import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG; import static com.android.quickstep.TouchInteractionService.startRecentsActivityAsync; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; @@ -35,6 +35,7 @@ import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.graphics.PointF; import android.graphics.RectF; import android.os.Build; @@ -43,12 +44,13 @@ import android.os.Looper; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; - +import androidx.annotation.UiThread; import com.android.launcher3.R; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RaceConditionTracker; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.BaseSwipeUpHandler; +import com.android.quickstep.BaseSwipeUpHandler.Factory; import com.android.quickstep.OverviewCallbacks; import com.android.quickstep.SwipeSharedState; import com.android.quickstep.SysUINavigationMode; @@ -59,11 +61,8 @@ import com.android.quickstep.util.NavBarPosition; import com.android.quickstep.util.RecentsAnimationListenerSet; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; - import java.util.function.Consumer; -import androidx.annotation.UiThread; - /** * Input consumer for handling events originating from an activity other than Launcher */ @@ -119,14 +118,16 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC ActivityManagerWrapper.getInstance().cancelRecentsAnimation( true /* restoreHomeStackPosition */); }; + private int mLogId; public OtherActivityInputConsumer(Context base, RunningTaskInfo runningTaskInfo, boolean isDeferredDownTarget, OverviewCallbacks overviewCallbacks, Consumer<OtherActivityInputConsumer> onCompleteCallback, SwipeSharedState swipeSharedState, InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, boolean disableHorizontalSwipe, - BaseSwipeUpHandler.Factory handlerFactory) { + Factory handlerFactory, int logId) { super(base); + mLogId = logId; mMainThreadHandler = new Handler(Looper.getMainLooper()); mRunningTask = runningTaskInfo; @@ -341,7 +342,9 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC RecentsAnimationListenerSet newListenerSet = mSwipeSharedState.newRecentsAnimationListenerSet(); newListenerSet.addListener(handler); - startRecentsActivityAsync(handler.getLaunchIntent(), newListenerSet); + Intent intent = handler.getLaunchIntent(); + intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mLogId); + startRecentsActivityAsync(intent, newListenerSet); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java index 3ce341d8c..bbb318a02 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/NavBarPosition.java @@ -21,9 +21,9 @@ import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import android.content.Context; import android.view.Surface; -import android.view.WindowManager; import com.android.launcher3.graphics.RotationMode; +import com.android.launcher3.util.DefaultDisplay; import com.android.quickstep.SysUINavigationMode; /** @@ -36,8 +36,7 @@ public class NavBarPosition { public NavBarPosition(Context context) { mMode = SysUINavigationMode.getMode(context); - mDisplayRotation = context.getSystemService(WindowManager.class) - .getDefaultDisplay().getRotation(); + mDisplayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation; } public boolean isRightEdge() { diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java index 14083dd95..b1999d716 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAnimationListenerSet.java @@ -15,11 +15,13 @@ */ package com.android.quickstep.util; -import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.graphics.Rect; import android.util.ArraySet; +import androidx.annotation.UiThread; + import com.android.launcher3.Utilities; import com.android.launcher3.util.Preconditions; import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener; @@ -31,8 +33,6 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.Set; import java.util.function.Consumer; -import androidx.annotation.UiThread; - /** * Wrapper around {@link RecentsAnimationListener} which delegates callbacks to multiple listeners * on the main thread @@ -82,7 +82,7 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { if (mCancelled) { targetSet.cancelAnimation(); } else { - Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> { + Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { for (SwipeAnimationListener listener : getListeners()) { listener.onRecentsAnimationStart(targetSet); } @@ -92,14 +92,14 @@ public class RecentsAnimationListenerSet implements RecentsAnimationListener { @Override public final void onAnimationCanceled(ThumbnailData thumbnailData) { - Utilities.postAsyncCallback(MAIN_THREAD_EXECUTOR.getHandler(), () -> { + Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> { for (SwipeAnimationListener listener : getListeners()) { listener.onRecentsAnimationCanceled(); } }); // TODO: handle the transition better instead of simply using a transition delay. if (thumbnailData != null) { - MAIN_THREAD_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(), + MAIN_EXECUTOR.getHandler().postDelayed(() -> mController.cleanupScreenshot(), TRANSITION_DELAY); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java index 381c27a28..3619d3a0e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SwipeAnimationTargetSet.java @@ -15,8 +15,8 @@ */ package com.android.quickstep.util; -import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; -import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import android.graphics.Rect; @@ -68,25 +68,25 @@ public class SwipeAnimationTargetSet extends RemoteAnimationTargetSet { public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) { mOnFinishListener.accept(this); - BACKGROUND_EXECUTOR.execute(() -> { + UI_HELPER_EXECUTOR.execute(() -> { controller.setInputConsumerEnabled(false); controller.finish(toRecents, sendUserLeaveHint); if (callback != null) { - MAIN_THREAD_EXECUTOR.execute(callback); + MAIN_EXECUTOR.execute(callback); } }); } public void enableInputConsumer() { - BACKGROUND_EXECUTOR.submit(() -> { + UI_HELPER_EXECUTOR.submit(() -> { controller.hideCurrentInputMethod(); controller.setInputConsumerEnabled(true); }); } public void setWindowThresholdCrossed(boolean thresholdCrossed) { - BACKGROUND_EXECUTOR.execute(() -> { + UI_HELPER_EXECUTOR.execute(() -> { controller.setAnimationTargetsBehindSystemBars(!thresholdCrossed); if (mShouldMinimizeSplitScreen && thresholdCrossed) { // NOTE: As a workaround for conflicting animations (Launcher animating the task diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java index 7fac81385..b06d4bc35 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java @@ -19,6 +19,7 @@ package com.android.quickstep.views; import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS; import static com.android.launcher3.Utilities.prefixTextWithIcon; +import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR; import android.annotation.TargetApi; import android.app.ActivityOptions; @@ -41,7 +42,6 @@ import androidx.annotation.StringRes; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; -import com.android.launcher3.Utilities; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.systemui.shared.recents.model.Task; @@ -117,7 +117,7 @@ public final class DigitalWellBeingToast { return; } - Utilities.THREAD_POOL_EXECUTOR.execute(() -> { + THREAD_POOL_EXECUTOR.execute(() -> { final AppUsageLimit usageLimit = mLauncherApps.getAppUsageLimit( task.getTopComponent().getPackageName(), UserHandle.of(task.key.userId)); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index d98ef425e..12b37cbd6 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -35,9 +35,9 @@ import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; -import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorSet; @@ -228,7 +228,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl return; } - BACKGROUND_EXECUTOR.execute(() -> { + UI_HELPER_EXECUTOR.execute(() -> { TaskView taskView = getTaskView(taskId); if (taskView == null) { return; @@ -306,6 +306,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl private Layout mEmptyTextLayout; private LiveTileOverlay mLiveTileOverlay; + // Keeps track of the index where the first TaskView should be + private int mTaskViewStartIndex = 0; + private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener = (inMultiWindowMode) -> { if (!inMultiWindowMode && mOverviewStateEnabled) { @@ -329,7 +332,6 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl mClearAllButton = (ClearAllButton) LayoutInflater.from(context) .inflate(R.layout.overview_clear_all_button, this, false); mClearAllButton.setOnClickListener(this::dismissAllTasks); - mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 10 /* initial size */); @@ -430,7 +432,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl super.onViewRemoved(child); // Clear the task data for the removed child if it was visible - if (child != mClearAllButton) { + if (child instanceof TaskView) { TaskView taskView = (TaskView) child; mHasVisibleTaskData.delete(taskView.getTask().key.id); mTaskViewPool.recycle(taskView); @@ -444,7 +446,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl public TaskView getTaskView(int taskId) { for (int i = 0; i < getTaskViewCount(); i++) { - TaskView tv = (TaskView) getChildAt(i); + TaskView tv = getTaskViewAt(i); if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) { return tv; } @@ -536,28 +538,26 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl } if (tasks == null || tasks.isEmpty()) { - removeAllViews(); + removeTasksViewsAndClearAllButton(); onTaskStackUpdated(); return; } - int oldChildCount = getChildCount(); - // Unload existing visible task data unloadVisibleTaskData(); - TaskView ignoreRestTaskView = + TaskView ignoreResetTaskView = mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId); final int requiredTaskCount = tasks.size(); if (getTaskViewCount() != requiredTaskCount) { - if (oldChildCount > 0) { + if (indexOfChild(mClearAllButton) != -1) { removeView(mClearAllButton); } - for (int i = getChildCount(); i < requiredTaskCount; i++) { + for (int i = getTaskViewCount(); i < requiredTaskCount; i++) { addView(mTaskViewPool.getView()); } - while (getChildCount() > requiredTaskCount) { + while (getTaskViewCount() > requiredTaskCount) { removeView(getChildAt(getChildCount() - 1)); } if (requiredTaskCount > 0) { @@ -567,17 +567,20 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl // Rebind and reset all task views for (int i = requiredTaskCount - 1; i >= 0; i--) { - final int pageIndex = requiredTaskCount - i - 1; + final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex; final Task task = tasks.get(i); final TaskView taskView = (TaskView) getChildAt(pageIndex); taskView.bind(task); } + TaskView runningTaskView = getRunningTaskView(); if (runningTaskView != null) { setCurrentPage(indexOfChild(runningTaskView)); + } else if (getTaskViewCount() > 0) { + setCurrentPage(indexOfChild(getTaskViewAt(0))); } - if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreRestTaskView) { + if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) { // If the taskView mapping is changing, do not preserve the visuals. Since we are // mostly preserving the first task, and new taskViews are added to the end, it should // generally map to the same task. @@ -588,17 +591,28 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl updateEnabledOverlays(); } + private void removeTasksViewsAndClearAllButton() { + for (int i = getTaskViewCount() - 1; i >= 0; i--) { + removeView(getTaskViewAt(i)); + } + if (indexOfChild(mClearAllButton) != -1) { + removeView(mClearAllButton); + } + } + public int getTaskViewCount() { - // Account for the clear all button. - int childCount = getChildCount(); - return childCount == 0 ? 0 : childCount - 1; + int taskViewCount = getChildCount() - mTaskViewStartIndex; + if (indexOfChild(mClearAllButton) != -1) { + taskViewCount--; + } + return taskViewCount; } protected void onTaskStackUpdated() { } public void resetTaskVisuals() { for (int i = getTaskViewCount() - 1; i >= 0; i--) { - TaskView taskView = (TaskView) getChildAt(i); + TaskView taskView = getTaskViewAt(i); if (mIgnoreResetTaskId != taskView.getTask().key.id) { taskView.resetVisualProperties(); taskView.setStableAlpha(mContentAlpha); @@ -704,7 +718,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl } /** - * Iterates through all thet asks, and loads the associated task data for newly visible tasks, + * Iterates through all the tasks, and loads the associated task data for newly visible tasks, * and unloads the associated task data for tasks that are no longer visible. */ public void loadVisibleTaskData() { @@ -715,15 +729,16 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl } int centerPageIndex = getPageNearestToCenterOfScreen(); - int numChildren = getTaskViewCount(); + int numChildren = getChildCount(); int lower = Math.max(0, centerPageIndex - 2); int upper = Math.min(centerPageIndex + 2, numChildren - 1); // Update the task data for the in/visible children - for (int i = 0; i < numChildren; i++) { - TaskView taskView = (TaskView) getChildAt(i); + for (int i = 0; i < getTaskViewCount(); i++) { + TaskView taskView = getTaskViewAt(i); Task task = taskView.getTask(); - boolean visible = lower <= i && i <= upper; + int index = indexOfChild(taskView); + boolean visible = lower <= index && index <= upper; if (visible) { if (task == mTmpRunningTask) { // Skip loading if this is the task that we are animating into @@ -798,6 +813,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl return tv == null ? -1 : indexOfChild(tv); } + public int getTaskViewStartIndex() { + return mTaskViewStartIndex; + } + /** * Reloads the view if anything in recents changed. */ @@ -853,10 +872,10 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl */ public void showCurrentTask(int runningTaskId) { if (getTaskView(runningTaskId) == null) { - boolean wasEmpty = getChildCount() == 0; + boolean wasEmpty = getTaskViewCount() == 0; // Add an empty view for now until the task plan is loaded and applied final TaskView taskView = mTaskViewPool.getView(); - addView(taskView, 0); + addView(taskView, mTaskViewStartIndex); if (wasEmpty) { addView(mClearAllButton); } @@ -922,14 +941,13 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl if (runningTaskView == null) { // Launch the first task if (getTaskViewCount() > 0) { - getTaskViewAt(0).launchTask(true /* animate */); + getTaskViewAt(0).launchTask(true); } } else { - TaskView nextTaskView = getNextTaskView(); - if (nextTaskView != null) { - nextTaskView.launchTask(true /* animate */); + if (getNextTaskView() != null) { + getNextTaskView().launchTask(true); } else { - runningTaskView.launchTask(true /* animate */); + runningTaskView.launchTask(true); } } } @@ -1193,7 +1211,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl int count = getTaskViewCount(); for (int i = 0; i < count; i++) { - addDismissedTaskAnimations(getChildAt(i), anim, duration); + addDismissedTaskAnimations(getTaskViewAt(i), anim, duration); } mPendingAnimation = pendingAnimation; @@ -1201,7 +1219,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl if (onEndListener.isSuccess) { // Remove all the task views now ActivityManagerWrapper.getInstance().removeAllRecentTasks(); - removeAllViews(); + removeTasksViewsAndClearAllButton(); startHome(); } mPendingAnimation = null; @@ -1348,26 +1366,50 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl child.setAlpha(mContentAlpha); } - /** - * @return The most recent task that is older than the currently running task. If there is - * currently no running task or there is no task older than it, then return null. - */ @Nullable public TaskView getNextTaskView() { - TaskView runningTaskView = getRunningTaskView(); - if (runningTaskView == null) { - return null; - } - return getTaskViewAt(indexOfChild(runningTaskView) + 1); + return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1); } + @Nullable + public TaskView getPreviousTaskView() { + return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() - 1); + } + + @Nullable + public TaskView getCurrentPageTaskView() { + return getTaskViewAtByAbsoluteIndex(getCurrentPage()); + } + + @Nullable + public TaskView getNextPageTaskView() { + return getTaskViewAtByAbsoluteIndex(getNextPage()); + } + + @Nullable + public TaskView getTaskViewNearestToCenterOfScreen() { + return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen()); + } + + /** + * Returns null instead of indexOutOfBoundsError when index is not in range + */ + @Nullable public TaskView getTaskViewAt(int index) { - View child = getChildAt(index); - return child == mClearAllButton ? null : (TaskView) child; + return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex); + } + + @Nullable + private TaskView getTaskViewAtByAbsoluteIndex(int index) { + if (index < getChildCount() && index >= 0) { + View child = getChildAt(index); + return child instanceof TaskView ? (TaskView) child : null; + } + return null; } public void updateEmptyMessage() { - boolean isEmpty = getChildCount() == 0; + boolean isEmpty = getTaskViewCount() == 0; boolean hasSizeChanged = mLastMeasureSize.x != getWidth() || mLastMeasureSize.y != getHeight(); if (isEmpty == mShowEmptyMessage && !hasSizeChanged) { @@ -1501,7 +1543,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl throw new IllegalStateException("Another pending animation is still running"); } - int count = getChildCount(); + int count = getTaskViewCount(); if (count == 0) { return new PendingAnimation(new AnimatorSet()); } @@ -1674,18 +1716,38 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl @Override protected int computeMinScrollX() { - if (mIsRtl && mDisallowScrollToClearAll) { - // We aren't showing the clear all button, so use the leftmost task as the min scroll. - return getScrollForPage(getTaskViewCount() - 1); + if (getTaskViewCount() > 0) { + if (mDisallowScrollToClearAll) { + // We aren't showing the clear all button, + // so use the leftmost task as the min scroll. + if (mIsRtl) { + return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1))); + } + return getScrollForPage(mTaskViewStartIndex); + } + if (mIsRtl) { + return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1); + } + return getScrollForPage(mTaskViewStartIndex); } return super.computeMinScrollX(); } @Override protected int computeMaxScrollX() { - if (!mIsRtl && mDisallowScrollToClearAll) { - // We aren't showing the clear all button, so use the rightmost task as the max scroll. - return getScrollForPage(getTaskViewCount() - 1); + if (getTaskViewCount() > 0) { + if (mDisallowScrollToClearAll) { + // We aren't showing the clear all button, + // so use the rightmost task as the min scroll. + if (mIsRtl) { + return getScrollForPage(mTaskViewStartIndex); + } + return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1))); + } + if (mIsRtl) { + return getScrollForPage(mTaskViewStartIndex); + } + return getScrollForPage(indexOfChild(getTaskViewAt(getTaskViewCount() - 1)) + 1); } return super.computeMaxScrollX(); } @@ -1737,7 +1799,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1; int taskCount = getTaskViewCount(); for (int i = 0; i < taskCount; i++) { - ((TaskView) getChildAt(i)).setOverlayEnabled(i == overlayEnabledPage); + getTaskViewAt(i).setOverlayEnabled(i == overlayEnabledPage); } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java index 910fa0df6..7beb9db84 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java +++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginInitializerImpl.java @@ -14,16 +14,17 @@ package com.android.launcher3.uioverrides.plugins; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.os.Looper; -import com.android.launcher3.LauncherModel; import com.android.systemui.shared.plugins.PluginInitializer; public class PluginInitializerImpl implements PluginInitializer { @Override public Looper getBgLooper() { - return LauncherModel.getWorkerLooper(); + return MODEL_EXECUTOR.getLooper(); } @Override diff --git a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java index c84013257..1ac7ed4a3 100644 --- a/quickstep/src/com/android/quickstep/BaseRecentsActivity.java +++ b/quickstep/src/com/android/quickstep/BaseRecentsActivity.java @@ -157,6 +157,6 @@ public abstract class BaseRecentsActivity extends BaseDraggingActivity { public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { super.dump(prefix, fd, writer, args); writer.println(prefix + "Misc:"); - dumpMisc(writer); + dumpMisc(prefix + "\t", writer); } } diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java index 78b48d77a..858c3b6b3 100644 --- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java +++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java @@ -15,20 +15,21 @@ */ package com.android.quickstep; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.Context; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.util.Log; +import androidx.annotation.WorkerThread; + import com.android.launcher3.Utilities; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.util.MainThreadInitializedObject; -import com.android.launcher3.util.UiThreadHelper; import com.android.systemui.shared.recents.ISystemUiProxy; -import androidx.annotation.WorkerThread; - /** * Sets alpha for the back button */ @@ -62,7 +63,7 @@ public class OverviewInteractionState { // because of its high send frequency and data may be very different than the previous value // For example, send back alpha on uihandler to avoid flickering when setting its visibility mUiHandler = new Handler(this::handleUiMessage); - mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage); + mBgHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleBgMessage); onNavigationModeChanged(SysUINavigationMode.INSTANCE.get(context) .addModeChangeListener(this::onNavigationModeChanged)); diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index e41dba94c..10f9febfb 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -16,19 +16,22 @@ package com.android.quickstep; -import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import android.annotation.TargetApi; import android.app.ActivityManager; -import android.content.Context; import android.os.Build; import android.os.Process; import android.util.SparseBooleanArray; -import com.android.launcher3.MainThreadExecutor; + +import androidx.annotation.VisibleForTesting; + +import com.android.launcher3.util.LooperExecutor; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.systemui.shared.system.TaskStackChangeListener; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -41,7 +44,8 @@ import java.util.function.Consumer; public class RecentTasksList extends TaskStackChangeListener { private final KeyguardManagerCompat mKeyguardManager; - private final MainThreadExecutor mMainThreadExecutor; + private final LooperExecutor mMainThreadExecutor; + private final ActivityManagerWrapper mActivityManagerWrapper; // The list change id, increments as the task list changes in the system private int mChangeId; @@ -52,11 +56,13 @@ public class RecentTasksList extends TaskStackChangeListener { ArrayList<Task> mTasks = new ArrayList<>(); - public RecentTasksList(Context context) { - mMainThreadExecutor = new MainThreadExecutor(); - mKeyguardManager = new KeyguardManagerCompat(context); + public RecentTasksList(LooperExecutor mainThreadExecutor, + KeyguardManagerCompat keyguardManager, ActivityManagerWrapper activityManagerWrapper) { + mMainThreadExecutor = mainThreadExecutor; + mKeyguardManager = keyguardManager; mChangeId = 1; - ActivityManagerWrapper.getInstance().registerTaskStackListener(this); + mActivityManagerWrapper = activityManagerWrapper; + mActivityManagerWrapper.registerTaskStackListener(this); } /** @@ -64,7 +70,7 @@ public class RecentTasksList extends TaskStackChangeListener { */ public void getTaskKeys(int numTasks, Consumer<ArrayList<Task>> callback) { // Kick off task loading in the background - BACKGROUND_EXECUTOR.execute(() -> { + UI_HELPER_EXECUTOR.execute(() -> { ArrayList<Task> tasks = loadTasksInBackground(numTasks, true /* loadKeysOnly */); mMainThreadExecutor.execute(() -> callback.accept(tasks)); }); @@ -86,12 +92,12 @@ public class RecentTasksList extends TaskStackChangeListener { if (mLastLoadedId == mChangeId && (!mLastLoadHadKeysOnly || loadKeysOnly)) { // The list is up to date, send the callback on the next frame, // so that requestID can be returned first. - mMainThreadExecutor.getHandler().post(resultCallback); + mMainThreadExecutor.post(resultCallback); return requestLoadId; } // Kick off task loading in the background - BACKGROUND_EXECUTOR.execute(() -> { + UI_HELPER_EXECUTOR.execute(() -> { ArrayList<Task> tasks = loadTasksInBackground(Integer.MAX_VALUE, loadKeysOnly); mMainThreadExecutor.execute(() -> { @@ -136,12 +142,13 @@ public class RecentTasksList extends TaskStackChangeListener { /** * Loads and creates a list of all the recent tasks. */ - private ArrayList<Task> loadTasksInBackground(int numTasks, + @VisibleForTesting + ArrayList<Task> loadTasksInBackground(int numTasks, boolean loadKeysOnly) { int currentUserId = Process.myUserHandle().getIdentifier(); ArrayList<Task> allTasks = new ArrayList<>(); List<ActivityManager.RecentTaskInfo> rawTasks = - ActivityManagerWrapper.getInstance().getRecentTasks(numTasks, currentUserId); + mActivityManagerWrapper.getRecentTasks(numTasks, currentUserId); // The raw tasks are given in most-recent to least-recent order, we need to reverse it Collections.reverse(rawTasks); diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java index f9d2f11cb..4d1d9ef8a 100644 --- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java +++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java @@ -15,6 +15,8 @@ */ package com.android.quickstep; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; @@ -22,7 +24,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; -import com.android.launcher3.MainThreadExecutor; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; import com.android.quickstep.util.RemoteAnimationProvider; @@ -92,14 +93,10 @@ public class RecentsActivityTracker<T extends BaseRecentsActivity> implements Ac private static class Scheduler implements Runnable { private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null); - private MainThreadExecutor mMainThreadExecutor; public synchronized void schedule(RecentsActivityTracker tracker) { mPendingTracker = new WeakReference<>(tracker); - if (mMainThreadExecutor == null) { - mMainThreadExecutor = new MainThreadExecutor(); - } - mMainThreadExecutor.execute(this); + MAIN_EXECUTOR.execute(this); } @Override diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index dfab43459..2e59ed5e4 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -15,6 +15,10 @@ */ package com.android.quickstep; +import static android.os.Process.THREAD_PRIORITY_BACKGROUND; + +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.createAndStartNewLooper; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; import android.annotation.TargetApi; @@ -22,16 +26,20 @@ import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.Context; import android.os.Build; -import android.os.HandlerThread; +import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.LauncherAppsCompat.OnAppsChangedCallbackCompat; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.KeyguardManagerCompat; import com.android.systemui.shared.system.TaskStackChangeListener; import java.util.ArrayList; @@ -61,13 +69,14 @@ public class RecentsModel extends TaskStackChangeListener { private RecentsModel(Context context) { mContext = context; - HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache", - Process.THREAD_PRIORITY_BACKGROUND); - loaderThread.start(); - mTaskList = new RecentTasksList(context); - mIconCache = new TaskIconCache(context, loaderThread.getLooper()); - mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper()); + Looper looper = + createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND); + mTaskList = new RecentTasksList(MAIN_EXECUTOR, + new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance()); + mIconCache = new TaskIconCache(context, looper); + mThumbnailCache = new TaskThumbnailCache(context, looper); ActivityManagerWrapper.getInstance().registerTaskStackListener(this); + setupPackageListener(); } public TaskIconCache getIconCache() { @@ -166,6 +175,7 @@ public class RecentsModel extends TaskStackChangeListener { public void onTaskRemoved(int taskId) { Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0); mThumbnailCache.remove(dummyKey); + mIconCache.onTaskRemoved(dummyKey); } public void setSystemUiProxy(ISystemUiProxy systemUiProxy) { @@ -200,6 +210,21 @@ public class RecentsModel extends TaskStackChangeListener { } } + private void setupPackageListener() { + LauncherAppsCompat.getInstance(mContext) + .addOnAppsChangedCallback(new OnAppsChangedCallbackCompat() { + @Override + public void onPackageRemoved(String packageName, UserHandle user) { + mIconCache.invalidatePackage(packageName); + } + + @Override + public void onPackageChanged(String packageName, UserHandle user) { + mIconCache.invalidatePackage(packageName); + } + }); + } + public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) { mThumbnailChangeListeners.add(listener); } diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java index 07af9b385..289a12970 100644 --- a/quickstep/src/com/android/quickstep/TaskIconCache.java +++ b/quickstep/src/com/android/quickstep/TaskIconCache.java @@ -16,6 +16,7 @@ package com.android.quickstep; import static com.android.launcher3.uioverrides.RecentsUiFactory.GO_LOW_RAM_RECENTS_ENABLED; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.content.ComponentName; import android.content.Context; @@ -27,26 +28,25 @@ import android.os.Looper; import android.util.LruCache; import android.view.accessibility.AccessibilityManager; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.cache.HandlerRunnable; -import com.android.launcher3.uioverrides.RecentsUiFactory; import com.android.launcher3.util.Preconditions; import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.TaskKeyLruCache; import com.android.systemui.shared.system.ActivityManagerWrapper; +import java.util.Map; import java.util.function.Consumer; /** * Manages the caching of task icons and related data. - * TODO: This class should later be merged into IconCache. + * TODO(b/138944598): This class should later be merged into IconCache. */ public class TaskIconCache { private final Handler mBackgroundHandler; - private final MainThreadExecutor mMainThreadExecutor; private final AccessibilityManager mAccessibilityManager; private final NormalizedIconLoader mIconLoader; @@ -67,7 +67,6 @@ public class TaskIconCache { public TaskIconCache(Context context, Looper backgroundLooper) { mBackgroundHandler = new Handler(backgroundLooper); - mMainThreadExecutor = new MainThreadExecutor(); mAccessibilityManager = context.getSystemService(AccessibilityManager.class); Resources res = context.getResources(); @@ -103,7 +102,7 @@ public class TaskIconCache { // We don't call back to the provided callback in this case return; } - mMainThreadExecutor.execute(() -> { + MAIN_EXECUTOR.execute(() -> { task.icon = icon; task.titleDescription = contentDescription; callback.accept(task); @@ -149,6 +148,21 @@ public class TaskIconCache { return label; } + + void onTaskRemoved(TaskKey taskKey) { + mIconCache.remove(taskKey); + } + + void invalidatePackage(String packageName) { + // TODO(b/138944598): Merge this class into IconCache so we can do this at the base level + Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); + for (ComponentName cn : activityInfoCache.keySet()) { + if (cn.getPackageName().equals(packageName)) { + mActivityInfoCache.remove(cn); + } + } + } + public static abstract class IconLoadRequest extends HandlerRunnable { IconLoadRequest(Handler handler) { super(handler, null); diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java index 57c5a2783..3b50c2623 100644 --- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java +++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java @@ -15,12 +15,14 @@ */ package com.android.quickstep; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; import android.os.Handler; import android.os.Looper; -import com.android.launcher3.MainThreadExecutor; + import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.icons.cache.HandlerRunnable; @@ -30,13 +32,13 @@ import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.recents.model.TaskKeyLruCache; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; + import java.util.ArrayList; import java.util.function.Consumer; public class TaskThumbnailCache { private final Handler mBackgroundHandler; - private final MainThreadExecutor mMainThreadExecutor; private final int mCacheSize; private final ThumbnailCache mCache; @@ -94,7 +96,6 @@ public class TaskThumbnailCache { public TaskThumbnailCache(Context context, Looper backgroundLooper) { mBackgroundHandler = new Handler(backgroundLooper); - mMainThreadExecutor = new MainThreadExecutor(); mHighResLoadingState = new HighResLoadingState(context); Resources res = context.getResources(); @@ -168,7 +169,7 @@ public class TaskThumbnailCache { // We don't call back to the provided callback in this case return; } - mMainThreadExecutor.execute(() -> { + MAIN_EXECUTOR.execute(() -> { mCache.put(key, thumbnail); callback.accept(thumbnail); onEnd(); diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java index d0956d1f6..7801775d4 100644 --- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java +++ b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java @@ -150,10 +150,10 @@ public class AppPredictionsUITests extends AbstractQuickStepTest { List<AppTarget> targets = new ArrayList<>(activities.length); for (LauncherActivityInfo info : activities) { ComponentName cn = info.getComponentName(); - AppTarget target = - new AppTarget.Builder(new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser()) - .setClassName(cn.getClassName()) - .build(); + AppTarget target = new AppTarget.Builder( + new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser()) + .setClassName(cn.getClassName()) + .build(); targets.add(target); } mCallback.onTargetsAvailable(targets); diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java new file mode 100644 index 000000000..34eb7f817 --- /dev/null +++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.quickstep; + +import static junit.framework.TestCase.assertNull; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; + +import androidx.test.filters.SmallTest; + +import com.android.launcher3.util.LooperExecutor; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.KeyguardManagerCompat; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +@SmallTest +public class RecentTasksListTest { + + private ActivityManagerWrapper mockActivityManagerWrapper; + + // Class under test + private RecentTasksList mRecentTasksList; + + @Before + public void setup() { + LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class); + KeyguardManagerCompat mockKeyguardManagerCompat = mock(KeyguardManagerCompat.class); + mockActivityManagerWrapper = mock(ActivityManagerWrapper.class); + mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManagerCompat, + mockActivityManagerWrapper); + } + + @Test + public void onTaskRemoved_reloadsAllTasks() { + mRecentTasksList.onTaskRemoved(0); + verify(mockActivityManagerWrapper, times(1)) + .getRecentTasks(anyInt(), anyInt()); + } + + @Test + public void onTaskStackChanged_doesNotFetchTasks() { + mRecentTasksList.onTaskStackChanged(); + verify(mockActivityManagerWrapper, times(0)) + .getRecentTasks(anyInt(), anyInt()); + } + + @Test + public void loadTasksInBackground_onlyKeys_noValidTaskDescription() { + ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo(); + when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt())) + .thenReturn(Collections.singletonList(recentTaskInfo)); + + List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, true); + + assertEquals(1, taskList.size()); + assertNull(taskList.get(0).taskDescription.getLabel()); + } + + @Test + public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() { + String taskDescription = "Wheeee!"; + ActivityManager.RecentTaskInfo recentTaskInfo = new ActivityManager.RecentTaskInfo(); + recentTaskInfo.taskDescription = new ActivityManager.TaskDescription(taskDescription); + when(mockActivityManagerWrapper.getRecentTasks(anyInt(), anyInt())) + .thenReturn(Collections.singletonList(recentTaskInfo)); + + List<Task> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, false); + + assertEquals(1, taskList.size()); + assertEquals(taskDescription, taskList.get(0).taskDescription.getLabel()); + } +} diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml index c156e113f..32a5419b8 100644 --- a/res/layout/folder_application.xml +++ b/res/layout/folder_application.xml @@ -20,4 +20,5 @@ style="@style/BaseIcon" android:textColor="?attr/folderTextColor" android:includeFontPadding="false" + android:hapticFeedbackEnabled="false" launcher:iconDisplay="folder" /> diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml index ca59afa55..ba16dd30b 100644 --- a/res/values-af/strings.xml +++ b/res/values-af/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Werk"</string> <string name="activity_not_found" msgid="8071924732094499514">"Program is nie geïnstalleer nie."</string> <string name="activity_not_available" msgid="7456344436509528827">"Program is nie beskikbaar nie"</string> diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml index d942e9e67..0396df617 100644 --- a/res/values-am/strings.xml +++ b/res/values-am/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"ስራ"</string> <string name="activity_not_found" msgid="8071924732094499514">"መተግበሪያ አልተጫነም።"</string> <string name="activity_not_available" msgid="7456344436509528827">"መተግበሪያ አይገኝም"</string> diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml index a80ecb0b1..2fbdae131 100644 --- a/res/values-ar/strings.xml +++ b/res/values-ar/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"العمل"</string> <string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string> <string name="activity_not_available" msgid="7456344436509528827">"التطبيق ليس متاحًا"</string> diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml index 298460381..221921f77 100644 --- a/res/values-as/strings.xml +++ b/res/values-as/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"কৰ্মস্থান"</string> <string name="activity_not_found" msgid="8071924732094499514">"এপটো ইনষ্টল কৰা নহ\'ল।"</string> <string name="activity_not_available" msgid="7456344436509528827">"এপটো নাই"</string> diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml index 5528a1912..7c1ce8447 100644 --- a/res/values-az/strings.xml +++ b/res/values-az/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"İş"</string> <string name="activity_not_found" msgid="8071924732094499514">"Tətbiq quraşdırılmayıb."</string> <string name="activity_not_available" msgid="7456344436509528827">"Tətbiq əlçatmazdır"</string> diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index 883003c5c..e6fe3bd1f 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string> diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml index b4cf913de..2ccd34fd0 100644 --- a/res/values-be/strings.xml +++ b/res/values-be/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Працоўная"</string> <string name="activity_not_found" msgid="8071924732094499514">"Праграма не ўсталявана."</string> <string name="activity_not_available" msgid="7456344436509528827">"Праграма недаступная"</string> diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml index 408f20500..f7d1d0f54 100644 --- a/res/values-bg/strings.xml +++ b/res/values-bg/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Работа"</string> <string name="activity_not_found" msgid="8071924732094499514">"Приложението не е инсталирано."</string> <string name="activity_not_available" msgid="7456344436509528827">"Приложението не е налично"</string> diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml index 775885caa..09608b542 100644 --- a/res/values-bn/strings.xml +++ b/res/values-bn/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"কাজ"</string> <string name="activity_not_found" msgid="8071924732094499514">"অ্যাপ্লিকেশান ইনস্টল করা নেই৷"</string> <string name="activity_not_available" msgid="7456344436509528827">"অ্যাপ্লিকেশান অনুপলব্ধ"</string> diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml index 565045707..65ad91e9b 100644 --- a/res/values-bs/strings.xml +++ b/res/values-bs/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Posao"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string> diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 50f92daf2..4ef9ec3a2 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Feina"</string> <string name="activity_not_found" msgid="8071924732094499514">"L\'aplicació no s\'ha instal·lat."</string> <string name="activity_not_available" msgid="7456344436509528827">"L\'aplicació no està disponible."</string> diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index 692b57d46..57c507208 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Práce"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikace není nainstalována."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikace není k dispozici."</string> diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index dc17516bc..9097ffde2 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Arbejde"</string> <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installeret."</string> <string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgængelig"</string> diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index a345babd5..1602a5356 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Arbeit"</string> <string name="activity_not_found" msgid="8071924732094499514">"App ist nicht installiert."</string> <string name="activity_not_available" msgid="7456344436509528827">"App nicht verfügbar"</string> diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 2363e6115..d80e905d8 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Εργασία"</string> <string name="activity_not_found" msgid="8071924732094499514">"Η εφαρμογή δεν έχει εγκατασταθεί."</string> <string name="activity_not_available" msgid="7456344436509528827">"Η εφαρμογή δεν είναι διαθέσιμη"</string> diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml index 0c48b5d2c..7adc2187e 100644 --- a/res/values-en-rAU/strings.xml +++ b/res/values-en-rAU/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string> <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string> diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml index 0c48b5d2c..7adc2187e 100644 --- a/res/values-en-rGB/strings.xml +++ b/res/values-en-rGB/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string> <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string> diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml index 0c48b5d2c..7adc2187e 100644 --- a/res/values-en-rIN/strings.xml +++ b/res/values-en-rIN/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"App isn\'t installed."</string> <string name="activity_not_available" msgid="7456344436509528827">"App isn\'t available"</string> diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml index d10c84b9c..960ef9381 100644 --- a/res/values-es-rUS/strings.xml +++ b/res/values-es-rUS/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string> <string name="activity_not_found" msgid="8071924732094499514">"No se instaló la aplicación."</string> <string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible."</string> diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index 09b123997..01f60e46a 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Trabajo"</string> <string name="activity_not_found" msgid="8071924732094499514">"La aplicación no está instalada."</string> <string name="activity_not_available" msgid="7456344436509528827">"La aplicación no está disponible"</string> diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml index 1e470e15a..ff121fae7 100644 --- a/res/values-et/strings.xml +++ b/res/values-et/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Töö"</string> <string name="activity_not_found" msgid="8071924732094499514">"Rakendus pole installitud."</string> <string name="activity_not_available" msgid="7456344436509528827">"Rakendus ei ole saadaval"</string> diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml index 9ba46c171..10aebe7ad 100644 --- a/res/values-eu/strings.xml +++ b/res/values-eu/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Lana"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikazioa instalatu gabe dago."</string> <string name="activity_not_available" msgid="7456344436509528827">"Ez dago erabilgarri aplikazioa"</string> diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml index 926cdb9b1..913ff48ea 100644 --- a/res/values-fa/strings.xml +++ b/res/values-fa/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"کاری"</string> <string name="activity_not_found" msgid="8071924732094499514">"برنامه نصب نشده است."</string> <string name="activity_not_available" msgid="7456344436509528827">"برنامه در دسترس نیست"</string> diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index f87441ff3..e0930b62a 100644 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Työ"</string> <string name="activity_not_found" msgid="8071924732094499514">"Sovellusta ei ole asennettu."</string> <string name="activity_not_available" msgid="7456344436509528827">"Sovellus ei ole käytettävissä"</string> diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml index 5ac514d44..50d6a06ad 100644 --- a/res/values-fr-rCA/strings.xml +++ b/res/values-fr-rCA/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Lanceur3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Travail"</string> <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string> <string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string> diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 7b4bcb1d8..65db47e8a 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Android Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"L\'application n\'est pas installée."</string> <string name="activity_not_available" msgid="7456344436509528827">"Application indisponible"</string> @@ -136,7 +135,7 @@ <string name="bottom_work_tab_user_education_title" msgid="5785851780786322825">"Retrouvez ici vos applications professionnelles"</string> <string name="bottom_work_tab_user_education_body" msgid="2818107472360579152">"Les applications professionnelles sont accompagnées d\'un badge et sont sécurisées par votre organisation. Vous pouvez les déplacer vers votre écran d\'accueil pour y accéder plus facilement."</string> <string name="work_mode_on_label" msgid="4781128097185272916">"Géré par votre organisation"</string> - <string name="work_mode_off_label" msgid="3194894777601421047">"Les notifications et les applications sont désactivées"</string> + <string name="work_mode_off_label" msgid="3194894777601421047">"Notifications et applications désactivées"</string> <string name="bottom_work_tab_user_education_close_button" msgid="4224492243977802135">"Fermer"</string> <string name="bottom_work_tab_user_education_closed" msgid="1098340939861869465">"Fermé"</string> <string name="remote_action_failed" msgid="1383965239183576790">"Échec : <xliff:g id="WHAT">%1$s</xliff:g>"</string> diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index e115a7262..ca5ba3c5e 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Traballo"</string> <string name="activity_not_found" msgid="8071924732094499514">"A aplicación non está instalada"</string> <string name="activity_not_available" msgid="7456344436509528827">"A aplicación non está dispoñible"</string> diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml index 7def3ace9..3228dea00 100644 --- a/res/values-gu/strings.xml +++ b/res/values-gu/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"કાર્યાલય"</string> <string name="activity_not_found" msgid="8071924732094499514">"ઍપ્લિકેશન ઇન્સ્ટોલ થઈ નથી."</string> <string name="activity_not_available" msgid="7456344436509528827">"ઍપ્લિકેશન ઉપલબ્ધ નથી"</string> diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml index 3b0432ee4..8fa02c016 100644 --- a/res/values-hi/strings.xml +++ b/res/values-hi/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"कार्यस्थल"</string> <string name="activity_not_found" msgid="8071924732094499514">"ऐप्लिकेशन इंस्टॉल नहीं है."</string> <string name="activity_not_available" msgid="7456344436509528827">"ऐप्लिकेशन उपलब्ध नहीं है"</string> diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml index 1caa27fea..b738bc073 100644 --- a/res/values-hr/strings.xml +++ b/res/values-hr/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Posao"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija nije instalirana."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija nije dostupna"</string> diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 3327590a4..7e26ca429 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Munka"</string> <string name="activity_not_found" msgid="8071924732094499514">"Az alkalmazás nincs telepítve."</string> <string name="activity_not_available" msgid="7456344436509528827">"Az alkalmazás nem érhető el"</string> diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index fbb657ebd..93404e6f5 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Աշխատանքային"</string> <string name="activity_not_found" msgid="8071924732094499514">"Ծրագիրը տեղադրված չէ:"</string> <string name="activity_not_available" msgid="7456344436509528827">"Հավելվածը հասանելի չէ"</string> @@ -87,7 +86,7 @@ <string name="allow_rotation_desc" msgid="8662546029078692509">"Հեռախոսը պտտելու դեպքում"</string> <string name="notification_dots_title" msgid="9062440428204120317">"Ծանուցումների կետիկներ"</string> <string name="notification_dots_desc_on" msgid="1679848116452218908">"Միացված է"</string> - <string name="notification_dots_desc_off" msgid="1760796511504341095">"Անջատած է"</string> + <string name="notification_dots_desc_off" msgid="1760796511504341095">"Անջատված է"</string> <string name="title_missing_notification_access" msgid="7503287056163941064">"Անհրաժեշտ է ծանուցման թույլտվություն"</string> <string name="msg_missing_notification_access" msgid="281113995110910548">"Ծանուցումների կետիկները ցուցադրելու համար միացրեք ծանուցումները <xliff:g id="NAME">%1$s</xliff:g>-ի համար"</string> <string name="title_change_settings" msgid="1376365968844349552">"Փոխել կարգավորումները"</string> diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml index 211f735b4..92c7accaa 100644 --- a/res/values-in/strings.xml +++ b/res/values-in/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Kantor"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikasi tidak dipasang."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikasi tidak tersedia"</string> diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml index d66ca3243..9d26c8cd4 100644 --- a/res/values-is/strings.xml +++ b/res/values-is/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Vinna"</string> <string name="activity_not_found" msgid="8071924732094499514">"Forritið er ekki uppsett."</string> <string name="activity_not_available" msgid="7456344436509528827">"Forritið er ekki í boði"</string> diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 8c4e3c528..aaceb531f 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Lavoro"</string> <string name="activity_not_found" msgid="8071924732094499514">"App non installata."</string> <string name="activity_not_available" msgid="7456344436509528827">"App non disponibile"</string> diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index c25e8795f..35256991d 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"עבודה"</string> <string name="activity_not_found" msgid="8071924732094499514">"האפליקציה לא מותקנת."</string> <string name="activity_not_available" msgid="7456344436509528827">"האפליקציה אינה זמינה"</string> diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 354f02032..dad48794a 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"仕事用"</string> <string name="activity_not_found" msgid="8071924732094499514">"このアプリはインストールされていません。"</string> <string name="activity_not_available" msgid="7456344436509528827">"このアプリは使用できません"</string> diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml index e9afef907..b348afb17 100644 --- a/res/values-ka/strings.xml +++ b/res/values-ka/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"სამუშაო"</string> <string name="activity_not_found" msgid="8071924732094499514">"აპი არ არის დაყენებული."</string> <string name="activity_not_available" msgid="7456344436509528827">"აპი მიუწვდომელია"</string> diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml index 1cd9045b7..bd25ac9b5 100644 --- a/res/values-kk/strings.xml +++ b/res/values-kk/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Жұмыс"</string> <string name="activity_not_found" msgid="8071924732094499514">"Қолданба орнатылмаған."</string> <string name="activity_not_available" msgid="7456344436509528827">"Қолданба қол жетімді емес"</string> diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml index f4a328df1..973804200 100644 --- a/res/values-km/strings.xml +++ b/res/values-km/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"ការងារ"</string> <string name="activity_not_found" msgid="8071924732094499514">"មិនបានដំឡើងកម្មវិធី។"</string> <string name="activity_not_available" msgid="7456344436509528827">"មិនមានកម្មវិធី"</string> diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml index 14c830064..67ea6a830 100644 --- a/res/values-kn/strings.xml +++ b/res/values-kn/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"ಕೆಲಸ"</string> <string name="activity_not_found" msgid="8071924732094499514">"ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸ್ಥಾಪಿಸಲಾಗಿಲ್ಲ"</string> <string name="activity_not_available" msgid="7456344436509528827">"ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ"</string> diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 58b65da03..e2767f9f3 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"업무"</string> <string name="activity_not_found" msgid="8071924732094499514">"앱이 설치되지 않았습니다."</string> <string name="activity_not_available" msgid="7456344436509528827">"앱을 사용할 수 없음"</string> diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml index 046a66253..d52446c83 100644 --- a/res/values-ky/strings.xml +++ b/res/values-ky/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Жумуш"</string> <string name="activity_not_found" msgid="8071924732094499514">"Колдонмо орнотулган эмес."</string> <string name="activity_not_available" msgid="7456344436509528827">"Колдонмо жеткиликтүү эмес"</string> diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml index afe7664d5..e76434703 100644 --- a/res/values-lo/strings.xml +++ b/res/values-lo/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"ວຽກ"</string> <string name="activity_not_found" msgid="8071924732094499514">"ແອັບຯບໍ່ໄດ້ຖືກຕິດຕັ້ງ."</string> <string name="activity_not_available" msgid="7456344436509528827">"ແອັບຯໃຊ້ບໍ່ໄດ້"</string> diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index 6571582ce..7f93ac815 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Darbas"</string> <string name="activity_not_found" msgid="8071924732094499514">"Programa neįdiegta."</string> <string name="activity_not_available" msgid="7456344436509528827">"Programa nepasiekiama"</string> diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml index 3da0fbb2e..13d88fe73 100644 --- a/res/values-lv/strings.xml +++ b/res/values-lv/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Darbs"</string> <string name="activity_not_found" msgid="8071924732094499514">"Lietotne nav instalēta."</string> <string name="activity_not_available" msgid="7456344436509528827">"Lietotne nav pieejama."</string> diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml index 9550a5f0f..ab14e8900 100644 --- a/res/values-mk/strings.xml +++ b/res/values-mk/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Стартер3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Работа"</string> <string name="activity_not_found" msgid="8071924732094499514">"Апликацијата не е инсталирана."</string> <string name="activity_not_available" msgid="7456344436509528827">"Апликацијата не е достапна"</string> diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml index 4362e7cca..a11836802 100644 --- a/res/values-ml/strings.xml +++ b/res/values-ml/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"ലോഞ്ചർ3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"ഔദ്യോഗികം"</string> <string name="activity_not_found" msgid="8071924732094499514">"അപ്ലിക്കേഷൻ ഇൻസ്റ്റാളുചെയ്തിട്ടില്ല."</string> <string name="activity_not_available" msgid="7456344436509528827">"അപ്ലിക്കേഷൻ ലഭ്യമല്ല"</string> diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml index ab02ac655..1b0e75308 100644 --- a/res/values-mn/strings.xml +++ b/res/values-mn/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Ажил"</string> <string name="activity_not_found" msgid="8071924732094499514">"Апп суугаагүй байна."</string> <string name="activity_not_available" msgid="7456344436509528827">"Апп-г ашиглах боломжгүй"</string> diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 49e38982c..f71bbd902 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"कार्य"</string> <string name="activity_not_found" msgid="8071924732094499514">"अॅप इंस्टॉल केलेला नाही."</string> <string name="activity_not_available" msgid="7456344436509528827">"अॅप उपलब्ध नाही"</string> @@ -83,7 +82,7 @@ <string name="styles_wallpaper_button_text" msgid="4342122323125579619">"शैली आणि वॉलपेपर"</string> <string name="settings_button_text" msgid="8873672322605444408">"होम सेटिंग्ज"</string> <string name="msg_disabled_by_admin" msgid="6898038085516271325">"आपल्या प्रशासकाने अक्षम केले"</string> - <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्यस्क्रीन फिरविण्यास अनुमती द्या"</string> + <string name="allow_rotation_title" msgid="7728578836261442095">"मुख्य स्क्रीन फिरविण्यास अनुमती द्या"</string> <string name="allow_rotation_desc" msgid="8662546029078692509">"फोन फिरविला जातो तेव्हा"</string> <string name="notification_dots_title" msgid="9062440428204120317">"सूचना बिंदू"</string> <string name="notification_dots_desc_on" msgid="1679848116452218908">"सुरू"</string> diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml index 7d054124f..492e6b624 100644 --- a/res/values-ms/strings.xml +++ b/res/values-ms/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Kerja"</string> <string name="activity_not_found" msgid="8071924732094499514">"Apl tidak dipasang."</string> <string name="activity_not_available" msgid="7456344436509528827">"Apl tidak tersedia"</string> diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml index fd1f7d953..78856f657 100644 --- a/res/values-my/strings.xml +++ b/res/values-my/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"ဖွင့်တင်စက်၃"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"အလုပ်"</string> <string name="activity_not_found" msgid="8071924732094499514">"အက်ပ်မထည့်သွင်းထားပါ"</string> <string name="activity_not_available" msgid="7456344436509528827">"အက်ပ်လက်လှမ်း မမှီပါ"</string> diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml index 2257367a6..356ed03c9 100644 --- a/res/values-nb/strings.xml +++ b/res/values-nb/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Jobb"</string> <string name="activity_not_found" msgid="8071924732094499514">"Appen er ikke installert."</string> <string name="activity_not_available" msgid="7456344436509528827">"Appen er ikke tilgjengelig"</string> diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml index 1e7aee9e9..58d61f93c 100644 --- a/res/values-ne/strings.xml +++ b/res/values-ne/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"कार्य"</string> <string name="activity_not_found" msgid="8071924732094499514">"अनुप्रयोग स्थापित छैन।"</string> <string name="activity_not_available" msgid="7456344436509528827">"अनुप्रयोग उपलब्ध छैन"</string> diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index ec30d8c6b..c2dfb71c7 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Werk"</string> <string name="activity_not_found" msgid="8071924732094499514">"App is niet geïnstalleerd."</string> <string name="activity_not_available" msgid="7456344436509528827">"App is niet beschikbaar"</string> diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index 4ddc90347..cc8d6641c 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"ଲଞ୍ଚର୍3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"କାମ"</string> <string name="activity_not_found" msgid="8071924732094499514">"ଆପ୍ ଇନଷ୍ଟଲ୍ ହୋଇନାହିଁ"</string> <string name="activity_not_available" msgid="7456344436509528827">"ଆପ୍ ଉପଲବ୍ଧ ନାହିଁ"</string> diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml index d4cd5be4b..24686c49d 100644 --- a/res/values-pa/strings.xml +++ b/res/values-pa/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"ਦਫ਼ਤਰ"</string> <string name="activity_not_found" msgid="8071924732094499514">"ਐਪ ਇੰਸਟੌਲ ਨਹੀਂ ਕੀਤਾ ਹੋਇਆ ਹੈ।"</string> <string name="activity_not_available" msgid="7456344436509528827">"ਐਪ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string> diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 68859605f..5d9b50e4f 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Praca"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikacja nie jest zainstalowana."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikacja niedostępna"</string> diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml index 071b71e38..e0d2c7464 100644 --- a/res/values-pt-rPT/strings.xml +++ b/res/values-pt-rPT/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Trabalho"</string> <string name="activity_not_found" msgid="8071924732094499514">"A aplicação não está instalada."</string> <string name="activity_not_available" msgid="7456344436509528827">"A aplicação não está disponível"</string> diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml index e88ff18b4..bb4834f69 100644 --- a/res/values-pt/strings.xml +++ b/res/values-pt/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Tela de início 3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Trabalho"</string> <string name="activity_not_found" msgid="8071924732094499514">"O app não está instalado."</string> <string name="activity_not_available" msgid="7456344436509528827">"O app não está disponível"</string> diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml index 1251d7e3f..f8a61f11f 100644 --- a/res/values-ro/strings.xml +++ b/res/values-ro/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplicația nu este instalată."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplicația nu este disponibilă"</string> diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 23b00d093..1cc699f1c 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Работа"</string> <string name="activity_not_found" msgid="8071924732094499514">"Приложение удалено"</string> <string name="activity_not_available" msgid="7456344436509528827">"Приложение недоступно"</string> diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml index ef99a59e4..2f9dc423a 100644 --- a/res/values-si/strings.xml +++ b/res/values-si/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"කාර්යාලය"</string> <string name="activity_not_found" msgid="8071924732094499514">"යෙදුම ස්ථාපනය කර නැත."</string> <string name="activity_not_available" msgid="7456344436509528827">"යෙදුම නොතිබේ"</string> diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 340a128d2..5bbf7c3cd 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Pracovné"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikácia nie je nainštalovaná."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikácia nie je k dispozícii"</string> diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 044a4b42b..417151b96 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Služba"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikacija ni nameščena."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikacija ni na voljo"</string> diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml index 48ae26ed8..7f2567c60 100644 --- a/res/values-sq/strings.xml +++ b/res/values-sq/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Nisësi3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Puna"</string> <string name="activity_not_found" msgid="8071924732094499514">"Aplikacioni nuk është i instaluar."</string> <string name="activity_not_available" msgid="7456344436509528827">"Aplikacioni nuk mundësohet"</string> @@ -41,8 +40,8 @@ <string name="all_apps_search_market_message" msgid="1366263386197059176">"Kërko për më shumë aplikacione"</string> <string name="label_application" msgid="8531721983832654978">"Aplikacioni"</string> <string name="notifications_header" msgid="1404149926117359025">"Njoftimet"</string> - <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Prek dhe mbaj prekur për të zgjedhur një shkurtore."</string> - <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Prek dy herë dhe mbaj prekur për të zgjedhur një shkurtore ose për të përdorur veprimet e personalizuara."</string> + <string name="long_press_shortcut_to_add" msgid="4524750017792716791">"Prek dhe mbaj të shtypur për të zgjedhur një shkurtore."</string> + <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"Prek dy herë dhe mbaj të shtypur për të zgjedhur një shkurtore ose për të përdorur veprimet e personalizuara."</string> <string name="out_of_space" msgid="4691004494942118364">"Nuk ka më hapësirë në këtë ekran bazë."</string> <string name="hotseat_out_of_space" msgid="7448809638125333693">"Nuk ka më hapësirë në tabakanë \"Të preferuarat\""</string> <string name="all_apps_button_label" msgid="8130441508702294465">"Lista e aplikacioneve"</string> diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml index 4e5259268..5e2c9545f 100644 --- a/res/values-sr/strings.xml +++ b/res/values-sr/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"Апликација није инсталирана."</string> <string name="activity_not_available" msgid="7456344436509528827">"Апликација није доступна"</string> diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index e7400a6d0..7df507b08 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Arbete"</string> <string name="activity_not_found" msgid="8071924732094499514">"Appen är inte installerad."</string> <string name="activity_not_available" msgid="7456344436509528827">"Appen är inte tillgänglig"</string> diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml index 6f33c4f04..445e382f2 100644 --- a/res/values-sw/strings.xml +++ b/res/values-sw/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Kazini"</string> <string name="activity_not_found" msgid="8071924732094499514">"Programu haijasakinishwa."</string> <string name="activity_not_available" msgid="7456344436509528827">"Programu haipatikani"</string> diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml index c926bc185..6e933a12e 100644 --- a/res/values-ta/strings.xml +++ b/res/values-ta/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"பணியிடம்"</string> <string name="activity_not_found" msgid="8071924732094499514">"ஆப்ஸ் நிறுவப்படவில்லை."</string> <string name="activity_not_available" msgid="7456344436509528827">"ஆப்ஸ் இல்லை"</string> @@ -36,7 +35,7 @@ <string name="add_item_request_drag_hint" msgid="5899764264480397019">"நீங்களே சேர்க்க, தொட்டுப் பிடித்திருக்கவும்"</string> <string name="place_automatically" msgid="8064208734425456485">"தானாகவே சேர்"</string> <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"பயன்பாடுகளில் தேடுக"</string> - <string name="all_apps_loading_message" msgid="5813968043155271636">"பயன்பாடுகளை ஏற்றுகிறது…"</string> + <string name="all_apps_loading_message" msgid="5813968043155271636">"ஆப்ஸை ஏற்றுகிறது…"</string> <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் பயன்பாடுகள் இல்லை"</string> <string name="all_apps_search_market_message" msgid="1366263386197059176">"கூடுதல் பயன்பாடுகளைத் தேடு"</string> <string name="label_application" msgid="8531721983832654978">"ஆப்ஸ்"</string> @@ -45,7 +44,7 @@ <string name="long_accessible_way_to_add_shortcut" msgid="3327314059613154633">"ஷார்ட்கட்டைச் சேர்க்க, இருமுறை தட்டிப் பிடித்திருக்கவும் (அ) தனிப்பயன் செயல்களைப் பயன்படுத்தவும்."</string> <string name="out_of_space" msgid="4691004494942118364">"முகப்புத் திரையில் இடமில்லை."</string> <string name="hotseat_out_of_space" msgid="7448809638125333693">"பிடித்தவை ட்ரேயில் இடமில்லை"</string> - <string name="all_apps_button_label" msgid="8130441508702294465">"பயன்பாடுகளின் பட்டியல்"</string> + <string name="all_apps_button_label" msgid="8130441508702294465">"ஆப்ஸின் பட்டியல்"</string> <string name="all_apps_button_personal_label" msgid="1315764287305224468">"தனிப்பட்ட ஆப்ஸ் பட்டியல்"</string> <string name="all_apps_button_work_label" msgid="7270707118948892488">"பணி ஆப்ஸ் பட்டியல்"</string> <string name="all_apps_home_button_label" msgid="252062713717058851">"முகப்பு"</string> diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml index c8f70fe05..1f8d3b8c4 100644 --- a/res/values-te/strings.xml +++ b/res/values-te/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"కార్యాలయం"</string> <string name="activity_not_found" msgid="8071924732094499514">"యాప్ ఇన్స్టాల్ చేయబడలేదు."</string> <string name="activity_not_available" msgid="7456344436509528827">"యాప్ అందుబాటులో లేదు"</string> @@ -53,7 +52,7 @@ <string name="uninstall_drop_target_label" msgid="4722034217958379417">"అన్ఇన్స్టాల్ చేయి"</string> <string name="app_info_drop_target_label" msgid="692894985365717661">"యాప్ సమాచారం"</string> <string name="install_drop_target_label" msgid="2539096853673231757">"ఇన్స్టాల్ చేయండి"</string> - <string name="permlab_install_shortcut" msgid="5632423390354674437">"సత్వరమార్గాలను ఇన్స్టాల్ చేయడం"</string> + <string name="permlab_install_shortcut" msgid="5632423390354674437">"షార్ట్కట్లను ఇన్స్టాల్ చేయడం"</string> <string name="permdesc_install_shortcut" msgid="923466509822011139">"వినియోగదారు ప్రమేయం లేకుండా సత్వరమార్గాలను జోడించడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> <string name="permlab_read_settings" msgid="1941457408239617576">"హోమ్ సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడం"</string> <string name="permdesc_read_settings" msgid="5833423719057558387">"హోమ్లో సెట్టింగ్లు మరియు సత్వరమార్గాలను చదవడానికి అనువర్తనాన్ని అనుమతిస్తుంది."</string> diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml index 03c02ad50..5ab9b860e 100644 --- a/res/values-th/strings.xml +++ b/res/values-th/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"งาน"</string> <string name="activity_not_found" msgid="8071924732094499514">"ไม่ได้ติดตั้งแอป"</string> <string name="activity_not_available" msgid="7456344436509528827">"แอปไม่พร้อมใช้งาน"</string> diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml index 0df94c710..de4863a0c 100644 --- a/res/values-tl/strings.xml +++ b/res/values-tl/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Trabaho"</string> <string name="activity_not_found" msgid="8071924732094499514">"Hindi naka-install ang app."</string> <string name="activity_not_available" msgid="7456344436509528827">"Hindi available ang app"</string> diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 56f8447bf..1b2b3d1d6 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"İş"</string> <string name="activity_not_found" msgid="8071924732094499514">"Uygulama yüklü değil."</string> <string name="activity_not_available" msgid="7456344436509528827">"Uygulama kullanılamıyor"</string> diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 13ba70181..622cdccbc 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Робоча папка"</string> <string name="activity_not_found" msgid="8071924732094499514">"Додаток видалено."</string> <string name="activity_not_available" msgid="7456344436509528827">"Додаток недоступний"</string> diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml index 4f776701e..74b5eaac4 100644 --- a/res/values-ur/strings.xml +++ b/res/values-ur/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"دفتری"</string> <string name="activity_not_found" msgid="8071924732094499514">"ایپ انسٹال نہیں ہے۔"</string> <string name="activity_not_available" msgid="7456344436509528827">"ایپ دستیاب نہیں ہے"</string> diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml index 69084d704..dac1ac92f 100644 --- a/res/values-uz/strings.xml +++ b/res/values-uz/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Ishga oid"</string> <string name="activity_not_found" msgid="8071924732094499514">"Ilova o‘rnatilmadi."</string> <string name="activity_not_available" msgid="7456344436509528827">"Ilova mavjud emas"</string> diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml index 71decfca6..4dc58b994 100644 --- a/res/values-vi/strings.xml +++ b/res/values-vi/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Trình chạy 3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"Ứng dụng chưa được cài đặt."</string> <string name="activity_not_available" msgid="7456344436509528827">"Ứng dụng không có sẵn"</string> @@ -86,7 +85,7 @@ <string name="allow_rotation_title" msgid="7728578836261442095">"Cho phép xoay Màn hình chính"</string> <string name="allow_rotation_desc" msgid="8662546029078692509">"Khi xoay điện thoại"</string> <string name="notification_dots_title" msgid="9062440428204120317">"Dấu chấm thông báo"</string> - <string name="notification_dots_desc_on" msgid="1679848116452218908">"Bật"</string> + <string name="notification_dots_desc_on" msgid="1679848116452218908">"Đang bật"</string> <string name="notification_dots_desc_off" msgid="1760796511504341095">"Tắt"</string> <string name="title_missing_notification_access" msgid="7503287056163941064">"Cần quyền truy cập thông báo"</string> <string name="msg_missing_notification_access" msgid="281113995110910548">"Để hiển thị Dấu chấm thông báo, hãy bật thông báo ứng dụng cho <xliff:g id="NAME">%1$s</xliff:g>"</string> diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 9804af1e8..047862735 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Work"</string> <string name="activity_not_found" msgid="8071924732094499514">"未安装该应用。"</string> <string name="activity_not_available" msgid="7456344436509528827">"应用不可用"</string> diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml index e7377440c..ed53c328c 100644 --- a/res/values-zh-rHK/strings.xml +++ b/res/values-zh-rHK/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"工作"</string> <string name="activity_not_found" msgid="8071924732094499514">"尚未安裝應用程式。"</string> <string name="activity_not_available" msgid="7456344436509528827">"目前無法使用這個應用程式"</string> diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index e971b69f1..14f2e062d 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Launcher3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"公司"</string> <string name="activity_not_found" msgid="8071924732094499514">"應用程式未安裝。"</string> <string name="activity_not_available" msgid="7456344436509528827">"應用程式目前無法使用"</string> diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml index 83178fe73..b937764ac 100644 --- a/res/values-zu/strings.xml +++ b/res/values-zu/strings.xml @@ -20,7 +20,6 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" msgid="649227358658669779">"Isiqalisi3"</string> - <string name="folder_name" msgid="7371454440695724752"></string> <string name="work_folder_name" msgid="3753320833950115786">"Umsebenzi"</string> <string name="activity_not_found" msgid="8071924732094499514">"Uhlelo lokusebenza alufakiwe."</string> <string name="activity_not_available" msgid="7456344436509528827">"Uhlelo lokusebenza alutholakali"</string> diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 69b8c8a22..e76b89826 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -115,7 +115,8 @@ <attr name="numFolderColumns" format="integer" /> <!-- numHotseatIcons defaults to numColumns, if not specified --> <attr name="numHotseatIcons" format="integer" /> - + <!-- numAllAppsColumns defaults to numColumns, if not specified --> + <attr name="numAllAppsColumns" format="integer" /> <attr name="defaultLayoutId" format="reference" /> <attr name="demoModeLayoutId" format="reference" /> </declare-styleable> @@ -131,6 +132,12 @@ <attr name="iconTextSize" format="float" /> <!-- If true, this display option is used to determine the default grid --> <attr name="canBeDefault" format="boolean" /> + + <!-- The following values are only enabled if grid is supported. --> + <!-- allAppsIconSize defaults to iconSize, if not specified --> + <attr name="allAppsIconSize" format="float" /> + <!-- allAppsIconTextSize defaults to iconTextSize, if not specified --> + <attr name="allAppsIconTextSize" format="float" /> </declare-styleable> <declare-styleable name="CellLayout"> diff --git a/res/values/strings.xml b/res/values/strings.xml index 13e096c95..9d9c2e8de 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -23,8 +23,6 @@ <!-- Application name --> <string name="app_name">Launcher3</string> - <!-- Default folder name --> - <string name="folder_name"></string> <!-- Work folder name --> <string name="work_folder_name">Work</string> <!-- Displayed when user selects a shortcut for an app that was uninstalled [CHAR_LIMIT=none]--> @@ -105,6 +103,9 @@ <!-- Label for install drop target. [CHAR_LIMIT=20] --> <string name="install_drop_target_label">Install</string> + <!-- Label for install dismiss prediction. --> + <string translatable="false" name="dismiss_prediction_label">Dismiss prediction</string> + <!-- Permissions: --> <skip /> <!-- Permission short label --> diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index ab3927466..bc936b7b2 100644 --- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -15,14 +15,13 @@ import android.graphics.Color; import android.os.Process; import android.os.UserHandle; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppFilter; import com.android.launcher3.AppInfo; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherProvider; import com.android.launcher3.icons.IconCache; diff --git a/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java new file mode 100644 index 000000000..c08e198a8 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/util/IntArrayTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** + * Robolectric unit tests for {@link IntArray} + */ +@RunWith(RobolectricTestRunner.class) +public class IntArrayTest { + + @Test + public void concatAndParseString() { + int[] array = new int[] {0, 2, 3, 9}; + String concat = IntArray.wrap(array).toConcatString(); + + int[] parsed = IntArray.fromConcatString(concat).toArray(); + assertThat(array).isEqualTo(parsed); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java index f846de5e5..8513353dc 100644 --- a/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java +++ b/robolectric_tests/src/com/android/launcher3/util/IntSetTest.java @@ -21,7 +21,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 65f9d6bc0..8fddf3cff 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -86,7 +86,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch // Type of popups which should be kept open during launcher rebind public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET - | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE; + | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE; // Usually we show the back button when a floating view is open. Instead, hide for these types. public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java index d884049f5..c8e7619c7 100644 --- a/src/com/android/launcher3/AppInfo.java +++ b/src/com/android/launcher3/AppInfo.java @@ -29,11 +29,19 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageManagerHelper; +import java.util.Comparator; + /** * Represents an app in AllAppsView. */ public class AppInfo extends ItemInfoWithIcon { + public static AppInfo[] EMPTY_ARRAY = new AppInfo[0]; + public static Comparator<AppInfo> COMPONENT_KEY_COMPARATOR = (a, b) -> { + int uc = a.user.hashCode() - b.user.hashCode(); + return uc != 0 ? uc : a.componentName.compareTo(b.componentName); + }; + /** * The intent used to start the application. */ @@ -41,6 +49,9 @@ public class AppInfo extends ItemInfoWithIcon { public ComponentName componentName; + // Section name used for indexing. + public String sectionName = ""; + public AppInfo() { itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; } @@ -74,6 +85,8 @@ public class AppInfo extends ItemInfoWithIcon { componentName = info.componentName; title = Utilities.trim(info.title); intent = new Intent(info.intent); + user = info.user; + runtimeStatusFlags = info.runtimeStatusFlags; } @Override @@ -116,4 +129,9 @@ public class AppInfo extends ItemInfoWithIcon { info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON; } } + + @Override + public AppInfo clone() { + return new AppInfo(this); + } } diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 9724869bf..ac4396778 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -38,11 +38,15 @@ import android.util.Pair; import android.util.Patterns; import android.util.Xml; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherProvider.SqlArguments; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Thunk; import org.xmlpull.v1.XmlPullParser; @@ -72,7 +76,7 @@ public class AutoInstallsLayout { static AutoInstallsLayout get(Context context, AppWidgetHost appWidgetHost, LayoutParserCallback callback) { - Pair<String, Resources> customizationApkInfo = Utilities.findSystemApk( + Pair<String, Resources> customizationApkInfo = PackageManagerHelper.findSystemApk( ACTION_LAUNCHER_CUSTOMIZATION, context.getPackageManager()); if (customizationApkInfo == null) { return null; @@ -83,7 +87,7 @@ public class AutoInstallsLayout { // Try with grid size and hotseat count String layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES_WITH_HOSTEAT, - grid.numColumns, grid.numRows, grid.numHotseatIcons); + grid.numColumns, grid.numRows, grid.numHotseatIcons); int layoutId = targetRes.getIdentifier(layoutName, "xml", pkg); // Try with only grid size @@ -91,7 +95,7 @@ public class AutoInstallsLayout { Log.d(TAG, "Formatted layout: " + layoutName + " not found. Trying layout without hosteat"); layoutName = String.format(Locale.ENGLISH, FORMATTED_LAYOUT_RES, - grid.numColumns, grid.numRows); + grid.numColumns, grid.numRows); layoutId = targetRes.getIdentifier(layoutName, "xml", pkg); } @@ -116,6 +120,7 @@ public class AutoInstallsLayout { private static final String TAG_AUTO_INSTALL = "autoinstall"; private static final String TAG_FOLDER = "folder"; private static final String TAG_APPWIDGET = "appwidget"; + protected static final String TAG_SEARCH_WIDGET = "searchwidget"; private static final String TAG_SHORTCUT = "shortcut"; private static final String TAG_EXTRA = "extra"; @@ -147,8 +152,10 @@ public class AutoInstallsLayout { private static final String HOTSEAT_CONTAINER_NAME = Favorites.containerToString(Favorites.CONTAINER_HOTSEAT); - @Thunk final Context mContext; - @Thunk final AppWidgetHost mAppWidgetHost; + @Thunk + final Context mContext; + @Thunk + final AppWidgetHost mAppWidgetHost; protected final LayoutParserCallback mCallback; protected final PackageManager mPackageManager; @@ -160,7 +167,8 @@ public class AutoInstallsLayout { private final int mColumnCount; private final int[] mTemp = new int[2]; - @Thunk final ContentValues mValues; + @Thunk + final ContentValues mValues; protected final String mRootTag; protected SQLiteDatabase mDb; @@ -244,7 +252,7 @@ public class AutoInstallsLayout { */ protected int parseAndAddNode( XmlPullParser parser, ArrayMap<String, TagParser> tagParserMap, IntArray screenIds) - throws XmlPullParserException, IOException { + throws XmlPullParserException, IOException { if (TAG_INCLUDE.equals(parser.getName())) { final int resId = getAttributeResourceValue(parser, ATTR_WORKSPACE, 0); @@ -315,6 +323,7 @@ public class AutoInstallsLayout { parsers.put(TAG_AUTO_INSTALL, new AutoInstallParser()); parsers.put(TAG_FOLDER, new FolderParser()); parsers.put(TAG_APPWIDGET, new PendingWidgetParser()); + parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser()); parsers.put(TAG_SHORTCUT, new ShortcutParser(mSourceRes)); return parsers; } @@ -347,15 +356,15 @@ public class AutoInstallsLayout { info = mPackageManager.getActivityInfo(cn, 0); } catch (PackageManager.NameNotFoundException nnfe) { String[] packages = mPackageManager.currentToCanonicalPackageNames( - new String[] { packageName }); + new String[]{packageName}); cn = new ComponentName(packages[0], className); info = mPackageManager.getActivityInfo(cn, 0); } final Intent intent = new Intent(Intent.ACTION_MAIN, null) - .addCategory(Intent.CATEGORY_LAUNCHER) - .setComponent(cn) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(cn) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); return addShortcut(info.loadLabel(mPackageManager).toString(), intent, Favorites.ITEM_TYPE_APPLICATION); @@ -393,10 +402,10 @@ public class AutoInstallsLayout { mValues.put(Favorites.RESTORED, WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON); final Intent intent = new Intent(Intent.ACTION_MAIN, null) - .addCategory(Intent.CATEGORY_LAUNCHER) - .setComponent(new ComponentName(packageName, className)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + .addCategory(Intent.CATEGORY_LAUNCHER) + .setComponent(new ComponentName(packageName, className)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); return addShortcut(mContext.getString(R.string.package_state_unknown), intent, Favorites.ITEM_TYPE_APPLICATION); } @@ -444,7 +453,7 @@ public class AutoInstallsLayout { mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); + Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); return addShortcut(mSourceRes.getString(titleResId), intent, Favorites.ITEM_TYPE_SHORTCUT); } @@ -469,12 +478,22 @@ public class AutoInstallsLayout { */ protected class PendingWidgetParser implements TagParser { - @Override - public int parseAndAdd(XmlPullParser parser) - throws XmlPullParserException, IOException { + @Nullable + public ComponentName getComponentName(XmlPullParser parser) { final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME); final String className = getAttributeValue(parser, ATTR_CLASS_NAME); if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) { + return null; + } + return new ComponentName(packageName, className); + } + + + @Override + public int parseAndAdd(XmlPullParser parser) + throws XmlPullParserException, IOException { + ComponentName cn = getComponentName(parser); + if (cn == null) { if (LOGD) Log.d(TAG, "Skipping invalid <appwidget> with no component"); return -1; } @@ -505,16 +524,15 @@ public class AutoInstallsLayout { throw new RuntimeException("Widgets can contain only extras"); } } - - return verifyAndInsert(new ComponentName(packageName, className), extras); + return verifyAndInsert(cn, extras); } protected int verifyAndInsert(ComponentName cn, Bundle extras) { mValues.put(Favorites.APPWIDGET_PROVIDER, cn.flattenToString()); mValues.put(Favorites.RESTORED, - LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | - LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | - LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID + | LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY + | LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); mValues.put(Favorites._ID, mCallback.generateNewItemId()); if (!extras.isEmpty()) { mValues.put(Favorites.INTENT, new Intent().putExtras(extras).toUri(0)); @@ -529,6 +547,23 @@ public class AutoInstallsLayout { } } + protected class SearchWidgetParser extends PendingWidgetParser { + @Override + @Nullable + public ComponentName getComponentName(XmlPullParser parser) { + return QsbContainerView.getSearchComponentName(mContext); + } + + @Override + protected int verifyAndInsert(ComponentName cn, Bundle extras) { + mValues.put(Favorites.OPTIONS, LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET); + int flags = mValues.getAsInteger(Favorites.RESTORED) + | WorkspaceItemInfo.FLAG_RESTORE_STARTED; + mValues.put(Favorites.RESTORED, flags); + return super.verifyAndInsert(cn, extras); + } + } + protected class FolderParser implements TagParser { private final ArrayMap<String, TagParser> mFolderElements; @@ -548,7 +583,7 @@ public class AutoInstallsLayout { if (titleResId != 0) { title = mSourceRes.getString(titleResId); } else { - title = mContext.getResources().getString(R.string.folder_name); + title = ""; } mValues.put(Favorites.TITLE, title); @@ -680,7 +715,8 @@ public class AutoInstallsLayout { int insertAndCheck(SQLiteDatabase db, ContentValues values); } - @Thunk static void copyInteger(ContentValues from, ContentValues to, String key) { + @Thunk + static void copyInteger(ContentValues from, ContentValues to, String key) { to.put(key, from.getAsInteger(key)); } } diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 64550560f..b28077f7c 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -27,6 +27,8 @@ import android.content.Intent; import android.content.res.Configuration; import android.view.ContextThemeWrapper; +import androidx.annotation.IntDef; + import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogUtils; @@ -44,8 +46,6 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.util.ArrayList; -import androidx.annotation.IntDef; - public abstract class BaseActivity extends Activity implements UserEventDelegate, LogStateProvider, ActivityContext { @@ -265,12 +265,13 @@ public abstract class BaseActivity extends Activity } } - protected void dumpMisc(PrintWriter writer) { - writer.println(" deviceProfile isTransposed=" + getDeviceProfile().isVerticalBarLayout()); - writer.println(" orientation=" + getResources().getConfiguration().orientation); - writer.println(" mSystemUiController: " + mSystemUiController); - writer.println(" mActivityFlags: " + mActivityFlags); - writer.println(" mForceInvisible: " + mForceInvisible); + protected void dumpMisc(String prefix, PrintWriter writer) { + writer.println(prefix + "deviceProfile isTransposed=" + + getDeviceProfile().isVerticalBarLayout()); + writer.println(prefix + "orientation=" + getResources().getConfiguration().orientation); + writer.println(prefix + "mSystemUiController: " + mSystemUiController); + writer.println(prefix + "mActivityFlags: " + mActivityFlags); + writer.println(prefix + "mForceInvisible: " + mForceInvisible); } public static <T extends BaseActivity> T fromContext(Context context) { diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index f0b3afd14..994ba65a4 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -30,17 +30,17 @@ import android.view.ActionMode; import android.view.View; import android.widget.Toast; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.shortcuts.DeepShortcutManager; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.uioverrides.DisplayRotationListener; import com.android.launcher3.uioverrides.WallpaperColorInfo; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Themes; -import androidx.annotation.Nullable; - /** * Extension of BaseActivity allowing support for drag-n-drop */ @@ -120,6 +120,10 @@ public abstract class BaseDraggingActivity extends BaseActivity public abstract View getRootView(); + public void returnToHomescreen() { + // no-op + } + public Rect getViewBounds(View v) { int[] pos = new int[2]; v.getLocationOnScreen(pos); @@ -135,7 +139,7 @@ public abstract class BaseDraggingActivity extends BaseActivity public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item, @Nullable String sourceContainer) { - if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) { + if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); return false; } @@ -237,14 +241,14 @@ public abstract class BaseDraggingActivity extends BaseActivity protected void onDeviceProfileInitiated() { if (mDeviceProfile.isVerticalBarLayout()) { mRotationListener.enable(); - mDeviceProfile.updateIsSeascape(getWindowManager()); + mDeviceProfile.updateIsSeascape(this); } else { mRotationListener.disable(); } } private void onDeviceRotationChanged() { - if (mDeviceProfile.updateIsSeascape(getWindowManager())) { + if (mDeviceProfile.updateIsSeascape(this)) { reapplyUi(); } } diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 09fb2446d..976ccd52e 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -2702,6 +2702,14 @@ public class CellLayout extends ViewGroup implements Transposable { } } + /** + * Sets the position to the provided point + */ + public void setXY(Point point) { + cellX = point.x; + cellY = point.y; + } + public String toString() { return "(" + this.cellX + ", " + this.cellY + ")"; } diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java index 75297f63b..af8559477 100644 --- a/src/com/android/launcher3/DefaultLayoutParser.java +++ b/src/com/android/launcher3/DefaultLayoutParser.java @@ -14,13 +14,16 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; + import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.util.Thunk; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; import java.net.URISyntaxException; import java.util.List; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; /** * Implements the layout parser with rules for internal layouts and partner layouts. @@ -55,7 +58,8 @@ public class DefaultLayoutParser extends AutoInstallsLayout { return getFolderElementsMap(mSourceRes); } - @Thunk ArrayMap<String, TagParser> getFolderElementsMap(Resources res) { + @Thunk + ArrayMap<String, TagParser> getFolderElementsMap(Resources res) { ArrayMap<String, TagParser> parsers = new ArrayMap<>(); parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser()); parsers.put(TAG_SHORTCUT, new UriShortcutParser(res)); @@ -67,6 +71,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { ArrayMap<String, TagParser> parsers = new ArrayMap<>(); parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser()); parsers.put(TAG_APPWIDGET, new AppWidgetParser()); + parsers.put(TAG_SEARCH_WIDGET, new SearchWidgetParser()); parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes)); parsers.put(TAG_RESOLVE, new ResolveParser()); parsers.put(TAG_FOLDER, new MyFolderParser()); @@ -229,7 +234,8 @@ public class DefaultLayoutParser extends AutoInstallsLayout { /** * A parser which adds a folder whose contents come from partner apk. */ - @Thunk class PartnerFolderParser implements TagParser { + @Thunk + class PartnerFolderParser implements TagParser { @Override public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, @@ -255,7 +261,8 @@ public class DefaultLayoutParser extends AutoInstallsLayout { /** * An extension of FolderParser which allows adding items from a different xml. */ - @Thunk class MyFolderParser extends FolderParser { + @Thunk + class MyFolderParser extends FolderParser { @Override public int parseAndAdd(XmlPullParser parser) throws XmlPullParserException, @@ -281,7 +288,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { mPackageManager.getReceiverInfo(cn, 0); } catch (Exception e) { String[] packages = mPackageManager.currentToCanonicalPackageNames( - new String[] { cn.getPackageName() }); + new String[]{cn.getPackageName()}); cn = new ComponentName(packages[0], cn.getClassName()); try { mPackageManager.getReceiverInfo(cn, 0); diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 883e8c642..44c307058 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -24,12 +24,12 @@ import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.view.Surface; -import android.view.WindowManager; import com.android.launcher3.CellLayout.ContainerType; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.IconNormalizer; +import com.android.launcher3.util.DefaultDisplay; public class DeviceProfile { @@ -307,11 +307,16 @@ public class DeviceProfile { updateAvailableFolderCellDimensions(dm, res); } + /** + * Updating the iconSize affects many aspects of the launcher layout, such as: iconSizePx, + * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, + * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. + */ private void updateIconSize(float scale, Resources res, DisplayMetrics dm) { // Workspace final boolean isVerticalLayout = isVerticalBarLayout(); - float invIconSizePx = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; - iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizePx, dm) * scale)); + float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; + iconSizePx = Math.max(1, (int) (ResourceUtils.pxFromDp(invIconSizeDp, dm) * scale)); iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, dm) * scale); iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale); @@ -329,12 +334,19 @@ public class DeviceProfile { cellWidthPx = iconSizePx + iconDrawablePaddingPx; // All apps - allAppsIconTextSizePx = iconTextSizePx; - allAppsIconSizePx = iconSizePx; - allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; - allAppsCellHeightPx = getCellSize().y; + if (allAppsHasDifferentNumColumns()) { + allAppsIconSizePx = ResourceUtils.pxFromDp(inv.allAppsIconSize, dm); + allAppsIconTextSizePx = Utilities.pxFromSp(inv.allAppsIconTextSize, dm); + allAppsCellHeightPx = getCellSize(inv.numAllAppsColumns, inv.numAllAppsColumns).y; + allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx; + } else { + allAppsIconSizePx = iconSizePx; + allAppsIconTextSizePx = iconTextSizePx; + allAppsIconDrawablePaddingPx = iconDrawablePaddingPx; + allAppsCellHeightPx = getCellSize().y; + } - if (isVerticalLayout) { + if (isVerticalBarLayout()) { // Always hide the Workspace text with vertical bar layout. adjustToHideWorkspaceLabels(); } @@ -419,14 +431,18 @@ public class DeviceProfile { } public Point getCellSize() { + return getCellSize(inv.numColumns, inv.numRows); + } + + private Point getCellSize(int numColumns, int numRows) { Point result = new Point(); // Since we are only concerned with the overall padding, layout direction does // not matter. Point padding = getTotalWorkspacePadding(); result.x = calculateCellWidth(availableWidthPx - padding.x - - cellLayoutPaddingLeftRightPx * 2, inv.numColumns); + - cellLayoutPaddingLeftRightPx * 2, numColumns); result.y = calculateCellHeight(availableHeightPx - padding.y - - cellLayoutBottomPaddingPx, inv.numRows); + - cellLayoutBottomPaddingPx, numRows); return result; } @@ -542,11 +558,19 @@ public class DeviceProfile { } /** + * Returns true when the number of workspace columns and all apps columns differs. + */ + private boolean allAppsHasDifferentNumColumns() { + return inv.numAllAppsColumns != inv.numColumns; + } + + /** * Updates orientation information and returns true if it has changed from the previous value. */ - public boolean updateIsSeascape(WindowManager wm) { + public boolean updateIsSeascape(Context context) { if (isVerticalBarLayout()) { - boolean isSeascape = wm.getDefaultDisplay().getRotation() == Surface.ROTATION_270; + boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation + == Surface.ROTATION_270; if (mIsSeascape != isSeascape) { mIsSeascape = isSeascape; return true; diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index b747d62ae..e2b7b68a9 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -50,9 +50,9 @@ public class FolderInfo extends ItemInfo { /** * The apps and shortcuts */ - public ArrayList<WorkspaceItemInfo> contents = new ArrayList<WorkspaceItemInfo>(); + public ArrayList<WorkspaceItemInfo> contents = new ArrayList<>(); - ArrayList<FolderListener> listeners = new ArrayList<FolderListener>(); + private ArrayList<FolderListener> mListeners = new ArrayList<>(); public FolderInfo() { itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER; @@ -72,10 +72,10 @@ public class FolderInfo extends ItemInfo { * Add an app or shortcut for a specified rank. */ public void add(WorkspaceItemInfo item, int rank, boolean animate) { - rank = Utilities.boundToRange(rank, 0, contents.size()); + rank = Utilities.boundToRange(rank, 0, contents.size() + 1); contents.add(rank, item); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onAdd(item, rank); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onAdd(item, rank); } itemsChanged(animate); } @@ -87,53 +87,37 @@ public class FolderInfo extends ItemInfo { */ public void remove(WorkspaceItemInfo item, boolean animate) { contents.remove(item); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onRemove(item); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onRemove(item); } itemsChanged(animate); } - public void setTitle(CharSequence title) { - this.title = title; - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onTitleChanged(title); - } - } - @Override public void onAddToDatabase(ContentWriter writer) { super.onAddToDatabase(writer); writer.put(LauncherSettings.Favorites.TITLE, title) .put(LauncherSettings.Favorites.OPTIONS, options); - } public void addListener(FolderListener listener) { - listeners.add(listener); + mListeners.add(listener); } public void removeListener(FolderListener listener) { - listeners.remove(listener); + mListeners.remove(listener); } public void itemsChanged(boolean animate) { - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onItemsChanged(animate); - } - } - - public void prepareAutoUpdate() { - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).prepareAutoUpdate(); + for (int i = 0; i < mListeners.size(); i++) { + mListeners.get(i).onItemsChanged(animate); } } public interface FolderListener { public void onAdd(WorkspaceItemInfo item, int rank); public void onRemove(WorkspaceItemInfo item); - public void onTitleChanged(CharSequence title); public void onItemsChanged(boolean animate); - public void prepareAutoUpdate(); } public boolean hasOption(int optionFlag) { diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index 00acdcd2f..03ee707af 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -105,8 +105,7 @@ public class Hotseat extends CellLayout implements LogContainerProvider, Insetta @Override public boolean onTouchEvent(MotionEvent event) { - // Don't let if follow through to workspace - return true; + return event.getY() > getCellHeight(); } @Override diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index 670cd2877..fb80537ab 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -16,10 +16,11 @@ package com.android.launcher3; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -29,8 +30,6 @@ import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Handler; -import android.os.Message; import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; @@ -39,6 +38,8 @@ import android.util.Base64; import android.util.Log; import android.util.Pair; +import androidx.annotation.WorkerThread; + import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.icons.BitmapInfo; @@ -65,9 +66,6 @@ import java.util.Set; public class InstallShortcutReceiver extends BroadcastReceiver { - private static final int MSG_ADD_TO_QUEUE = 1; - private static final int MSG_FLUSH_QUEUE = 2; - public static final int FLAG_ACTIVITY_PAUSED = 1; public static final int FLAG_LOADER_RUNNING = 2; public static final int FLAG_DRAG_AND_DROP = 4; @@ -100,66 +98,56 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; public static final int NEW_SHORTCUT_STAGGER_DELAY = 85; - private static final Handler sHandler = new Handler(LauncherModel.getWorkerLooper()) { - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ADD_TO_QUEUE: { - Pair<Context, PendingInstallShortcutInfo> pair = - (Pair<Context, PendingInstallShortcutInfo>) msg.obj; - String encoded = pair.second.encodeToString(); - SharedPreferences prefs = Utilities.getPrefs(pair.first); - Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); - strings = (strings != null) ? new HashSet<>(strings) : new HashSet<String>(1); - strings.add(encoded); - prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply(); - return; - } - case MSG_FLUSH_QUEUE: { - Context context = (Context) msg.obj; - LauncherModel model = LauncherAppState.getInstance(context).getModel(); - if (model.getCallback() == null) { - // Launcher not loaded - return; - } - - ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>(); - SharedPreferences prefs = Utilities.getPrefs(context); - Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); - if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); - if (strings == null) { - return; - } - - LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - for (String encoded : strings) { - PendingInstallShortcutInfo info = decode(encoded, context); - if (info == null) { - continue; - } - - String pkg = getIntentPackage(info.launchIntent); - if (!TextUtils.isEmpty(pkg) - && !launcherApps.isPackageEnabledForProfile(pkg, info.user) - && !info.isActivity) { - if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: " - + info.launchIntent); - continue; - } - - // Generate a shortcut info to add into the model - installQueue.add(info.getItemInfo()); - } - prefs.edit().remove(APPS_PENDING_INSTALL).apply(); - if (!installQueue.isEmpty()) { - model.addAndBindAddedWorkspaceItems(installQueue); - } - return; + @WorkerThread + private static void addToQueue(Context context, PendingInstallShortcutInfo info) { + String encoded = info.encodeToString(); + SharedPreferences prefs = Utilities.getPrefs(context); + Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); + strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1); + strings.add(encoded); + prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply(); + } + + @WorkerThread + private static void flushQueueInBackground(Context context) { + LauncherModel model = LauncherAppState.getInstance(context).getModel(); + if (model.getCallback() == null) { + // Launcher not loaded + return; + } + + ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>(); + SharedPreferences prefs = Utilities.getPrefs(context); + Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); + if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); + if (strings == null) { + return; + } + + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); + for (String encoded : strings) { + PendingInstallShortcutInfo info = decode(encoded, context); + if (info == null) { + continue; + } + + String pkg = getIntentPackage(info.launchIntent); + if (!TextUtils.isEmpty(pkg) + && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) { + if (DBG) { + Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent); } + continue; } + + // Generate a shortcut info to add into the model + installQueue.add(info.getItemInfo()); } - }; + prefs.edit().remove(APPS_PENDING_INSTALL).apply(); + if (!installQueue.isEmpty()) { + model.addAndBindAddedWorkspaceItems(installQueue); + } + } public static void removeFromInstallQueue(Context context, HashSet<String> packageNames, UserHandle user) { @@ -291,7 +279,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { // Queue the item up for adding if launcher has not loaded properly yet - Message.obtain(sHandler, MSG_ADD_TO_QUEUE, Pair.create(context, info)).sendToTarget(); + MODEL_EXECUTOR.post(() -> addToQueue(context, info)); flushInstallQueue(context); } @@ -307,7 +295,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { if (sInstallQueueDisabledFlags != 0) { return; } - Message.obtain(sHandler, MSG_FLUSH_QUEUE, context.getApplicationContext()).sendToTarget(); + MODEL_EXECUTOR.post(() -> flushQueueInBackground(context)); } /** @@ -617,7 +605,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { // Already an activity target return original; } - if (!Utilities.isLauncherAppTarget(original.launchIntent)) { + if (!PackageManagerHelper.isLauncherAppTarget(original.launchIntent)) { return original; } diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index bde87cb50..310a9e92f 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -18,6 +18,8 @@ package com.android.launcher3; import static com.android.launcher3.Utilities.getDevicePrefs; import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY; import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter; import android.annotation.TargetApi; @@ -39,11 +41,13 @@ import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.util.Xml; -import android.view.Display; -import android.view.WindowManager; + +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.graphics.IconShape; import com.android.launcher3.util.ConfigMonitor; +import com.android.launcher3.util.DefaultDisplay; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Themes; @@ -55,9 +59,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; - public class InvariantDeviceProfile { public static final String TAG = "IDP"; @@ -102,6 +103,8 @@ public class InvariantDeviceProfile { public int iconBitmapSize; public int fillResIconDpi; public float iconTextSize; + public float allAppsIconSize; + public float allAppsIconTextSize; private SparseArray<TypedValue> mExtraAttrs; @@ -110,6 +113,11 @@ public class InvariantDeviceProfile { */ public int numHotseatIcons; + /** + * Number of columns in the all apps list. + */ + public int numAllAppsColumns; + public int defaultLayoutId; int demoModeLayoutId; @@ -136,6 +144,9 @@ public class InvariantDeviceProfile { landscapeIconSize = p.landscapeIconSize; iconTextSize = p.iconTextSize; numHotseatIcons = p.numHotseatIcons; + numAllAppsColumns = p.numAllAppsColumns; + allAppsIconSize = p.allAppsIconSize; + allAppsIconTextSize = p.allAppsIconTextSize; defaultLayoutId = p.defaultLayoutId; demoModeLayoutId = p.demoModeLayoutId; mExtraAttrs = p.mExtraAttrs; @@ -144,7 +155,10 @@ public class InvariantDeviceProfile { @TargetApi(23) private InvariantDeviceProfile(Context context) { - initGrid(context, Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)); + String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) + ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) + : null; + initGrid(context, gridName); mConfigMonitor = new ConfigMonitor(context, APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess); mOverlayMonitor = new OverlayMonitor(context); @@ -172,19 +186,17 @@ public class InvariantDeviceProfile { } private String initGrid(Context context, String gridName) { - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - DisplayMetrics dm = new DisplayMetrics(); - display.getMetrics(dm); + DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo(); - Point smallestSize = new Point(); - Point largestSize = new Point(); - display.getCurrentSizeRange(smallestSize, largestSize); + Point smallestSize = new Point(displayInfo.smallestSize); + Point largestSize = new Point(displayInfo.largestSize); ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName); // This guarantees that width < height - float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), dm); - float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), dm); + float minWidthDps = Utilities.dpiFromPx(Math.min(smallestSize.x, smallestSize.y), + displayInfo.metrics); + float minHeightDps = Utilities.dpiFromPx(Math.min(largestSize.x, largestSize.y), + displayInfo.metrics); // Sort the profiles based on the closeness to the device size Collections.sort(allOptions, (a, b) -> Float.compare(dist(minWidthDps, minHeightDps, a.minWidthDps, a.minHeightDps), @@ -200,6 +212,8 @@ public class InvariantDeviceProfile { demoModeLayoutId = closestProfile.demoModeLayoutId; numFolderRows = closestProfile.numFolderRows; numFolderColumns = closestProfile.numFolderColumns; + numAllAppsColumns = closestProfile.numAllAppsColumns; + mExtraAttrs = closestProfile.extraAttrs; if (!closestProfile.name.equals(gridName)) { @@ -210,16 +224,23 @@ public class InvariantDeviceProfile { iconSize = interpolatedDisplayOption.iconSize; iconShapePath = getIconShapePath(context); landscapeIconSize = interpolatedDisplayOption.landscapeIconSize; - iconBitmapSize = ResourceUtils.pxFromDp(iconSize, dm); + iconBitmapSize = ResourceUtils.pxFromDp(iconSize, displayInfo.metrics); iconTextSize = interpolatedDisplayOption.iconTextSize; fillResIconDpi = getLauncherIconDensity(iconBitmapSize); + if (Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)) { + allAppsIconSize = interpolatedDisplayOption.allAppsIconSize; + allAppsIconTextSize = interpolatedDisplayOption.allAppsIconTextSize; + } else { + allAppsIconSize = iconSize; + allAppsIconTextSize = iconTextSize; + } + // If the partner customization apk contains any grid overrides, apply them // Supported overrides: numRows, numColumns, iconSize - applyPartnerDeviceProfileOverrides(context, dm); + applyPartnerDeviceProfileOverrides(context, displayInfo.metrics); - Point realSize = new Point(); - display.getRealSize(realSize); + Point realSize = new Point(displayInfo.realSize); // The real size never changes. smallSide and largeSide will remain the // same in any orientation. int smallSide = Math.min(realSize.x, realSize.y); @@ -280,7 +301,7 @@ public class InvariantDeviceProfile { public void setCurrentGrid(Context context, String gridName) { Context appContext = context.getApplicationContext(); Utilities.getPrefs(appContext).edit().putString(KEY_IDP_GRID_NAME, gridName).apply(); - new MainThreadExecutor().execute(() -> onConfigChanged(appContext)); + MAIN_EXECUTOR.execute(() -> onConfigChanged(appContext)); } private void onConfigChanged(Context context) { @@ -288,9 +309,10 @@ public class InvariantDeviceProfile { InvariantDeviceProfile oldProfile = new InvariantDeviceProfile(this); // Re-init grid - // TODO(b/131867841): We pass in null here so that we can calculate the closest profile - // without the bias of the grid name. - initGrid(context, null); + String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false) + ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null) + : null; + initGrid(context, gridName); int changeFlags = 0; if (numRows != oldProfile.numRows || @@ -492,6 +514,8 @@ public class InvariantDeviceProfile { private final int numHotseatIcons; + private final int numAllAppsColumns; + private final int defaultLayoutId; private final int demoModeLayoutId; @@ -514,6 +538,9 @@ public class InvariantDeviceProfile { R.styleable.GridDisplayOption_numFolderRows, numRows); numFolderColumns = a.getInt( R.styleable.GridDisplayOption_numFolderColumns, numColumns); + numAllAppsColumns = a.getInt( + R.styleable.GridDisplayOption_numAllAppsColumns, numColumns); + a.recycle(); extraAttrs = Themes.createValueMap(context, attrs, @@ -530,8 +557,10 @@ public class InvariantDeviceProfile { private final boolean canBeDefault; private float iconSize; - private float landscapeIconSize; private float iconTextSize; + private float landscapeIconSize; + private float allAppsIconSize; + private float allAppsIconTextSize; DisplayOption(GridOption grid, Context context, AttributeSet attrs) { this.grid = grid; @@ -549,6 +578,11 @@ public class InvariantDeviceProfile { landscapeIconSize = a.getFloat(R.styleable.ProfileDisplayOption_landscapeIconSize, iconSize); iconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_iconTextSize, 0); + + allAppsIconSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconSize, + iconSize); + allAppsIconTextSize = a.getFloat(R.styleable.ProfileDisplayOption_allAppsIconTextSize, + iconTextSize); a.recycle(); } @@ -563,14 +597,18 @@ public class InvariantDeviceProfile { private DisplayOption multiply(float w) { iconSize *= w; landscapeIconSize *= w; + allAppsIconSize *= w; iconTextSize *= w; + allAppsIconTextSize *= w; return this; } private DisplayOption add(DisplayOption p) { iconSize += p.iconSize; landscapeIconSize += p.landscapeIconSize; + allAppsIconSize += p.allAppsIconSize; iconTextSize += p.iconTextSize; + allAppsIconTextSize += p.allAppsIconTextSize; return this; } } diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index e29f92713..1550bb080 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -27,6 +27,8 @@ import com.android.launcher3.icons.BitmapInfo; */ public abstract class ItemInfoWithIcon extends ItemInfo { + public static final String TAG = "ItemInfoDebug"; + /** * A bitmap version of the application icon. */ @@ -126,4 +128,8 @@ public abstract class ItemInfoWithIcon extends ItemInfo { iconColor = info.color; } + /** + * @return a copy of this + */ + public abstract ItemInfoWithIcon clone(); } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 257f0dfcf..d667e8c0c 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -16,10 +16,11 @@ package com.android.launcher3; -import static android.content.pm.ActivityInfo.CONFIG_LOCALE; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE; +import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; +import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.ALL_APPS; @@ -77,6 +78,8 @@ import android.view.accessibility.AccessibilityEvent; import android.view.animation.OvershootInterpolator; import android.widget.Toast; +import androidx.annotation.Nullable; + import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; @@ -91,9 +94,8 @@ import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; -import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.folder.FolderIconPreviewVerifier; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.icons.IconCache; import com.android.launcher3.keyboard.CustomActionsPopup; @@ -103,14 +105,14 @@ import com.android.launcher3.logging.StatsLogUtils; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate; import com.android.launcher3.model.AppLaunchTracker; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; -import com.android.launcher3.shortcuts.DeepShortcutManager; +import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.states.InternalStateHandler; import com.android.launcher3.states.RotationHelper; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -163,7 +165,7 @@ import androidx.annotation.VisibleForTesting; * Default launcher application. */ public class Launcher extends BaseDraggingActivity implements LauncherExterns, - LauncherModel.Callbacks, LauncherProviderChangeListener, UserEventDelegate, + Callbacks, LauncherProviderChangeListener, UserEventDelegate, InvariantDeviceProfile.OnIDPChangeListener { public static final String TAG = "Launcher"; static final boolean LOGD = false; @@ -220,9 +222,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private LauncherAppTransitionManager mAppTransitionManager; private Configuration mOldConfig; - @Thunk Workspace mWorkspace; + @Thunk + Workspace mWorkspace; private View mLauncherView; - @Thunk DragLayer mDragLayer; + @Thunk + DragLayer mDragLayer; private DragController mDragController; private AppWidgetManagerCompat mAppWidgetManager; @@ -230,21 +234,25 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private final int[] mTmpAddItemCellCoordinates = new int[2]; - @Thunk Hotseat mHotseat; + @Thunk + Hotseat mHotseat; private DropTargetBar mDropTargetBar; // Main container view for the all apps screen. - @Thunk AllAppsContainerView mAppsView; + @Thunk + AllAppsContainerView mAppsView; AllAppsTransitionController mAllAppsController; // Scrim view for the all apps and overview state. - @Thunk ScrimView mScrimView; + @Thunk + ScrimView mScrimView; // UI and state for the overview panel private View mOverviewPanel; - @Thunk boolean mWorkspaceLoading = true; + @Thunk + boolean mWorkspaceLoading = true; private ArrayList<OnResumeCallback> mOnResumeCallbacks = new ArrayList<>(); @@ -326,8 +334,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, UiFactory.onCreate(this); mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); - - mAppWidgetHost = new LauncherAppWidgetHost(this); + mAppWidgetHost = new LauncherAppWidgetHost(this, + appWidgetId -> getWorkspace().removeWidget(appWidgetId)); mAppWidgetHost.startListening(); mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null); @@ -389,7 +397,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, RaceConditionTracker.onEvent(ON_CREATE_EVT, EXIT); mStateManager.addStateListener(new LauncherStateManager.StateListener() { @Override - public void onStateTransitionStart(LauncherState toState) {} + public void onStateTransitionStart(LauncherState toState) { + } @Override public void onStateTransitionComplete(LauncherState finalState) { @@ -419,10 +428,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, public void onConfigurationChanged(Configuration newConfig) { int diff = newConfig.diff(mOldConfig); - if ((diff & CONFIG_LOCALE) != 0) { - Folder.setLocaleDependentFields(getResources(), true /* force */); - } - if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) { onIdpChanged(mDeviceProfile.inv); } @@ -497,6 +502,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Load configuration-specific DeviceProfile mDeviceProfile = idp.getDeviceProfile(this); if (isInMultiWindowMode()) { + // Note: Calls to getSize() can't rely on our cached DefaultDisplay since it can return + // the app window size Display display = getWindowManager().getDefaultDisplay(); Point mwSize = new Point(); display.getSize(mwSize); @@ -604,10 +611,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (info.container >= 0) { View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container); if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) { - FolderIconPreviewVerifier verifier = - new FolderIconPreviewVerifier(getDeviceProfile().inv); - verifier.setFolderInfo((FolderInfo) folderIcon.getTag()); - if (verifier.isItemInPreview(info.rank)) { + if (new FolderGridOrganizer(getDeviceProfile().inv) + .setFolderInfo((FolderInfo) folderIcon.getTag()) + .isItemInPreview(info.rank)) { folderIcon.invalidate(); } } @@ -647,7 +653,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, .getLauncherAppWidgetInfo(widgetId); if (provider != null) { new WidgetAddFlowHandler(provider) - .startConfigActivity(this, widgetInfo, REQUEST_RECONFIGURE_APPWIDGET); + .startConfigActivity(this, widgetInfo, + REQUEST_RECONFIGURE_APPWIDGET); } } break; @@ -835,7 +842,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } } - @Thunk void completeTwoStageWidgetDrop( + @Thunk + void completeTwoStageWidgetDrop( final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) { CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId); Runnable onCompleteRunnable = null; @@ -932,15 +940,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, int containerType = mStateManager.getState().containerType; if (containerType == ContainerType.WORKSPACE && mWorkspace != null) { getUserEventDispatcher().logActionCommand(command, - containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0); + containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0); } else { getUserEventDispatcher().logActionCommand(command, containerType, -1); } } - protected void onStateSet(LauncherState state) { - getAppWidgetHost().setResumed(state == LauncherState.NORMAL); + public void onStateSetStart(LauncherState state) { if (mDeferredResumePending) { handleDeferredResume(); } @@ -949,6 +956,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } } + public void onStateSetEnd(LauncherState state) { + getAppWidgetHost().setResumed(state == LauncherState.NORMAL); + getWorkspace().setClipChildren(!state.disablePageClipping); + finishAutoCancelActionMode(); + } + @Override protected void onResume() { RaceConditionTracker.onEvent(ON_RESUME_EVT, ENTER); @@ -1061,7 +1074,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mStateManager.goToState(state, false /* animated */); } - PendingRequestArgs requestArgs = savedState.getParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS); + PendingRequestArgs requestArgs = savedState.getParcelable( + RUNTIME_STATE_PENDING_REQUEST_ARGS); if (requestArgs != null) { setWaitingForResult(requestArgs); } @@ -1131,8 +1145,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, * Creates a view representing a shortcut inflated from the specified resource. * * @param parent The group the shortcut belongs to. - * @param info The data structure describing the shortcut. - * + * @param info The data structure describing the shortcut. * @return A View inflated from layoutResId. */ public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) { @@ -1234,7 +1247,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, * * @param appWidgetId The app widget id */ - @Thunk void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, + @Thunk + void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) { if (appWidgetInfo == null) { @@ -1352,7 +1366,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return mSharedPrefs; } - public int getOrientation() { return mOldConfig.orientation; } + public int getOrientation() { + return mOldConfig.orientation; + } @Override protected void onNewIntent(Intent intent) { @@ -1436,9 +1452,10 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, outState.remove(RUNTIME_STATE_WIDGET_PANEL); } - // We close any open folders and shortcut containers since they will not be re-opened, + // We close any open folders and shortcut containers that are not safe for rebind, // and we need to make sure this state is reflected. - AbstractFloatingView.closeAllOpenViews(this, false); + AbstractFloatingView.closeOpenViews(this, false, TYPE_ALL & ~TYPE_REBIND_SAFE); + finishAutoCancelActionMode(); if (mPendingRequestArgs != null) { outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs); @@ -1575,7 +1592,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, void addAppWidgetImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) { - if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, REQUEST_CREATE_APPWIDGET)) { + if (!addFlowHandler.startConfigActivity(this, appWidgetId, info, + REQUEST_CREATE_APPWIDGET)) { // If the configuration flow was not started, add the widget Runnable onComplete = new Runnable() { @@ -1585,7 +1603,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY); } }; - completeAddAppWidget(appWidgetId, info, boundWidget, addFlowHandler.getProviderInfo(this)); + completeAddAppWidget(appWidgetId, info, boundWidget, + addFlowHandler.getProviderInfo(this)); mWorkspace.removeExtraEmptyScreenDelayed(true, onComplete, delay, false); } } @@ -1611,7 +1630,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, break; default: throw new IllegalStateException("Unknown item type: " + info.itemType); - } + } } /** @@ -1669,7 +1688,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX, int cellY) { final FolderInfo folderInfo = new FolderInfo(); - folderInfo.title = getText(R.string.folder_name); + folderInfo.title = ""; // Update the model getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY); @@ -1723,8 +1742,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return true; } - - @Override public boolean dispatchKeyEvent(KeyEvent event) { return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event); @@ -1788,7 +1805,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, @Override public int getCurrentState() { - if(mStateManager.getState() == LauncherState.ALL_APPS) { + if (mStateManager.getState() == LauncherState.ALL_APPS) { return StatsLogUtils.LAUNCHER_STATE_ALLAPPS; } else if (mStateManager.getState() == OVERVIEW) { return StatsLogUtils.LAUNCHER_STATE_OVERVIEW; @@ -1923,8 +1940,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Floating panels (except the full widget sheet) are associated with individual icons. If // we are starting a fresh bind, close all such panels as all the icons are about // to go away. - AbstractFloatingView.closeOpenViews(this, true, - AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); + AbstractFloatingView.closeOpenViews(this, true, TYPE_ALL & ~TYPE_REBIND_SAFE); setWorkspaceLoading(true); @@ -2052,7 +2068,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, throw new RuntimeException("Invalid Item Type"); } - /* + /* * Remove colliding items. */ if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { @@ -2124,6 +2140,14 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } private View inflateAppWidget(LauncherAppWidgetInfo item) { + if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) { + item.providerName = QsbContainerView.getSearchComponentName(this); + if (item.providerName == null) { + getModelWriter().deleteItemFromDatabase(item); + return null; + } + } + if (mIsSafeModeEnabled) { PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item, mIconCache, true); @@ -2170,7 +2194,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, pendingInfo.spanY = item.spanY; pendingInfo.minSpanX = item.minSpanX; pendingInfo.minSpanY = item.minSpanY; - Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); + Bundle options = WidgetHostViewLoader.getDefaultOptionsForWidget(this, + pendingInfo); boolean isDirectConfig = item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG); @@ -2324,6 +2349,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // override the previous page so we don't log the page switch. mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */); + // Cache one page worth of icons + getViewCache().setCacheSize(R.layout.folder_application, + mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows); + getViewCache().setCacheSize(R.layout.folder_page, 2); + TraceHelper.endSection("finishBindingItems"); } @@ -2345,7 +2375,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, * * Implementation of the method from LauncherModel.Callbacks. */ - public void bindAllApplications(ArrayList<AppInfo> apps) { + public void bindAllApplications(AppInfo[] apps) { mAppsView.getAppsStore().setApps(apps); } @@ -2358,16 +2388,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy); } - /** - * A package was updated. - * - * Implementation of the method from LauncherModel.Callbacks. - */ - @Override - public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps) { - mAppsView.getAppsStore().addOrUpdateApps(apps); - } - @Override public void bindPromiseAppProgressUpdated(PromiseAppInfo app) { mAppsView.getAppsStore().updatePromiseAppProgress(app); @@ -2415,11 +2435,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } @Override - public void bindAppInfosRemoved(final ArrayList<AppInfo> appInfos) { - mAppsView.getAppsStore().removeApps(appInfos); - } - - @Override public void bindAllWidgets(final ArrayList<WidgetListRowEntry> allWidgets) { mPopupDataProvider.setAllWidgets(allWidgets); } @@ -2464,14 +2479,16 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } writer.println(prefix + "Misc:"); - writer.print(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading); - writer.print(" mPendingRequestArgs=" + mPendingRequestArgs); - writer.println(" mPendingActivityResult=" + mPendingActivityResult); - writer.println(" mRotationHelper: " + mRotationHelper); + dumpMisc(prefix + "\t", writer); + writer.println(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading); + writer.println(prefix + "\tmPendingRequestArgs=" + mPendingRequestArgs + + " mPendingActivityResult=" + mPendingActivityResult); + writer.println(prefix + "\tmRotationHelper: " + mRotationHelper); + writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening()); + // Extra logging for b/116853349 mDragLayer.dump(prefix, writer); mStateManager.dump(prefix, writer); - dumpMisc(writer); try { FileLog.flushAll(writer); @@ -2532,8 +2549,8 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (focusedView instanceof BubbleTextView && focusedView.getTag() instanceof ItemInfo && mAccessibilityDelegate.performAction(focusedView, - (ItemInfo) focusedView.getTag(), - LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { + (ItemInfo) focusedView.getTag(), + LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { PopupContainerWithArrow.getOpen(this).requestFocus(); return true; } @@ -2578,6 +2595,12 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, return (Launcher) fromContext(context); } + @Override + public void returnToHomescreen() { + super.returnToHomescreen(); + getStateManager().goToState(LauncherState.NORMAL); + } + /** * Just a wrapper around the type cast to allow easier tracking of calls. */ diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java index 7f5ac5276..64ec73007 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/LauncherAppWidgetHost.java @@ -35,6 +35,7 @@ import com.android.launcher3.widget.DeferredAppWidgetHostView; import com.android.launcher3.widget.LauncherAppWidgetHostView; import java.util.ArrayList; +import java.util.function.IntConsumer; /** @@ -56,9 +57,17 @@ public class LauncherAppWidgetHost extends AppWidgetHost { private final Context mContext; private int mFlags = FLAG_RESUMED; + private IntConsumer mAppWidgetRemovedCallback = null; + public LauncherAppWidgetHost(Context context) { + this(context, null); + } + + public LauncherAppWidgetHost(Context context, + IntConsumer appWidgetRemovedCallback) { super(context, APPWIDGET_HOST_ID); mContext = context; + mAppWidgetRemovedCallback = appWidgetRemovedCallback; } @Override @@ -105,6 +114,10 @@ public class LauncherAppWidgetHost extends AppWidgetHost { super.stopListening(); } + public boolean isListening() { + return (mFlags & FLAG_LISTENING) != 0; + } + /** * Updates the resumed state of the host. * When a host is not resumed, it defers calls to startListening until host is resumed again. @@ -207,7 +220,7 @@ public class LauncherAppWidgetHost extends AppWidgetHost { } view.setAppWidget(appWidgetId, appWidget); view.switchToErrorView(); - return view; + return view; } } } @@ -225,6 +238,18 @@ public class LauncherAppWidgetHost extends AppWidgetHost { info.initSpans(mContext); } + /** + * Called on an appWidget is removed for a widgetId + * @param appWidgetId + * TODO: make this override when SDK is updated + */ + public void onAppWidgetRemoved(int appWidgetId) { + if (mAppWidgetRemovedCallback == null) { + return; + } + mAppWidgetRemovedCallback.accept(appWidgetId); + } + @Override public void deleteAppWidgetId(int appWidgetId) { super.deleteAppWidgetId(appWidgetId); diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index 051846c87..b82430184 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -29,6 +29,9 @@ import com.android.launcher3.util.ContentWriter; */ public class LauncherAppWidgetInfo extends ItemInfo { + public static final int OPTION_SEARCH_WIDGET = 1; + + public static final int RESTORE_COMPLETED = 0; /** @@ -97,6 +100,11 @@ public class LauncherAppWidgetInfo extends ItemInfo { public Intent bindOptions; /** + * Widget options + */ + public int options; + + /** * Nonnull for pending widgets. We use this to get the icon and title for the widget. */ public PackageItemInfo pendingItemInfo; @@ -137,6 +145,7 @@ public class LauncherAppWidgetInfo extends ItemInfo { writer.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId) .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString()) .put(LauncherSettings.Favorites.RESTORED, restoreStatus) + .put(LauncherSettings.Favorites.OPTIONS, options) .put(LauncherSettings.Favorites.INTENT, bindOptions); } @@ -164,4 +173,13 @@ public class LauncherAppWidgetInfo extends ItemInfo { public final boolean hasRestoreFlag(int flag) { return (restoreStatus & flag) == flag; } + + /** + * returns if widget options include an option or not + * @param option + * @return + */ + public final boolean hasOptionFlag(int option) { + return (options & option) != 0; + } } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index a0414894b..14f659810 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -18,29 +18,33 @@ package com.android.launcher3; import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import androidx.annotation.Nullable; + import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.AddWorkspaceItemsTask; +import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BaseModelUpdateTask; import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.LoaderResults; import com.android.launcher3.model.LoaderTask; @@ -57,22 +61,17 @@ import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; -import com.android.launcher3.util.ViewOnDrawExecutor; -import com.android.launcher3.widget.WidgetListRowEntry; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.Executor; import java.util.function.Supplier; -import androidx.annotation.Nullable; - /** * Maintains in-memory state of the Launcher. It is expected that there should be only one * LauncherModel object held in a static. Also provide APIs for updating the database state @@ -84,21 +83,12 @@ public class LauncherModel extends BroadcastReceiver static final String TAG = "Launcher.Model"; - private final MainThreadExecutor mUiExecutor = new MainThreadExecutor(); @Thunk final LauncherAppState mApp; @Thunk final Object mLock = new Object(); @Thunk LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; - @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader"); - private static final Looper mWorkerLooper; - static { - sWorkerThread.start(); - mWorkerLooper = sWorkerThread.getLooper(); - } - @Thunk static final Handler sWorker = new Handler(mWorkerLooper); - // Indicates whether the current model data is valid or not. // We start off with everything not loaded. After that, we assume that // our monitoring of the package manager provides all updates and we never @@ -135,33 +125,6 @@ public class LauncherModel extends BroadcastReceiver } }; - public interface Callbacks { - public void rebindModel(); - - public int getCurrentWorkspaceScreen(); - public void clearPendingBinds(); - public void startBinding(); - public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons); - public void bindScreens(IntArray orderedScreenIds); - public void finishFirstPageBind(ViewOnDrawExecutor executor); - public void finishBindingItems(int pageBoundFirst); - public void bindAllApplications(ArrayList<AppInfo> apps); - public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps); - public void preAddApps(); - public void bindAppsAdded(IntArray newScreens, - ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated); - public void bindPromiseAppProgressUpdated(PromiseAppInfo app); - public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated); - public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); - public void bindRestoreItemsChange(HashSet<ItemInfo> updates); - public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); - public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos); - public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets); - public void onPageBoundSynchronously(int page); - public void executeOnNextDraw(ViewOnDrawExecutor executor); - public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap); - } - LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) { mApp = app; mBgAllAppsList = new AllAppsList(iconCache, appFilter); @@ -244,6 +207,7 @@ public class LauncherModel extends BroadcastReceiver public void onPackagesRemoved(UserHandle user, String... packages) { int op = PackageUpdatedTask.OP_REMOVE; + FileLog.d(TAG, "package removed received " + String.join("," + packages)); enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); } @@ -299,7 +263,6 @@ public class LauncherModel extends BroadcastReceiver @Override public void onReceive(Context context, Intent intent) { if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent); - final String action = intent.getAction(); if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { // If we have changed locale we need to clear out the labels in all apps/workspace. @@ -375,7 +338,7 @@ public class LauncherModel extends BroadcastReceiver if (mCallbacks != null && mCallbacks.get() != null) { final Callbacks oldCallbacks = mCallbacks.get(); // Clear any pending bind-runnables from the synchronized load process. - mUiExecutor.execute(oldCallbacks::clearPendingBinds); + MAIN_EXECUTOR.execute(oldCallbacks::clearPendingBinds); // If there is already one running, tell it to stop. stopLoader(); @@ -419,7 +382,7 @@ public class LauncherModel extends BroadcastReceiver // Always post the loader task, instead of running directly (even on same thread) so // that we exit any nested synchronized blocks - sWorker.post(mLoaderTask); + MODEL_EXECUTOR.post(mLoaderTask); } } @@ -437,16 +400,7 @@ public class LauncherModel extends BroadcastReceiver @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { apps.addPromiseApp(app.getContext(), sessionInfo); - if (!apps.added.isEmpty()) { - final ArrayList<AppInfo> arrayList = new ArrayList<>(apps.added); - apps.added.clear(); - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAppsAddedOrUpdated(arrayList); - } - }); - } + bindApplicationsIfNeeded(); } }); } @@ -495,8 +449,8 @@ public class LauncherModel extends BroadcastReceiver * use partial updates similar to {@link UserManagerCompat} */ public void refreshShortcutsIfRequired() { - sWorker.removeCallbacks(mShortcutPermissionCheckRunnable); - sWorker.post(mShortcutPermissionCheckRunnable); + MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable); + MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable); } /** @@ -523,14 +477,8 @@ public class LauncherModel extends BroadcastReceiver } public void enqueueModelUpdateTask(ModelUpdateTask task) { - task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor); - - if (sWorkerThread.getThreadId() == Process.myTid()) { - task.run(); - } else { - // If we are not on the worker thread, then post to the worker handler - sWorker.post(task); - } + task.init(mApp, this, sBgDataModel, mBgAllAppsList, MAIN_EXECUTOR); + MODEL_EXECUTOR.execute(task); } /** @@ -606,14 +554,4 @@ public class LauncherModel extends BroadcastReceiver return mCallbacks != null ? mCallbacks.get() : null; } - /** - * @return the looper for the worker thread which can be used to start background tasks. - */ - public static Looper getWorkerLooper() { - return mWorkerLooper; - } - - public static void setWorkerPriority(final int priority) { - Process.setThreadPriority(sWorkerThread.getThreadId(), priority); - } } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 6ad5c3684..6081300ce 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -33,7 +33,6 @@ import android.content.Context; import android.content.Intent; import android.content.OperationApplicationException; import android.content.SharedPreferences; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; import android.content.res.Resources; import android.database.Cursor; @@ -50,7 +49,6 @@ import android.os.Handler; import android.os.Message; import android.os.Process; import android.os.UserHandle; -import android.os.UserManager; import android.provider.BaseColumns; import android.provider.Settings; import android.text.TextUtils; @@ -70,6 +68,7 @@ import com.android.launcher3.util.IOUtils; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.NoLocaleSQLiteHelper; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; @@ -77,7 +76,6 @@ import org.xmlpull.v1.XmlPullParser; import java.io.File; import java.io.FileDescriptor; -import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringReader; @@ -873,7 +871,7 @@ public class LauncherProvider extends ContentProvider { continue; } - if (!Utilities.isLauncherAppTarget(intent)) { + if (!PackageManagerHelper.isLauncherAppTarget(intent)) { continue; } diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index e248ba016..242e099b2 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -122,11 +122,13 @@ public class LauncherSettings { */ public static final int CONTAINER_DESKTOP = -100; public static final int CONTAINER_HOTSEAT = -101; + public static final int CONTAINER_PREDICTION = -102; static final String containerToString(int container) { switch (container) { case CONTAINER_DESKTOP: return "desktop"; case CONTAINER_HOTSEAT: return "hotseat"; + case CONTAINER_PREDICTION: return "prediction"; default: return String.valueOf(container); } } diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index d66e5813b..848e19fb5 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -136,7 +136,7 @@ public class LauncherStateManager { } public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "LauncherState"); + writer.println(prefix + "LauncherState:"); writer.println(prefix + "\tmLastStableState:" + mLastStableState); writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState); writer.println(prefix + "\tmState:" + mState); @@ -408,7 +408,7 @@ public class LauncherStateManager { } mState = state; mState.onStateEnabled(mLauncher); - mLauncher.onStateSet(mState); + mLauncher.onStateSetStart(mState); if (state.disablePageClipping) { // Only disable clipping if needed, otherwise leave it as previous value. @@ -429,8 +429,7 @@ public class LauncherStateManager { } state.onStateTransitionEnd(mLauncher); - mLauncher.getWorkspace().setClipChildren(!state.disablePageClipping); - mLauncher.finishAutoCancelActionMode(); + mLauncher.onStateSetEnd(state); if (state == NORMAL) { setRestState(null); diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index 70b55a44d..a99c7c28f 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -1294,6 +1294,10 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: { + Launcher launcher = Launcher.getLauncher(getContext()); + if (launcher != null) { + AbstractFloatingView.closeAllOpenViews(launcher); + } // Handle mouse (or ext. device) by shifting the page depending on the scroll final float vscroll; final float hscroll; @@ -1304,6 +1308,9 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); } + if (Math.abs(vscroll) > Math.abs(hscroll) && !isVerticalScrollable()) { + return true; + } if (hscroll != 0 || vscroll != 0) { boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0) : (hscroll > 0 || vscroll > 0); @@ -1320,6 +1327,10 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou return super.onGenericMotionEvent(event); } + protected boolean isVerticalScrollable() { + return true; + } + private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); diff --git a/src/com/android/launcher3/Partner.java b/src/com/android/launcher3/Partner.java index 380078b26..af5402ae2 100644 --- a/src/com/android/launcher3/Partner.java +++ b/src/com/android/launcher3/Partner.java @@ -16,6 +16,8 @@ package com.android.launcher3; +import static com.android.launcher3.util.PackageManagerHelper.findSystemApk; + import android.content.pm.PackageManager; import android.content.res.Resources; import android.util.DisplayMetrics; @@ -59,7 +61,7 @@ public class Partner { */ public static synchronized Partner get(PackageManager pm) { if (!sSearched) { - Pair<String, Resources> apkInfo = Utilities.findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm); + Pair<String, Resources> apkInfo = findSystemApk(ACTION_PARTNER_CUSTOMIZATION, pm); if (apkInfo != null) { sPartner = new Partner(apkInfo.first, apkInfo.second); } diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java index 55cb6f214..c8c590d9d 100644 --- a/src/com/android/launcher3/SecondaryDropTarget.java +++ b/src/com/android/launcher3/SecondaryDropTarget.java @@ -2,6 +2,7 @@ package com.android.launcher3; import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID; import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE; + import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK; import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; @@ -29,6 +30,7 @@ import android.widget.Toast; import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.dragndrop.DragOptions; +import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; @@ -240,6 +242,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName())) .putExtra(Intent.EXTRA_USER, info.user); mLauncher.startActivity(i); + FileLog.d(TAG, "start uninstall activity " + cn.getPackageName()); return cn; } catch (URISyntaxException e) { Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info); diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index b4078ee0e..6853bf694 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -22,7 +22,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; @@ -39,6 +38,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.util.Executors; import com.android.launcher3.compat.PackageInstallerCompat; import java.util.List; @@ -133,7 +133,7 @@ public class SessionCommitReceiver extends BroadcastReceiver { // grid. prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply(); } else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) { - new PrefInitTask(context).executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); + new PrefInitTask(context).executeOnExecutor(Executors.THREAD_POOL_EXECUTOR); } } diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index ba122f944..6ddebe75d 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -24,15 +24,9 @@ import android.app.ActivityManager; import android.app.Person; import android.app.WallpaperManager; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.content.res.Resources; @@ -47,7 +41,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.os.Build; -import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; import android.os.Message; @@ -60,7 +53,6 @@ import android.text.TextUtils; import android.text.style.TtsSpan; import android.util.DisplayMetrics; import android.util.Log; -import android.util.Pair; import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; @@ -69,7 +61,6 @@ import android.view.animation.Interpolator; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.FolderAdaptiveIcon; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.graphics.TintedDrawableSpan; @@ -81,17 +72,10 @@ import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.views.Transposable; import com.android.launcher3.widget.PendingAddShortcutInfo; -import java.io.Closeable; -import java.io.IOException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; import java.util.Locale; -import java.util.StringTokenizer; -import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -124,8 +108,6 @@ public final class Utilities { public static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - public static final int SINGLE_FRAME_MS = 16; - /** * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}. */ @@ -148,18 +130,6 @@ public final class Utilities { public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET"; public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR"; - // These values are same as that in {@link AsyncTask}. - private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); - private static final int CORE_POOL_SIZE = CPU_COUNT + 1; - private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; - private static final int KEEP_ALIVE = 1; - /** - * An {@link Executor} to be used with async task with no limit on the queue size. - */ - public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( - CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, - TimeUnit.SECONDS, new LinkedBlockingQueue<>()); - public static boolean IS_RUNNING_IN_TEST_HARNESS = ActivityManager.isRunningInTestHarness(); @@ -247,7 +217,6 @@ public final class Utilities { return scale; } - /** * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, float[], boolean)}. */ @@ -383,53 +352,6 @@ public final class Utilities { return min + (value * (max - min)); } - public static boolean isSystemApp(Context context, Intent intent) { - PackageManager pm = context.getPackageManager(); - ComponentName cn = intent.getComponent(); - String packageName = null; - if (cn == null) { - ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); - if ((info != null) && (info.activityInfo != null)) { - packageName = info.activityInfo.packageName; - } - } else { - packageName = cn.getPackageName(); - } - if (packageName != null) { - try { - PackageInfo info = pm.getPackageInfo(packageName, 0); - return (info != null) && (info.applicationInfo != null) && - ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); - } catch (NameNotFoundException e) { - return false; - } - } else { - return false; - } - } - - /* - * Finds a system apk which had a broadcast receiver listening to a particular action. - * @param action intent action used to find the apk - * @return a pair of apk package name and the resources. - */ - static Pair<String, Resources> findSystemApk(String action, PackageManager pm) { - final Intent intent = new Intent(action); - for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) { - if (info.activityInfo != null && - (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - final String packageName = info.activityInfo.packageName; - try { - final Resources res = pm.getResourcesForApplication(packageName); - return Pair.create(packageName, res); - } catch (NameNotFoundException e) { - Log.w(TAG, "Failed to find resources for " + packageName); - } - } - } - return null; - } - /** * Trims the string, removing all whitespace at the beginning and end of the string. * Non-breaking whitespaces are also removed. @@ -454,51 +376,10 @@ public final class Utilities { return (int) Math.ceil(fm.bottom - fm.top); } - /** - * Convenience println with multiple args. - */ - public static void println(String key, Object... args) { - StringBuilder b = new StringBuilder(); - b.append(key); - b.append(": "); - boolean isFirstArgument = true; - for (Object arg : args) { - if (isFirstArgument) { - isFirstArgument = false; - } else { - b.append(", "); - } - b.append(arg); - } - System.out.println(b.toString()); - } - public static boolean isRtl(Resources res) { return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; } - /** - * Returns true if the intent is a valid launch intent for a launcher activity of an app. - * This is used to identify shortcuts which are different from the ones exposed by the - * applications' manifest file. - * - * @param launchIntent The intent that will be launched when the shortcut is clicked. - */ - public static boolean isLauncherAppTarget(Intent launchIntent) { - if (launchIntent != null - && Intent.ACTION_MAIN.equals(launchIntent.getAction()) - && launchIntent.getComponent() != null - && launchIntent.getCategories() != null - && launchIntent.getCategories().size() == 1 - && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER) - && TextUtils.isEmpty(launchIntent.getDataString())) { - // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE. - Bundle extras = launchIntent.getExtras(); - return extras == null || extras.keySet().isEmpty(); - } - return false; - } - public static float dpiFromPx(int size, DisplayMetrics metrics){ float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT; return (size / densityRatio); @@ -601,18 +482,6 @@ public final class Utilities { return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed(); } - public static void closeSilently(Closeable c) { - if (c != null) { - try { - c.close(); - } catch (IOException e) { - if (FeatureFlags.IS_DOGFOOD_BUILD) { - Log.d(TAG, "Error closing", e); - } - } - } - } - public static boolean isBinderSizeError(Exception e) { return e.getCause() instanceof TransactionTooLargeException || e.getCause() instanceof DeadObjectException; @@ -727,25 +596,6 @@ public final class Utilities { } } - public static int[] getIntArrayFromString(String tokenized) { - StringTokenizer tokenizer = new StringTokenizer(tokenized, ","); - int[] array = new int[tokenizer.countTokens()]; - int count = 0; - while (tokenizer.hasMoreTokens()) { - array[count] = Integer.parseInt(tokenizer.nextToken().trim()); - count++; - } - return array; - } - - public static String getStringFromIntArray(int[] array) { - StringBuilder str = new StringBuilder(); - for (int value : array) { - str.append(value).append(","); - } - return str.toString(); - } - public static float squaredHypot(float x, float y) { return x * x + y * y; } diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 6d1bc1a9c..003bcc1ce 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -1,5 +1,8 @@ package com.android.launcher3; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; @@ -23,21 +26,23 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.CancellationSignal; -import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.util.Log; import android.util.LongSparseArray; +import androidx.annotation.Nullable; + import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.icons.GraphicsUtils; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.ShadowGenerator; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.SQLiteCacheHelper; @@ -50,11 +55,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.WeakHashMap; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import androidx.annotation.Nullable; - public class WidgetPreviewLoader { private static final String TAG = "WidgetPreviewLoader"; @@ -68,23 +70,18 @@ public class WidgetPreviewLoader { * Note: synchronized block used for this variable is expensive and the block should always * be posted to a background thread. */ - @Thunk final Set<Bitmap> mUnusedBitmaps = - Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>()); + @Thunk final Set<Bitmap> mUnusedBitmaps = Collections.newSetFromMap(new WeakHashMap<>()); private final Context mContext; private final IconCache mIconCache; private final UserManagerCompat mUserManager; private final CacheDb mDb; - private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - @Thunk final Handler mWorkerHandler; - public WidgetPreviewLoader(Context context, IconCache iconCache) { mContext = context; mIconCache = iconCache; mUserManager = UserManagerCompat.getInstance(context); mDb = new CacheDb(context); - mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); } /** @@ -99,7 +96,7 @@ public class WidgetPreviewLoader { WidgetCacheKey key = new WidgetCacheKey(item.componentName, item.user, size); PreviewLoadTask task = new PreviewLoadTask(key, item, previewWidth, previewHeight, caller); - task.executeOnExecutor(Utilities.THREAD_POOL_EXECUTOR); + task.executeOnExecutor(Executors.THREAD_POOL_EXECUTOR); CancellationSignal signal = new CancellationSignal(); signal.setOnCancelListener(task); @@ -494,12 +491,7 @@ public class WidgetPreviewLoader { private Drawable mutateOnMainThread(final Drawable drawable) { try { - return mMainThreadExecutor.submit(new Callable<Drawable>() { - @Override - public Drawable call() throws Exception { - return drawable.mutate(); - } - }).get(); + return MAIN_EXECUTOR.submit(drawable::mutate).get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); @@ -607,7 +599,7 @@ public class WidgetPreviewLoader { // Write the generated preview to the DB in the worker thread if (mVersions != null) { - mWorkerHandler.post(new Runnable() { + MODEL_EXECUTOR.post(new Runnable() { @Override public void run() { if (!isCancelled()) { @@ -637,7 +629,7 @@ public class WidgetPreviewLoader { // recycled set immediately. Otherwise, it will be recycled after the preview is written // to disk. if (preview != null) { - mWorkerHandler.post(new Runnable() { + MODEL_EXECUTOR.post(new Runnable() { @Override public void run() { synchronized (mUnusedBitmaps) { @@ -658,7 +650,7 @@ public class WidgetPreviewLoader { // in the tasks's onCancelled() call, and if cancelled while the task is writing to // disk, it will be cancelled in the task's onPostExecute() call. if (mBitmapToRecycle != null) { - mWorkerHandler.post(new Runnable() { + MODEL_EXECUTOR.post(new Runnable() { @Override public void run() { synchronized (mUnusedBitmaps) { diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 6612662ea..f9201d04c 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -67,9 +67,9 @@ import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.dot.FolderDotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; @@ -90,8 +90,8 @@ import com.android.launcher3.touch.WorkspaceTouchListener; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; @@ -133,9 +133,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator> private static final int DEFAULT_PAGE = 0; - public static final boolean MAP_NO_RECURSE = false; - public static final boolean MAP_RECURSE = true; - private LayoutTransition mLayoutTransition; @Thunk final WallpaperManager mWallpaperManager; @@ -1161,7 +1158,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } protected void setWallpaperDimension() { - Utilities.THREAD_POOL_EXECUTOR.execute(new Runnable() { + Executors.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override public void run() { final Point size = LauncherAppState.getIDP(getContext()).defaultWallpaperSize; @@ -2532,22 +2529,22 @@ public class Workspace extends PagedView<WorkspacePageIndicator> View view; switch (info.itemType) { - case ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: - case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - if (info.container == NO_ID && info instanceof AppInfo) { - // Came from all apps -- make a copy - info = ((AppInfo) info).makeWorkspaceItem(); - d.dragInfo = info; - } - view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info); - break; - case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, - (FolderInfo) info); - break; - default: - throw new IllegalStateException("Unknown item type: " + info.itemType); + case ITEM_TYPE_APPLICATION: + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: + if (info instanceof AppInfo) { + // Came from all apps -- make a copy + info = ((AppInfo) info).makeWorkspaceItem(); + d.dragInfo = info; + } + view = mLauncher.createShortcut(cellLayout, (WorkspaceItemInfo) info); + break; + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + view = FolderIcon.fromXml(R.layout.folder_icon, mLauncher, cellLayout, + (FolderInfo) info); + break; + default: + throw new IllegalStateException("Unknown item type: " + info.itemType); } // First we find the cell nearest to point at which the item is @@ -2805,10 +2802,27 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } /** + * Removed widget from workspace by appWidgetId + * @param appWidgetId + */ + public void removeWidget(int appWidgetId) { + mapOverItems((info, view) -> { + if (info instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo appWidgetInfo = (LauncherAppWidgetInfo) info; + if (appWidgetInfo.appWidgetId == appWidgetId) { + mLauncher.removeItem(view, appWidgetInfo, true); + return true; + } + } + return false; + }); + } + + /** * Removes all folder listeners */ public void removeFolderListeners() { - mapOverItems(false, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View view) { if (view instanceof FolderIcon) { @@ -2960,7 +2974,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> public View getFirstMatch(final ItemOperator operator) { final View[] value = new View[1]; - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v) { if (operator.evaluate(info, v)) { @@ -2983,7 +2997,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> final View[] matches = new View[operators.length]; // For efficiency, the outer loop should be CellLayout. for (CellLayout cellLayout : cellLayouts) { - mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> { + mapOverCellLayout(cellLayout, (info, v) -> { for (int i = 0; i < operators.length; ++i) { if (matches[i] == null && operators[i].evaluate(info, v)) { matches[i] = v; @@ -3008,7 +3022,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } void clearDropTargets() { - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v) { if (v instanceof DropTarget) { @@ -3053,10 +3067,12 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } else if (itemToRemove.container >= 0) { // The item may belong to a folder. View parent = idToViewMap.get(itemToRemove.container); - if (parent != null) { + if (parent instanceof FolderIcon) { FolderInfo folderInfo = (FolderInfo) parent.getTag(); - folderInfo.prepareAutoUpdate(); folderInfo.remove((WorkspaceItemInfo) itemToRemove, false); + if (((FolderIcon) parent).getFolder().isOpen()) { + ((FolderIcon) parent).getFolder().close(false /* animate */); + } } } } @@ -3080,18 +3096,17 @@ public class Workspace extends PagedView<WorkspacePageIndicator> /** * Map the operator over the shortcuts and widgets, return the first-non-null value. * - * @param recurse true: iterate over folder children. false: op get the folders themselves. * @param op the operator to map over the shortcuts */ - public void mapOverItems(boolean recurse, ItemOperator op) { + public void mapOverItems(ItemOperator op) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { - if (mapOverCellLayout(recurse, layout, op)) { + if (mapOverCellLayout(layout, op)) { return; } } } - private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) { + private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) { // TODO(b/128460496) Potential race condition where layout is not yet loaded if (layout == null) { return false; @@ -3101,103 +3116,68 @@ public class Workspace extends PagedView<WorkspacePageIndicator> final int itemCount = container.getChildCount(); for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { View item = container.getChildAt(itemIdx); - ItemInfo info = (ItemInfo) item.getTag(); - if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { - FolderIcon folder = (FolderIcon) item; - ArrayList<View> folderChildren = folder.getFolder().getItemsInReadingOrder(); - // map over all the children in the folder - final int childCount = folderChildren.size(); - for (int childIdx = 0; childIdx < childCount; childIdx++) { - View child = folderChildren.get(childIdx); - info = (ItemInfo) child.getTag(); - if (op.evaluate(info, child)) { - return true; - } - } - } else { - if (op.evaluate(info, item)) { - return true; - } + if (op.evaluate((ItemInfo) item.getTag(), item)) { + return true; } } return false; } void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) { - int total = shortcuts.size(); - final HashSet<WorkspaceItemInfo> updates = new HashSet<>(total); - final IntSet folderIds = new IntSet(); + final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts); + ItemOperator op = (info, v) -> { + if (v instanceof BubbleTextView && updates.contains(info)) { + WorkspaceItemInfo si = (WorkspaceItemInfo) info; + BubbleTextView shortcut = (BubbleTextView) v; + Drawable oldIcon = shortcut.getIcon(); + boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) + && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); + shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } + + // Iterate all items + return false; + }; - for (int i = 0; i < total; i++) { - WorkspaceItemInfo s = shortcuts.get(i); - updates.add(s); - folderIds.add(s.container); + mapOverItems(op); + Folder openFolder = Folder.getOpen(mLauncher); + if (openFolder != null) { + openFolder.iterateOverItems(op); } - - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView && - updates.contains(info)) { - WorkspaceItemInfo si = (WorkspaceItemInfo) info; - BubbleTextView shortcut = (BubbleTextView) v; - Drawable oldIcon = shortcut.getIcon(); - boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) - && ((PreloadIconDrawable) oldIcon).hasNotCompleted(); - shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); - } - // process all the shortcuts - return false; - } - }); - - // Update folder icons - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof FolderInfo && folderIds.contains(info.id)) { - ((FolderInfo) info).itemsChanged(false); - } - // process all the shortcuts - return false; - } - }); } public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) { final PackageUserKey packageUserKey = new PackageUserKey(null, null); - final IntSet folderIds = new IntSet(); - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { - if (!packageUserKey.updateFromItemInfo(info) - || updatedDots.test(packageUserKey)) { - ((BubbleTextView) v).applyDotState(info, true /* animate */); - folderIds.add(info.container); - } - } - // process all the shortcuts - return false; - } - }); + Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info) + || updatedDots.test(packageUserKey); - // Update folder icons - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof FolderInfo && folderIds.contains(info.id) - && v instanceof FolderIcon) { + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { + if (matcher.test(info)) { + ((BubbleTextView) v).applyDotState(info, true /* animate */); + } + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + FolderInfo fi = (FolderInfo) info; + if (fi.contents.stream().anyMatch(matcher)) { FolderDotInfo folderDotInfo = new FolderDotInfo(); - for (WorkspaceItemInfo si : ((FolderInfo) info).contents) { + for (WorkspaceItemInfo si : fi.contents) { folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si)); } ((FolderIcon) v).setDotInfo(folderDotInfo); } - // process all the shortcuts - return false; } - }); + + // process all the shortcuts + return false; + }; + + mapOverItems(op); + Folder folder = Folder.getOpen(mLauncher); + if (folder != null) { + folder.iterateOverItems(op); + } } public void removeAbandonedPromise(String packageName, UserHandle user) { @@ -3209,21 +3189,25 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } public void updateRestoreItems(final HashSet<ItemInfo> updates) { - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView - && updates.contains(info)) { - ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */); - } else if (v instanceof PendingAppWidgetHostView - && info instanceof LauncherAppWidgetInfo - && updates.contains(info)) { - ((PendingAppWidgetHostView) v).applyState(); - } - // process all the shortcuts - return false; - } - }); + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView + && updates.contains(info)) { + ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */); + } else if (v instanceof PendingAppWidgetHostView + && info instanceof LauncherAppWidgetInfo + && updates.contains(info)) { + ((PendingAppWidgetHostView) v).applyState(); + } else if (v instanceof FolderIcon && info instanceof FolderInfo) { + ((FolderIcon) v).updatePreviewItems(updates::contains); + } + // process all the shortcuts + return false; + }; + mapOverItems(op); + Folder folder = Folder.getOpen(mLauncher); + if (folder != null) { + folder.iterateOverItems(op); + } } public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) { @@ -3247,7 +3231,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> } else { // widgetRefresh will automatically run when the packages are updated. // For now just update the progress bars - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View view) { if (view instanceof PendingAppWidgetHostView @@ -3371,7 +3355,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator> mRefreshPending = false; ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size()); - mapOverItems(MAP_NO_RECURSE, (info, view) -> { + mapOverItems((info, view) -> { if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { views.add((PendingAppWidgetHostView) view); } diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java index 9451aae98..1323588e3 100644 --- a/src/com/android/launcher3/WorkspaceItemInfo.java +++ b/src/com/android/launcher3/WorkspaceItemInfo.java @@ -212,7 +212,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { public ComponentName getTargetComponent() { ComponentName cn = super.getTargetComponent(); if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT - || hasStatusFlag(FLAG_SUPPORTS_WEB_UI|FLAG_AUTOINSTALL_ICON|FLAG_RESTORED_ICON))) { + || hasStatusFlag(FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON))) { // Legacy shortcuts and promise icons with web UI may not have a componentName but just // a packageName. In that case create a dummy componentName instead of adding additional // check everywhere. @@ -221,4 +221,9 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { } return cn; } + + @Override + public ItemInfoWithIcon clone() { + return new WorkspaceItemInfo(this); + } } diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 293b86722..37ee24850 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -312,6 +312,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo + grid.cellLayoutPaddingLeftRightPx; for (int i = 0; i < mAH.length; i++) { + mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns); mAH[i].padding.bottom = insets.bottom; mAH[i].padding.left = mAH[i].padding.right = leftRightPadding; mAH[i].applyPadding(); diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 3cfa0b1ec..bb212686e 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -180,7 +180,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. private final GridLayoutManager mGridLayoutMgr; private final GridSpanSizer mGridSizer; - private final int mAppsPerRow; + private int mAppsPerRow; private BindViewCallback mBindViewCallback; private OnFocusChangeListener mIconFocusListener; @@ -200,7 +200,11 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter. mGridLayoutMgr.setSpanSizeLookup(mGridSizer); mLayoutInflater = LayoutInflater.from(launcher); - mAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns; + setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns); + } + + public void setAppsPerRow(int appsPerRow) { + mAppsPerRow = appsPerRow; mGridLayoutMgr.setSpanCount(mAppsPerRow); } diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java index 69068c6ee..5b7394048 100644 --- a/src/com/android/launcher3/allapps/AllAppsPagedView.java +++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java @@ -81,4 +81,9 @@ public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> { public boolean hasOverlappingRendering() { return false; } + + @Override + protected boolean isVerticalScrollable() { + return false; + } } diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index ca8dbebfc..c4b2f68c9 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -15,6 +15,9 @@ */ package com.android.launcher3.allapps; +import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR; +import static com.android.launcher3.AppInfo.EMPTY_ARRAY; + import android.view.View; import android.view.ViewGroup; @@ -26,8 +29,7 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; +import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; @@ -43,27 +45,33 @@ public class AllAppsStore { public static final int DEFER_UPDATES_TEST = 1 << 1; private PackageUserKey mTempKey = new PackageUserKey(null, null); - private final HashMap<ComponentKey, AppInfo> mComponentToAppMap = new HashMap<>(); + private AppInfo mTempInfo = new AppInfo(); + + private AppInfo[] mApps = EMPTY_ARRAY; + private final List<OnUpdateListener> mUpdateListeners = new ArrayList<>(); private final ArrayList<ViewGroup> mIconContainers = new ArrayList<>(); private int mDeferUpdatesFlags = 0; private boolean mUpdatePending = false; - public Collection<AppInfo> getApps() { - return mComponentToAppMap.values(); + public AppInfo[] getApps() { + return mApps; } /** * Sets the current set of apps. */ - public void setApps(List<AppInfo> apps) { - mComponentToAppMap.clear(); - addOrUpdateApps(apps); + public void setApps(AppInfo[] apps) { + mApps = apps; + notifyUpdate(); } public AppInfo getApp(ComponentKey key) { - return mComponentToAppMap.get(key); + mTempInfo.componentName = key.componentName; + mTempInfo.user = key.user; + int index = Arrays.binarySearch(mApps, mTempInfo, COMPONENT_KEY_COMPARATOR); + return index < 0 ? null : mApps[index]; } public void enableDeferUpdates(int flag) { @@ -86,27 +94,6 @@ public class AllAppsStore { return mDeferUpdatesFlags; } - /** - * Adds or updates existing apps in the list - */ - public void addOrUpdateApps(List<AppInfo> apps) { - for (AppInfo app : apps) { - mComponentToAppMap.put(app.toComponentKey(), app); - } - notifyUpdate(); - } - - /** - * Removes some apps from the list. - */ - public void removeApps(List<AppInfo> apps) { - for (AppInfo app : apps) { - mComponentToAppMap.remove(app.toComponentKey()); - } - notifyUpdate(); - } - - private void notifyUpdate() { if (mDeferUpdatesFlags != 0) { mUpdatePending = true; diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 1369441fe..0c4be6287 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -21,16 +21,13 @@ import android.content.pm.PackageManager; import com.android.launcher3.AppInfo; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; -import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.shortcuts.DeepShortcutManager; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LabelComparator; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -145,9 +142,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { // The of ordered component names as a result of a search query private ArrayList<ComponentKey> mSearchResults; - private HashMap<CharSequence, String> mCachedSectionNames = new HashMap<>(); private AllAppsGridAdapter mAdapter; - private AlphabeticIndexCompat mIndexer; private AppInfoComparator mAppNameComparator; private final int mNumAppsPerRow; private int mNumAppRowsInAdapter; @@ -156,7 +151,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) { mAllAppsStore = appsStore; mLauncher = Launcher.getLauncher(context); - mIndexer = new AlphabeticIndexCompat(context); mAppNameComparator = new AppInfoComparator(context); mIsWork = isWork; mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns; @@ -263,7 +257,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { TreeMap<String, ArrayList<AppInfo>> sectionMap = new TreeMap<>(new LabelComparator()); for (AppInfo info : mApps) { // Add the section to the cache - String sectionName = getAndUpdateCachedSectionName(info.title); + String sectionName = info.sectionName; // Add it to the mapping ArrayList<AppInfo> sectionApps = sectionMap.get(sectionName); @@ -279,12 +273,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { for (Map.Entry<String, ArrayList<AppInfo>> entry : sectionMap.entrySet()) { mApps.addAll(entry.getValue()); } - } else { - // Just compute the section headers for use below - for (AppInfo info : mApps) { - // Add the section to the cache - getAndUpdateCachedSectionName(info.title); - } } // Recompose the set of adapter items from the current set of apps @@ -320,7 +308,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the // ordered set of sections for (AppInfo info : getFiltersAppInfos()) { - String sectionName = getAndUpdateCachedSectionName(info.title); + String sectionName = info.sectionName; // Create a new section if the section names do not match if (!sectionName.equals(lastSectionName)) { @@ -428,18 +416,4 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } return result; } - - /** - * Returns the cached section name for the given title, recomputing and updating the cache if - * the title has no cached section name. - */ - private String getAndUpdateCachedSectionName(CharSequence title) { - String sectionName = mCachedSectionNames.get(title); - if (sectionName == null) { - sectionName = mIndexer.computeSectionName(title); - mCachedSectionNames.put(title, sectionName); - } - return sectionName; - } - } diff --git a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java index dfdcc7089..46c9006dd 100644 --- a/src/com/android/launcher3/compat/AlphabeticIndexCompat.java +++ b/src/com/android/launcher3/compat/AlphabeticIndexCompat.java @@ -3,7 +3,6 @@ package com.android.launcher3.compat; import android.content.Context; import android.icu.text.AlphabeticIndex; import android.os.LocaleList; -import android.util.Log; import com.android.launcher3.Utilities; @@ -12,28 +11,32 @@ import java.util.Locale; import androidx.annotation.NonNull; public class AlphabeticIndexCompat { - private static final String TAG = "AlphabeticIndexCompat"; private static final String MID_DOT = "\u2219"; - private final BaseIndex mBaseIndex; private final String mDefaultMiscLabel; + private final AlphabeticIndex.ImmutableIndex mBaseIndex; + public AlphabeticIndexCompat(Context context) { - BaseIndex index = null; + this(context.getResources().getConfiguration().getLocales()); + } - try { - index = new AlphabeticIndexVN(context); - } catch (Exception e) { - Log.d(TAG, "Unable to load the system index", e); - } + public AlphabeticIndexCompat(LocaleList locales) { + int localeCount = locales.size(); - mBaseIndex = index == null ? new BaseIndex() : index; + Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0); + AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale); + for (int i = 1; i < localeCount; i++) { + indexBuilder.addLabels(locales.get(i)); + } + indexBuilder.addLabels(Locale.ENGLISH); + mBaseIndex = indexBuilder.buildImmutableIndex(); - if (context.getResources().getConfiguration().locale - .getLanguage().equals(Locale.JAPANESE.getLanguage())) { + if (primaryLocale.getLanguage().equals(Locale.JAPANESE.getLanguage())) { // Japanese character 他 ("misc") mDefaultMiscLabel = "\u4ed6"; - // TODO(winsonc, omakoto): We need to handle Japanese sections better, especially the kanji + // TODO(winsonc, omakoto): We need to handle Japanese sections better, + // especially the kanji } else { // Dot mDefaultMiscLabel = MID_DOT; @@ -45,7 +48,7 @@ public class AlphabeticIndexCompat { */ public String computeSectionName(@NonNull CharSequence cs) { String s = Utilities.trim(cs); - String sectionName = mBaseIndex.getBucketLabel(mBaseIndex.getBucketIndex(s)); + String sectionName = mBaseIndex.getBucket(mBaseIndex.getBucketIndex(s)).getLabel(); if (Utilities.trim(sectionName).isEmpty() && s.length() > 0) { int c = s.codePointAt(0); boolean startsWithDigit = Character.isDigit(c); @@ -66,71 +69,4 @@ public class AlphabeticIndexCompat { } return sectionName; } - - /** - * Base class to support Alphabetic indexing if not supported by the framework. - * TODO(winsonc): disable for non-english locales - */ - private static class BaseIndex { - - private static final String BUCKETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-"; - private static final int UNKNOWN_BUCKET_INDEX = BUCKETS.length() - 1; - - /** - * Returns the index of the bucket in which the given string should appear. - */ - protected int getBucketIndex(@NonNull String s) { - if (s.isEmpty()) { - return UNKNOWN_BUCKET_INDEX; - } - int index = BUCKETS.indexOf(s.substring(0, 1).toUpperCase()); - if (index != -1) { - return index; - } - return UNKNOWN_BUCKET_INDEX; - } - - /** - * Returns the label for the bucket at the given index (as returned by getBucketIndex). - */ - protected String getBucketLabel(int index) { - return BUCKETS.substring(index, index + 1); - } - } - - /** - * Implementation based on {@link AlphabeticIndex}. - */ - private static class AlphabeticIndexVN extends BaseIndex { - - private final AlphabeticIndex.ImmutableIndex mAlphabeticIndex; - - public AlphabeticIndexVN(Context context) { - LocaleList locales = context.getResources().getConfiguration().getLocales(); - int localeCount = locales.size(); - - Locale primaryLocale = localeCount == 0 ? Locale.ENGLISH : locales.get(0); - AlphabeticIndex indexBuilder = new AlphabeticIndex(primaryLocale); - for (int i = 1; i < localeCount; i++) { - indexBuilder.addLabels(locales.get(i)); - } - indexBuilder.addLabels(Locale.ENGLISH); - - mAlphabeticIndex = indexBuilder.buildImmutableIndex(); - } - - /** - * Returns the index of the bucket in which {@param s} should appear. - */ - protected int getBucketIndex(String s) { - return mAlphabeticIndex.getBucketIndex(s); - } - - /** - * Returns the label for the bucket at the given index - */ - protected String getBucketLabel(int index) { - return mAlphabeticIndex.getBucket(index).getLabel(); - } - } } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index 58fc73d23..cdb5c4d2b 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -27,25 +27,27 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.Utilities; import com.android.launcher3.util.PackageUserKey; import java.util.List; -import androidx.annotation.Nullable; - public abstract class LauncherAppsCompat { public interface OnAppsChangedCallbackCompat { - void onPackageRemoved(String packageName, UserHandle user); - void onPackageAdded(String packageName, UserHandle user); - void onPackageChanged(String packageName, UserHandle user); - void onPackagesAvailable(String[] packageNames, UserHandle user, boolean replacing); - void onPackagesUnavailable(String[] packageNames, UserHandle user, boolean replacing); - void onPackagesSuspended(String[] packageNames, UserHandle user); - void onPackagesUnsuspended(String[] packageNames, UserHandle user); - void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts, - UserHandle user); + default void onPackageRemoved(String packageName, UserHandle user) { } + default void onPackageAdded(String packageName, UserHandle user) { } + default void onPackageChanged(String packageName, UserHandle user) { } + default void onPackagesAvailable(String[] packageNames, UserHandle user, + boolean replacing) { } + default void onPackagesUnavailable(String[] packageNames, UserHandle user, + boolean replacing) { } + default void onPackagesSuspended(String[] packageNames, UserHandle user) { } + default void onPackagesUnsuspended(String[] packageNames, UserHandle user) { } + default void onShortcutsChanged(String packageName, List<ShortcutInfo> shortcuts, + UserHandle user) { } } protected LauncherAppsCompat() { diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java index 6e7a1bdcf..5e13d00b7 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java @@ -16,6 +16,8 @@ package com.android.launcher3.compat; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; @@ -30,19 +32,17 @@ import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVO; import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; import java.util.List; -import androidx.annotation.Nullable; - @TargetApi(26) public class LauncherAppsCompatVO extends LauncherAppsCompatVL { @@ -120,7 +120,7 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL { } } else { // Block the worker thread until the accept() is called. - new LooperExecutor(LauncherModel.getWorkerLooper()).execute(new Runnable() { + MODEL_EXECUTOR.execute(new Runnable() { @Override public void run() { try { diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index 879d963c7..9c57e8ab5 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -16,22 +16,23 @@ package com.android.launcher3.compat; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; -import android.os.Handler; import android.os.UserHandle; import android.text.TextUtils; import android.util.SparseArray; +import com.android.launcher3.LauncherModel; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; import com.android.launcher3.icons.IconCache; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; @@ -53,7 +54,6 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { @Thunk final PackageInstaller mInstaller; private final IconCache mCache; - private final Handler mWorker; private final Context mAppContext; private final HashMap<String,Boolean> mSessionVerifiedMap = new HashMap<>(); private final LauncherAppsCompat mLauncherApps; @@ -63,11 +63,10 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { mAppContext = context.getApplicationContext(); mInstaller = context.getPackageManager().getPackageInstaller(); mCache = LauncherAppState.getInstance(context).getIconCache(); - mWorker = new Handler(LauncherModel.getWorkerLooper()); - mInstaller.registerSessionCallback(mCallback, mWorker); + mInstaller.registerSessionCallback(mCallback, MODEL_EXECUTOR.getHandler()); mLauncherApps = LauncherAppsCompat.getInstance(context); - mPromiseIconIds = IntSet.wrap(IntArray.wrap(Utilities.getIntArrayFromString( - getPrefs(context).getString(PROMISE_ICON_IDS, "")))); + mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString( + getPrefs(context).getString(PROMISE_ICON_IDS, ""))); cleanUpPromiseIconIds(); } diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 025087b9d..945588941 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -115,6 +115,10 @@ public abstract class BaseFlags { "APP_SEARCH_IMPROVEMENTS", false, "Adds localized title and keyword search and ranking"); + public static final TogglableFlag ENABLE_PREDICTION_DISMISS = new TogglableFlag( + "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list"); + + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index a2dcbf87d..9fb10905f 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -20,6 +20,7 @@ import static com.android.launcher3.logging.LoggerUtils.newCommandAction; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.logging.LoggerUtils.newItemTarget; import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; import android.app.ActivityOptions; @@ -47,7 +48,6 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetProviderInfo; -import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompatVO; @@ -55,7 +55,6 @@ import com.android.launcher3.model.WidgetItem; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.InstantAppResolver; -import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; @@ -234,7 +233,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); mWidgetCell.ensurePreview(); } - }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper())); + }.executeOnExecutor(MODEL_EXECUTOR); // TODO: Create a worker looper executor and reuse that everywhere. } diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 09c5e5b2b..f66d07e3d 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -17,6 +17,7 @@ package com.android.launcher3.dragndrop; import static com.android.launcher3.Utilities.getBadge; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -41,16 +42,19 @@ import android.os.Handler; import android.os.Looper; import android.view.View; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + import com.android.launcher3.FastBitmapDrawable; +import com.android.launcher3.FirstFrameAnimatorHelper; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.FirstFrameAnimatorHelper; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.util.Themes; @@ -58,10 +62,6 @@ import com.android.launcher3.util.Thunk; import java.util.Arrays; -import androidx.dynamicanimation.animation.FloatPropertyCompat; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - public class DragView extends View implements LauncherStateManager.StateListener { private static final ColorMatrix sTempMatrix1 = new ColorMatrix(); private static final ColorMatrix sTempMatrix2 = new ColorMatrix(); @@ -210,7 +210,7 @@ public class DragView extends View implements LauncherStateManager.StateListener return; } // Load the adaptive icon on a background thread and add the view in ui thread. - new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(new Runnable() { + MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(new Runnable() { @Override public void run() { Object[] outObj = new Object[1]; diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java index d8a1f9951..0bb3fbac5 100644 --- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java +++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java @@ -16,6 +16,8 @@ package com.android.launcher3.dragndrop; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -31,7 +33,6 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.launcher3.Launcher; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.folder.PreviewBackground; @@ -85,7 +86,7 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable { // Create the actual drawable on the UI thread to avoid race conditions with // FolderIcon draw pass try { - return new MainThreadExecutor().submit(() -> { + return MAIN_EXECUTOR.submit(() -> { FolderIcon icon = launcher.findFolderIcon(folderId); return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize); }).get(); diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index f22b53338..65d593cdc 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -26,12 +26,12 @@ import android.animation.AnimatorSet; import android.annotation.SuppressLint; import android.appwidget.AppWidgetHostView; import android.content.Context; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.Rect; import android.text.InputType; import android.text.Selection; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; @@ -126,9 +126,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private static final Rect sTempRect = new Rect(); private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10; - private static String sDefaultFolderName; - private static String sHintText; - private final Alarm mReorderAlarm = new Alarm(); private final Alarm mOnExitAlarm = new Alarm(); private final Alarm mOnScrollHintAlarm = new Alarm(); @@ -173,8 +170,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private boolean mDeleteFolderOnDropCompleted = false; private boolean mSuppressFolderDeletion = false; private boolean mItemAddedBackToSelfViaIcon = false; - @Thunk float mFolderIconPivotX; - @Thunk float mFolderIconPivotY; private boolean mIsEditingName = false; @ViewDebug.ExportedProperty(category = "launcher") @@ -196,8 +191,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo super(context, attrs); setAlwaysDrawnWithCacheEnabled(false); - setLocaleDependentFields(getResources(), false /* force */); - mLauncher = Launcher.getLauncher(context); // We need this view to be focusable in touch mode so that when text editing of the folder // name is complete, we have something to focus on, thus hiding the cursor and giving @@ -315,10 +308,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Convert to a string here to ensure that no other state associated with the text field // gets saved. String newTitle = mFolderName.getText().toString(); - mInfo.setTitle(newTitle); + mInfo.title = newTitle; + mFolderIcon.onTitleChanged(newTitle); mLauncher.getModelWriter().updateItemInDatabase(mInfo); - mFolderName.setHint(sDefaultFolderName.contentEquals(newTitle) ? sHintText : null); + if (TextUtils.isEmpty(mInfo.title)) { + mFolderName.setHint(R.string.folder_hint_text); + } else { + mFolderName.setHint(null); + } sendCustomAccessibilityEvent( this, AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, @@ -385,7 +383,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mInfo = info; ArrayList<WorkspaceItemInfo> children = info.contents; Collections.sort(children, ITEM_POS_COMPARATOR); - mContent.bindItems(children); + updateItemLocationsInDatabaseBatch(); DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); if (lp == null) { @@ -393,26 +391,21 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.customPosition = true; setLayoutParams(lp); } - centerAboutIcon(); - mItemsInvalidated = true; - updateTextViewFocus(); mInfo.addListener(this); - if (!sDefaultFolderName.contentEquals(mInfo.title)) { + if (!TextUtils.isEmpty(mInfo.title)) { mFolderName.setText(mInfo.title); mFolderName.setHint(null); } else { mFolderName.setText(""); - mFolderName.setHint(sHintText); + mFolderName.setHint(R.string.folder_hint_text); } // In case any children didn't come across during loading, clean up the folder accordingly - mFolderIcon.post(new Runnable() { - public void run() { - if (getItemCount() <= 1) { - replaceFolderWithFinalItem(); - } + mFolderIcon.post(() -> { + if (getItemCount() <= 1) { + replaceFolderWithFinalItem(); } }); } @@ -473,17 +466,49 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } /** + * Opens the folder as part of a drag operation + */ + public void beginExternalDrag() { + mIsExternalDrag = true; + mDragInProgress = true; + + // Since this folder opened by another controller, it might not get onDrop or + // onDropComplete. Perform cleanup once drag-n-drop ends. + mDragController.addDragListener(this); + + ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents); + mEmptyCellRank = items.size(); + items.add(null); // Add an empty spot at the end + + animateOpen(items, mEmptyCellRank / mContent.itemsPerPage()); + } + + /** * Opens the user folder described by the specified tag. The opening of the folder * is animated relative to the specified View. If the View is null, no animation * is played. */ public void animateOpen() { + animateOpen(mInfo.contents, 0); + } + + /** + * Opens the user folder described by the specified tag. The opening of the folder + * is animated relative to the specified View. If the View is null, no animation + * is played. + */ + private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) { Folder openFolder = getOpen(mLauncher); if (openFolder != null && openFolder != this) { // Close any open folder before opening a folder. openFolder.close(true); } + mContent.bindItems(items); + centerAboutIcon(); + mItemsInvalidated = true; + updateTextViewFocus(); + mIsOpen = true; DragLayer dragLayer = mLauncher.getDragLayer(); @@ -500,10 +525,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } mContent.completePendingPageChanges(); - if (!mDragInProgress) { - // Open on the first page. - mContent.snapToPageImmediately(0); - } + mContent.snapToPageImmediately(pageNo); // This is set to true in close(), but isn't reset to false until onDropCompleted(). This // leads to an inconsistent state if you drag out of the folder and drag back in without @@ -574,16 +596,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mContent.verifyVisibleHighResIcons(mContent.getNextPage()); } - public void beginExternalDrag() { - mEmptyCellRank = mContent.allocateRankForNewItem(); - mIsExternalDrag = true; - mDragInProgress = true; - - // Since this folder opened by another controller, it might not get onDrop or - // onDropComplete. Perform cleanup once drag-n-drop ends. - mDragController.addDragListener(this); - } - @Override protected boolean isOfType(int type) { return (type & TYPE_FOLDER) != 0; @@ -668,6 +680,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } else if (mDragInProgress) { mDeleteFolderOnDropCompleted = true; } + } else if (!mDragInProgress) { + mContent.unbindItems(); } mSuppressFolderDeletion = false; clearDragInfo(); @@ -822,9 +836,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } } + @Override public void onDropCompleted(final View target, final DragObject d, final boolean success) { - if (success) { if (mDeleteFolderOnDropCompleted && !mItemAddedBackToSelfViaIcon && target != this) { replaceFolderWithFinalItem(); @@ -834,9 +848,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo WorkspaceItemInfo info = (WorkspaceItemInfo) d.dragInfo; View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) ? mCurrentDragView : mContent.createNewView(info); - ArrayList<View> views = getItemsInReadingOrder(); + ArrayList<View> views = getIconsInReadingOrder(); views.add(info.rank, icon); - mContent.arrangeChildren(views, views.size()); + mContent.arrangeChildren(views); mItemsInvalidated = true; try (SuppressInfoChanges s = new SuppressInfoChanges()) { @@ -874,16 +888,21 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private void updateItemLocationsInDatabaseBatch() { - ArrayList<View> list = getItemsInReadingOrder(); - ArrayList<ItemInfo> items = new ArrayList<ItemInfo>(); - for (int i = 0; i < list.size(); i++) { - View v = list.get(i); - ItemInfo info = (ItemInfo) v.getTag(); - info.rank = i; - items.add(info); + FolderGridOrganizer verifier = new FolderGridOrganizer( + mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo); + + ArrayList<ItemInfo> items = new ArrayList<>(); + int total = mInfo.contents.size(); + for (int i = 0; i < total; i++) { + WorkspaceItemInfo itemInfo = mInfo.contents.get(i); + if (verifier.updateRankAndPos(itemInfo, i)) { + items.add(itemInfo); + } } - mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0); + if (!items.isEmpty()) { + mLauncher.getModelWriter().moveItemsInDatabase(items, mInfo.id, 0); + } } public void notifyDrop() { @@ -948,24 +967,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo setPivotX(folderPivotX); setPivotY(folderPivotY); - mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * - (1.0f * folderPivotX / width)); - mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * - (1.0f * folderPivotY / height)); - lp.width = width; lp.height = height; lp.x = left; lp.y = top; } - public float getPivotXForIconAnimation() { - return mFolderIconPivotX; - } - public float getPivotYForIconAnimation() { - return mFolderIconPivotY; - } - private int getContentAreaHeight() { DeviceProfile grid = mLauncher.getDeviceProfile(); int maxContentAreaHeight = grid.availableHeightPx @@ -1021,22 +1028,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo * Rearranges the children based on their rank. */ public void rearrangeChildren() { - rearrangeChildren(-1); - } - - /** - * Rearranges the children based on their rank. - * @param itemCount if greater than the total children count, empty spaces are left at the end, - * otherwise it is ignored. - */ - public void rearrangeChildren(int itemCount) { - ArrayList<View> views = getItemsInReadingOrder(); - mContent.arrangeChildren(views, Math.max(itemCount, views.size())); + mContent.arrangeChildren(getIconsInReadingOrder()); mItemsInvalidated = true; } public int getItemCount() { - return mContent.getItemCount(); + return mInfo.contents.size(); } @Thunk void replaceFolderWithFinalItem() { @@ -1044,7 +1041,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo Runnable onCompleteRunnable = new Runnable() { @Override public void run() { - int itemCount = mInfo.contents.size(); + int itemCount = getItemCount(); if (itemCount <= 1) { View newIcon = null; @@ -1121,9 +1118,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return false; } }); + } else { + setOnKeyListener(null); } } + @Override public void onDrop(DragObject d, DragOptions options) { // If the icon was dropped while the page was being scrolled, we need to compute // the target location again such that the icon is placed of the final page. @@ -1171,12 +1171,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // before creating the view, so that WorkspaceItemInfo is updated appropriately. mLauncher.getModelWriter().addOrMoveItemInDatabase( si, mInfo.id, 0, si.cellX, si.cellY); - - // We only need to update the locations if it doesn't get handled in - // #onDropCompleted. - if (d.dragSource != this) { - updateItemLocationsInDatabaseBatch(); - } mIsExternalDrag = false; } else { currentDragView = mCurrentDragView; @@ -1203,7 +1197,13 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Temporarily suppress the listener, as we did all the work already here. try (SuppressInfoChanges s = new SuppressInfoChanges()) { - mInfo.add(si, false); + mInfo.add(si, mEmptyCellRank, false); + } + + // We only need to update the locations if it doesn't get handled in + // #onDropCompleted. + if (d.dragSource != this) { + updateItemLocationsInDatabaseBatch(); } } @@ -1226,22 +1226,29 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // to correspond to the animation of the icon back into the folder. This is public void hideItem(WorkspaceItemInfo info) { View v = getViewForInfo(info); - v.setVisibility(INVISIBLE); + if (v != null) { + v.setVisibility(INVISIBLE); + } } public void showItem(WorkspaceItemInfo info) { View v = getViewForInfo(info); - v.setVisibility(VISIBLE); + if (v != null) { + v.setVisibility(VISIBLE); + } } @Override public void onAdd(WorkspaceItemInfo item, int rank) { - View view = mContent.createAndAddViewForRank(item, rank); + FolderGridOrganizer verifier = new FolderGridOrganizer( + mLauncher.getDeviceProfile().inv).setFolderInfo(mInfo); + verifier.updateRankAndPos(item, rank); mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, item.cellY); + updateItemLocationsInDatabaseBatch(); - ArrayList<View> items = new ArrayList<>(getItemsInReadingOrder()); - items.add(rank, view); - mContent.arrangeChildren(items, items.size()); + if (mContent.areViewsBound()) { + mContent.createAndAddViewForRank(item, rank); + } mItemsInvalidated = true; } @@ -1264,13 +1271,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } private View getViewForInfo(final WorkspaceItemInfo item) { - return mContent.iterateOverItems(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View view) { - return info == item; - } - }); + return mContent.iterateOverItems((info, view) -> info == item); } @Override @@ -1278,32 +1279,27 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo updateTextViewFocus(); } - @Override - public void prepareAutoUpdate() { - close(false); - } - - public void onTitleChanged(CharSequence title) { + /** + * Utility methods to iterate over items of the view + */ + public void iterateOverItems(ItemOperator op) { + mContent.iterateOverItems(op); } - public ArrayList<View> getItemsInReadingOrder() { + /** + * Returns the sorted list of all the icons in the folder + */ + public ArrayList<View> getIconsInReadingOrder() { if (mItemsInvalidated) { mItemsInReadingOrder.clear(); - mContent.iterateOverItems(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View view) { - mItemsInReadingOrder.add(view); - return false; - } - }); + mContent.iterateOverItems((i, v) -> !mItemsInReadingOrder.add(v)); mItemsInvalidated = false; } return mItemsInReadingOrder; } public List<BubbleTextView> getItemsOnPage(int page) { - ArrayList<View> allItems = getItemsInReadingOrder(); + ArrayList<View> allItems = getIconsInReadingOrder(); int lastPage = mContent.getPageCount() - 1; int totalItemsInFolder = allItems.size(); int itemsPerPage = mContent.itemsPerPage(); @@ -1482,15 +1478,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return false; } - public static void setLocaleDependentFields(Resources res, boolean force) { - if (sDefaultFolderName == null || force) { - sDefaultFolderName = res.getString(R.string.folder_name); - } - if (sHintText == null || force) { - sHintText = res.getString(R.string.folder_hint_text); - } - } - /** * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of * rounded rect. diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index 962f21560..9eb06938a 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -79,7 +79,7 @@ public class FolderAnimationManager { private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator; private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); - + private final FolderGridOrganizer mPreviewVerifier; public FolderAnimationManager(Folder folder, boolean isOpening) { mFolder = folder; @@ -91,6 +91,7 @@ public class FolderAnimationManager { mContext = folder.getContext(); mLauncher = folder.mLauncher; + mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv); mIsOpening = isOpening; @@ -113,7 +114,7 @@ public class FolderAnimationManager { public AnimatorSet getAnimator() { final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams(); ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule(); - final List<BubbleTextView> itemsInPreview = mFolderIcon.getPreviewItems(); + final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0); // Match position of the FolderIcon final Rect folderIconPos = new Rect(); @@ -226,15 +227,22 @@ public class FolderAnimationManager { } /** + * Returns the list of "preview items" on {@param page}. + */ + private List<BubbleTextView> getPreviewIconsOnPage(int page) { + return mPreviewVerifier.setFolderInfo(mFolder.mInfo) + .previewItemsForPage(page, mFolder.getIconsInReadingOrder()); + } + + /** * Animate the items on the current page. */ private void addPreviewItemAnimators(AnimatorSet animatorSet, final float folderScale, int previewItemOffsetX, int previewItemOffsetY) { ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule(); boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0; - final List<BubbleTextView> itemsInPreview = isOnFirstPage - ? mFolderIcon.getPreviewItems() - : mFolderIcon.getPreviewItemsOnPage(mFolder.mContent.getCurrentPage()); + final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage( + isOnFirstPage ? 0 : mFolder.mContent.getCurrentPage()); final int numItemsInPreview = itemsInPreview.size(); final int numItemsInFirstPagePreview = isOnFirstPage ? numItemsInPreview : MAX_NUM_ITEMS_IN_PREVIEW; diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java new file mode 100644 index 000000000..9d14a5ffb --- /dev/null +++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.folder; + +import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; + +import android.graphics.Point; + +import com.android.launcher3.FolderInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for managing item positions in a folder based on rank + */ +public class FolderGridOrganizer { + + private final Point mPoint = new Point(); + private final int mMaxCountX; + private final int mMaxCountY; + private final int mMaxItemsPerPage; + + private int mNumItemsInFolder; + private int mCountX; + private int mCountY; + private boolean mDisplayingUpperLeftQuadrant = false; + + /** + * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work. + */ + public FolderGridOrganizer(InvariantDeviceProfile profile) { + mMaxCountX = profile.numFolderColumns; + mMaxCountY = profile.numFolderRows; + mMaxItemsPerPage = mMaxCountX * mMaxCountY; + } + + /** + * Updates the organizer with the provided folder info + */ + public FolderGridOrganizer setFolderInfo(FolderInfo info) { + return setContentSize(info.contents.size()); + } + + /** + * Updates the organizer to reflect the content size + */ + public FolderGridOrganizer setContentSize(int contentSize) { + if (contentSize != mNumItemsInFolder) { + calculateGridSize(contentSize); + + mDisplayingUpperLeftQuadrant = contentSize > MAX_NUM_ITEMS_IN_PREVIEW; + mNumItemsInFolder = contentSize; + } + return this; + } + + public int getCountX() { + return mCountX; + } + + public int getCountY() { + return mCountY; + } + + public int getMaxItemsPerPage() { + return mMaxItemsPerPage; + } + + /** + * Calculates the grid size such that {@param count} items can fit in the grid. + * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while + * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}. + */ + private void calculateGridSize(int count) { + boolean done; + int gridCountX = mCountX; + int gridCountY = mCountY; + + if (count >= mMaxItemsPerPage) { + gridCountX = mMaxCountX; + gridCountY = mMaxCountY; + done = true; + } else { + done = false; + } + + while (!done) { + int oldCountX = gridCountX; + int oldCountY = gridCountY; + if (gridCountX * gridCountY < count) { + // Current grid is too small, expand it + if ((gridCountX <= gridCountY || gridCountY == mMaxCountY) + && gridCountX < mMaxCountX) { + gridCountX++; + } else if (gridCountY < mMaxCountY) { + gridCountY++; + } + if (gridCountY == 0) gridCountY++; + } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) { + gridCountY = Math.max(0, gridCountY - 1); + } else if ((gridCountX - 1) * gridCountY >= count) { + gridCountX = Math.max(0, gridCountX - 1); + } + done = gridCountX == oldCountX && gridCountY == oldCountY; + } + + mCountX = gridCountX; + mCountY = gridCountY; + } + + /** + * Updates the item's cellX, cellY and rank corresponding to the provided rank. + * @return true if there was any change + */ + public boolean updateRankAndPos(ItemInfo item, int rank) { + Point pos = getPosForRank(rank); + if (!pos.equals(item.cellX, item.cellY) || rank != item.rank) { + item.rank = rank; + item.cellX = pos.x; + item.cellY = pos.y; + return true; + } + return false; + } + + /** + * Returns the position of the item in the grid + */ + public Point getPosForRank(int rank) { + int pagePos = rank % mMaxItemsPerPage; + mPoint.x = pagePos % mCountX; + mPoint.y = pagePos / mCountX; + return mPoint; + } + + /** + * Returns the preview items for the provided pageNo using the full list of contents + */ + public <T, R extends T> ArrayList<R> previewItemsForPage(int page, List<T> contents) { + ArrayList<R> result = new ArrayList<>(); + int itemsPerPage = mCountX * mCountY; + int start = itemsPerPage * page; + int end = Math.min(start + itemsPerPage, contents.size()); + + for (int i = start, rank = 0; i < end; i++, rank++) { + if (isItemInPreview(page, rank)) { + result.add((R) contents.get(i)); + } + + if (result.size() == MAX_NUM_ITEMS_IN_PREVIEW) { + break; + } + } + return result; + } + + /** + * Returns whether the item with rank is in the default Folder icon preview. + */ + public boolean isItemInPreview(int rank) { + return isItemInPreview(0, rank); + } + + /** + * @param page The page the item is on. + * @param rank The rank of the item. + * @return True iff the icon is in the 2x2 upper left quadrant of the Folder. + */ + public boolean isItemInPreview(int page, int rank) { + // First page items are laid out such that the first 4 items are always in the upper + // left quadrant. For all other pages, we need to check the row and col. + if (page > 0 || mDisplayingUpperLeftQuadrant) { + int col = rank % mCountX; + int row = rank / mCountX; + return col < 2 && row < 2; + } + return rank < MAX_NUM_ITEMS_IN_PREVIEW; + } +} diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 0e2d4673e..a463c7a30 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -70,6 +70,7 @@ import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; /** * An icon that can appear on in the workspace representing an {@link Folder}. @@ -96,11 +97,11 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel PreviewBackground mBackground = new PreviewBackground(); private boolean mBackgroundIsVisible = true; - FolderIconPreviewVerifier mPreviewVerifier; + FolderGridOrganizer mPreviewVerifier; ClippedFolderIconLayoutRule mPreviewLayoutRule; private PreviewItemManager mPreviewItemManager; private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); - private List<BubbleTextView> mCurrentPreviewItems = new ArrayList<>(); + private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>(); boolean mAnimating = false; @@ -214,7 +215,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private void setFolder(Folder folder) { mFolder = folder; - mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); + mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv); mPreviewVerifier.setFolderInfo(mFolder.getInfo()); updatePreviewItems(false); } @@ -232,11 +233,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } public void addItem(WorkspaceItemInfo item) { - addItem(item, true); - } - - public void addItem(WorkspaceItemInfo item, boolean animate) { - mInfo.add(item, animate); + mInfo.add(item, true); } public void removeItem(WorkspaceItemInfo item, boolean animate) { @@ -261,7 +258,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel OnAlarmListener mOnOpenListener = new OnAlarmListener() { public void onAlarm(Alarm alarm) { mFolder.beginExternalDrag(); - mFolder.animateOpen(); } }; @@ -296,8 +292,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } private void onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect, - float scaleRelativeToDragLayer, int index, - boolean itemReturnedOnFailedDrop) { + float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) { item.cellX = -1; item.cellY = -1; @@ -328,18 +323,17 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1); boolean itemAdded = false; if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) { - List<BubbleTextView> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); - addItem(item, false); + List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); + mInfo.add(item, index, false); mCurrentPreviewItems.clear(); - mCurrentPreviewItems.addAll(getPreviewItems()); + mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); if (!oldPreviewItems.equals(mCurrentPreviewItems)) { - for (int i = 0; i < mCurrentPreviewItems.size(); ++i) { - if (mCurrentPreviewItems.get(i).getTag().equals(item)) { - // If the item dropped is going to be in the preview, we update the - // index here to reflect its position in the preview. - index = i; - } + int newIndex = mCurrentPreviewItems.indexOf(item); + if (newIndex >= 0) { + // If the item dropped is going to be in the preview, we update the + // index here to reflect its position in the preview. + index = newIndex; } mPreviewItemManager.hidePreviewItem(index, true); @@ -351,13 +345,13 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } if (!itemAdded) { - addItem(item); + mInfo.add(item, index, true); } int[] center = new int[2]; float scale = getLocalCenterForIndex(index, numItemsInPreview, center); - center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]); - center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]); + center[0] = Math.round(scaleRelativeToDragLayer * center[0]); + center[1] = Math.round(scaleRelativeToDragLayer * center[1]); to.offset(center[0] - animateView.getMeasuredWidth() / 2, center[1] - animateView.getMeasuredHeight() / 2); @@ -398,7 +392,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel item = (WorkspaceItemInfo) d.dragInfo; } mFolder.notifyDrop(); - onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), + onDrop(item, d.dragView, null, 1.0f, + itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(), itemReturnedOnFailedDrop); } @@ -510,8 +505,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mBackground.drawBackground(canvas); } - if (mFolder == null) return; - if (mFolder.getItemCount() == 0 && !mAnimating) return; + if (mCurrentPreviewItems.isEmpty() && !mAnimating) return; final int saveCount = canvas.save(); canvas.clipPath(mBackground.getClipPath()); @@ -553,31 +547,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } /** - * Returns the list of preview items displayed in the icon. + * Returns the list of items which should be visible in the preview */ - public List<BubbleTextView> getPreviewItems() { - return getPreviewItemsOnPage(0); - } - - /** - * Returns the list of "preview items" on {@param page}. - */ - public List<BubbleTextView> getPreviewItemsOnPage(int page) { - mPreviewVerifier.setFolderInfo(mFolder.getInfo()); - - List<BubbleTextView> itemsToDisplay = new ArrayList<>(); - List<BubbleTextView> itemsOnPage = mFolder.getItemsOnPage(page); - int numItems = itemsOnPage.size(); - for (int rank = 0; rank < numItems; ++rank) { - if (mPreviewVerifier.isItemInPreview(page, rank)) { - itemsToDisplay.add(itemsOnPage.get(rank)); - } - - if (itemsToDisplay.size() == MAX_NUM_ITEMS_IN_PREVIEW) { - break; - } - } - return itemsToDisplay; + public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) { + return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents); } @Override @@ -595,11 +568,14 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel private void updatePreviewItems(boolean animate) { mPreviewItemManager.updatePreviewItems(animate); mCurrentPreviewItems.clear(); - mCurrentPreviewItems.addAll(getPreviewItems()); + mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); } - @Override - public void prepareAutoUpdate() { + /** + * Updates the preview items which match the provided condition + */ + public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) { + mPreviewItemManager.updatePreviewItems(itemCheck); } @Override @@ -622,7 +598,6 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel requestLayout(); } - @Override public void onTitleChanged(CharSequence title) { mFolderName.setText(title); setContentDescription(getContext().getString(R.string.folder_name_format, title)); diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java deleted file mode 100644 index 4c84e354d..000000000 --- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.folder; - -import android.util.Log; - -import com.android.launcher3.FolderInfo; -import com.android.launcher3.InvariantDeviceProfile; - -import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; - -/** - * Verifies whether an item in a Folder is displayed in the FolderIcon preview. - */ -public class FolderIconPreviewVerifier { - - private static final String TAG = "FolderPreviewVerifier"; - - private final int mMaxGridCountX; - private final int mMaxGridCountY; - private final int mMaxItemsPerPage; - private final int[] mGridSize = new int[] { 1, 1 }; - - private int mNumItemsInFolder; - private int mGridCountX; - private boolean mDisplayingUpperLeftQuadrant = false; - - /** - * Note: must call {@link #setFolderInfo(FolderInfo)} manually for verifier to work. - */ - public FolderIconPreviewVerifier(InvariantDeviceProfile profile) { - mMaxGridCountX = profile.numFolderColumns; - mMaxGridCountY = profile.numFolderRows; - mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY; - } - - public void setFolderInfo(FolderInfo info) { - int numItemsInFolder = info.contents.size(); - if (numItemsInFolder != mNumItemsInFolder) { - FolderPagedView.calculateGridSize(numItemsInFolder, 0, 0, mMaxGridCountX, - mMaxGridCountY, mMaxItemsPerPage, mGridSize); - mGridCountX = mGridSize[0]; - - mDisplayingUpperLeftQuadrant = numItemsInFolder > MAX_NUM_ITEMS_IN_PREVIEW; - mNumItemsInFolder = numItemsInFolder; - } - } - - /** - * Returns whether the item with {@param rank} is in the default Folder icon preview. - */ - public boolean isItemInPreview(int rank) { - return isItemInPreview(0, rank); - } - - /** - * @param page The page the item is on. - * @param rank The rank of the item. - * @return True iff the icon is in the 2x2 upper left quadrant of the Folder. - */ - public boolean isItemInPreview(int page, int rank) { - if (mGridSize[0] == 1) { - Log.w(TAG, "setFolderInfo not called before checking if item is in preview."); - } - - // First page items are laid out such that the first 4 items are always in the upper - // left quadrant. For all other pages, we need to check the row and col. - if (page > 0 || mDisplayingUpperLeftQuadrant) { - int col = rank % mGridCountX; - int row = rank / mGridCountX; - return col < 2 && row < 2; - } - return rank < MAX_NUM_ITEMS_IN_PREVIEW; - } -} diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 57105e7ca..54b363e90 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -24,10 +24,10 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewDebug; +import com.android.launcher3.BaseActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; @@ -38,18 +38,22 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.PagedView; import com.android.launcher3.R; import com.android.launcher3.ShortcutAndWidgetContainer; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.ViewCache; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.function.ToIntFunction; +import java.util.stream.Collectors; public class FolderPagedView extends PagedView<PageIndicatorDots> { @@ -68,17 +72,12 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { public final boolean mIsRtl; - private final LayoutInflater mInflater; private final ViewGroupFocusHelper mFocusIndicatorHelper; @Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>(); - @ViewDebug.ExportedProperty(category = "launcher") - private final int mMaxCountX; - @ViewDebug.ExportedProperty(category = "launcher") - private final int mMaxCountY; - @ViewDebug.ExportedProperty(category = "launcher") - private final int mMaxItemsPerPage; + private final FolderGridOrganizer mOrganizer; + private final ViewCache mViewCache; private int mAllocatedContentSize; @ViewDebug.ExportedProperty(category = "launcher") @@ -88,20 +87,20 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { private Folder mFolder; + // If the views are attached to the folder or not. A folder should be bound when its + // animating or is open. + private boolean mViewsBound = false; + public FolderPagedView(Context context, AttributeSet attrs) { super(context, attrs); InvariantDeviceProfile profile = LauncherAppState.getIDP(context); - mMaxCountX = profile.numFolderColumns; - mMaxCountY = profile.numFolderRows; - - mMaxItemsPerPage = mMaxCountX * mMaxCountY; - - mInflater = LayoutInflater.from(context); + mOrganizer = new FolderGridOrganizer(profile); mIsRtl = Utilities.isRtl(getResources()); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); + mViewCache = BaseActivity.fromContext(context).getViewCache(); } public void setFolder(Folder folder) { @@ -111,57 +110,13 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { } /** - * Calculates the grid size such that {@param count} items can fit in the grid. - * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while - * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}. - */ - public static void calculateGridSize(int count, int countX, int countY, int maxCountX, - int maxCountY, int maxItemsPerPage, int[] out) { - boolean done; - int gridCountX = countX; - int gridCountY = countY; - - if (count >= maxItemsPerPage) { - gridCountX = maxCountX; - gridCountY = maxCountY; - done = true; - } else { - done = false; - } - - while (!done) { - int oldCountX = gridCountX; - int oldCountY = gridCountY; - if (gridCountX * gridCountY < count) { - // Current grid is too small, expand it - if ((gridCountX <= gridCountY || gridCountY == maxCountY) - && gridCountX < maxCountX) { - gridCountX++; - } else if (gridCountY < maxCountY) { - gridCountY++; - } - if (gridCountY == 0) gridCountY++; - } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) { - gridCountY = Math.max(0, gridCountY - 1); - } else if ((gridCountX - 1) * gridCountY >= count) { - gridCountX = Math.max(0, gridCountX - 1); - } - done = gridCountX == oldCountX && gridCountY == oldCountY; - } - - out[0] = gridCountX; - out[1] = gridCountY; - } - - /** * Sets up the grid size such that {@param count} items can fit in the grid. */ - public void setupContentDimensions(int count) { + private void setupContentDimensions(int count) { mAllocatedContentSize = count; - calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage, - sTmpArray); - mGridCountX = sTmpArray[0]; - mGridCountY = sTmpArray[1]; + mOrganizer.setContentSize(count); + mGridCountX = mOrganizer.getCountX(); + mGridCountY = mOrganizer.getCountY(); // Update grid size for (int i = getPageCount() - 1; i >= 0; i--) { @@ -178,35 +133,50 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { /** * Binds items to the layout. */ - public void bindItems(ArrayList<WorkspaceItemInfo> items) { - ArrayList<View> icons = new ArrayList<>(); - for (WorkspaceItemInfo item : items) { - icons.add(createNewView(item)); + public void bindItems(List<WorkspaceItemInfo> items) { + if (mViewsBound) { + unbindItems(); } - arrangeChildren(icons, icons.size(), false); + arrangeChildren(items.stream().map(this::createNewView).collect(Collectors.toList())); + mViewsBound = true; } - public void allocateSpaceForRank(int rank) { - ArrayList<View> views = new ArrayList<>(mFolder.getItemsInReadingOrder()); - views.add(rank, null); - arrangeChildren(views, views.size(), false); + /** + * Removes all the icons from the folder + */ + public void unbindItems() { + for (int i = getChildCount() - 1; i >= 0; i--) { + CellLayout page = (CellLayout) getChildAt(i); + ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets(); + for (int j = container.getChildCount() - 1; j >= 0; j--) { + mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j)); + } + page.removeAllViews(); + mViewCache.recycleView(R.layout.folder_page, page); + } + removeAllViews(); + mViewsBound = false; } /** - * Create space for a new item at the end, and returns the rank for that item. - * Also sets the current page to the last page. + * Returns true if the icons are bound to the folder */ - public int allocateRankForNewItem() { - int rank = getItemCount(); - allocateSpaceForRank(rank); - setCurrentPage(rank / mMaxItemsPerPage); - return rank; + public boolean areViewsBound() { + return mViewsBound; } + /** + * Creates and adds an icon corresponding to the provided rank + * @return the created icon + */ public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) { View icon = createNewView(item); - allocateSpaceForRank(rank); - addViewForRank(icon, item, rank); + if (!mViewsBound) { + return icon; + } + ArrayList<View> views = new ArrayList<>(mFolder.getIconsInReadingOrder()); + views.add(rank, icon); + arrangeChildren(views); return icon; } @@ -215,31 +185,33 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { * related attributes. It assumes that {@param item} is already attached to the view. */ public void addViewForRank(View view, WorkspaceItemInfo item, int rank) { - int pagePos = rank % mMaxItemsPerPage; - int pageNo = rank / mMaxItemsPerPage; - - item.rank = rank; - item.cellX = pagePos % mGridCountX; - item.cellY = pagePos / mGridCountX; + int pageNo = rank / mOrganizer.getMaxItemsPerPage(); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); - lp.cellX = item.cellX; - lp.cellY = item.cellY; + lp.setXY(mOrganizer.getPosForRank(rank)); getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true); } @SuppressLint("InflateParams") public View createNewView(WorkspaceItemInfo item) { - final BubbleTextView textView = (BubbleTextView) mInflater.inflate( - R.layout.folder_application, null, false); + if (item == null) { + return null; + } + final BubbleTextView textView = mViewCache.getView( + R.layout.folder_application, getContext(), null); textView.applyFromWorkspaceItem(item); - textView.setHapticFeedbackEnabled(false); textView.setOnClickListener(ItemClickHandler.INSTANCE); textView.setOnLongClickListener(mFolder); textView.setOnFocusChangeListener(mFocusIndicatorHelper); - - textView.setLayoutParams(new CellLayout.LayoutParams( - item.cellX, item.cellY, item.spanX, item.spanY)); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams(); + if (lp == null) { + textView.setLayoutParams(new CellLayout.LayoutParams( + item.cellX, item.cellY, item.spanX, item.spanY)); + } else { + lp.cellX = item.cellX; + lp.cellY = item.cellY; + lp.cellHSpan = lp.cellVSpan = 1; + } return textView; } @@ -254,7 +226,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { private CellLayout createAndAddNewPage() { DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false); + CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this); page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx); page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); page.setInvertIfRtl(true); @@ -295,16 +267,10 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { * page. * * @param list the ordered list of children. - * @param itemCount if greater than the total children count, empty spaces are left - * at the end, otherwise it is ignored. - * */ - public void arrangeChildren(ArrayList<View> list, int itemCount) { - arrangeChildren(list, itemCount, true); - } - @SuppressLint("RtlHardcoded") - private void arrangeChildren(ArrayList<View> list, int itemCount, boolean saveChanges) { + public void arrangeChildren(List<View> list) { + int itemCount = list.size(); ArrayList<CellLayout> pages = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { CellLayout page = (CellLayout) getChildAt(i); @@ -317,15 +283,12 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { CellLayout currentPage = null; int position = 0; - int newX, newY, rank; + int rank = 0; - FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier( - Launcher.getLauncher(getContext()).getDeviceProfile().inv); - verifier.setFolderInfo(mFolder.getInfo()); - rank = 0; + mOrganizer.setFolderInfo(mFolder.getInfo()); for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; - if (currentPage == null || position >= mMaxItemsPerPage) { + if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) { // Next page if (pageItr.hasNext()) { currentPage = pageItr.next(); @@ -337,28 +300,16 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { if (v != null) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); - newX = position % mGridCountX; - newY = position / mGridCountX; ItemInfo info = (ItemInfo) v.getTag(); - if (info.cellX != newX || info.cellY != newY || info.rank != rank) { - info.cellX = newX; - info.cellY = newY; - info.rank = rank; - if (saveChanges) { - mFolder.mLauncher.getModelWriter().addOrMoveItemInDatabase(info, - mFolder.mInfo.id, 0, info.cellX, info.cellY); - } - } - lp.cellX = info.cellX; - lp.cellY = info.cellY; + lp.setXY(mOrganizer.getPosForRank(rank)); currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true); - if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) { + if (mOrganizer.isItemInPreview(rank) && v instanceof BubbleTextView) { ((BubbleTextView) v).verifyHighRes(); } } - rank ++; + rank++; position++; } @@ -391,16 +342,6 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0; } - public int getItemCount() { - int lastPageIndex = getChildCount() - 1; - if (lastPageIndex < 0) { - // If there are no pages, nothing has yet been added to the folder. - return 0; - } - return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount() - + lastPageIndex * mMaxItemsPerPage; - } - /** * @return the rank of the cell nearest to the provided pixel position. */ @@ -412,31 +353,28 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1; } return Math.min(mAllocatedContentSize - 1, - pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]); + pageIndex * mOrganizer.getMaxItemsPerPage() + + sTmpArray[1] * mGridCountX + sTmpArray[0]); } public View getFirstItem() { - if (getChildCount() < 1) { - return null; - } - ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets(); - if (mGridCountX > 0) { - return currContainer.getChildAt(0, 0); - } else { - return currContainer.getChildAt(0); - } + return getViewInCurrentPage(c -> 0); } public View getLastItem() { + return getViewInCurrentPage(c -> c.getChildCount() - 1); + } + + private View getViewInCurrentPage(ToIntFunction<ShortcutAndWidgetContainer> rankProvider) { if (getChildCount() < 1) { return null; } - ShortcutAndWidgetContainer currContainer = getCurrentCellLayout().getShortcutsAndWidgets(); - int lastRank = currContainer.getChildCount() - 1; + ShortcutAndWidgetContainer container = getCurrentCellLayout().getShortcutsAndWidgets(); + int rank = rankProvider.applyAsInt(container); if (mGridCountX > 0) { - return currContainer.getChildAt(lastRank % mGridCountX, lastRank / mGridCountX); + return container.getChildAt(rank % mGridCountX, rank / mGridCountX); } else { - return currContainer.getChildAt(lastRank); + return container.getChildAt(rank); } } @@ -517,7 +455,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { } public boolean rankOnCurrentPage(int rank) { - int p = rank / mMaxItemsPerPage; + int p = rank / mOrganizer.getMaxItemsPerPage(); return p == getNextPage(); } @@ -563,15 +501,16 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { // Animation only happens on the current page. int pageToAnimate = getNextPage(); + int maxItemsPerPage = mOrganizer.getMaxItemsPerPage(); - int pageT = target / mMaxItemsPerPage; - int pagePosT = target % mMaxItemsPerPage; + int pageT = target / maxItemsPerPage; + int pagePosT = target % maxItemsPerPage; if (pageT != pageToAnimate) { Log.e(TAG, "Cannot animate when the target cell is invisible"); } - int pagePosE = empty % mMaxItemsPerPage; - int pageE = empty / mMaxItemsPerPage; + int pagePosE = empty % maxItemsPerPage; + int pageE = empty / maxItemsPerPage; int startPos, endPos; int moveStart, moveEnd; @@ -588,7 +527,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { if (pageE < pageToAnimate) { moveStart = empty; // Instantly move the first item in the current page. - moveEnd = pageToAnimate * mMaxItemsPerPage; + moveEnd = pageToAnimate * maxItemsPerPage; // Animate the 2nd item in the current page, as the first item was already moved to // the last page. startPos = 0; @@ -606,10 +545,10 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { // Move the items immediately. moveStart = empty; // Instantly move the last item in the current page. - moveEnd = (pageToAnimate + 1) * mMaxItemsPerPage - 1; + moveEnd = (pageToAnimate + 1) * maxItemsPerPage - 1; // Animations start with the second last item in the page - startPos = mMaxItemsPerPage - 1; + startPos = maxItemsPerPage - 1; } else { moveStart = moveEnd = -1; startPos = pagePosE; @@ -621,8 +560,8 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { // Instant moving views. while (moveStart != moveEnd) { int rankToMove = moveStart + direction; - int p = rankToMove / mMaxItemsPerPage; - int pagePos = rankToMove % mMaxItemsPerPage; + int p = rankToMove / maxItemsPerPage; + int pagePos = rankToMove % maxItemsPerPage; int x = pagePos % mGridCountX; int y = pagePos / mGridCountX; @@ -667,9 +606,6 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { for (int i = startPos; i != endPos; i += direction) { int nextPos = i + direction; View v = page.getChildAt(nextPos % mGridCountX, nextPos / mGridCountX); - if (v != null) { - ((ItemInfo) v.getTag()).rank -= direction; - } if (page.animateChildToPosition(v, i % mGridCountX, i / mGridCountX, REORDER_ANIMATION_DURATION, delay, true, true)) { delay += delayAmount; @@ -679,6 +615,6 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> { } public int itemsPerPage() { - return mMaxItemsPerPage; + return mOrganizer.getMaxItemsPerPage(); } } diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java index c81846256..caf6e55b7 100644 --- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java +++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java @@ -17,6 +17,8 @@ package com.android.launcher3.folder; import android.graphics.drawable.Drawable; +import com.android.launcher3.WorkspaceItemInfo; + /** * Manages the parameters used to draw a Folder preview item. */ @@ -25,9 +27,10 @@ class PreviewItemDrawingParams { float transY; float scale; float overlayAlpha; - FolderPreviewItemAnim anim; + public FolderPreviewItemAnim anim; public boolean hidden; - Drawable drawable; + public Drawable drawable; + public WorkspaceItemInfo item; PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) { this.transX = transX; diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 49763ba9d..2d817e6d2 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -23,28 +23,51 @@ import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; +import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.util.FloatProperty; import android.view.View; import android.widget.TextView; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.WorkspaceItemInfo; +import androidx.annotation.NonNull; + +import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.graphics.DrawableFactory; +import com.android.launcher3.graphics.PreloadIconDrawable; import java.util.ArrayList; import java.util.List; - -import androidx.annotation.NonNull; +import java.util.function.Predicate; /** * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}. */ public class PreviewItemManager { - private FolderIcon mIcon; + private static final FloatProperty<PreviewItemManager> CURRENT_PAGE_ITEMS_TRANS_X = + new FloatProperty<PreviewItemManager>("currentPageItemsTransX") { + @Override + public void setValue(PreviewItemManager manager, float v) { + manager.mCurrentPageItemsTransX = v; + manager.onParamsChanged(); + } + + @Override + public Float get(PreviewItemManager manager) { + return manager.mCurrentPageItemsTransX; + } + }; + + private final Context mContext; + private final FolderIcon mIcon; + private final DrawableFactory mDrawableFactory; + private final int mIconSize; // These variables are all associated with the drawing of the preview; they are stored // as member variables for shared usage and to avoid computation on each frame @@ -69,7 +92,10 @@ public class PreviewItemManager { private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200; public PreviewItemManager(FolderIcon icon) { + mContext = icon.getContext(); mIcon = icon; + mDrawableFactory = DrawableFactory.INSTANCE.get(mContext); + mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx; } /** @@ -200,7 +226,7 @@ public class PreviewItemManager { } void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) { - List<BubbleTextView> items = mIcon.getPreviewItemsOnPage(page); + List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page); int prevNumItems = params.size(); // We adjust the size of the list to match the number of items in the preview. @@ -214,13 +240,7 @@ public class PreviewItemManager { int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW; for (int i = 0; i < params.size(); i++) { PreviewItemDrawingParams p = params.get(i); - p.drawable = items.get(i).getCompoundDrawables()[1]; - - if (p.drawable != null && !mIcon.mFolder.isOpen()) { - // Set the callback to FolderIcon as it is responsible to drawing the icon. The - // callback will be released when the folder is opened. - p.drawable.setCallback(mIcon); - } + setDrawable(p, items.get(i)); if (!animate) { computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p); @@ -253,14 +273,8 @@ public class PreviewItemManager { buildParamsForPage(currentPage, mCurrentPageParams, false); onParamsChanged(); - ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX); - slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue(); - onParamsChanged(); - } - }); + ValueAnimator slideAnimator = ObjectAnimator + .ofFloat(this, CURRENT_PAGE_ITEMS_TRANS_X, 0, ITEM_SLIDE_IN_OUT_DISTANCE_PX); slideAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -277,6 +291,25 @@ public class PreviewItemManager { buildParamsForPage(0, mFirstPageParams, animate); } + void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) { + boolean modified = false; + for (PreviewItemDrawingParams param : mFirstPageParams) { + if (itemCheck.test(param.item)) { + setDrawable(param, param.item); + modified = true; + } + } + for (PreviewItemDrawingParams param : mCurrentPageParams) { + if (itemCheck.test(param.item)) { + setDrawable(param, param.item); + modified = true; + } + } + if (modified) { + mIcon.invalidate(); + } + } + boolean verifyDrawable(@NonNull Drawable who) { for (int i = 0; i < mFirstPageParams.size(); i++) { if (mFirstPageParams.get(i).drawable == who) { @@ -296,46 +329,46 @@ public class PreviewItemManager { * - Moving into a new position * - Moving out of the preview * - * @param oldParams The list of items in the old preview. - * @param newParams The list of items in the new preview. + * @param oldItems The list of items in the old preview. + * @param newItems The list of items in the new preview. * @param dropped The item that was dropped onto the FolderIcon. */ - public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams, + public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems, WorkspaceItemInfo dropped) { - int numItems = newParams.size(); + int numItems = newItems.size(); final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams; buildParamsForPage(0, params, false); // New preview items for items that are moving in (except for the dropped item). - List<BubbleTextView> moveIn = new ArrayList<>(); - for (BubbleTextView btv : newParams) { - if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) { - moveIn.add(btv); + List<WorkspaceItemInfo> moveIn = new ArrayList<>(); + for (WorkspaceItemInfo newItem : newItems) { + if (!oldItems.contains(newItem) && !newItem.equals(dropped)) { + moveIn.add(newItem); } } for (int i = 0; i < moveIn.size(); ++i) { - int prevIndex = newParams.indexOf(moveIn.get(i)); + int prevIndex = newItems.indexOf(moveIn.get(i)); PreviewItemDrawingParams p = params.get(prevIndex); computePreviewItemDrawingParams(prevIndex, numItems, p); - updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)), + updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newItems.indexOf(moveIn.get(i)), numItems); } // Items that are moving into new positions within the preview. - for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) { - int oldIndex = oldParams.indexOf(newParams.get(newIndex)); + for (int newIndex = 0; newIndex < newItems.size(); ++newIndex) { + int oldIndex = oldItems.indexOf(newItems.get(newIndex)); if (oldIndex >= 0 && newIndex != oldIndex) { PreviewItemDrawingParams p = params.get(newIndex); - updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems); + updateTransitionParam(p, newItems.get(newIndex), oldIndex, newIndex, numItems); } } // Old preview items that need to be moved out. - List<BubbleTextView> moveOut = new ArrayList<>(oldParams); - moveOut.removeAll(newParams); + List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems); + moveOut.removeAll(newItems); for (int i = 0; i < moveOut.size(); ++i) { - BubbleTextView item = moveOut.get(i); - int oldIndex = oldParams.indexOf(item); + WorkspaceItemInfo item = moveOut.get(i); + int oldIndex = oldItems.indexOf(item); PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null); updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems); params.add(0, p); // We want these items first so that they are on drawn last. @@ -348,14 +381,9 @@ public class PreviewItemManager { } } - private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv, + private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item, int prevIndex, int newIndex, int numItems) { - p.drawable = btv.getCompoundDrawables()[1]; - if (!mIcon.mFolder.isOpen()) { - // Set the callback to FolderIcon as it is responsible to drawing the icon. The - // callback will be released when the folder is opened. - p.drawable.setCallback(mIcon); - } + setDrawable(p, item); FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems, newIndex, numItems, DROP_IN_ANIMATION_DURATION, null); @@ -364,4 +392,20 @@ public class PreviewItemManager { } p.anim = anim; } + + private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) { + if (item.hasPromiseIconUi()) { + PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item); + drawable.setLevel(item.getInstallProgress()); + p.drawable = drawable; + } else { + p.drawable = mDrawableFactory.newIcon(mContext, item); + } + p.drawable.setBounds(0, 0, mIconSize, mIconSize); + p.item = item; + + // Set the callback to FolderIcon as it is responsible to drawing the icon. The + // callback will be released when the folder is opened. + p.drawable.setCallback(mIcon); + } } diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index 9263a2ac9..747efe307 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -16,6 +16,8 @@ package com.android.launcher3.graphics; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.Context; import android.graphics.Bitmap; import android.graphics.BlurMaskFilter; @@ -25,7 +27,6 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Handler; import android.view.View; import com.android.launcher3.BubbleTextView; @@ -35,7 +36,6 @@ import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.BitmapRenderer; -import com.android.launcher3.util.UiThreadHelper; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.launcher3.widget.PendingAppWidgetHostView; @@ -157,7 +157,7 @@ public class DragPreviewProvider { } mOutlineGeneratorCallback = new OutlineGeneratorCallback(preview); - new Handler(UiThreadHelper.getBackgroundLooper()).post(mOutlineGeneratorCallback); + UI_HELPER_EXECUTOR.post(mOutlineGeneratorCallback); } protected static Rect getDrawableBounds(Drawable d) { diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java index efd39ee8e..71b436600 100644 --- a/src/com/android/launcher3/graphics/GridOptionsProvider.java +++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java @@ -1,5 +1,7 @@ package com.android.launcher3.graphics; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.ContentProvider; import android.content.ContentValues; import android.content.res.XmlResourceParser; @@ -17,8 +19,6 @@ import android.util.Xml; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.GridOption; import com.android.launcher3.R; -import com.android.launcher3.util.LooperExecutor; -import com.android.launcher3.util.UiThreadHelper; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -180,10 +180,10 @@ public class GridOptionsProvider extends ContentProvider { throw new FileNotFoundException(e.getMessage()); } - LooperExecutor executor = new LooperExecutor(UiThreadHelper.getBackgroundLooper()); try { return openPipeHelper(uri, MIME_TYPE_PNG, null, - executor.submit(new LauncherPreviewRenderer(getContext(), idp)), BITMAP_WRITER); + UI_HELPER_EXECUTOR.submit(new LauncherPreviewRenderer(getContext(), idp)), + BITMAP_WRITER); } catch (Exception e) { throw new FileNotFoundException(e.getMessage()); } diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java index 46b500265..832956d7e 100644 --- a/src/com/android/launcher3/icons/ComponentWithLabel.java +++ b/src/com/android/launcher3/icons/ComponentWithLabel.java @@ -34,9 +34,11 @@ public interface ComponentWithLabel { class ComponentCachingLogic implements CachingLogic<ComponentWithLabel> { private final PackageManager mPackageManager; + private final boolean mAddToMemCache; - public ComponentCachingLogic(Context context) { + public ComponentCachingLogic(Context context, boolean addToMemCache) { mPackageManager = context.getPackageManager(); + mAddToMemCache = addToMemCache; } @Override @@ -60,5 +62,10 @@ public interface ComponentWithLabel { // Do not load icon. target.icon = BitmapInfo.LOW_RES_ICON; } + + @Override + public boolean addToMemCache() { + return mAddToMemCache; + } } } diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index abff237e8..11c7f2053 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -16,6 +16,9 @@ package com.android.launcher3.icons; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -23,6 +26,7 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ShortcutInfo; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Process; @@ -36,8 +40,6 @@ import com.android.launcher3.IconProvider; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherFiles; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.LauncherAppsCompat; @@ -48,6 +50,7 @@ import com.android.launcher3.icons.cache.BaseIconCache; import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.icons.cache.HandlerRunnable; import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.Preconditions; @@ -60,10 +63,9 @@ public class IconCache extends BaseIconCache { private static final String TAG = "Launcher.IconCache"; - private final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic; private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic; + private final CachingLogic<ShortcutInfo> mShortcutCachingLogic; private final LauncherAppsCompat mLauncherApps; private final UserManagerCompat mUserManager; @@ -73,10 +75,11 @@ public class IconCache extends BaseIconCache { private int mPendingIconRequestCount = 0; public IconCache(Context context, InvariantDeviceProfile inv) { - super(context, LauncherFiles.APP_ICONS_DB, LauncherModel.getWorkerLooper(), + super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(), inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */); - mComponentWithLabelCachingLogic = new ComponentCachingLogic(context); + mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false); mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context); + mShortcutCachingLogic = new ShortcutCachingLogic(); mLauncherApps = LauncherAppsCompat.getInstance(mContext); mUserManager = UserManagerCompat.getInstance(mContext); mInstantAppResolver = InstantAppResolver.newInstance(mContext); @@ -124,7 +127,7 @@ public class IconCache extends BaseIconCache { final ItemInfoWithIcon info) { Preconditions.assertUIThread(); if (mPendingIconRequestCount <= 0) { - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_FOREGROUND); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); } mPendingIconRequestCount ++; @@ -136,7 +139,7 @@ public class IconCache extends BaseIconCache { } else if (info instanceof PackageItemInfo) { getTitleAndIconForApp((PackageItemInfo) info, false); } - mMainThreadExecutor.execute(() -> { + MAIN_EXECUTOR.execute(() -> { caller.reapplyItemInfo(info); onEnd(); }); @@ -149,7 +152,7 @@ public class IconCache extends BaseIconCache { private void onIconRequestEnd() { mPendingIconRequestCount --; if (mPendingIconRequestCount <= 0) { - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } } @@ -175,6 +178,14 @@ public class IconCache extends BaseIconCache { } /** + * Fill in info with the icon and label for deep shortcut. + */ + public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) { + return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(), + () -> info, mShortcutCachingLogic, false, false); + } + + /** * Fill in {@param info} with the icon and label. If the * corresponding activity is not found, it reverts to the package icon. */ @@ -195,7 +206,7 @@ public class IconCache extends BaseIconCache { public synchronized String getTitleNoCache(ComponentWithLabel info) { CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info, mComponentWithLabelCachingLogic, false /* usePackageIcon */, - true /* useLowResIcon */, false /* addToMemCache */); + true /* useLowResIcon */); return Utilities.trim(entry.title); } diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java index 763240885..c6949afc3 100644 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -21,9 +21,10 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; import android.os.Process; +import androidx.annotation.Nullable; + import com.android.launcher3.AppInfo; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.InvariantDeviceProfile; @@ -31,14 +32,12 @@ import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.graphics.IconShape; +import com.android.launcher3.icons.cache.BaseIconCache; import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.Themes; import java.util.function.Supplier; -import androidx.annotation.Nullable; - /** * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class * that are threadsafe. @@ -126,13 +125,12 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) { - Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext) - .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); IconCache cache = LauncherAppState.getInstance(mContext).getIconCache(); + BaseIconCache.CacheEntry entry = cache.getDeepShortcutTitleAndIcon(shortcutInfo); final Bitmap unbadgedBitmap; - if (unbadgedDrawable != null) { - unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0); + if (entry.icon != null) { + unbadgedBitmap = entry.icon; } else { if (fallbackIconProvider != null) { // Fallback icons are already badged and with appropriate shadow diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java new file mode 100644 index 000000000..5d696fd6f --- /dev/null +++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.icons; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.icons.cache.CachingLogic; +import com.android.launcher3.shortcuts.DeepShortcutManager; +import com.android.launcher3.shortcuts.ShortcutKey; + +/** + * Caching logic for shortcuts. + */ +public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> { + + @Override + public ComponentName getComponent(ShortcutInfo info) { + return ShortcutKey.fromInfo(info).componentName; + } + + @Override + public UserHandle getUser(ShortcutInfo info) { + return info.getUserHandle(); + } + + @Override + public CharSequence getLabel(ShortcutInfo info) { + return info.getShortLabel(); + } + + @Override + public void loadIcon(Context context, ShortcutInfo info, BitmapInfo target) { + LauncherIcons li = LauncherIcons.obtain(context); + Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context) + .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi); + if (unbadgedDrawable != null) { + target.icon = li.createScaledBitmapWithoutShadow(unbadgedDrawable, 0); + } + li.recycle(); + } + + @Override + public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) { + if (shortcutInfo == null) return info.lastUpdateTime; + return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime); + } + + @Override + public boolean addToMemCache() { + return false; + } +} diff --git a/src/com/android/launcher3/logging/EventLogArray.java b/src/com/android/launcher3/logging/EventLogArray.java index f20f3659e..3ecfb23c2 100644 --- a/src/com/android/launcher3/logging/EventLogArray.java +++ b/src/com/android/launcher3/logging/EventLogArray.java @@ -16,11 +16,13 @@ package com.android.launcher3.logging; +import android.util.Log; import java.io.PrintWriter; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.Locale; +import java.util.Random; /** * A utility class to record and log events. Events are stored in a fixed size array and old logs @@ -37,6 +39,7 @@ public class EventLogArray { private final String name; private final EventEntry[] logs; private int nextIndex; + private int mLogId; public EventLogArray(String name, int size) { this.name = name; @@ -52,10 +55,6 @@ public class EventLogArray { addLog(TYPE_INTEGER, event, extras); } - public void addLog(String event, float extras) { - addLog(TYPE_FLOAT, event, extras); - } - public void addLog(String event, boolean extras) { addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0); } @@ -65,7 +64,7 @@ public class EventLogArray { int last = (nextIndex + logs.length - 1) % logs.length; int secondLast = (nextIndex + logs.length - 2) % logs.length; if (isEntrySame(logs[last], type, event) && isEntrySame(logs[secondLast], type, event)) { - logs[last].update(type, event, extras); + logs[last].update(type, event, extras, mLogId); logs[secondLast].duplicateCount++; return; } @@ -73,7 +72,7 @@ public class EventLogArray { if (logs[nextIndex] == null) { logs[nextIndex] = new EventEntry(); } - logs[nextIndex].update(type, event, extras); + logs[nextIndex].update(type, event, extras, mLogId); nextIndex = (nextIndex + 1) % logs.length; } @@ -113,10 +112,18 @@ public class EventLogArray { if (log.duplicateCount > 0) { msg.append(" & ").append(log.duplicateCount).append(" similar events"); } + msg.append(" traceId: ").append(log.traceId); writer.println(msg); } } + /** Returns a 3 digit random number between 100-999 */ + public int generateAndSetLogId() { + Random r = new Random(); + mLogId = r.nextInt(900) + 100; + return mLogId; + } + private boolean isEntrySame(EventEntry entry, int type, String event) { return entry != null && entry.type == type && entry.event.equals(event); } @@ -129,11 +136,13 @@ public class EventLogArray { private float extras; private long time; private int duplicateCount; + private int traceId; - public void update(int type, String event, float extras) { + public void update(int type, String event, float extras, int traceId) { this.type = type; this.event = event; this.extras = extras; + this.traceId = traceId; time = System.currentTimeMillis(); duplicateCount = 0; } diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java index f7f8ef18f..923a89b1c 100644 --- a/src/com/android/launcher3/logging/FileLog.java +++ b/src/com/android/launcher3/logging/FileLog.java @@ -1,13 +1,14 @@ package com.android.launcher3.logging; +import static com.android.launcher3.util.Executors.createAndStartNewLooper; + import android.os.Handler; import android.os.HandlerThread; import android.os.Message; import android.util.Log; import android.util.Pair; -import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.IOUtils; import java.io.BufferedReader; import java.io.File; @@ -29,8 +30,7 @@ import java.util.concurrent.TimeUnit; */ public final class FileLog { - protected static final boolean ENABLED = - FeatureFlags.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE; + protected static final boolean ENABLED = true; private static final String FILE_NAME_PREFIX = "log-"; private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); @@ -91,9 +91,8 @@ public final class FileLog { private static Handler getHandler() { synchronized (DATE_FORMAT) { if (sHandler == null) { - HandlerThread thread = new HandlerThread("file-logger"); - thread.start(); - sHandler = new Handler(thread.getLooper(), new LogWriterCallback()); + sHandler = new Handler(createAndStartNewLooper("file-logger"), + new LogWriterCallback()); } } return sHandler; @@ -131,7 +130,7 @@ public final class FileLog { private PrintWriter mCurrentWriter = null; private void closeWriter() { - Utilities.closeSilently(mCurrentWriter); + IOUtils.closeSilently(mCurrentWriter); mCurrentWriter = null; } @@ -219,7 +218,7 @@ public final class FileLog { } catch (Exception e) { // ignore } finally { - Utilities.closeSilently(in); + IOUtils.closeSilently(in); } } } diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java index c72b07a7f..21ca74e12 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -427,10 +427,16 @@ public class UserEventDispatcher implements ResourceBasedOverride { mAppOrTaskLaunch = false; ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis; ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis; - if (!IS_VERBOSE) { return; } + Log.d(TAG, generateLog(ev)); + } + + /** + * Returns a human-readable log for given user event. + */ + public static String generateLog(LauncherEvent ev) { String log = "\n-----------------------------------------------------" + "\naction:" + LoggerUtils.getActionStr(ev.action); if (ev.srcTarget != null && ev.srcTarget.length > 0) { @@ -445,8 +451,7 @@ public class UserEventDispatcher implements ResourceBasedOverride { ev.elapsedSessionMillis, ev.actionDurationMillis); log += "\n\n"; - Log.d(TAG, log); - return; + return log; } private static String getTargetsStr(Target[] targets) { diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index 7d4f2f722..dfd5a708b 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -22,7 +22,6 @@ import android.os.UserHandle; import android.util.LongSparseArray; import android.util.Pair; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InvariantDeviceProfile; @@ -30,14 +29,14 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.LauncherSettings; import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.PackageManagerHelper; import java.util.ArrayList; import java.util.List; @@ -200,7 +199,7 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { intentWithoutPkg = intent.toUri(0); } - boolean isLauncherAppTarget = Utilities.isLauncherAppTarget(intent); + boolean isLauncherAppTarget = PackageManagerHelper.isLauncherAppTarget(intent); synchronized (dataModel) { for (ItemInfo item : dataModel.itemsIdMap) { if (item instanceof WorkspaceItemInfo) { diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java index 8b49c0638..3873a17e0 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/model/AllAppsList.java @@ -14,25 +14,37 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.model; + +import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR; +import static com.android.launcher3.AppInfo.EMPTY_ARRAY; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.os.LocaleList; import android.os.Process; import android.os.UserHandle; import android.util.Log; +import com.android.launcher3.AppFilter; +import com.android.launcher3.AppInfo; +import com.android.launcher3.PromiseAppInfo; +import com.android.launcher3.compat.AlphabeticIndexCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; +import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.icons.IconCache; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.SafeCloseable; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.function.Consumer; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -42,29 +54,40 @@ import androidx.annotation.Nullable; * Stores the list of all applications for the all apps view. */ public class AllAppsList { + private static final String TAG = "AllAppsList"; + private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { }; + public static final int DEFAULT_APPLICATIONS_NUMBER = 42; /** The list off all apps. */ public final ArrayList<AppInfo> data = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); - /** The list of apps that have been added since the last notify() call. */ - public ArrayList<AppInfo> added = new ArrayList<>(DEFAULT_APPLICATIONS_NUMBER); - /** The list of apps that have been removed since the last notify() call. */ - public ArrayList<AppInfo> removed = new ArrayList<>(); - /** The list of apps that have been modified since the last notify() call. */ - public ArrayList<AppInfo> modified = new ArrayList<>(); private IconCache mIconCache; - private AppFilter mAppFilter; + private boolean mDataChanged = false; + private Consumer<AppInfo> mRemoveListener = NO_OP_CONSUMER; + + private AlphabeticIndexCompat mIndex; + /** * Boring constructor. */ public AllAppsList(IconCache iconCache, AppFilter appFilter) { mIconCache = iconCache; mAppFilter = appFilter; + mIndex = new AlphabeticIndexCompat(LocaleList.getDefault()); + } + + /** + * Returns true if there have been any changes since last call. + */ + public boolean getAndResetChangeFlag() { + boolean result = mDataChanged; + mDataChanged = false; + return result; } /** @@ -81,9 +104,10 @@ public class AllAppsList { return; } mIconCache.getTitleAndIcon(info, activityInfo, true /* useLowResIcon */); + info.sectionName = mIndex.computeSectionName(info.title); data.add(info); - added.add(info); + mDataChanged = true; } public void addPromiseApp(Context context, @@ -94,31 +118,46 @@ public class AllAppsList { if (applicationInfo == null) { PromiseAppInfo info = new PromiseAppInfo(installInfo); mIconCache.getTitleAndIcon(info, info.usingLowResIcon()); + info.sectionName = mIndex.computeSectionName(info.title); + data.add(info); - added.add(info); + mDataChanged = true; } } - public void removePromiseApp(AppInfo appInfo) { - // the <em>removed</em> list is handled by the caller - // so not adding it here - data.remove(appInfo); - } - - public void clear() { - data.clear(); - // TODO: do we clear these too? - added.clear(); - removed.clear(); - modified.clear(); + public PromiseAppInfo updatePromiseInstallInfo(PackageInstallInfo installInfo) { + UserHandle user = Process.myUserHandle(); + for (int i=0; i < data.size(); i++) { + final AppInfo appInfo = data.get(i); + final ComponentName tgtComp = appInfo.getTargetComponent(); + if (tgtComp != null && tgtComp.getPackageName().equals(installInfo.packageName) + && appInfo.user.equals(user) + && appInfo instanceof PromiseAppInfo) { + final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo; + if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLING) { + promiseAppInfo.level = installInfo.progress; + return promiseAppInfo; + } else if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { + removeApp(i); + } + } + } + return null; } - public int size() { - return data.size(); + private void removeApp(int index) { + AppInfo removed = data.remove(index); + if (removed != null) { + mDataChanged = true; + mRemoveListener.accept(removed); + } } - public AppInfo get(int index) { - return data.get(index); + public void clear() { + data.clear(); + mDataChanged = false; + // Reset the index as locales might have changed + mIndex = new AlphabeticIndexCompat(LocaleList.getDefault()); } /** @@ -142,8 +181,7 @@ public class AllAppsList { for (int i = data.size() - 1; i >= 0; i--) { AppInfo info = data.get(i); if (info.user.equals(user) && packageName.equals(info.componentName.getPackageName())) { - removed.add(info); - data.remove(i); + removeApp(i); } } } @@ -157,17 +195,17 @@ public class AllAppsList { AppInfo info = data.get(i); if (matcher.matches(info, info.componentName)) { info.runtimeStatusFlags = op.apply(info.runtimeStatusFlags); - modified.add(info); + mDataChanged = true; } } } - public void updateIconsAndLabels(HashSet<String> packages, UserHandle user, - ArrayList<AppInfo> outUpdates) { + public void updateIconsAndLabels(HashSet<String> packages, UserHandle user) { for (AppInfo info : data) { if (info.user.equals(user) && packages.contains(info.componentName.getPackageName())) { mIconCache.updateTitleAndIcon(info); - outUpdates.add(info); + info.sectionName = mIndex.computeSectionName(info.title); + mDataChanged = true; } } } @@ -188,8 +226,7 @@ public class AllAppsList { && packageName.equals(applicationInfo.componentName.getPackageName())) { if (!findActivity(matches, applicationInfo.componentName)) { Log.w(TAG, "Changing shortcut target due to app component name change."); - removed.add(applicationInfo); - data.remove(i); + removeApp(i); } } } @@ -202,7 +239,9 @@ public class AllAppsList { add(new AppInfo(context, info, user), info); } else { mIconCache.getTitleAndIcon(applicationInfo, info, true /* useLowResIcon */); - modified.add(applicationInfo); + applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title); + + mDataChanged = true; } } } else { @@ -211,15 +250,13 @@ public class AllAppsList { final AppInfo applicationInfo = data.get(i); if (user.equals(applicationInfo.user) && packageName.equals(applicationInfo.componentName.getPackageName())) { - removed.add(applicationInfo); mIconCache.remove(applicationInfo.componentName, user); - data.remove(i); + removeApp(i); } } } } - /** * Returns whether <em>apps</em> contains <em>component</em>. */ @@ -247,4 +284,16 @@ public class AllAppsList { } return null; } + + public AppInfo[] copyData() { + AppInfo[] result = data.toArray(EMPTY_ARRAY); + Arrays.sort(result, COMPONENT_KEY_COMPARATOR); + return result; + } + + public SafeCloseable trackRemoves(Consumer<AppInfo> removeListener) { + mRemoveListener = removeListener; + + return () -> mRemoveListener = NO_OP_CONSUMER; + } } diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java index 29a46cfa5..13ab033e4 100644 --- a/src/com/android/launcher3/model/AppLaunchTracker.java +++ b/src/com/android/launcher3/model/AppLaunchTracker.java @@ -51,5 +51,8 @@ public class AppLaunchTracker implements ResourceBasedOverride { public void onStartApp(ComponentName componentName, UserHandle user, @Nullable String container) { } + public void onDismissApp(ComponentName componentName, UserHandle user, + @Nullable String container){} + public void onReturnedToHome() { } } diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 97cf267d3..0a4f00582 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -16,21 +16,21 @@ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.os.Looper; import android.util.Log; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.PagedView; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.LooperIdleLock; @@ -65,7 +65,7 @@ public abstract class BaseLoaderResults { public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel, AllAppsList allAppsList, int pageToBindFirst, WeakReference<Callbacks> callbacks) { - mUiExecutor = new MainThreadExecutor(); + mUiExecutor = MAIN_EXECUTOR; mApp = app; mBgDataModel = dataModel; mBgAllAppsList = allAppsList; @@ -279,9 +279,8 @@ public abstract class BaseLoaderResults { public void bindAllApps() { // shallow copy - @SuppressWarnings("unchecked") - ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone(); - executeCallbacksTask(c -> c.bindAllApplications(list), mUiExecutor); + AppInfo[] apps = mBgAllAppsList.copyData(); + executeCallbacksTask(c -> c.bindAllApplications(apps), mUiExecutor); } public abstract void bindWidgets(); diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java index eea3d8c35..e12633bcd 100644 --- a/src/com/android/launcher3/model/BaseModelUpdateTask.java +++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java @@ -17,12 +17,12 @@ package com.android.launcher3.model; import android.util.Log; -import com.android.launcher3.AllAppsList; +import com.android.launcher3.AppInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; @@ -30,6 +30,7 @@ import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.concurrent.Executor; /** @@ -95,12 +96,7 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) { if (!updatedShortcuts.isEmpty()) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindWorkspaceItemsChanged(updatedShortcuts); - } - }); + scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts)); } } @@ -113,23 +109,20 @@ public abstract class BaseModelUpdateTask implements ModelUpdateTask { public void bindUpdatedWidgets(BgDataModel dataModel) { final ArrayList<WidgetListRowEntry> widgets = dataModel.widgetsModel.getWidgetsList(mApp.getContext()); - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAllWidgets(widgets); - } - }); + scheduleCallbackTask(c -> c.bindAllWidgets(widgets)); } public void deleteAndBindComponentsRemoved(final ItemInfoMatcher matcher) { getModelWriter().deleteItemsFromDatabase(matcher); // Call the components-removed callback - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindWorkspaceComponentsRemoved(matcher); - } - }); + scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher)); + } + + public void bindApplicationsIfNeeded() { + if (mAllAppsList.getAndResetChangeFlag()) { + AppInfo[] apps = mAllAppsList.copyData(); + scheduleCallbackTask(c -> c.bindAllApplications(apps)); + } } } diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 8f0cd08a9..0e2027050 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -22,11 +22,13 @@ import android.text.TextUtils; import android.util.Log; import android.util.MutableInt; +import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherSettings; +import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Workspace; import com.android.launcher3.config.FeatureFlags; @@ -40,6 +42,10 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.ViewOnDrawExecutor; +import com.android.launcher3.widget.WidgetListRowEntry; + import com.google.protobuf.nano.MessageNano; import java.io.FileDescriptor; @@ -49,6 +55,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -391,4 +398,30 @@ public class BgDataModel { } } } + + public interface Callbacks { + void rebindModel(); + + int getCurrentWorkspaceScreen(); + void clearPendingBinds(); + void startBinding(); + void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons); + void bindScreens(IntArray orderedScreenIds); + void finishFirstPageBind(ViewOnDrawExecutor executor); + void finishBindingItems(int pageBoundFirst); + void preAddApps(); + void bindAppsAdded(IntArray newScreens, + ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated); + void bindPromiseAppProgressUpdated(PromiseAppInfo app); + void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated); + void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets); + void bindRestoreItemsChange(HashSet<ItemInfo> updates); + void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher); + void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets); + void onPageBoundSynchronously(int page); + void executeOnNextDraw(ViewOnDrawExecutor executor); + void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap); + + void bindAllApplications(AppInfo[] apps); + } } diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java index 7852444de..c1c8be316 100644 --- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java +++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java @@ -18,14 +18,10 @@ package com.android.launcher3.model; import android.content.ComponentName; import android.os.UserHandle; -import com.android.launcher3.AllAppsList; -import com.android.launcher3.AppInfo; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.icons.IconCache; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; import java.util.ArrayList; @@ -53,9 +49,9 @@ public class CacheDataUpdatedTask extends BaseModelUpdateTask { public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { IconCache iconCache = app.getIconCache(); - final ArrayList<AppInfo> updatedApps = new ArrayList<>(); ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>(); + synchronized (dataModel) { for (ItemInfo info : dataModel.itemsIdMap) { if (info instanceof WorkspaceItemInfo && mUser.equals(info.user)) { @@ -69,18 +65,10 @@ public class CacheDataUpdatedTask extends BaseModelUpdateTask { } } } - apps.updateIconsAndLabels(mPackages, mUser, updatedApps); + apps.updateIconsAndLabels(mPackages, mUser); } bindUpdatedWorkspaceItems(updatedShortcuts); - - if (!updatedApps.isEmpty()) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAppsAddedOrUpdated(updatedApps); - } - }); - } + bindApplicationsIfNeeded(); } public boolean isValidShortcut(WorkspaceItemInfo si) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 7593a3371..583ebee0b 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -19,8 +19,9 @@ package com.android.launcher3.model; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; -import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle; import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import static com.android.launcher3.util.PackageManagerHelper.isSystemApp; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; @@ -32,7 +33,6 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.ShortcutInfo; -import android.os.Handler; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; @@ -40,7 +40,6 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.MutableInt; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; @@ -58,18 +57,21 @@ import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIconPreviewVerifier; +import com.android.launcher3.folder.FolderGridOrganizer; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherActivityCachingLogic; import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.icons.ShortcutCachingLogic; import com.android.launcher3.icons.cache.IconCacheUpdateHandler; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.ImportDataTask; +import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.IOUtils; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; @@ -170,7 +172,8 @@ public class LoaderTask implements Runnable { TraceHelper.beginSection(TAG); try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) { TraceHelper.partitionSection(TAG, "step 1.1: loading workspace"); - loadWorkspace(); + List<ShortcutInfo> allShortcuts = new ArrayList<>(); + loadWorkspace(allShortcuts); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace"); @@ -189,18 +192,23 @@ public class LoaderTask implements Runnable { TraceHelper.partitionSection(TAG, "step 2.1: loading all apps"); List<LauncherActivityInfo> allActivityList = loadAllApps(); - TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps"); + TraceHelper.partitionSection(TAG, "step 2.2: binding all apps"); verifyNotStopped(); mResults.bindAllApps(); verifyNotStopped(); - TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache"); + TraceHelper.partitionSection(TAG, "step 2.3: save app icons in icon cache"); IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); setIgnorePackages(updateHandler); updateHandler.updateIcons(allActivityList, LauncherActivityCachingLogic.newInstance(mApp.getContext()), mApp.getModel()::onPackageIconsUpdated); + verifyNotStopped(); + TraceHelper.partitionSection(TAG, "step 2.4: save shortcuts in icon cache"); + updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(), + mApp.getModel()::onPackageIconsUpdated); + // Take a break TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle"); waitForIdle(); @@ -208,12 +216,17 @@ public class LoaderTask implements Runnable { // third step TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts"); - loadDeepShortcuts(); + List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts(); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts"); mResults.bindDeepShortcuts(); + verifyNotStopped(); + TraceHelper.partitionSection(TAG, "step 3.3: save deep shortcuts in icon cache"); + updateHandler.updateIcons(allDeepShortcuts, + new ShortcutCachingLogic(), (pkgs, user) -> { }); + // Take a break TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle"); waitForIdle(); @@ -228,9 +241,9 @@ public class LoaderTask implements Runnable { mResults.bindWidgets(); verifyNotStopped(); - TraceHelper.partitionSection(TAG, "step 4.3: Update icon cache"); - updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(mApp.getContext()), - mApp.getModel()::onWidgetLabelsUpdated); + TraceHelper.partitionSection(TAG, "step 4.3: save widgets in icon cache"); + updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic( + mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update"); @@ -249,7 +262,7 @@ public class LoaderTask implements Runnable { this.notify(); } - private void loadWorkspace() { + private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) { final Context context = mApp.getContext(); final ContentResolver contentResolver = context.getContentResolver(); final PackageManagerHelper pmHelper = new PackageManagerHelper(context); @@ -320,9 +333,9 @@ public class LoaderTask implements Runnable { // We can only query for shortcuts when the user is unlocked. if (userUnlocked) { - List<ShortcutInfo> pinnedShortcuts = + DeepShortcutManager.QueryResult pinnedShortcuts = mShortcutManager.queryForPinnedShortcuts(null, user); - if (mShortcutManager.wasLastCallSuccess()) { + if (pinnedShortcuts.wasSuccess()) { for (ShortcutInfo shortcut : pinnedShortcuts) { shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut); @@ -387,7 +400,9 @@ public class LoaderTask implements Runnable { boolean validTarget = TextUtils.isEmpty(targetPkg) || mLauncherApps.isPackageEnabledForProfile(targetPkg, c.user); - if (cn != null && validTarget) { + // If it's a deep shortcut, we'll use pinned shortcuts to restore it + if (cn != null && validTarget && c.itemType + != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { // If the apk is present and the shortcut points to a specific // component. @@ -500,6 +515,7 @@ public class LoaderTask implements Runnable { info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED; } intent = info.intent; + allDeepShortcuts.add(pinnedShortcut); } else { // Create a shortcut info in disabled mode for now. info = c.loadSimpleWorkspaceItem(); @@ -535,7 +551,7 @@ public class LoaderTask implements Runnable { info.spanX = 1; info.spanY = 1; info.runtimeStatusFlags |= disabledState; - if (isSafeMode && !Utilities.isSystemApp(context, intent)) { + if (isSafeMode && !isSystemApp(context, intent)) { info.runtimeStatusFlags |= FLAG_DISABLED_SAFEMODE; } @@ -584,10 +600,19 @@ public class LoaderTask implements Runnable { int appWidgetId = c.getInt(appWidgetIdIndex); String savedProvider = c.getString(appWidgetProviderIndex); - - final ComponentName component = - ComponentName.unflattenFromString(savedProvider); - + final ComponentName component; + + boolean isSearchWidget = (c.getInt(optionsIndex) + & LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET) != 0; + if (isSearchWidget) { + component = QsbContainerView.getSearchComponentName(context); + if (component == null) { + c.markDeleted("Discarding SearchWidget without packagename "); + continue; + } + } else { + component = ComponentName.unflattenFromString(savedProvider); + } final boolean isIdValid = !c.hasRestoreFlag( LauncherAppWidgetInfo.FLAG_ID_NOT_VALID); final boolean wasProviderReady = !c.hasRestoreFlag( @@ -597,9 +622,7 @@ public class LoaderTask implements Runnable { widgetProvidersMap = mAppWidgetManager.getAllProvidersMap(); } final AppWidgetProviderInfo provider = widgetProvidersMap.get( - new ComponentKey( - ComponentName.unflattenFromString(savedProvider), - c.user)); + new ComponentKey(component, c.user)); final boolean isProviderReady = isValidProvider(provider); if (!isSafeMode && !customWidget && @@ -665,6 +688,7 @@ public class LoaderTask implements Runnable { c.applyCommonProperties(appWidgetInfo); appWidgetInfo.spanX = c.getInt(spanXIndex); appWidgetInfo.spanY = c.getInt(spanYIndex); + appWidgetInfo.options = c.getInt(optionsIndex); appWidgetInfo.user = c.user; if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) { @@ -710,7 +734,7 @@ public class LoaderTask implements Runnable { } } } finally { - Utilities.closeSilently(c); + IOUtils.closeSilently(c); } // Break early if we've stopped loading @@ -750,8 +774,8 @@ public class LoaderTask implements Runnable { } // Sort the folder items, update ranks, and make sure all preview items are high res. - FolderIconPreviewVerifier verifier = - new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); + FolderGridOrganizer verifier = + new FolderGridOrganizer(mApp.getInvariantDeviceProfile()); for (FolderInfo folder : mBgDataModel.folders) { Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); verifier.setFolderInfo(folder); @@ -778,7 +802,7 @@ public class LoaderTask implements Runnable { new SdCardAvailableReceiver(mApp, pendingPackages), new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, - new Handler(LauncherModel.getWorkerLooper())); + MODEL_EXECUTOR.getHandler()); } } } @@ -836,11 +860,12 @@ public class LoaderTask implements Runnable { } } - mBgAllAppsList.added = new ArrayList<>(); + mBgAllAppsList.getAndResetChangeFlag(); return allActivityList; } - private void loadDeepShortcuts() { + private List<ShortcutInfo> loadDeepShortcuts() { + List<ShortcutInfo> allShortcuts = new ArrayList<>(); mBgDataModel.deepShortcutMap.clear(); mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission(); if (mBgDataModel.hasShortcutHostPermission) { @@ -848,10 +873,12 @@ public class LoaderTask implements Runnable { if (mUserManager.isUserUnlocked(user)) { List<ShortcutInfo> shortcuts = mShortcutManager.queryForAllShortcuts(user); + allShortcuts.addAll(shortcuts); mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts); } } } + return allShortcuts; } public static boolean isValidProvider(AppWidgetProviderInfo provider) { diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java index b353810ef..2bd6cd4db 100644 --- a/src/com/android/launcher3/model/ModelPreload.java +++ b/src/com/android/launcher3/model/ModelPreload.java @@ -18,7 +18,6 @@ package com.android.launcher3.model; import android.content.Context; import android.util.Log; -import com.android.launcher3.AllAppsList; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.ModelUpdateTask; diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 4ce2f4ba9..c69ace946 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -16,6 +16,8 @@ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.ContentProviderOperation; import android.content.ContentResolver; import android.content.ContentValues; @@ -31,23 +33,25 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.logging.FileLog; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.LooperExecutor; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Supplier; +import java.util.stream.Collectors; /** * Class for handling model updates. @@ -61,7 +65,6 @@ public class ModelWriter { private final BgDataModel mBgDataModel; private final Handler mUiHandler; - private final Executor mWorkerExecutor; private final boolean mHasVerticalHotseat; private final boolean mVerifyChanges; @@ -74,7 +77,6 @@ public class ModelWriter { mContext = context; mModel = model; mBgDataModel = dataModel; - mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper()); mHasVerticalHotseat = hasVerticalHotseat; mVerifyChanges = verifyChanges; mUiHandler = new Handler(Looper.getMainLooper()); @@ -194,7 +196,7 @@ public class ModelWriter { item.spanX = spanX; item.spanY = spanY; - mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> + ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> new ContentWriter(mContext) .put(Favorites.CONTAINER, item.container) .put(Favorites.CELLX, item.cellX) @@ -209,7 +211,7 @@ public class ModelWriter { * Update an item to the database in a specified container. */ public void updateItemInDatabase(ItemInfo item) { - mWorkerExecutor.execute(new UpdateItemRunnable(item, () -> { + ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> { ContentWriter writer = new ContentWriter(mContext); item.onAddToDatabase(writer); return writer; @@ -229,7 +231,7 @@ public class ModelWriter { ModelVerifier verifier = new ModelVerifier(); final StackTraceElement[] stackTrace = new Throwable().getStackTrace(); - mWorkerExecutor.execute(() -> { + ((Executor) MODEL_EXECUTOR).execute(() -> { // Write the item on background thread, as some properties might have been updated in // the background. final ContentWriter writer = new ContentWriter(mContext); @@ -263,9 +265,12 @@ public class ModelWriter { /** * Removes the specified items from the database */ - public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) { + public void deleteItemsFromDatabase(final Collection<? extends ItemInfo> items) { ModelVerifier verifier = new ModelVerifier(); - + FileLog.d(TAG, "removing items from db " + items.stream().map( + (item) -> item.getTargetComponent() == null ? "" + : item.getTargetComponent().getPackageName()).collect( + Collectors.joining(",")), new Exception()); enqueueDeleteRunnable(() -> { for (ItemInfo item : items) { final Uri uri = Favorites.getContentUri(item.id); @@ -333,14 +338,14 @@ public class ModelWriter { if (mPreparingToUndo) { mDeleteRunnables.add(r); } else { - mWorkerExecutor.execute(r); + ((Executor) MODEL_EXECUTOR).execute(r); } } public void commitDelete() { mPreparingToUndo = false; for (Runnable runnable : mDeleteRunnables) { - mWorkerExecutor.execute(runnable); + ((Executor) MODEL_EXECUTOR).execute(runnable); } mDeleteRunnables.clear(); } diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index 9fcab3887..802cbc7c5 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -19,20 +19,18 @@ import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.PromiseAppInfo; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.util.InstantAppResolver; -import java.util.ArrayList; import java.util.HashSet; /** @@ -65,41 +63,11 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask { } synchronized (apps) { - PromiseAppInfo updated = null; - final ArrayList<AppInfo> removed = new ArrayList<>(); - for (int i=0; i < apps.size(); i++) { - final AppInfo appInfo = apps.get(i); - final ComponentName tgtComp = appInfo.getTargetComponent(); - if (tgtComp != null && tgtComp.getPackageName().equals(mInstallInfo.packageName)) { - if (appInfo instanceof PromiseAppInfo) { - final PromiseAppInfo promiseAppInfo = (PromiseAppInfo) appInfo; - if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLING) { - promiseAppInfo.level = mInstallInfo.progress; - updated = promiseAppInfo; - } else if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) { - apps.removePromiseApp(appInfo); - removed.add(appInfo); - } - } - } - } + PromiseAppInfo updated = apps.updatePromiseInstallInfo(mInstallInfo); if (updated != null) { - final PromiseAppInfo updatedPromiseApp = updated; - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindPromiseAppProgressUpdated(updatedPromiseApp); - } - }); - } - if (!removed.isEmpty()) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAppInfosRemoved(removed); - } - }); + scheduleCallbackTask(c -> c.bindPromiseAppProgressUpdated(updated)); } + bindApplicationsIfNeeded(); } synchronized (dataModel) { diff --git a/src/com/android/launcher3/model/PackageItemInfo.java b/src/com/android/launcher3/model/PackageItemInfo.java index baeaa9492..741be66a0 100644 --- a/src/com/android/launcher3/model/PackageItemInfo.java +++ b/src/com/android/launcher3/model/PackageItemInfo.java @@ -32,8 +32,17 @@ public class PackageItemInfo extends ItemInfoWithIcon { this.packageName = packageName; } + public PackageItemInfo(PackageItemInfo copy) { + this.packageName = copy.packageName; + } + @Override protected String dumpProperties() { return super.dumpProperties() + " packageName=" + packageName; } + + @Override + public PackageItemInfo clone() { + return new PackageItemInfo(this); + } } diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 2d1e94ff8..5e6c7b8c2 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.model; +import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -23,23 +25,19 @@ import android.os.Process; import android.os.UserHandle; import android.util.Log; -import com.android.launcher3.AllAppsList; -import com.android.launcher3.AppInfo; -import com.android.launcher3.WorkspaceItemInfo; -import com.android.launcher3.icons.IconCache; import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; -import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -49,6 +47,7 @@ import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.SafeCloseable; import java.util.ArrayList; import java.util.Arrays; @@ -56,9 +55,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; -import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; -import static com.android.launcher3.WorkspaceItemInfo.FLAG_RESTORED_ICON; - /** * Handles updates due to changes in package manager (app installed/updated/removed) * or when a user availability changes. @@ -101,6 +97,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { FlagOp flagOp = FlagOp.NO_OP; final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages)); ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packageSet, mUser); + final HashSet<ComponentName> removedComponents = new HashSet<>(); + switch (mOp) { case OP_ADD: { for (int i = 0; i < N; i++) { @@ -120,17 +118,21 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { break; } case OP_UPDATE: - for (int i = 0; i < N; i++) { - if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); - iconCache.updateIconsForPkg(packages[i], mUser); - appsList.updatePackage(context, packages[i], mUser); - app.getWidgetCache().removePackage(packages[i], mUser); + try (SafeCloseable t = + appsList.trackRemoves(a -> removedComponents.add(a.componentName))) { + for (int i = 0; i < N; i++) { + if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); + iconCache.updateIconsForPkg(packages[i], mUser); + appsList.updatePackage(context, packages[i], mUser); + app.getWidgetCache().removePackage(packages[i], mUser); + } } // Since package was just updated, the target must be available now. flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE); break; case OP_REMOVE: { for (int i = 0; i < N; i++) { + FileLog.d(TAG, "Removing app icon" + packages[i]); iconCache.removeIconsForPkg(packages[i], mUser); } // Fall through @@ -161,23 +163,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { break; } - final ArrayList<AppInfo> addedOrModified = new ArrayList<>(); - addedOrModified.addAll(appsList.added); - appsList.added.clear(); - addedOrModified.addAll(appsList.modified); - appsList.modified.clear(); - if (!addedOrModified.isEmpty()) { - scheduleCallbackTask((callbacks) -> callbacks.bindAppsAddedOrUpdated(addedOrModified)); - } - - final ArrayList<AppInfo> removedApps = new ArrayList<>(appsList.removed); - appsList.removed.clear(); - final HashSet<ComponentName> removedComponents = new HashSet<>(); - if (mOp == OP_UPDATE) { - for (AppInfo ai : removedApps) { - removedComponents.add(ai.componentName); - } - } + bindApplicationsIfNeeded(); final IntSparseArrayMap<Boolean> removedShortcuts = new IntSparseArrayMap<>(); @@ -235,7 +221,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { isTargetValid = LauncherAppsCompat.getInstance(context) .isActivityEnabledForProfile(cn, mUser); } - if (si.hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)) { + if (si.hasStatusFlag(FLAG_AUTOINSTALL_ICON)) { if (updateWorkspaceItemIntent(context, si, packageName)) { infoUpdated = true; } else if (si.hasPromiseIconUi()) { @@ -303,12 +289,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { } if (!widgets.isEmpty()) { - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindWidgetsRestored(widgets); - } - }); + scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets)); } } @@ -339,16 +320,6 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); } - if (!removedApps.isEmpty()) { - // Remove corresponding apps from All-Apps - scheduleCallbackTask(new CallbackTask() { - @Override - public void execute(Callbacks callbacks) { - callbacks.bindAppInfosRemoved(removedApps); - } - }); - } - if (Utilities.ATLEAST_OREO && mOp == OP_ADD) { // Load widgets for the new package. Changes due to app updates are handled through // AppWidgetHost events, this is just to initialize the long-press options. diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 8528228f2..6c358b170 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -19,7 +19,6 @@ import android.content.Context; import android.content.pm.ShortcutInfo; import android.os.UserHandle; -import com.android.launcher3.AllAppsList; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; @@ -65,7 +64,7 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { for (ItemInfo itemInfo : dataModel.itemsIdMap) { if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { WorkspaceItemInfo si = (WorkspaceItemInfo) itemInfo; - if (si.getIntent().getPackage().equals(mPackageName) && si.user.equals(mUser)) { + if (mPackageName.equals(si.getIntent().getPackage()) && si.user.equals(mUser)) { keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si); allIds.add(si.getDeepShortcutId()); } diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 2cb256e09..4b773d720 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -21,7 +21,6 @@ import android.content.Context; import android.content.pm.ShortcutInfo; import android.os.UserHandle; -import com.android.launcher3.AllAppsList; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; @@ -37,7 +36,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.List; /** * Task to handle changing of lock state of the user @@ -58,9 +56,9 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>(); if (isUserUnlocked) { - List<ShortcutInfo> shortcuts = + DeepShortcutManager.QueryResult shortcuts = deepShortcutManager.queryForPinnedShortcuts(null, mUser); - if (deepShortcutManager.wasLastCallSuccess()) { + if (shortcuts.wasSuccess()) { for (ShortcutInfo shortcut : shortcuts) { pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut); } diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java index e57a051f8..10378ee43 100644 --- a/src/com/android/launcher3/notification/NotificationListener.java +++ b/src/com/android/launcher3/notification/NotificationListener.java @@ -16,6 +16,7 @@ package com.android.launcher3.notification; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; import android.annotation.TargetApi; @@ -31,7 +32,8 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; -import com.android.launcher3.LauncherModel; +import androidx.annotation.Nullable; + import com.android.launcher3.util.IntSet; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.SecureSettingsObserver; @@ -43,8 +45,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import androidx.annotation.Nullable; - /** * A {@link NotificationListenerService} that sends updates to its * {@link NotificationsChangedListener} when notifications are posted or canceled, @@ -141,7 +141,7 @@ public class NotificationListener extends NotificationListenerService { public NotificationListener() { super(); - mWorkerHandler = new Handler(LauncherModel.getWorkerLooper(), mWorkerCallback); + mWorkerHandler = new Handler(MODEL_EXECUTOR.getLooper(), mWorkerCallback); mUiHandler = new Handler(Looper.getMainLooper(), mUiCallback); sNotificationListenerInstance = this; } diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 15fb4cea6..c2aabca2c 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -24,6 +24,7 @@ import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFI import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType; import static com.android.launcher3.userevent.nano.LauncherLogProto.Target; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.AnimatorSet; import android.animation.LayoutTransition; @@ -50,7 +51,6 @@ import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.ItemInfo; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate; @@ -166,7 +166,10 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, } public OnClickListener getItemClickListener() { - return ItemClickHandler.INSTANCE; + return (view) -> { + ItemClickHandler.INSTANCE.onClick(view); + close(true); + }; } @Override @@ -303,8 +306,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, setLayoutTransition(new LayoutTransition()); // Load the shortcuts on a background thread and update the container as it animates. - final Looper workerLooper = LauncherModel.getWorkerLooper(); - new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable( + MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable( mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()), this, mShortcuts, notificationKeys)); } @@ -565,9 +567,9 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, @Override protected void closeComplete() { - super.closeComplete(); mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); mOriginalIcon.setForceHideDot(false); + super.closeComplete(); } @Override diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index 78bd81b46..a87b7b897 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -1,14 +1,11 @@ package com.android.launcher3.popup; -import static com.android.launcher3.userevent.nano.LauncherLogProto.Action; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.graphics.drawable.Icon; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.View; @@ -20,23 +17,30 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.userevent.nano.LauncherLogProto.Action; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.widget.WidgetsBottomSheet; import java.util.List; - /** * Represents a system shortcut for a given app. The shortcut should have a label and icon, and an * onClickListener that depends on the item that the shortcut services. * * Example system shortcuts, defined as inner classes, include Widgets and AppInfo. + * @param <T> */ -public abstract class SystemShortcut<T extends BaseDraggingActivity> extends ItemInfo { +public abstract class SystemShortcut<T extends BaseDraggingActivity> + extends ItemInfo { private final int mIconResId; private final int mLabelResId; private final Icon mIcon; @@ -202,6 +206,27 @@ public abstract class SystemShortcut<T extends BaseDraggingActivity> extends Ite } } + public static class DismissPrediction extends SystemShortcut<Launcher> { + public DismissPrediction() { + super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label); + } + + @Override + public View.OnClickListener getOnClickListener(Launcher activity, ItemInfo itemInfo) { + if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null; + if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) return null; + return (view) -> { + PopupContainerWithArrow.closeAllOpenViews(activity); + activity.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, + ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS); + AppLaunchTracker.INSTANCE.get(view.getContext()) + .onDismissApp(itemInfo.getTargetComponent(), + itemInfo.user, + AppLaunchTracker.CONTAINER_PREDICTIONS); + }; + } + } + protected static void dismissTaskMenuView(BaseDraggingActivity activity) { AbstractFloatingView.closeOpenViews(activity, true, AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE); diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java index 37a209289..dfcc2f822 100644 --- a/src/com/android/launcher3/popup/SystemShortcutFactory.java +++ b/src/com/android/launcher3/popup/SystemShortcutFactory.java @@ -39,7 +39,9 @@ public class SystemShortcutFactory implements ResourceBasedOverride { @SuppressWarnings("unused") public SystemShortcutFactory() { this(new SystemShortcut.AppInfo(), - new SystemShortcut.Widgets(), new SystemShortcut.Install()); + new SystemShortcut.Widgets(), + new SystemShortcut.Install(), + new SystemShortcut.DismissPrediction()); } protected SystemShortcutFactory(SystemShortcut... shortcuts) { @@ -53,6 +55,7 @@ public class SystemShortcutFactory implements ResourceBasedOverride { systemShortcuts.add(systemShortcut); } } + return systemShortcuts; } } diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java index 7b62f53fe..970a03e80 100644 --- a/src/com/android/launcher3/provider/ImportDataTask.java +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -42,7 +42,6 @@ import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; -import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; @@ -50,6 +49,7 @@ import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.PackageManagerHelper; import java.net.URISyntaxException; import java.util.ArrayList; @@ -223,7 +223,7 @@ public class ImportDataTask { case Favorites.ITEM_TYPE_SHORTCUT: case Favorites.ITEM_TYPE_APPLICATION: { intent = Intent.parseUri(c.getString(intentIndex), 0); - if (Utilities.isLauncherAppTarget(intent)) { + if (PackageManagerHelper.isLauncherAppTarget(intent)) { type = Favorites.ITEM_TYPE_APPLICATION; } else { values.put(Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index d643a0b49..fb335516f 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -16,8 +16,6 @@ package com.android.launcher3.provider; -import static com.android.launcher3.Utilities.getIntArrayFromString; -import static com.android.launcher3.Utilities.getStringFromIntArray; import static com.android.launcher3.provider.LauncherDbUtils.dropTable; import android.app.backup.BackupManager; @@ -40,6 +38,7 @@ import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; +import com.android.launcher3.util.IntArray; import com.android.launcher3.util.LogConfig; import java.io.InvalidObjectException; @@ -240,8 +239,8 @@ public class RestoreDbTask { SharedPreferences prefs = Utilities.getPrefs(context); if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) { AppWidgetsRestoredReceiver.restoreAppWidgetIds(context, - getIntArrayFromString(prefs.getString(APPWIDGET_OLD_IDS, "")), - getIntArrayFromString(prefs.getString(APPWIDGET_IDS, ""))); + IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(), + IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray()); } else { FileLog.d(TAG, "No app widget ids to restore."); } @@ -253,8 +252,8 @@ public class RestoreDbTask { public static void setRestoredAppWidgetIds(Context context, @NonNull int[] oldIds, @NonNull int[] newIds) { Utilities.getPrefs(context).edit() - .putString(APPWIDGET_OLD_IDS, getStringFromIntArray(oldIds)) - .putString(APPWIDGET_IDS, getStringFromIntArray(newIds)) + .putString(APPWIDGET_OLD_IDS, IntArray.wrap(oldIds).toConcatString()) + .putString(APPWIDGET_IDS, IntArray.wrap(newIds).toConcatString()) .commit(); } diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java index 857ea05d6..0eb428527 100644 --- a/src/com/android/launcher3/qsb/QsbContainerView.java +++ b/src/com/android/launcher3/qsb/QsbContainerView.java @@ -32,12 +32,16 @@ import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.Bundle; +import android.provider.Settings; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.android.launcher3.AppWidgetResizeFrame; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; @@ -55,6 +59,74 @@ import com.android.launcher3.graphics.FragmentWithPreview; */ public class QsbContainerView extends FrameLayout { + public static final String SEARCH_PROVIDER_SETTINGS_KEY = "SEARCH_PROVIDER_PACKAGE_NAME"; + + /** + * Returns the package name for user configured search provider or from searchManager + * @param context + * @return String + */ + @Nullable + public static String getSearchWidgetPackageName(@NonNull Context context) { + String providerPkg = Settings.Global.getString(context.getContentResolver(), + SEARCH_PROVIDER_SETTINGS_KEY); + if (providerPkg == null) { + SearchManager searchManager = context.getSystemService(SearchManager.class); + ComponentName componentName = searchManager.getGlobalSearchActivity(); + if (componentName != null) { + providerPkg = searchManager.getGlobalSearchActivity().getPackageName(); + } + } + return providerPkg; + } + + /** + * returns it's AppWidgetProviderInfo using package name from getSearchWidgetPackageName + * @param context + * @return AppWidgetProviderInfo + */ + @Nullable + public static AppWidgetProviderInfo getSearchWidgetProviderInfo(@NonNull Context context) { + String providerPkg = getSearchWidgetPackageName(context); + if (providerPkg == null) { + return null; + } + + AppWidgetProviderInfo defaultWidgetForSearchPackage = null; + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + for (AppWidgetProviderInfo info : + appWidgetManager.getInstalledProvidersForPackage(providerPkg, null)) { + if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) { + if ((info.widgetCategory + & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) { + return info; + } else if (defaultWidgetForSearchPackage == null) { + defaultWidgetForSearchPackage = info; + } + } + } + return defaultWidgetForSearchPackage; + } + + /** + * returns componentName for searchWidget if package name is known. + */ + @Nullable + public static ComponentName getSearchComponentName(@NonNull Context context) { + AppWidgetProviderInfo providerInfo = + QsbContainerView.getSearchWidgetProviderInfo(context); + if (providerInfo != null) { + return providerInfo.provider; + } else { + String pkgName = QsbContainerView.getSearchWidgetPackageName(context); + if (pkgName != null) { + //we don't know the class name yet. we'll put the package name as placeholder + return new ComponentName(pkgName, pkgName); + } + return null; + } + } + public QsbContainerView(Context context) { super(context); } @@ -101,7 +173,7 @@ public class QsbContainerView extends FrameLayout { protected QsbWidgetHost createHost() { return new QsbWidgetHost(getContext(), QSB_WIDGET_HOST_ID, - (c) -> new QsbWidgetHostView(c)); + (c) -> new QsbWidgetHostView(c), this::rebindFragment); } private FrameLayout mWrapper; @@ -111,9 +183,9 @@ public class QsbContainerView extends FrameLayout { LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mWrapper = new FrameLayout(getContext()); - // Only add the view when enabled if (isQsbEnabled()) { + mQsbWidgetHost.startListening(); mWrapper.addView(createQsb(mWrapper)); } return mWrapper; @@ -155,7 +227,8 @@ public class QsbContainerView extends FrameLayout { } if (isWidgetBound) { - mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId, mWidgetInfo); + mQsb = (QsbWidgetHostView) mQsbWidgetHost.createView(context, widgetId, + mWidgetInfo); mQsb.setId(R.id.qsb_widget); if (!isInPreviewMode()) { @@ -163,7 +236,6 @@ public class QsbContainerView extends FrameLayout { .getAppWidgetOptions(widgetId), opts)) { mQsb.updateAppWidgetOptions(opts); } - mQsbWidgetHost.startListening(); } return mQsb; } @@ -246,43 +318,32 @@ public class QsbContainerView extends FrameLayout { return v; } + /** * Returns a widget with category {@link AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX} - * provided by the same package which is set to be global search activity. + * provided by the package from getSearchProviderPackageName * If widgetCategory is not supported, or no such widget is found, returns the first widget * provided by the package. */ protected AppWidgetProviderInfo getSearchWidgetProvider() { - SearchManager searchManager = - (SearchManager) getContext().getSystemService(Context.SEARCH_SERVICE); - ComponentName searchComponent = searchManager.getGlobalSearchActivity(); - if (searchComponent == null) return null; - String providerPkg = searchComponent.getPackageName(); - - AppWidgetProviderInfo defaultWidgetForSearchPackage = null; - - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext()); - for (AppWidgetProviderInfo info : appWidgetManager.getInstalledProviders()) { - if (info.provider.getPackageName().equals(providerPkg) && info.configure == null) { - if ((info.widgetCategory - & AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX) != 0) { - return info; - } else if (defaultWidgetForSearchPackage == null) { - defaultWidgetForSearchPackage = info; - } - } - } - return defaultWidgetForSearchPackage; + return getSearchWidgetProviderInfo(getContext()); } } public static class QsbWidgetHost extends AppWidgetHost { private final WidgetViewFactory mViewFactory; + private final WidgetProvidersUpdateCallback mWidgetsUpdateCallback; - public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) { + public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory, + WidgetProvidersUpdateCallback widgetProvidersUpdateCallback) { super(context, hostId); mViewFactory = viewFactory; + mWidgetsUpdateCallback = widgetProvidersUpdateCallback; + } + + public QsbWidgetHost(Context context, int hostId, WidgetViewFactory viewFactory) { + this(context, hostId, viewFactory, null); } @Override @@ -290,6 +351,14 @@ public class QsbContainerView extends FrameLayout { Context context, int appWidgetId, AppWidgetProviderInfo appWidget) { return mViewFactory.newView(context); } + + @Override + protected void onProvidersChanged() { + super.onProvidersChanged(); + if (mWidgetsUpdateCallback != null) { + mWidgetsUpdateCallback.onProvidersUpdated(); + } + } } public interface WidgetViewFactory { @@ -298,6 +367,17 @@ public class QsbContainerView extends FrameLayout { } /** + * Callback interface for packages list update. + */ + @FunctionalInterface + public interface WidgetProvidersUpdateCallback { + /** + * Gets called when widget providers list changes + */ + void onProvidersUpdated(); + } + + /** * Returns true if {@param original} contains all entries defined in {@param updates} and * have the same value. * The comparison uses {@link Object#equals(Object)} to compare the values. @@ -316,4 +396,5 @@ public class QsbContainerView extends FrameLayout { } return true; } + } diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java index c6370c5c5..a23cd6d2e 100644 --- a/src/com/android/launcher3/states/InternalStateHandler.java +++ b/src/com/android/launcher3/states/InternalStateHandler.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.states; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.Intent; import android.os.Binder; import android.os.Bundle; @@ -22,8 +24,7 @@ import android.os.IBinder; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel.Callbacks; -import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.model.BgDataModel.Callbacks; import java.lang.ref.WeakReference; @@ -94,16 +95,12 @@ public abstract class InternalStateHandler extends Binder { private static class Scheduler implements Runnable { private WeakReference<InternalStateHandler> mPendingHandler = new WeakReference<>(null); - private MainThreadExecutor mMainThreadExecutor; public void schedule(InternalStateHandler handler) { synchronized (this) { mPendingHandler = new WeakReference<>(handler); - if (mMainThreadExecutor == null) { - mMainThreadExecutor = new MainThreadExecutor(); - } } - mMainThreadExecutor.execute(this); + MAIN_EXECUTOR.execute(this); } @Override diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index 790a2e844..0d1d1a670 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -15,6 +15,7 @@ */ package com.android.launcher3.testing; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static android.graphics.Bitmap.Config.ARGB_8888; import android.content.Context; @@ -28,7 +29,6 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherState; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.util.ResourceBasedOverride; @@ -95,20 +95,20 @@ public class TestInformationHandler implements ResourceBasedOverride { break; case TestProtocol.REQUEST_FREEZE_APP_LIST: - new MainThreadExecutor().execute(() -> + MAIN_EXECUTOR.execute(() -> mLauncher.getAppsView().getAppsStore().enableDeferUpdates( AllAppsStore.DEFER_UPDATES_TEST)); break; case TestProtocol.REQUEST_UNFREEZE_APP_LIST: - new MainThreadExecutor().execute(() -> + MAIN_EXECUTOR.execute(() -> mLauncher.getAppsView().getAppsStore().disableDeferUpdates( AllAppsStore.DEFER_UPDATES_TEST)); break; case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: { try { - final int deferUpdatesFlags = new MainThreadExecutor().submit(() -> + final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() -> mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get(); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, deferUpdatesFlags); @@ -152,4 +152,4 @@ public class TestInformationHandler implements ResourceBasedOverride { } return response; } -}
\ No newline at end of file +} diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java index babbcdd16..6cd2b2dc2 100644 --- a/src/com/android/launcher3/touch/ItemLongClickListener.java +++ b/src/com/android/launcher3/touch/ItemLongClickListener.java @@ -17,6 +17,7 @@ package com.android.launcher3.touch; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; + import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; @@ -60,7 +61,7 @@ public class ItemLongClickListener { if (info.container >= 0) { Folder folder = Folder.getOpen(launcher); if (folder != null) { - if (!folder.getItemsInReadingOrder().contains(v)) { + if (!folder.getIconsInReadingOrder().contains(v)) { folder.close(true); } else { folder.startDrag(v, dragOptions); diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java index 12d35e962..0f8152057 100644 --- a/src/com/android/launcher3/util/ConfigMonitor.java +++ b/src/com/android/launcher3/util/ConfigMonitor.java @@ -16,20 +16,15 @@ package com.android.launcher3.util; * limitations under the License. */ +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Point; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; -import android.os.Handler; import android.util.Log; -import android.view.Display; -import android.view.WindowManager; - -import com.android.launcher3.MainThreadExecutor; import java.util.function.Consumer; @@ -37,7 +32,8 @@ import java.util.function.Consumer; * {@link BroadcastReceiver} which watches configuration changes and * notifies the callback in case changes which affect the device profile occur. */ -public class ConfigMonitor extends BroadcastReceiver implements DisplayListener { +public class ConfigMonitor extends BroadcastReceiver implements + DefaultDisplay.DisplayInfoChangeListener { private static final String TAG = "ConfigMonitor"; @@ -61,24 +57,19 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener mFontScale = config.fontScale; mDensity = config.densityDpi; - Display display = getDefaultDisplay(context); - mDisplayId = display.getDisplayId(); - - mRealSize = new Point(); - display.getRealSize(mRealSize); + DefaultDisplay display = DefaultDisplay.INSTANCE.get(context); + display.addChangeListener(this); + DefaultDisplay.Info displayInfo = display.getInfo(); + mDisplayId = displayInfo.id; - mSmallestSize = new Point(); - mLargestSize = new Point(); - display.getCurrentSizeRange(mSmallestSize, mLargestSize); + mRealSize = new Point(displayInfo.realSize); + mSmallestSize = new Point(displayInfo.smallestSize); + mLargestSize = new Point(displayInfo.largestSize); mCallback = callback; // Listen for configuration change mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)); - - // Listen for display manager change - mContext.getSystemService(DisplayManager.class) - .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper())); } @Override @@ -91,26 +82,19 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener } @Override - public void onDisplayAdded(int displayId) { } - - @Override - public void onDisplayRemoved(int displayId) { } - - @Override - public void onDisplayChanged(int displayId) { - if (displayId != mDisplayId) { + public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) { + if (info.id != mDisplayId) { return; } - Display display = getDefaultDisplay(mContext); - display.getRealSize(mTmpPoint1); - + mTmpPoint1.set(info.realSize.x, info.realSize.y); if (!mRealSize.equals(mTmpPoint1) && !mRealSize.equals(mTmpPoint1.y, mTmpPoint1.x)) { Log.d(TAG, String.format("Display size changed from %s to %s", mRealSize, mTmpPoint1)); notifyChange(); return; } - display.getCurrentSizeRange(mTmpPoint1, mTmpPoint2); + mTmpPoint1.set(info.smallestSize.x, info.smallestSize.y); + mTmpPoint2.set(info.largestSize.x, info.largestSize.y); if (!mSmallestSize.equals(mTmpPoint1) || !mLargestSize.equals(mTmpPoint2)) { Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]", mSmallestSize, mLargestSize, mTmpPoint1, mTmpPoint2)); @@ -122,18 +106,15 @@ public class ConfigMonitor extends BroadcastReceiver implements DisplayListener if (mCallback != null) { Consumer<Context> callback = mCallback; mCallback = null; - new MainThreadExecutor().execute(() -> callback.accept(mContext)); + MAIN_EXECUTOR.execute(() -> callback.accept(mContext)); } } - private Display getDefaultDisplay(Context context) { - return context.getSystemService(WindowManager.class).getDefaultDisplay(); - } - public void unregister() { try { mContext.unregisterReceiver(this); - mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext); + display.removeChangeListener(this); } catch (Exception e) { Log.e(TAG, "Failed to unregister config monitor", e); } diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java index 7719f084d..8529d50ca 100644 --- a/src/com/android/launcher3/util/DefaultDisplay.java +++ b/src/com/android/launcher3/util/DefaultDisplay.java @@ -15,12 +15,15 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Handler; import android.os.Message; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Display; import android.view.WindowManager; @@ -54,7 +57,7 @@ public class DefaultDisplay implements DisplayListener { mChangeHandler = new Handler(this::onChange); context.getSystemService(DisplayManager.class) - .registerDisplayListener(this, new Handler(UiThreadHelper.getBackgroundLooper())); + .registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler()); } @Override @@ -122,6 +125,8 @@ public class DefaultDisplay implements DisplayListener { public final Point smallestSize; public final Point largestSize; + public final DisplayMetrics metrics; + private Info(Context context) { Display display = context.getSystemService(WindowManager.class).getDefaultDisplay(); @@ -136,6 +141,9 @@ public class DefaultDisplay implements DisplayListener { largestSize = new Point(); display.getRealSize(realSize); display.getCurrentSizeRange(smallestSize, largestSize); + + metrics = new DisplayMetrics(); + display.getMetrics(metrics); } private boolean hasDifferentSize(Info info) { diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java new file mode 100644 index 000000000..4d5ee49e8 --- /dev/null +++ b/src/com/android/launcher3/util/Executors.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.util; + +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Process; + +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Various different executors used in Launcher + */ +public class Executors { + + // These values are same as that in {@link AsyncTask}. + private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CORE_POOL_SIZE = CPU_COUNT + 1; + private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; + private static final int KEEP_ALIVE = 1; + + /** + * An {@link Executor} to be used with async task with no limit on the queue size. + */ + public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( + CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, + TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + + /** + * Returns the executor for running tasks on the main thread. + */ + public static final LooperExecutor MAIN_EXECUTOR = + new LooperExecutor(Looper.getMainLooper()); + + /** + * A background executor for using time sensitive actions where user is waiting for response. + */ + public static final LooperExecutor UI_HELPER_EXECUTOR = + new LooperExecutor(createAndStartNewForegroundLooper("UiThreadHelper")); + + /** + * Utility method to get a started handler thread statically + */ + public static Looper createAndStartNewLooper(String name) { + return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT); + } + + /** + * Utility method to get a started handler thread statically with the provided priority + */ + public static Looper createAndStartNewLooper(String name, int priority) { + HandlerThread thread = new HandlerThread(name, priority); + thread.start(); + return thread.getLooper(); + } + + /** + * Similar to {@link #createAndStartNewLooper(String)}, but starts the thread with + * foreground priority. + * Think before using + */ + public static Looper createAndStartNewForegroundLooper(String name) { + return createAndStartNewLooper(name, Process.THREAD_PRIORITY_FOREGROUND); + } + + /** + * Executor used for running Launcher model related tasks (eg loading icons or updated db) + */ + public static final LooperExecutor MODEL_EXECUTOR = + new LooperExecutor(createAndStartNewLooper("launcher-loader")); +} diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java index f95f74d60..4a4a5ca22 100644 --- a/src/com/android/launcher3/util/IOUtils.java +++ b/src/com/android/launcher3/util/IOUtils.java @@ -17,10 +17,13 @@ package com.android.launcher3.util; import android.content.Context; +import android.util.Log; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -35,6 +38,7 @@ import java.util.UUID; public class IOUtils { private static final int BUF_SIZE = 0x1000; // 4K + private static final String TAG = "IOUtils"; public static byte[] toByteArray(File file) throws IOException { try (InputStream in = new FileInputStream(file)) { @@ -77,4 +81,16 @@ public class IOUtils { } return file.getAbsolutePath(); } + + public static void closeSilently(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException e) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { + Log.d(TAG, "Error closing", e); + } + } + } + } } diff --git a/src/com/android/launcher3/util/IntArray.java b/src/com/android/launcher3/util/IntArray.java index d2a551f45..7252f7ac1 100644 --- a/src/com/android/launcher3/util/IntArray.java +++ b/src/com/android/launcher3/util/IntArray.java @@ -17,6 +17,7 @@ package com.android.launcher3.util; import java.util.Arrays; +import java.util.StringTokenizer; /** * Copy of the platform hidden implementation of android.util.IntArray. @@ -248,6 +249,17 @@ public class IntArray implements Cloneable { return b.toString(); } + public static IntArray fromConcatString(String concatString) { + StringTokenizer tokenizer = new StringTokenizer(concatString, ","); + int[] array = new int[tokenizer.countTokens()]; + int count = 0; + while (tokenizer.hasMoreTokens()) { + array[count] = Integer.parseInt(tokenizer.nextToken().trim()); + count++; + } + return new IntArray(array, array.length); + } + /** * Throws {@link ArrayIndexOutOfBoundsException} if the index is out of bounds. * diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java index cc0746997..8ac600f73 100644 --- a/src/com/android/launcher3/util/LooperExecutor.java +++ b/src/com/android/launcher3/util/LooperExecutor.java @@ -16,7 +16,9 @@ package com.android.launcher3.util; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; +import android.os.Process; import java.util.List; import java.util.concurrent.AbstractExecutorService; @@ -47,6 +49,13 @@ public class LooperExecutor extends AbstractExecutorService { } /** + * Same as execute, but never runs the action inline. + */ + public void post(Runnable runnable) { + mHandler.post(runnable); + } + + /** * Not supported and throws an exception when used. */ @Override @@ -79,7 +88,31 @@ public class LooperExecutor extends AbstractExecutorService { */ @Override @Deprecated - public boolean awaitTermination(long l, TimeUnit timeUnit) throws InterruptedException { + public boolean awaitTermination(long l, TimeUnit timeUnit) { throw new UnsupportedOperationException(); } + + /** + * Returns the thread for this executor + */ + public Thread getThread() { + return mHandler.getLooper().getThread(); + } + + /** + * Returns the looper for this executor + */ + public Looper getLooper() { + return mHandler.getLooper(); + } + + /** + * Set the priority of a thread, based on Linux priorities. + * @param priority Linux priority level, from -20 for highest scheduling priority + * to 19 for lowest scheduling priority. + * @see Process#setThreadPriority(int, int) + */ + public void setThreadPriority(int priority) { + Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority); + } } diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java index e185a3199..fe9c2c468 100644 --- a/src/com/android/launcher3/util/MainThreadInitializedObject.java +++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java @@ -15,12 +15,13 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + import android.content.Context; import android.os.Looper; import androidx.annotation.VisibleForTesting; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.util.ResourceBasedOverride.Overrides; import java.util.concurrent.ExecutionException; @@ -43,7 +44,7 @@ public class MainThreadInitializedObject<T> { mValue = mProvider.get(context.getApplicationContext()); } else { try { - return new MainThreadExecutor().submit(() -> get(context)).get(); + return MAIN_EXECUTOR.submit(() -> get(context)).get(); } catch (InterruptedException|ExecutionException e) { throw new RuntimeException(e); } diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index 7d3a94162..ef4307ec5 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -24,9 +24,11 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.graphics.Rect; import android.net.Uri; import android.os.Build; @@ -35,6 +37,7 @@ import android.os.PatternMatcher; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.widget.Toast; import com.android.launcher3.AppInfo; @@ -220,4 +223,73 @@ public class PackageManagerHelper { packageFilter.addDataSchemeSpecificPart(pkg, PatternMatcher.PATTERN_LITERAL); return packageFilter; } + + public static boolean isSystemApp(Context context, Intent intent) { + PackageManager pm = context.getPackageManager(); + ComponentName cn = intent.getComponent(); + String packageName = null; + if (cn == null) { + ResolveInfo info = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + if ((info != null) && (info.activityInfo != null)) { + packageName = info.activityInfo.packageName; + } + } else { + packageName = cn.getPackageName(); + } + if (packageName != null) { + try { + PackageInfo info = pm.getPackageInfo(packageName, 0); + return (info != null) && (info.applicationInfo != null) && + ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + } catch (NameNotFoundException e) { + return false; + } + } else { + return false; + } + } + + /** + * Finds a system apk which had a broadcast receiver listening to a particular action. + * @param action intent action used to find the apk + * @return a pair of apk package name and the resources. + */ + public static Pair<String, Resources> findSystemApk(String action, PackageManager pm) { + final Intent intent = new Intent(action); + for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) { + if (info.activityInfo != null && + (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + final String packageName = info.activityInfo.packageName; + try { + final Resources res = pm.getResourcesForApplication(packageName); + return Pair.create(packageName, res); + } catch (NameNotFoundException e) { + Log.w(TAG, "Failed to find resources for " + packageName); + } + } + } + return null; + } + + /** + * Returns true if the intent is a valid launch intent for a launcher activity of an app. + * This is used to identify shortcuts which are different from the ones exposed by the + * applications' manifest file. + * + * @param launchIntent The intent that will be launched when the shortcut is clicked. + */ + public static boolean isLauncherAppTarget(Intent launchIntent) { + if (launchIntent != null + && Intent.ACTION_MAIN.equals(launchIntent.getAction()) + && launchIntent.getComponent() != null + && launchIntent.getCategories() != null + && launchIntent.getCategories().size() == 1 + && launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER) + && TextUtils.isEmpty(launchIntent.getDataString())) { + // An app target can either have no extra or have ItemInfo.EXTRA_PROFILE. + Bundle extras = launchIntent.getExtras(); + return extras == null || extras.keySet().isEmpty(); + } + return false; + } } diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java index aa11968e4..f243ca628 100644 --- a/src/com/android/launcher3/util/PackageUserKey.java +++ b/src/com/android/launcher3/util/PackageUserKey.java @@ -6,7 +6,6 @@ import android.service.notification.StatusBarNotification; import androidx.annotation.Nullable; import com.android.launcher3.ItemInfo; -import com.android.launcher3.shortcuts.DeepShortcutManager; import java.util.Arrays; diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java index 7ab0d3103..ed66422be 100644 --- a/src/com/android/launcher3/util/Preconditions.java +++ b/src/com/android/launcher3/util/Preconditions.java @@ -16,9 +16,10 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.os.Looper; -import com.android.launcher3.LauncherModel; import com.android.launcher3.config.FeatureFlags; /** @@ -33,7 +34,7 @@ public class Preconditions { } public static void assertWorkerThread() { - if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) { + if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) { throw new IllegalStateException(); } } diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/util/SafeCloseable.java index 509468233..ba8ee04d2 100644 --- a/src/com/android/launcher3/MainThreadExecutor.java +++ b/src/com/android/launcher3/util/SafeCloseable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,23 +11,16 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.launcher3; - -import android.os.Looper; - -import com.android.launcher3.util.LooperExecutor; +package com.android.launcher3.util; /** - * An executor service that executes its tasks on the main thread. - * - * Shutting down this executor is not supported. + * Extension of closeable which does not throw an exception */ -public class MainThreadExecutor extends LooperExecutor { +public interface SafeCloseable extends AutoCloseable { - public MainThreadExecutor() { - super(Looper.getMainLooper()); - } + @Override + void close(); } diff --git a/src/com/android/launcher3/util/UiThreadHelper.java b/src/com/android/launcher3/util/UiThreadHelper.java index cc442f988..f8d163230 100644 --- a/src/com/android/launcher3/util/UiThreadHelper.java +++ b/src/com/android/launcher3/util/UiThreadHelper.java @@ -15,14 +15,13 @@ */ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.app.Activity; import android.content.Context; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.Looper; import android.os.Message; -import android.os.Process; import android.view.inputmethod.InputMethodManager; /** @@ -30,25 +29,15 @@ import android.view.inputmethod.InputMethodManager; */ public class UiThreadHelper { - private static HandlerThread sHandlerThread; private static Handler sHandler; private static final int MSG_HIDE_KEYBOARD = 1; private static final int MSG_SET_ORIENTATION = 2; private static final int MSG_RUN_COMMAND = 3; - public static Looper getBackgroundLooper() { - if (sHandlerThread == null) { - sHandlerThread = - new HandlerThread("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND); - sHandlerThread.start(); - } - return sHandlerThread.getLooper(); - } - private static Handler getHandler(Context context) { if (sHandler == null) { - sHandler = new Handler(getBackgroundLooper(), + sHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), new UiCallbacks(context.getApplicationContext())); } return sHandler; diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java index acce30860..61ba4e566 100644 --- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -16,13 +16,14 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.os.Process; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewTreeObserver.OnDrawListener; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import java.util.ArrayList; import java.util.concurrent.Executor; @@ -66,7 +67,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, @Override public void execute(Runnable command) { mTasks.add(command); - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_BACKGROUND); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } @Override @@ -108,7 +109,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, if (mLauncher != null) { mLauncher.clearPendingExecutor(this); } - LauncherModel.setWorkerPriority(Process.THREAD_PRIORITY_DEFAULT); + MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); } protected boolean isCompleted() { diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java index 5c2468740..2ad80cf66 100644 --- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java +++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java @@ -1,5 +1,7 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + import android.app.WallpaperManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -198,7 +200,7 @@ public class WallpaperOffsetInterpolator extends BroadcastReceiver { private float mOffsetX; public OffsetHandler(Context context) { - super(UiThreadHelper.getBackgroundLooper()); + super(UI_HELPER_EXECUTOR.getLooper()); mInterpolator = Interpolators.DEACCEL_1_5; mWM = WallpaperManager.getInstance(context); } diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index c08b65931..2a4c5a7bf 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -465,7 +465,7 @@ public abstract class BaseDragLayer<T extends Context & ActivityContext> } public void dump(String prefix, PrintWriter writer) { - writer.println(prefix + "DragLayer"); + writer.println(prefix + "DragLayer:"); if (mActiveController != null) { writer.println(prefix + "\tactiveController: " + mActiveController); mActiveController.dump(prefix + "\t", writer); diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index f728a6776..3912b4468 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -22,6 +22,7 @@ import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -41,7 +42,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.CancellationSignal; -import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -50,11 +50,17 @@ import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; +import androidx.annotation.Nullable; +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; +import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; + import com.android.launcher3.BubbleTextView; import com.android.launcher3.InsettableFrameLayout.LayoutParams; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherModel; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.dragndrop.DragLayer; @@ -66,13 +72,6 @@ import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.DeepShortcutView; -import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import androidx.annotation.WorkerThread; -import androidx.dynamicanimation.animation.FloatPropertyCompat; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - /** * A view that is created to look like another view with the purpose of creating fluid animations. */ @@ -722,7 +721,7 @@ public class FloatingIconView extends View implements @UiThread public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) { IconLoadResult result = new IconLoadResult(info); - new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> { + MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { RectF position = new RectF(); getLocationBoundsForView(l, v, isOpening, position); getIconResult(l, v, info, position, result); diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index dce839f39..f3fd7ca4d 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -44,6 +44,7 @@ import com.android.launcher3.SimpleOnStylusPressListener; import com.android.launcher3.StylusEventHelper; import com.android.launcher3.Utilities; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.util.Executors; import com.android.launcher3.util.Themes; import com.android.launcher3.views.BaseDragLayer.TouchCompleteListener; @@ -96,7 +97,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView setBackgroundResource(R.drawable.widget_internal_focus_bg); if (Utilities.ATLEAST_OREO) { - setExecutor(Utilities.THREAD_POOL_EXECUTOR); + setExecutor(Executors.THREAD_POOL_EXECUTOR); } if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) { setOnLightBackground(true); @@ -332,12 +333,7 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView if (shouldRegisterAutoAdvance != mIsAutoAdvanceRegistered) { mIsAutoAdvanceRegistered = shouldRegisterAutoAdvance; if (mAutoAdvanceRunnable == null) { - mAutoAdvanceRunnable = new Runnable() { - @Override - public void run() { - runAutoAdvance(); - } - }; + mAutoAdvanceRunnable = this::runAutoAdvance; } handler.removeCallbacks(mAutoAdvanceRunnable); diff --git a/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java new file mode 100644 index 000000000..15a0ffaa0 --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/AppLaunchEventsPlugin.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.plugins; + +import android.content.ComponentName; +import android.os.UserHandle; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Plugin interface which sends app launch events. + */ +@ProvidesInterface(action = AppLaunchEventsPlugin.ACTION, version = AppLaunchEventsPlugin.VERSION) +public interface AppLaunchEventsPlugin extends Plugin { + String ACTION = "com.android.systemui.action.PLUGIN_APP_EVENTS"; + int VERSION = 1; + + /** + * Receives onStartShortcut event from + * {@link com.android.launcher3.appprediction.PredictionAppTracker}. + */ + void onStartShortcut(String packageName, String shortcutId, UserHandle user, String container); + + /** + * Receives onStartApp event from + * {@link com.android.launcher3.appprediction.PredictionAppTracker}. + */ + void onStartApp(ComponentName componentName, UserHandle user, String container); + + /** + * Receives onDismissApp event from + * {@link com.android.launcher3.appprediction.PredictionAppTracker}. + */ + void onDismissApp(ComponentName componentName, UserHandle user, String container); + + /** + * Receives onReturnedToHome event from + * {@link com.android.launcher3.appprediction.PredictionAppTracker}. + */ + void onReturnedToHome(); +} diff --git a/src_plugins/com/android/systemui/plugins/HotseatPlugin.java b/src_plugins/com/android/systemui/plugins/HotseatPlugin.java new file mode 100644 index 000000000..1264e0d3b --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/HotseatPlugin.java @@ -0,0 +1,20 @@ +package com.android.systemui.plugins; + +import android.view.View; +import android.view.ViewGroup; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Implement this plugin interface to add a sub-view in the Hotseat. + */ +@ProvidesInterface(action = HotseatPlugin.ACTION, version = HotseatPlugin.VERSION) +public interface HotseatPlugin extends Plugin { + String ACTION = "com.android.systemui.action.PLUGIN_HOTSEAT"; + int VERSION = 1; + + /** + * Creates a plugin view which will be added to the Hotseat. + */ + View createView(ViewGroup parent); +} diff --git a/src_plugins/com/android/systemui/plugins/UserEventPlugin.java b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java new file mode 100644 index 000000000..0e3664ae3 --- /dev/null +++ b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.plugins; + +import com.android.systemui.plugins.annotations.ProvidesInterface; + +/** + * Implement this plugin interface to access user event log on the device for prototype purpose. + * NOTE: plugin is for internal prototype only and is not visible in production environment. + */ +@ProvidesInterface(action = UserEventPlugin.ACTION, version = UserEventPlugin.VERSION) +public interface UserEventPlugin extends Plugin { + String ACTION = "com.android.launcher3.action.PLUGIN_USER_EVENT_LOG"; + int VERSION = 1; + + /** + * Callback to be triggered whenever an user event occurs. + */ + void onUserEvent(Object event); +} diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java index 1710aef12..789bfd868 100644 --- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java +++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java @@ -16,9 +16,8 @@ package com.android.launcher3.model; -import com.android.launcher3.AllAppsList; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.widget.WidgetListRowEntry; diff --git a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java index 09b189049..57f416495 100644 --- a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java +++ b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -43,35 +43,27 @@ public class DeepShortcutManager { | ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_PINNED; private static DeepShortcutManager sInstance; - private static final Object sInstanceLock = new Object(); public static DeepShortcutManager getInstance(Context context) { - synchronized (sInstanceLock) { - if (sInstance == null) { - sInstance = new DeepShortcutManager(context.getApplicationContext()); - } - return sInstance; + if (sInstance == null) { + sInstance = new DeepShortcutManager(context.getApplicationContext()); } + return sInstance; } private final LauncherApps mLauncherApps; - private boolean mWasLastCallSuccess; private DeepShortcutManager(Context context) { mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE); } - public boolean wasLastCallSuccess() { - return mWasLastCallSuccess; - } - /** * Queries for the shortcuts with the package name and provided ids. * * This method is intended to get the full details for shortcuts when they are added or updated, * because we only get "key" fields in onShortcutsChanged(). */ - public List<ShortcutInfo> queryForFullDetails(String packageName, + public QueryResult queryForFullDetails(String packageName, List<String> shortcutIds, UserHandle user) { return query(FLAG_GET_ALL, packageName, null, shortcutIds, user); } @@ -80,9 +72,9 @@ public class DeepShortcutManager { * Gets all the manifest and dynamic shortcuts associated with the given package and user, * to be displayed in the shortcuts container on long press. */ - public List<ShortcutInfo> queryForShortcutsContainer(@Nullable ComponentName activity, + public QueryResult queryForShortcutsContainer(@Nullable ComponentName activity, UserHandle user) { - if (activity == null) return Collections.EMPTY_LIST; + if (activity == null) return QueryResult.FAILURE; return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC, activity.getPackageName(), activity, null, user); } @@ -99,10 +91,8 @@ public class DeepShortcutManager { pinnedIds.remove(id); try { mLauncherApps.pinShortcuts(packageName, pinnedIds, user); - mWasLastCallSuccess = true; } catch (SecurityException|IllegalStateException e) { Log.w(TAG, "Failed to unpin shortcut", e); - mWasLastCallSuccess = false; } } @@ -118,10 +108,8 @@ public class DeepShortcutManager { pinnedIds.add(id); try { mLauncherApps.pinShortcuts(packageName, pinnedIds, user); - mWasLastCallSuccess = true; } catch (SecurityException|IllegalStateException e) { Log.w(TAG, "Failed to pin shortcut", e); - mWasLastCallSuccess = false; } } @@ -130,23 +118,18 @@ public class DeepShortcutManager { try { mLauncherApps.startShortcut(packageName, id, sourceBounds, startActivityOptions, user); - mWasLastCallSuccess = true; } catch (SecurityException|IllegalStateException e) { Log.e(TAG, "Failed to start shortcut", e); - mWasLastCallSuccess = false; } } public Drawable getShortcutIconDrawable(ShortcutInfo shortcutInfo, int density) { try { - Drawable icon = mLauncherApps.getShortcutIconDrawable(shortcutInfo, density); - mWasLastCallSuccess = true; - return icon; + return mLauncherApps.getShortcutIconDrawable(shortcutInfo, density); } catch (SecurityException|IllegalStateException e) { Log.e(TAG, "Failed to get shortcut icon", e); - mWasLastCallSuccess = false; + return null; } - return null; } /** @@ -154,20 +137,20 @@ public class DeepShortcutManager { * * If packageName is null, returns all pinned shortcuts regardless of package. */ - public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, UserHandle user) { + public QueryResult queryForPinnedShortcuts(String packageName, UserHandle user) { return queryForPinnedShortcuts(packageName, null, user); } - public List<ShortcutInfo> queryForPinnedShortcuts(String packageName, - List<String> shortcutIds, UserHandle user) { + public QueryResult queryForPinnedShortcuts(String packageName, List<String> shortcutIds, + UserHandle user) { return query(ShortcutQuery.FLAG_MATCH_PINNED, packageName, null, shortcutIds, user); } - public List<ShortcutInfo> queryForAllShortcuts(UserHandle user) { + public QueryResult queryForAllShortcuts(UserHandle user) { return query(FLAG_GET_ALL, null, null, null, user); } - private List<String> extractIds(List<ShortcutInfo> shortcuts) { + private static List<String> extractIds(List<ShortcutInfo> shortcuts) { List<String> shortcutIds = new ArrayList<>(shortcuts.size()); for (ShortcutInfo shortcut : shortcuts) { shortcutIds.add(shortcut.getId()); @@ -181,8 +164,8 @@ public class DeepShortcutManager { * * TODO: Use the cache to optimize this so we don't make an RPC every time. */ - private List<ShortcutInfo> query(int flags, String packageName, - ComponentName activity, List<String> shortcutIds, UserHandle user) { + private QueryResult query(int flags, String packageName, ComponentName activity, + List<String> shortcutIds, UserHandle user) { ShortcutQuery q = new ShortcutQuery(); q.setQueryFlags(flags); if (packageName != null) { @@ -190,18 +173,12 @@ public class DeepShortcutManager { q.setActivity(activity); q.setShortcutIds(shortcutIds); } - List<ShortcutInfo> shortcutInfos = null; try { - shortcutInfos = mLauncherApps.getShortcuts(q, user); - mWasLastCallSuccess = true; + return new QueryResult(mLauncherApps.getShortcuts(q, user)); } catch (SecurityException|IllegalStateException e) { Log.e(TAG, "Failed to query for shortcuts", e); - mWasLastCallSuccess = false; + return QueryResult.FAILURE; } - if (shortcutInfos == null) { - return Collections.EMPTY_LIST; - } - return shortcutInfos; } public boolean hasHostPermission() { @@ -212,4 +189,25 @@ public class DeepShortcutManager { } return false; } + + public static class QueryResult extends ArrayList<ShortcutInfo> { + + static QueryResult FAILURE = new QueryResult(); + + private final boolean mWasSuccess; + + QueryResult(List<ShortcutInfo> result) { + super(result == null ? Collections.emptyList() : result); + mWasSuccess = true; + } + + QueryResult() { + mWasSuccess = false; + } + + + public boolean wasSuccess() { + return mWasSuccess; + } + } } diff --git a/tests/Android.mk b/tests/Android.mk index 0c412415c..02ead4ef4 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -27,7 +27,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ ifneq (,$(wildcard frameworks/base)) else - LOCAL_STATIC_JAVA_LIBRARIES += libSharedSystemUI + LOCAL_STATIC_JAVA_LIBRARIES += SystemUISharedLib LOCAL_SRC_FILES := $(call all-java-files-under, tapl) \ ../src/com/android/launcher3/ResourceUtils.java \ diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index 7d60ad65b..64df8e0e9 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -15,9 +15,9 @@ import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.util.PackageManagerHelper; import org.junit.Before; import org.junit.Test; @@ -115,7 +115,7 @@ public class LoaderCursorTest { WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo( new Intent().setComponent(cn), false /* allowMissingTarget */, true); assertNotNull(info); - assertTrue(Utilities.isLauncherAppTarget(info.intent)); + assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent)); } @Test @@ -127,7 +127,7 @@ public class LoaderCursorTest { WorkspaceItemInfo info = mLoaderCursor.getAppShortcutInfo( new Intent().setComponent(cn), true /* allowMissingTarget */, true); assertNotNull(info); - assertTrue(Utilities.isLauncherAppTarget(info.intent)); + assertTrue(PackageManagerHelper.isLauncherAppTarget(info.intent)); } @Test diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 1f4ba1af0..fc3118703 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -19,6 +19,7 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation; import static com.android.launcher3.tapl.LauncherInstrumentation.ContainerType; import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static org.junit.Assert.assertTrue; @@ -47,7 +48,6 @@ import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.model.AppLaunchTracker; @@ -55,6 +55,7 @@ import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.testing.TestProtocol; +import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.FailureWatcher; @@ -90,7 +91,7 @@ public abstract class AbstractLauncherUiTest { public static final long DEFAULT_UI_TIMEOUT = 60000; // b/136278866 private static final String TAG = "AbstractLauncherUiTest"; - protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); + protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR; protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation()); protected final LauncherInstrumentation mLauncher = new LauncherInstrumentation(getInstrumentation()); diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java index b564a1a87..d85dd3a2d 100644 --- a/tests/src/com/android/launcher3/util/Condition.java +++ b/tests/src/com/android/launcher3/util/Condition.java @@ -1,8 +1,8 @@ package com.android.launcher3.util; -import androidx.test.uiautomator.UiObject2; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import com.android.launcher3.MainThreadExecutor; +import androidx.test.uiautomator.UiObject2; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -16,7 +16,7 @@ public interface Condition { * Converts the condition to be run on UI thread. */ static Condition runOnUiThread(final Condition condition) { - final MainThreadExecutor executor = new MainThreadExecutor(); + final LooperExecutor executor = MAIN_EXECUTOR; return () -> { final AtomicBoolean value = new AtomicBoolean(false); final Throwable[] exceptions = new Throwable[1]; diff --git a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java index 0235f95d0..8f8917320 100644 --- a/tests/src/com/android/launcher3/util/RaceConditionReproducer.java +++ b/tests/src/com/android/launcher3/util/RaceConditionReproducer.java @@ -16,6 +16,7 @@ package com.android.launcher3.util; +import static com.android.launcher3.util.Executors.createAndStartNewLooper; import static com.android.launcher3.util.RaceConditionTracker.ENTER_POSTFIX; import static com.android.launcher3.util.RaceConditionTracker.EXIT_POSTFIX; @@ -23,7 +24,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.os.Handler; -import android.os.HandlerThread; import android.util.Log; import java.util.ArrayList; @@ -72,9 +72,7 @@ public class RaceConditionReproducer implements RaceConditionTracker.EventProces private static final Handler POSTPONED_EVENT_RESUME_HANDLER = createEventResumeHandler(); private static Handler createEventResumeHandler() { - final HandlerThread thread = new HandlerThread("RaceConditionEventResumer"); - thread.start(); - return new Handler(thread.getLooper()); + return new Handler(createAndStartNewLooper("RaceConditionEventResumer")); } /** |