From 60e68c91e93395f7ebe51d3d9a3986ff5d381c8e Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 12 Aug 2020 13:59:27 -0700 Subject: [PATCH 1/8] Removing static state management from the install shortcut queue Change-Id: I0b74a843b2081ee314e0840d5ea8ab8ddabde32b --- src/com/android/launcher3/Launcher.java | 24 ++-- src/com/android/launcher3/LauncherModel.java | 4 +- .../launcher3/SessionCommitReceiver.java | 4 +- .../launcher3/dragndrop/AddItemActivity.java | 7 +- .../android/launcher3/model/BgDataModel.java | 3 +- .../ItemInstallQueue.java} | 126 ++++++++++++------ .../launcher3/model/PackageUpdatedTask.java | 4 +- .../launcher3/pm/InstallSessionHelper.java | 6 +- 8 files changed, 113 insertions(+), 65 deletions(-) rename src/com/android/launcher3/{InstallShortcutReceiver.java => model/ItemInstallQueue.java} (81%) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 3f64df3702..2ddaaea4db 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -24,7 +24,6 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_ALL; import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE; import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR; -import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP; import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS; @@ -43,6 +42,9 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKG import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP; import static com.android.launcher3.logging.StatsLogManager.containerTypeToAtomState; +import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED; +import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP; +import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING; import static com.android.launcher3.popup.SystemShortcut.APP_INFO; import static com.android.launcher3.popup.SystemShortcut.INSTALL; import static com.android.launcher3.popup.SystemShortcut.WIDGETS; @@ -120,6 +122,7 @@ import com.android.launcher3.logging.FileLog; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.model.ModelUtils; import com.android.launcher3.model.ModelWriter; import com.android.launcher3.model.data.AppInfo; @@ -912,8 +915,8 @@ public class Launcher extends StatefulActivity implements Launche getUserEventDispatcher().startSession(); // Process any items that were added while Launcher was away. - InstallShortcutReceiver.disableAndFlushInstallQueue( - InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this); + ItemInstallQueue.INSTANCE.get(this) + .resumeModelPush(FLAG_ACTIVITY_PAUSED); // Refresh shortcuts if the permission changed. mModel.validateModelDataOnResume(); @@ -1007,7 +1010,7 @@ public class Launcher extends StatefulActivity implements Launche if (state == SPRING_LOADED) { // Prevent any Un/InstallShortcutReceivers from updating the db while we are // not on homescreen - InstallShortcutReceiver.enableInstallQueue(FLAG_DRAG_AND_DROP); + ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_DRAG_AND_DROP); getRotationHelper().setCurrentStateRequest(REQUEST_LOCK); mWorkspace.showPageIndicatorAtCurrentScroll(); @@ -1032,7 +1035,8 @@ public class Launcher extends StatefulActivity implements Launche if (state == NORMAL) { // Re-enable any Un/InstallShortcutReceiver and now process any queued items - InstallShortcutReceiver.disableAndFlushInstallQueue(FLAG_DRAG_AND_DROP, this); + ItemInstallQueue.INSTANCE.get(this) + .resumeModelPush(FLAG_DRAG_AND_DROP); // Clear any rotation locks when going to normal state getRotationHelper().setCurrentStateRequest(REQUEST_NONE); @@ -1066,7 +1070,7 @@ public class Launcher extends StatefulActivity implements Launche @Override protected void onPause() { // Ensure that items added to Launcher are queued until Launcher returns - InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED); + ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED); super.onPause(); mDragController.cancelDrag(); @@ -2421,8 +2425,8 @@ public class Launcher extends StatefulActivity implements Launche mPendingActivityResult = null; } - InstallShortcutReceiver.disableAndFlushInstallQueue( - InstallShortcutReceiver.FLAG_LOADER_RUNNING, this); + ItemInstallQueue.INSTANCE.get(this) + .resumeModelPush(FLAG_LOADER_RUNNING); // When undoing the removal of the last item on a page, return to that page. // Since we are just resetting the current page without user interaction, @@ -2449,8 +2453,8 @@ public class Launcher extends StatefulActivity implements Launche private ValueAnimator createNewAppBounceAnimation(View v, int i) { ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v) - .setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION); - bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY); + .setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION); + bounceAnim.setStartDelay(i * ItemInstallQueue.NEW_SHORTCUT_STAGGER_DELAY); bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION)); return bounceAnim; } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index e2568d5ef6..f306c78d02 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -43,6 +43,7 @@ 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.ItemInstallQueue; import com.android.launcher3.model.LoaderResults; import com.android.launcher3.model.LoaderTask; import com.android.launcher3.model.ModelDelegate; @@ -330,7 +331,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi */ public boolean startLoader() { // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems - InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING); + ItemInstallQueue.INSTANCE.get(mApp.getContext()) + .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING); synchronized (mLock) { // Don't bother to start the thread if we know it's not going to do anything final Callbacks[] callbacksList = getCallbacks(); diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index e48ffb95ea..007e5f5370 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -25,6 +25,7 @@ import android.content.pm.PackageManager; import android.os.UserHandle; import android.text.TextUtils; +import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.pm.InstallSessionHelper; /** @@ -59,7 +60,8 @@ public class SessionCommitReceiver extends BroadcastReceiver { return; } - InstallShortcutReceiver.queueApplication(info.getAppPackageName(), user, context); + ItemInstallQueue.INSTANCE.get(context) + .queueItem(info.getAppPackageName(), user); } public static boolean isEnabled(Context context) { diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java index 0df67133ab..2d625c5373 100644 --- a/src/com/android/launcher3/dragndrop/AddItemActivity.java +++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java @@ -43,13 +43,13 @@ import android.view.View.OnLongClickListener; import android.view.View.OnTouchListener; import com.android.launcher3.BaseActivity; -import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetHost; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.R; +import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.pm.PinRequestHelper; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; @@ -249,7 +249,7 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener */ public void onPlaceAutomaticallyClick(View v) { if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) { - InstallShortcutReceiver.queueShortcut(mRequest.getShortcutInfo(), this); + ItemInstallQueue.INSTANCE.get(this).queueItem(mRequest.getShortcutInfo()); logCommand(Action.Command.CONFIRM); mRequest.accept(); finish(); @@ -270,7 +270,8 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener } private void acceptWidget(int widgetId) { - InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this); + ItemInstallQueue.INSTANCE.get(this) + .queueItem(mRequest.getAppWidgetProviderInfo(this), widgetId); mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); mRequest.accept(mWidgetOptions); logCommand(Action.Command.CONFIRM); diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 140342f98c..7e45021bf3 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -31,7 +31,6 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; -import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.Workspace; @@ -298,7 +297,7 @@ public class BgDataModel { .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) .map(ShortcutKey::fromItemInfo), // Pending shortcuts - InstallShortcutReceiver.getPendingShortcuts(context) + ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts() .stream().filter(si -> si.user.equals(user))) .collect(groupingBy(ShortcutKey::getPackageName, mapping(ShortcutKey::getId, Collectors.toSet()))); diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/model/ItemInstallQueue.java similarity index 81% rename from src/com/android/launcher3/InstallShortcutReceiver.java rename to src/com/android/launcher3/model/ItemInstallQueue.java index d2b05c5b01..6238db3152 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/model/ItemInstallQueue.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3; +package com.android.launcher3.model; import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; @@ -42,13 +42,19 @@ import android.util.Pair; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.Utilities; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pm.UserCache; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.shortcuts.ShortcutRequest; +import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.Preconditions; import org.json.JSONException; @@ -63,16 +69,15 @@ import java.util.Iterator; import java.util.List; import java.util.Set; -public class InstallShortcutReceiver { +/** + * Class to maintain a queue of pending items to be added to the workspace. + */ +public class ItemInstallQueue { 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; - // Determines whether to defer installing shortcuts immediately until - // processAllPendingInstalls() is called. - private static int sInstallQueueDisabledFlags = 0; - private static final String TAG = "InstallShortcutReceiver"; private static final boolean DBG = false; @@ -82,10 +87,23 @@ public class InstallShortcutReceiver { public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450; public static final int NEW_SHORTCUT_STAGGER_DELAY = 85; + public static MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(ItemInstallQueue::new); + + private final Context mContext; + + // Determines whether to defer installing shortcuts immediately until + // processAllPendingInstalls() is called. + private int mInstallQueueDisabledFlags = 0; + + private ItemInstallQueue(Context context) { + mContext = context; + } + @WorkerThread - private static void addToQueue(Context context, PendingInstallShortcutInfo info) { - String encoded = info.encodeToString(context); - SharedPreferences prefs = Utilities.getPrefs(context); + private void addToQueue(PendingInstallShortcutInfo info) { + String encoded = info.encodeToString(mContext); + SharedPreferences prefs = Utilities.getPrefs(mContext); Set strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1); strings.add(encoded); @@ -93,14 +111,15 @@ public class InstallShortcutReceiver { } @WorkerThread - private static void flushQueueInBackground(Context context) { - if (Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null) { + private void flushQueueInBackground() { + Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity(); + if (launcher == null) { // Launcher not loaded return; } ArrayList> installQueue = new ArrayList<>(); - SharedPreferences prefs = Utilities.getPrefs(context); + SharedPreferences prefs = Utilities.getPrefs(mContext); Set strings = prefs.getStringSet(APPS_PENDING_INSTALL, null); if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings); if (strings == null) { @@ -108,29 +127,31 @@ public class InstallShortcutReceiver { } for (String encoded : strings) { - PendingInstallShortcutInfo info = decode(encoded, context); + PendingInstallShortcutInfo info = decode(encoded, mContext); if (info == null) { continue; } // Generate a shortcut info to add into the model - installQueue.add(info.getItemInfo(context)); + installQueue.add(info.getItemInfo(mContext)); } prefs.edit().remove(APPS_PENDING_INSTALL).apply(); if (!installQueue.isEmpty()) { - LauncherAppState.getInstance(context).getModel() - .addAndBindAddedWorkspaceItems(installQueue); + launcher.getModel().addAndBindAddedWorkspaceItems(installQueue); } } - public static void removeFromInstallQueue(Context context, HashSet packageNames, - UserHandle user) { + /** + * Removes previously added items from the queue. + */ + @WorkerThread + public void removeFromInstallQueue(HashSet packageNames, UserHandle user) { if (packageNames.isEmpty()) { return; } Preconditions.assertWorkerThread(); - SharedPreferences sp = Utilities.getPrefs(context); + SharedPreferences sp = Utilities.getPrefs(mContext); Set strings = sp.getStringSet(APPS_PENDING_INSTALL, null); if (DBG) { Log.d(TAG, "APPS_PENDING_INSTALL: " + strings @@ -144,7 +165,7 @@ public class InstallShortcutReceiver { while (newStringsIter.hasNext()) { String encoded = newStringsIter.next(); try { - Decoder decoder = new Decoder(encoded, context); + Decoder decoder = new Decoder(encoded, mContext); if (packageNames.contains(getIntentPackage(decoder.intent)) && user.equals(decoder.user)) { newStringsIter.remove(); @@ -157,30 +178,42 @@ public class InstallShortcutReceiver { sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply(); } - public static void queueShortcut(ShortcutInfo info, Context context) { - queuePendingShortcutInfo(new PendingInstallShortcutInfo(info), context); + /** + * Adds an item to the install queue + */ + public void queueItem(ShortcutInfo info) { + queuePendingShortcutInfo(new PendingInstallShortcutInfo(info)); } - public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) { - queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId), context); + /** + * Adds an item to the install queue + */ + public void queueItem(AppWidgetProviderInfo info, int widgetId) { + queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId)); } - public static void queueApplication( - String packageName, UserHandle userHandle, Context context) { - queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle), context); + /** + * Adds an item to the install queue + */ + public void queueItem(String packageName, UserHandle userHandle) { + queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle)); } - public static HashSet getPendingShortcuts(Context context) { + /** + * Returns all pending shorts in the queue + */ + @WorkerThread + public HashSet getPendingShortcuts() { HashSet result = new HashSet<>(); - Set strings = Utilities.getPrefs(context).getStringSet(APPS_PENDING_INSTALL, null); + Set strings = Utilities.getPrefs(mContext).getStringSet(APPS_PENDING_INSTALL, null); if (strings == null || ((Collection) strings).isEmpty()) { return result; } for (String encoded : strings) { try { - Decoder decoder = new Decoder(encoded, context); + Decoder decoder = new Decoder(encoded, mContext); if (decoder.optInt(Favorites.ITEM_TYPE, -1) == ITEM_TYPE_DEEP_SHORTCUT) { result.add(ShortcutKey.fromIntent(decoder.intent, decoder.user)); } @@ -191,28 +224,35 @@ public class InstallShortcutReceiver { return result; } - private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) { + private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) { // Queue the item up for adding if launcher has not loaded properly yet - MODEL_EXECUTOR.post(() -> addToQueue(context, info)); - flushInstallQueue(context); + MODEL_EXECUTOR.post(() -> addToQueue(info)); + flushInstallQueue(); } - public static void enableInstallQueue(int flag) { - sInstallQueueDisabledFlags |= flag; - } - public static void disableAndFlushInstallQueue(int flag, Context context) { - sInstallQueueDisabledFlags &= ~flag; - flushInstallQueue(context); + /** + * Pauses the push-to-model flow until unpaused. All items are held in the queue and + * not added to the model. + */ + public void pauseModelPush(int flag) { + mInstallQueueDisabledFlags |= flag; } - static void flushInstallQueue(Context context) { - if (sInstallQueueDisabledFlags != 0) { + /** + * Adds all the queue items to the model if the use is completely resumed. + */ + public void resumeModelPush(int flag) { + mInstallQueueDisabledFlags &= ~flag; + flushInstallQueue(); + } + + private void flushInstallQueue() { + if (mInstallQueueDisabledFlags != 0) { return; } - MODEL_EXECUTOR.post(() -> flushQueueInBackground(context)); + MODEL_EXECUTOR.post(this::flushQueueInBackground); } - private static class PendingInstallShortcutInfo extends ItemInfo { final Intent intent; diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index c0ae6f903c..896bfb666e 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -28,7 +28,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Log; -import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.config.FeatureFlags; @@ -320,7 +319,8 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { deleteAndBindComponentsRemoved(removeMatch); // Remove any queued items from the install queue - InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); + ItemInstallQueue.INSTANCE.get(context) + .removeFromInstallQueue(removedPackages, mUser); } if (mOp == OP_ADD) { diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java index d546013fca..753a6dd85e 100644 --- a/src/com/android/launcher3/pm/InstallSessionHelper.java +++ b/src/com/android/launcher3/pm/InstallSessionHelper.java @@ -32,11 +32,11 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; -import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherSettings; import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.model.ItemInstallQueue; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSet; import com.android.launcher3.util.LooperExecutor; @@ -213,8 +213,8 @@ public class InstallSessionHelper { && !mPromiseIconIds.contains(sessionInfo.getSessionId()) && new PackageManagerHelper(mAppContext).getApplicationInfo( sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) { - InstallShortcutReceiver.queueApplication( - sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), mAppContext); + ItemInstallQueue.INSTANCE.get(mAppContext) + .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo)); mPromiseIconIds.add(sessionInfo.getSessionId()); updatePromiseIconPrefs(); From 66f3ffe39fe4be1b53a5ef70a1a8e25ad3111f82 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Sat, 15 Aug 2020 00:43:22 -0700 Subject: [PATCH 2/8] [Live Tile] Tapping home in Overview does not take user back to app Fixes: 162340084 Test: Manual Change-Id: I8e2caa4ec0d3203d70d35fc409e8053cae51e21e --- .../com/android/quickstep/TouchInteractionService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 5ec6377819..ba0a81fab5 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -659,17 +659,17 @@ public class TouchInteractionService extends Service implements PluginListener Date: Mon, 17 Aug 2020 22:14:28 -0700 Subject: [PATCH 3/8] Update rect in LiveTileOverlay via copying Probably a recent change updates the rect when reaching overview, which wasn't happening before. Regardless, we shouldn't have copied the rect by reference since the referenced copy can be changed. Fixes: 165143463 Test: manual Change-Id: I9315a20226f0a4b3440ce5ee6cc1f4ed1ddca487 --- .../android/quickstep/views/LiveTileOverlay.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java index 30c9f77aa8..c6c2d7e4f0 100644 --- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java +++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java @@ -40,14 +40,13 @@ public class LiveTileOverlay extends Drawable { public static final LiveTileOverlay INSTANCE = new LiveTileOverlay(); private final Paint mPaint = new Paint(); + private final RectF mCurrentRect = new RectF(); private final Rect mBoundsRect = new Rect(); - private RectF mCurrentRect; private float mCornerRadius; private Drawable mIcon; private Animator mIconAnimator; - private boolean mDrawEnabled = true; private float mIconAnimationProgress = 0f; private boolean mIsAttached; @@ -58,7 +57,7 @@ public class LiveTileOverlay extends Drawable { public void update(RectF currentRect, float cornerRadius) { invalidateSelf(); - mCurrentRect = currentRect; + mCurrentRect.set(currentRect); mCornerRadius = cornerRadius; mCurrentRect.roundOut(mBoundsRect); @@ -93,16 +92,9 @@ public class LiveTileOverlay extends Drawable { return mIconAnimationProgress; } - public void setDrawEnabled(boolean drawEnabled) { - if (mDrawEnabled != drawEnabled) { - mDrawEnabled = drawEnabled; - invalidateSelf(); - } - } - @Override public void draw(Canvas canvas) { - if (mCurrentRect != null && mDrawEnabled) { + if (mCurrentRect != null) { canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint); if (mIcon != null && mIconAnimationProgress > 0f) { canvas.save(); From 114de69c9812284f541b0588ea68c71c2b8bb739 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Sat, 15 Aug 2020 09:40:26 -0700 Subject: [PATCH 4/8] Introduce support for play results in launcher Introduces PluginSearchPipeline class, a plugin listener for AllAppsSearchPlugin. Coverts from List results from callback to AdapterItems to be rendered in SearchController. - Moves AdapterItem to AllAppsGridAdapter Bug: 164699827 Test: Manual Change-Id: I20ec147e6b3f4707cf69d62b4b4ac70a90196345 --- res/layout/search_result_play_item.xml | 75 +++++++++ res/layout/search_section_title.xml | 16 +- res/values/strings.xml | 2 + .../allapps/AllAppsContainerView.java | 21 +++ .../launcher3/allapps/AllAppsGridAdapter.java | 125 ++++++++++++-- .../allapps/AllAppsRecyclerView.java | 12 +- .../allapps/AllAppsSectionDecorator.java | 4 +- .../allapps/AlphabeticalAppsList.java | 101 +----------- .../search/AllAppsSearchBarController.java | 95 ++++------- .../search/AppsSearchContainerLayout.java | 3 +- .../allapps/search/AppsSearchPipeline.java | 11 +- .../allapps/search/SearchPipeline.java | 4 +- .../allapps/search/SearchSectionInfo.java | 17 +- .../launcher3/views/HeroSearchResultView.java | 13 +- .../launcher3/views/SearchResultPlayItem.java | 154 ++++++++++++++++++ .../views/SearchSectionHeaderView.java | 55 +++++++ 16 files changed, 493 insertions(+), 215 deletions(-) create mode 100644 res/layout/search_result_play_item.xml create mode 100644 src/com/android/launcher3/views/SearchResultPlayItem.java create mode 100644 src/com/android/launcher3/views/SearchSectionHeaderView.java diff --git a/res/layout/search_result_play_item.xml b/res/layout/search_result_play_item.xml new file mode 100644 index 0000000000..4e82eafc61 --- /dev/null +++ b/res/layout/search_result_play_item.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/search_section_title.xml b/res/layout/search_section_title.xml index c39a641d66..941901591c 100644 --- a/res/layout/search_section_title.xml +++ b/res/layout/search_section_title.xml @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. --> - \ No newline at end of file + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index ef47eefba1..ad3e2b79f4 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -70,6 +70,8 @@ Apps + + Try Now diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index af3722a1f8..99fff4d095 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.allapps; +import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem; +import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION; import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION; @@ -527,6 +529,25 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo return mViewPager == null ? getActiveRecyclerView() : mViewPager; } + /** + * Handles selection on focused view and returns success + */ + public boolean selectFocusedView(View v) { + ItemInfo itemInfo = getHighlightedItemInfo(); + if (itemInfo != null) { + return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo); + } + AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild(); + if (focusedItem instanceof AdapterItemWithPayload) { + Runnable onSelection = ((AdapterItemWithPayload) focusedItem).getSelectionHandler(); + if (onSelection != null) { + onSelection.run(); + return true; + } + } + return false; + } + /** * Returns the ItemInfo of a view that is in focus, ready to be launched by an IME. */ diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 2cec797fd0..c61f01f206 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -40,10 +40,10 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; -import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; +import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler; +import com.android.launcher3.allapps.search.SearchSectionInfo; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.util.PackageManagerHelper; -import com.android.launcher3.views.HeroSearchResultView; import java.util.List; @@ -71,6 +71,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter Play load Type + */ + public static class AdapterItemWithPayload extends AdapterItem { + private T mPayload; + private Runnable mSelectionHandler; + + public AdapterItemWithPayload(T payload, int type) { + mPayload = payload; + viewType = type; + } + + public void setSelectionHandler(Runnable runnable) { + mSelectionHandler = runnable; + } + + public Runnable getSelectionHandler() { + return mSelectionHandler; + } + + public T getPayload() { + return mPayload; + } + } + /** * A subclass of GridLayoutManager that overrides accessibility values during app search. */ @@ -286,6 +390,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter items = mApps.getAdapterItems(); + List items = mApps.getAdapterItems(); // Skip early if there are no items or we haven't been measured if (items.isEmpty() || mNumAppsPerRow == 0) { @@ -352,7 +352,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine @Override public int getCurrentScrollY() { // Return early if there are no items or we haven't been measured - List items = mApps.getAdapterItems(); + List items = mApps.getAdapterItems(); if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) { return -1; } @@ -368,14 +368,14 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine } public int getCurrentScrollY(int position, int offset) { - List items = mApps.getAdapterItems(); - AlphabeticalAppsList.AdapterItem posItem = position < items.size() ? - items.get(position) : null; + List items = mApps.getAdapterItems(); + AllAppsGridAdapter.AdapterItem posItem = position < items.size() + ? items.get(position) : null; int y = mCachedScrollPositions.get(position, -1); if (y < 0) { y = 0; for (int i = 0; i < position; i++) { - AlphabeticalAppsList.AdapterItem item = items.get(i); + AllAppsGridAdapter.AdapterItem item = items.get(i); if (AllAppsGridAdapter.isIconViewType(item.viewType)) { // Break once we reach the desired row if (posItem != null && posItem.viewType == item.viewType && diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java index a168c06d72..6f29e1102c 100644 --- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java +++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java @@ -47,13 +47,13 @@ public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration { // Since views in the same section will follow each other, we can skip to a last view in // a section to get the bounds of the section without having to iterate on every item. int itemCount = parent.getChildCount(); - List adapterItems = mAppsView.getApps().getAdapterItems(); + List adapterItems = mAppsView.getApps().getAdapterItems(); SectionDecorationHandler lastDecorationHandler = null; int i = 0; while (i < itemCount) { View view = parent.getChildAt(i); int position = parent.getChildAdapterPosition(view); - AlphabeticalAppsList.AdapterItem adapterItem = adapterItems.get(position); + AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position); if (adapterItem.searchSectionInfo != null) { SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo; int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1); diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index 7379dbed9a..8c059d5fce 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -19,10 +19,10 @@ package com.android.launcher3.allapps; import android.content.Context; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem; import com.android.launcher3.allapps.search.SearchSectionInfo; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.AppInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LabelComparator; @@ -62,101 +62,6 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { } } - /** - * Info about a particular adapter item (can be either section or app) - */ - public static class AdapterItem { - /** Common properties */ - // The index of this adapter item in the list - public int position; - // The type of this item - public int viewType; - - /** App-only properties */ - // The section name of this app. Note that there can be multiple items with different - // sectionNames in the same section - public String sectionName = null; - // The row that this item shows up on - public int rowIndex; - // The index of this app in the row - public int rowAppIndex; - // The associated AppInfo for the app - public AppInfo appInfo = null; - // The index of this app not including sections - public int appIndex = -1; - // Search section associated to result - public SearchSectionInfo searchSectionInfo = null; - - public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo, - int appIndex) { - AdapterItem item = new AdapterItem(); - item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON; - item.position = pos; - item.sectionName = sectionName; - item.appInfo = appInfo; - item.appIndex = appIndex; - return item; - } - - public static AdapterItem asEmptySearch(int pos) { - AdapterItem item = new AdapterItem(); - item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH; - item.position = pos; - return item; - } - - public static AdapterItem asAllAppsDivider(int pos) { - AdapterItem item = new AdapterItem(); - item.viewType = AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER; - item.position = pos; - return item; - } - - public static AdapterItem asMarketSearch(int pos) { - AdapterItem item = new AdapterItem(); - item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET; - item.position = pos; - return item; - } - - /** - * Factory method for search section title AdapterItem - */ - public static AdapterItem asSearchTitle(SearchSectionInfo sectionInfo, int pos) { - AdapterItem item = new AdapterItem(); - item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE; - item.position = pos; - item.searchSectionInfo = sectionInfo; - return item; - } - - boolean isCountedForAccessibility() { - return viewType == AllAppsGridAdapter.VIEW_TYPE_ICON - || viewType == AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP; - } - } - - /** - * Extension of AdapterItem that contains shortcut workspace items - */ - public static class HeroAppAdapterItem extends AdapterItem { - private ArrayList mShortcutInfos; - - public HeroAppAdapterItem(AppInfo info, ArrayList shortcutInfos) { - viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP; - mShortcutInfos = shortcutInfos; - appInfo = info; - } - - /** - * Returns list of shortcuts for appInfo - */ - public ArrayList getShortcutInfos() { - return mShortcutInfos; - } - - } - private final BaseDraggingActivity mLauncher; @@ -396,7 +301,9 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener { adapterItem.position = i; mAdapterItems.add(adapterItem); if (adapterItem.searchSectionInfo != lastSection) { - adapterItem.searchSectionInfo.setPosStart(i); + if (adapterItem.searchSectionInfo != null) { + adapterItem.searchSectionInfo.setPosStart(i); + } if (lastSection != null) { lastSection.setPosEnd(i - 1); } diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java index 0d8748165a..2e5ed3ec35 100644 --- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java +++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java @@ -15,35 +15,26 @@ */ package com.android.launcher3.allapps.search; -import android.content.Context; -import android.content.pm.PackageManager; import android.os.Bundle; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; -import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.OnFocusChangeListener; import android.view.inputmethod.EditorInfo; import android.widget.TextView; import android.widget.TextView.OnEditorActionListener; -import android.widget.Toast; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; -import com.android.launcher3.allapps.AlphabeticalAppsList; +import com.android.launcher3.allapps.AllAppsGridAdapter; +import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.PackageManagerHelper; import com.android.systemui.plugins.AllAppsSearchPlugin; -import com.android.systemui.plugins.PluginListener; import java.util.ArrayList; import java.util.List; @@ -54,17 +45,14 @@ import java.util.function.Consumer; */ public class AllAppsSearchBarController implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener, - OnFocusChangeListener, PluginListener { + OnFocusChangeListener { - private static final String TAG = "AllAppsSearchBarContoller"; protected BaseDraggingActivity mLauncher; protected Callbacks mCb; protected ExtendedEditText mInput; protected String mQuery; protected SearchAlgorithm mSearchAlgorithm; - private AllAppsSearchPlugin mPlugin; - private Consumer mPlubinCb; public void setVisibility(int visibility) { mInput.setVisibility(visibility); @@ -85,18 +73,13 @@ public class AllAppsSearchBarController mInput.setOnBackKeyListener(this); mInput.setOnFocusChangeListener(this); mSearchAlgorithm = searchAlgorithm; - - PluginManagerWrapper.INSTANCE.get(launcher).addPluginListener(this, - AllAppsSearchPlugin.class); - mPlubinCb = secondaryCb; } @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - if (mPlugin != null) { - if (s.length() == 0) { - mPlugin.startedTyping(); - } + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + if (mSearchAlgorithm instanceof PluginWrapper) { + ((PluginWrapper) mSearchAlgorithm).runOnPluginIfConnected( + AllAppsSearchPlugin::startedTyping); } } @@ -114,9 +97,6 @@ public class AllAppsSearchBarController } else { mSearchAlgorithm.cancel(false); mSearchAlgorithm.doSearch(mQuery, mCb); - if (mPlugin != null) { - mPlugin.performSearch(mQuery, mPlubinCb); - } } } @@ -133,10 +113,8 @@ public class AllAppsSearchBarController public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { if (actionId == EditorInfo.IME_ACTION_SEARCH) { - ItemInfo info = Launcher.getLauncher(mLauncher).getAppsView() - .getHighlightedItemInfo(); - if (info != null) { - return mLauncher.startActivitySafely(v, info.getIntent(), info); + if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) { + return true; } } } @@ -197,43 +175,14 @@ public class AllAppsSearchBarController return mInput.isFocused(); } - @Override - public void onPluginConnected(AllAppsSearchPlugin allAppsSearchPlugin, Context context) { - if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) { - mPlugin = allAppsSearchPlugin; - checkCallPermission(); - } - } - /** - * Check call permissions. + * A wrapper setup for running essential calls to plugin from search controller */ - public void checkCallPermission() { - final String[] permission = {"android.permission.CALL_PHONE", - "android.permission.READ_CONTACTS"}; - boolean request = false; - for (String p : permission) { - int permissionCheck = ContextCompat.checkSelfPermission(mLauncher, p); - if (permissionCheck != PackageManager.PERMISSION_GRANTED) { - request = true; - } - } - - if (!request) return; - boolean rationale = false; - for (String p : permission) { - if (mLauncher.shouldShowRequestPermissionRationale(p)) { - rationale = true; - } - if (rationale) { - Log.e(TAG, p + " Show rationale"); - Toast.makeText(mLauncher, "Requesting Permissions", Toast.LENGTH_SHORT).show(); - } else { - ActivityCompat.requestPermissions(mLauncher, permission, 123); - Log.e(TAG, p + " request permission"); - } - } - + public interface PluginWrapper { + /** + * executes call if plugin is connected + */ + void runOnPluginIfConnected(Consumer plugin); } /** @@ -246,11 +195,23 @@ public class AllAppsSearchBarController * * @param items sorted list of search result adapter items. */ - void onSearchResult(String query, ArrayList items); + void onSearchResult(String query, ArrayList items); /** * Called when the search results should be cleared. */ void clearSearchResult(); } + + /** + * An interface for supporting dynamic search results + * + * @param Type of payload + */ + public interface PayloadResultHandler { + /** + * Updates View using Adapter's payload + */ + void applyAdapterInfo(AdapterItemWithPayload adapterItemWithPayload); + } } \ No newline at end of file diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java index e8a0d7a493..6f183eebc0 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java @@ -42,6 +42,7 @@ import com.android.launcher3.Insettable; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsContainerView; +import com.android.launcher3.allapps.AllAppsGridAdapter; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.AlphabeticalAppsList; import com.android.launcher3.allapps.SearchUiManager; @@ -172,7 +173,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText } @Override - public void onSearchResult(String query, ArrayList items) { + public void onSearchResult(String query, ArrayList items) { if (items != null) { mApps.setSearchResults(items); notifyResultChanged(); diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java index e67e8972e7..fb3d953bac 100644 --- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java +++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java @@ -16,6 +16,7 @@ package com.android.launcher3.allapps.search; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; +import static com.android.launcher3.allapps.AllAppsGridAdapter.VIEW_TYPE_SEARCH_HERO_APP; import android.content.Context; import android.content.pm.ShortcutInfo; @@ -23,9 +24,9 @@ import android.content.pm.ShortcutInfo; import androidx.annotation.WorkerThread; import com.android.launcher3.LauncherAppState; +import com.android.launcher3.allapps.AllAppsGridAdapter; +import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem; import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler; -import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem; -import com.android.launcher3.allapps.AlphabeticalAppsList.HeroAppAdapterItem; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BaseModelUpdateTask; @@ -82,7 +83,7 @@ public class AppsSearchPipeline implements SearchPipeline { /** * Returns MAX_SHORTCUTS_COUNT shortcuts from local cache - * TODO: Shortcuts should be ranked based on relevancy + * TODO: Shortcuts should be ranked based on relevancy */ private ArrayList getShortcutInfos(Context context, AppInfo appInfo) { List shortcuts = new ShortcutRequest(context, appInfo.user) @@ -126,7 +127,9 @@ public class AppsSearchPipeline implements SearchPipeline { //hero app AppInfo appInfo = apps.get(i); ArrayList shortcuts = getShortcutInfos(context, appInfo); - AdapterItem adapterItem = new HeroAppAdapterItem(appInfo, shortcuts); + AdapterItem adapterItem = new AllAppsGridAdapter.AdapterItemWithPayload(shortcuts, + VIEW_TYPE_SEARCH_HERO_APP); + adapterItem.appInfo = appInfo; adapterItem.searchSectionInfo = mSearchSectionInfo; adapterItems.add(adapterItem); } diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java index 321674025a..545f0e3fa2 100644 --- a/src/com/android/launcher3/allapps/search/SearchPipeline.java +++ b/src/com/android/launcher3/allapps/search/SearchPipeline.java @@ -15,7 +15,7 @@ */ package com.android.launcher3.allapps.search; -import com.android.launcher3.allapps.AlphabeticalAppsList; +import com.android.launcher3.allapps.AllAppsGridAdapter; import java.util.ArrayList; import java.util.function.Consumer; @@ -28,5 +28,5 @@ public interface SearchPipeline { /** * Perform query */ - void performSearch(String query, Consumer> cb); + void performSearch(String query, Consumer> cb); } diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java index dee0ffd2df..e026e8449d 100644 --- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java +++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java @@ -15,8 +15,6 @@ */ package com.android.launcher3.allapps.search; -import android.content.Context; - import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler; /** @@ -24,7 +22,7 @@ import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHa */ public class SearchSectionInfo { - private final int mTitleResId; + private String mTitle; private SectionDecorationHandler mDecorationHandler; public int getPosStart() { @@ -47,11 +45,11 @@ public class SearchSectionInfo { private int mPosEnd; public SearchSectionInfo() { - this(-1); + this(null); } - public SearchSectionInfo(int titleResId) { - mTitleResId = titleResId; + public SearchSectionInfo(String title) { + mTitle = title; } public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) { @@ -66,10 +64,7 @@ public class SearchSectionInfo { /** * Returns the section's title */ - public String getTitle(Context context) { - if (mTitleResId == -1) { - return ""; - } - return context.getString(mTitleResId); + public String getTitle() { + return mTitle == null ? "" : mTitle; } } diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java index c2a02bcdab..10f3c41f35 100644 --- a/src/com/android/launcher3/views/HeroSearchResultView.java +++ b/src/com/android/launcher3/views/HeroSearchResultView.java @@ -30,7 +30,8 @@ import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.allapps.AlphabeticalAppsList; +import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload; +import com.android.launcher3.allapps.search.AllAppsSearchBarController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.graphics.DragPreviewProvider; @@ -47,7 +48,8 @@ import java.util.List; /** * A view representing a high confidence app search result that includes shortcuts */ -public class HeroSearchResultView extends LinearLayout implements DragSource { +public class HeroSearchResultView extends LinearLayout implements DragSource, + AllAppsSearchBarController.PayloadResultHandler> { BubbleTextView mBubbleTextView; View mIconView; @@ -96,18 +98,18 @@ public class HeroSearchResultView extends LinearLayout implements DragSource { /** * Apply {@link ItemInfo} for appIcon and shortcut Icons */ - public void prepareUsingAdapterItem(AlphabeticalAppsList.HeroAppAdapterItem adapterItem) { + @Override + public void applyAdapterInfo(AdapterItemWithPayload> adapterItem) { mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo); mIconView.setBackground(mBubbleTextView.getIcon()); mIconView.setTag(adapterItem.appInfo); - List shorcutInfos = adapterItem.getShortcutInfos(); + List shorcutInfos = adapterItem.getPayload(); for (int i = 0; i < mDeepShortcutTextViews.length; i++) { mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE); if (i < shorcutInfos.size()) { mDeepShortcutTextViews[i].applyFromWorkspaceItem(shorcutInfos.get(i)); } } - } @Override @@ -126,7 +128,6 @@ public class HeroSearchResultView extends LinearLayout implements DragSource { mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE); } - /** * Drag and drop handler for popup items in Launcher activity */ diff --git a/src/com/android/launcher3/views/SearchResultPlayItem.java b/src/com/android/launcher3/views/SearchResultPlayItem.java new file mode 100644 index 0000000000..19a4c5d84c --- /dev/null +++ b/src/com/android/launcher3/views/SearchResultPlayItem.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2015 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.views; + +import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.R; +import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload; +import com.android.launcher3.allapps.search.AllAppsSearchBarController; + +import java.io.IOException; +import java.net.URL; + +/** + * A View representing a PlayStore item. + */ +public class SearchResultPlayItem extends LinearLayout implements + AllAppsSearchBarController.PayloadResultHandler { + private final DeviceProfile mDeviceProfile; + private View mIconView; + private TextView mTitleView; + private TextView[] mDetailViews = new TextView[3]; + private Button mPreviewButton; + private String mPackageName; + private boolean mIsInstantGame; + + public SearchResultPlayItem(Context context) { + this(context, null, 0); + } + + public SearchResultPlayItem(Context context, + @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public SearchResultPlayItem(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + mDeviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mIconView = findViewById(R.id.icon); + mTitleView = findViewById(R.id.title_view); + mPreviewButton = findViewById(R.id.try_button); + mPreviewButton.setOnClickListener(view -> launchInstantGame()); + mDetailViews[0] = findViewById(R.id.detail_0); + mDetailViews[1] = findViewById(R.id.detail_1); + mDetailViews[2] = findViewById(R.id.detail_2); + + ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams(); + iconParams.height = mDeviceProfile.allAppsIconSizePx; + iconParams.width = mDeviceProfile.allAppsIconSizePx; + setOnClickListener(view -> handleSelection()); + + } + + @Override + public void applyAdapterInfo(AdapterItemWithPayload adapterItemWithPayload) { + Bundle bundle = adapterItemWithPayload.getPayload(); + adapterItemWithPayload.setSelectionHandler(this::handleSelection); + if (bundle.getString("package", "").equals(mPackageName)) { + return; + } + mIsInstantGame = bundle.getBoolean("instant_game", false); + mPackageName = bundle.getString("package"); + mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE); + mTitleView.setText(bundle.getString("title")); +// TODO: Should use a generic type to get values b/165320033 + showIfNecessary(mDetailViews[0], bundle.getString("price")); + showIfNecessary(mDetailViews[1], bundle.getString("rating")); + showIfNecessary(mDetailViews[2], bundle.getString("category")); + + mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder); + UI_HELPER_EXECUTOR.execute(() -> { + try { +// TODO: Handle caching + URL url = new URL(bundle.getString("icon_url")); + Bitmap bitmap = BitmapFactory.decodeStream(url.openStream()); + BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), + Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx, + mDeviceProfile.allAppsIconSizePx, false)); + mIconView.post(() -> mIconView.setBackground(bitmapDrawable)); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + + private void showIfNecessary(TextView textView, @Nullable String string) { + if (string == null || string.isEmpty()) { + textView.setVisibility(GONE); + } else { + textView.setText(string); + textView.setVisibility(VISIBLE); + } + } + + private void handleSelection() { + if (mPackageName == null) return; + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse( + "https://play.google.com/store/apps/details?id=" + + mPackageName)); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(i); + } + + private void launchInstantGame() { + if (!mIsInstantGame) return; + Intent intent = new Intent(Intent.ACTION_VIEW); + String referrer = "Pixel_Launcher"; + String id = mPackageName; + String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer; + intent.setPackage("com.android.vending"); + intent.setData(Uri.parse(deepLinkUrl)); + intent.putExtra("overlay", true); + intent.putExtra("callerId", getContext().getPackageName()); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(intent); + } +} diff --git a/src/com/android/launcher3/views/SearchSectionHeaderView.java b/src/com/android/launcher3/views/SearchSectionHeaderView.java new file mode 100644 index 0000000000..d439ee3d68 --- /dev/null +++ b/src/com/android/launcher3/views/SearchSectionHeaderView.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 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.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.android.launcher3.allapps.AllAppsGridAdapter; +import com.android.launcher3.allapps.search.AllAppsSearchBarController; + +/** + * Header text view that shows a title for a given section in All apps search + */ +public class SearchSectionHeaderView extends TextView implements + AllAppsSearchBarController.PayloadResultHandler { + public SearchSectionHeaderView(Context context) { + super(context); + } + + public SearchSectionHeaderView(Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) { + super(context, attrs, styleAttr); + } + + @Override + public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload adapterItem) { + String title = adapterItem.getPayload(); + if (title == null || !title.isEmpty()) { + setText(title); + setVisibility(VISIBLE); + } else { + setVisibility(INVISIBLE); + } + } +} From 1fbab3ad2ccb61024611dd7bde282f6a5f19f4e8 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Tue, 18 Aug 2020 15:12:56 -0700 Subject: [PATCH 5/8] Update worksace page indicator during HINT_STATE transition Test: Scroll to workspace page 2, then swipe up from the nav bar; ensure that the page indicator fades in and scrolls to page 1 Change-Id: Ie0157c8e67b94509d3817f7e160d26f505185bbb --- src/com/android/launcher3/Workspace.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 3be9ac7e07..6bfd3495e5 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE; import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED; import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE; +import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.SPRING_LOADED; @@ -943,7 +944,10 @@ public class Workspace extends PagedView super.onScrollChanged(l, t, oldl, oldt); // Update the page indicator progress. - boolean isTransitioning = mIsSwitchingState + // Unlike from other states, we show the page indicator when transitioning from HINT_STATE. + boolean isSwitchingState = mIsSwitchingState + && mLauncher.getStateManager().getCurrentStableState() != HINT_STATE; + boolean isTransitioning = isSwitchingState || (getLayoutTransition() != null && getLayoutTransition().isRunning()); if (!isTransitioning) { showPageIndicatorAtCurrentScroll(); From 96186aff87b9fd37ac12f3d90295a5fec2145121 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Mon, 20 Jul 2020 11:03:39 -0700 Subject: [PATCH 6/8] Minimal Phone Mode (part-3) Introduces a separate database for minimal device mode. When minimal device mode is enabled/disabled: 1. WellbeingModel receives onChange event from ContentObserver 2. WellbeingModel called DWB's ContentProvider for latest state in minimal device mode 3. Based on the state, WellbeingModel calls LauncherProvider to put launcher into normal/minimal mode. 4. When going from normal -> minimal, Launcher switches to a different database, namely minimal.db, then proceed to database initialization. 5. If the database hasn't been initialized yet, Launcher will call ContentResolver#openInputStream with following uri: content://com.google.android.apps.wellbeing.api/launcher_layout to get the default layout xml. 6. The default layout is then saved in database, and the database is considered initialized and doesn't need to go through step 5 again in the future. 7. In case of minimal -> normal, Launcher switches back to its original database (e.g. launcher.db if the grid size is 5x5), then reload launcher. Bug: 161462256 Change-Id: I6bafa66440da23281f63454b698ea56b15960022 --- .../launcher3/model/WellbeingModel.java | 48 ++++++++++++++--- .../android/launcher3/LauncherProvider.java | 53 ++++++++++++++++--- .../android/launcher3/LauncherSettings.java | 10 +++- 3 files changed, 97 insertions(+), 14 deletions(-) diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java index cc7b712842..810f4e3c59 100644 --- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java +++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java @@ -43,9 +43,13 @@ import android.util.ArrayMap; import android.util.Log; import androidx.annotation.MainThread; +import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherProvider; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.data.ItemInfo; @@ -74,6 +78,9 @@ public final class WellbeingModel { private static final int MSG_PACKAGE_REMOVED = 2; private static final int MSG_FULL_REFRESH = 3; + private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0; + private static final int IN_MINIMAL_DEVICE = 2; + // Welbeing contract private static final String PATH_ACTIONS = "actions"; private static final String PATH_MINIMAL_DEVICE = "minimal_device"; @@ -84,6 +91,8 @@ public final class WellbeingModel { private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown"; private static final String EXTRA_PACKAGES = "packages"; private static final String EXTRA_SUCCESS = "success"; + private static final String EXTRA_MINIMAL_DEVICE_STATE = "minimal_device_state"; + private static final String DB_NAME_MINIMAL_DEVICE = "minimal.db"; public static final MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(WellbeingModel::new); @@ -121,11 +130,12 @@ public final class WellbeingModel { updateWellbeingData(); } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) { // Wellbeing reports that minimal device state or config is changed. - updateLauncherModel(); + updateLauncherModel(context); } } }; - FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, this::updateLauncherModel); + FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () -> + updateLauncherModel(context)); if (!TextUtils.isEmpty(mWellbeingProviderPkg)) { context.registerReceiver( @@ -170,7 +180,6 @@ public final class WellbeingModel { Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e); if (mIsInTest) throw new RuntimeException(e); } - updateWellbeingData(); } @@ -208,10 +217,34 @@ public final class WellbeingModel { mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH); } - private void updateLauncherModel() { - if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) return; + private void updateLauncherModel(@NonNull final Context context) { + if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) { + reloadLauncherInNormalMode(context); + return; + } + runWithMinimalDeviceConfigs((bundle) -> { + if (bundle.getInt(EXTRA_MINIMAL_DEVICE_STATE, UNKNOWN_MINIMAL_DEVICE_STATE) + == IN_MINIMAL_DEVICE) { + reloadLauncherInMinimalMode(context); + } else { + reloadLauncherInNormalMode(context); + } + }); + } - // TODO: init Launcher in minimal device / normal mode + private void reloadLauncherInNormalMode(@NonNull final Context context) { + LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.METHOD_SWITCH_DATABASE, + InvariantDeviceProfile.INSTANCE.get(context).dbFile); + } + + private void reloadLauncherInMinimalMode(@NonNull final Context context) { + final Bundle extras = new Bundle(); + extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY, + mWellbeingProviderPkg + ".api"); + LauncherSettings.Settings.call(context.getContentResolver(), + LauncherSettings.Settings.METHOD_SWITCH_DATABASE, + DB_NAME_MINIMAL_DEVICE, extras); } private Uri.Builder apiBuilder() { @@ -225,6 +258,9 @@ public final class WellbeingModel { */ @WorkerThread private void runWithMinimalDeviceConfigs(Consumer consumer) { + if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) { + return; + } if (DEBUG || mIsInTest) { Log.d(TAG, "runWithMinimalDeviceConfigs() called"); } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index e8b5568783..19a314ffe2 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -100,10 +100,12 @@ public class LauncherProvider extends ContentProvider { public static final int SCHEMA_VERSION = 28; public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings"; + public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY"; static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED"; protected DatabaseHelper mOpenHelper; + protected String mProviderAuthority; private long mLastRestoreTimestamp = 0L; @@ -367,7 +369,8 @@ public class LauncherProvider extends ContentProvider { case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : { Bundle result = new Bundle(); result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, - Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false)); + Utilities.getPrefs(getContext()).getBoolean( + mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)); return result; } case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: { @@ -437,6 +440,7 @@ public class LauncherProvider extends ContentProvider { getContext(), true /* forMigration */))); return result; } + return null; } case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: { if (MULTI_DB_GRID_MIRATION_ALGO.get()) { @@ -450,6 +454,23 @@ public class LauncherProvider extends ContentProvider { () -> mOpenHelper)); return result; } + return null; + } + case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: { + if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null; + final DatabaseHelper helper = mOpenHelper; + if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) { + mProviderAuthority = null; + } else { + mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY); + } + mOpenHelper = DatabaseHelper.createDatabaseHelper( + getContext(), arg, false /* forMigration */); + helper.close(); + LauncherAppState app = LauncherAppState.getInstanceNoCreate(); + if (app == null) return null; + app.getModel().forceReload(); + return null; } } return null; @@ -492,7 +513,8 @@ public class LauncherProvider extends ContentProvider { } private void clearFlagEmptyDbCreated() { - Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit(); + Utilities.getPrefs(getContext()).edit() + .remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit(); } /** @@ -505,7 +527,7 @@ public class LauncherProvider extends ContentProvider { synchronized private void loadDefaultFavoritesIfNecessary() { SharedPreferences sp = Utilities.getPrefs(getContext()); - if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) { + if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) { Log.d(TAG, "loading default workspace"); AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost(); @@ -553,8 +575,13 @@ public class LauncherProvider extends ContentProvider { */ private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) { Context ctx = getContext(); - String authority = Settings.Secure.getString(ctx.getContentResolver(), - "launcher3.layout.provider"); + final String authority; + if (!TextUtils.isEmpty(mProviderAuthority)) { + authority = mProviderAuthority; + } else { + authority = Settings.Secure.getString(ctx.getContentResolver(), + "launcher3.layout.provider"); + } if (TextUtils.isEmpty(authority)) { return null; } @@ -693,12 +720,26 @@ public class LauncherProvider extends ContentProvider { } } + /** + * Re-composite given key in respect to database. If the current db is + * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to + * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning + * string will be "EMPTY_DATABASE_CREATED@minimal.db". + */ + String getKey(final String key) { + if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) { + return key; + } + return key + "@" + getDatabaseName(); + } + /** * Overriden in tests. */ protected void onEmptyDbCreated() { // Set the flag for empty DB - Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit(); + Utilities.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true) + .commit(); } public long getSerialNumberForUser(UserHandle user) { diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 5512654390..58a418edec 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -354,14 +354,20 @@ public class LauncherSettings { public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview"; + public static final String METHOD_SWITCH_DATABASE = "switch_database"; + public static final String EXTRA_VALUE = "value"; public static Bundle call(ContentResolver cr, String method) { - return call(cr, method, null); + return call(cr, method, null /* arg */); } public static Bundle call(ContentResolver cr, String method, String arg) { - return cr.call(CONTENT_URI, method, arg, null); + return call(cr, method, arg, null /* extras */); + } + + public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) { + return cr.call(CONTENT_URI, method, arg, extras); } } } From 5af9c3537da7f14ecd91b7976f902a8360d1c48f Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 19 Aug 2020 17:21:53 -0700 Subject: [PATCH 7/8] Allow swipe up to go home from overivew during task dismiss animation Test: go to overview, dismiss a task, swipe up to go home during the transition (easier to try with animation duraation scaled up) Bug: 144170434 Change-Id: I28b846b43260179aeac877351a7069743f66f3ea --- .../touchcontrollers/TaskViewTouchController.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 3586b4f183..df6194d4b2 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -82,7 +82,15 @@ public abstract class TaskViewTouchController mDetector = new SingleAxisSwipeDetector(activity, this, dir); } - private boolean canInterceptTouch() { + private boolean canInterceptTouch(MotionEvent ev) { + if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0) { + // Don't intercept swipes on the nav bar, as user might be trying to go home + // during a task dismiss animation. + if (mCurrentAnimation != null) { + mCurrentAnimation.getAnimationPlayer().end(); + } + return false; + } if (mCurrentAnimation != null) { mCurrentAnimation.forceFinishIfCloseToEnd(); } @@ -118,7 +126,7 @@ public abstract class TaskViewTouchController clearState(); } if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mNoIntercept = !canInterceptTouch(); + mNoIntercept = !canInterceptTouch(ev); if (mNoIntercept) { return false; } From b176dc99b225e8562c04d69b9cea99d1553f0bc3 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 12 Aug 2020 13:28:24 -0700 Subject: [PATCH 8/8] Set new scrimMultiplier instead of scrimProgress for overview -> home Animating this new property allows us to adjust the scrim without competing with other state animations that could be setting the scrim progress. Also reset scrimMultipler = 1 for state transitions. Test: Swipe from home to overview, then during that transition swipe from overview to home; when the former transition ends while the second is still in progress, ensure scrim value doesn't jump Bug: 144170434 Change-Id: I44b57ced1c6902e558fe1818bcbf11ceb4ff2f6f --- .../BaseRecentsViewStateController.java | 4 +++ .../NavBarToHomeTouchController.java | 12 ++++---- .../launcher3/graphics/OverviewScrim.java | 28 +++++++++++++++++++ src/com/android/launcher3/graphics/Scrim.java | 6 +++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java index 1b8e244ac1..aad7e17df0 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java @@ -18,6 +18,7 @@ package com.android.launcher3.uioverrides; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT; import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.launcher3.graphics.OverviewScrim.SCRIM_MULTIPLIER; import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL; @@ -70,6 +71,7 @@ public abstract class BaseRecentsViewStateController getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0); OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim(); SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher)); + SCRIM_MULTIPLIER.set(scrim, 1f); getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness()); } @@ -108,6 +110,8 @@ public abstract class BaseRecentsViewStateController OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim(); setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher), config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR)); + setter.setFloat(scrim, SCRIM_MULTIPLIER, 1f, + config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR)); setter.setFloat( mRecentsView, getTaskModalnessProperty(), diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index 57fd11a174..da3c4858a8 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -66,8 +66,8 @@ public class NavBarToHomeTouchController implements TouchController, SingleAxisSwipeDetector.Listener { private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3; - // How much of the overview scrim we can remove during the transition. - private static final float OVERVIEW_TO_HOME_SCRIM_PROGRESS = 0.5f; + // The min amount of overview scrim we keep during the transition. + private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f; private final Launcher mLauncher; private final SingleAxisSwipeDetector mSwipeDetector; @@ -163,11 +163,11 @@ public class NavBarToHomeTouchController implements TouchController, RecentsView recentsView = mLauncher.getOverviewPanel(); AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher, builder); - float endScrimAlpha = Utilities.mapRange(OVERVIEW_TO_HOME_SCRIM_PROGRESS, - mStartState.getOverviewScrimAlpha(mLauncher), - mEndState.getOverviewScrimAlpha(mLauncher)); + builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(), - OverviewScrim.SCRIM_PROGRESS, endScrimAlpha, PULLBACK_INTERPOLATOR); + OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER, + PULLBACK_INTERPOLATOR); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { builder.addOnFrameCallback(recentsView::redrawLiveTile); } diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java index 94acbfdcca..c0c3e5e9c2 100644 --- a/src/com/android/launcher3/graphics/OverviewScrim.java +++ b/src/com/android/launcher3/graphics/OverviewScrim.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.OVERVIEW; import android.graphics.Rect; +import android.util.FloatProperty; import android.view.View; import android.view.ViewGroup; @@ -33,10 +34,25 @@ import androidx.annotation.Nullable; */ public class OverviewScrim extends Scrim { + public static final FloatProperty SCRIM_MULTIPLIER = + new FloatProperty("scrimMultiplier") { + @Override + public Float get(OverviewScrim scrim) { + return scrim.mScrimMultiplier; + } + + @Override + public void setValue(OverviewScrim scrim, float v) { + scrim.setScrimMultiplier(v); + } + }; + private @NonNull View mStableScrimmedView; // Might be higher up if mStableScrimmedView is invisible. private @Nullable View mCurrentScrimmedView; + private float mScrimMultiplier = 1f; + public OverviewScrim(View view) { super(view); mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel(); @@ -68,4 +84,16 @@ public class OverviewScrim extends Scrim { public @Nullable View getScrimmedView() { return mCurrentScrimmedView; } + + private void setScrimMultiplier(float scrimMultiplier) { + if (Float.compare(mScrimMultiplier, scrimMultiplier) != 0) { + mScrimMultiplier = scrimMultiplier; + invalidate(); + } + } + + @Override + protected int getScrimAlpha() { + return Math.round(super.getScrimAlpha() * mScrimMultiplier); + } } diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java index f90962d5d1..a151cba47d 100644 --- a/src/com/android/launcher3/graphics/Scrim.java +++ b/src/com/android/launcher3/graphics/Scrim.java @@ -61,7 +61,11 @@ public class Scrim implements View.OnAttachStateChangeListener, } public void draw(Canvas canvas) { - canvas.drawColor(setColorAlphaBound(mScrimColor, mScrimAlpha)); + canvas.drawColor(setColorAlphaBound(mScrimColor, getScrimAlpha())); + } + + protected int getScrimAlpha() { + return mScrimAlpha; } private void setScrimProgress(float progress) {