diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java index da588173af..5b01185ca3 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java @@ -26,6 +26,7 @@ import android.view.View; import androidx.core.app.NotificationCompat; +import com.android.launcher3.ArrowTipView; import com.android.launcher3.CellLayout; import com.android.launcher3.FolderInfo; import com.android.launcher3.Hotseat; @@ -42,6 +43,7 @@ import com.android.launcher3.util.ActivityTracker; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.Themes; +import com.android.launcher3.views.Snackbar; import java.util.ArrayDeque; import java.util.ArrayList; @@ -56,7 +58,11 @@ public class HotseatEduController { private static final String NOTIFICATION_CHANNEL_ID = "launcher_onboarding"; private static final int ONBOARDING_NOTIFICATION_ID = 7641; + private static final String SETTINGS_ACTION = + "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS"; + private final Launcher mLauncher; + private final Hotseat mHotseat; private final NotificationManager mNotificationManager; private final Notification mNotification; private List mPredictedApps; @@ -68,6 +74,7 @@ public class HotseatEduController { HotseatEduController(Launcher launcher, Runnable runnable) { mLauncher = launcher; + mHotseat = launcher.getHotseat(); mOnOnboardingComplete = runnable; mNotificationManager = mLauncher.getSystemService(NotificationManager.class); createNotificationChannel(); @@ -98,7 +105,7 @@ public class HotseatEduController { //separate folders and items that can get in folders for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) { - View view = mLauncher.getHotseat().getChildAt(i, 0); + View view = mHotseat.getChildAt(i, 0); if (view == null) continue; ItemInfo info = (ItemInfo) view.getTag(); if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { @@ -178,7 +185,6 @@ public class HotseatEduController { */ private int migrateHotseatWhole() { Workspace workspace = mLauncher.getWorkspace(); - Hotseat hotseatVG = mLauncher.getHotseat(); int pageId = -1; int toRow = 0; @@ -196,7 +202,7 @@ public class HotseatEduController { .getInt(LauncherSettings.Settings.EXTRA_VALUE); } for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) { - View child = hotseatVG.getChildAt(i, 0); + View child = mHotseat.getChildAt(i, 0); if (child == null || child.getTag() == null) continue; ItemInfo tag = (ItemInfo) child.getTag(); mLauncher.getModelWriter().moveItemInDatabase(tag, @@ -211,8 +217,8 @@ public class HotseatEduController { mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID); } - void finishOnboarding() { - mLauncher.getHotseat().removeAllViewsInLayout(); + void moveHotseatItems() { + mHotseat.removeAllViewsInLayout(); if (!mNewItems.isEmpty()) { int lastPage = mNewItems.get(mNewItems.size() - 1).screenId; ArrayList animated = new ArrayList<>(); @@ -227,17 +233,34 @@ public class HotseatEduController { } mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated); } + } + + void finishOnboarding() { mOnOnboardingComplete.run(); destroy(); mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply(); } + void showDimissTip() { + if (mHotseat.getShortcutsAndWidgets().getChildCount() + < mLauncher.getDeviceProfile().inv.numHotseatIcons) { + Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled, R.string.hotseat_turn_off, + null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION))); + } else { + new ArrowTipView(mLauncher).show( + mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop()); + } + } + void setPredictedApps(List predictedApps) { mPredictedApps = predictedApps; if (!mPredictedApps.isEmpty() && mLauncher.getOrientation() == Configuration.ORIENTATION_PORTRAIT) { mNotificationManager.notify(ONBOARDING_NOTIFICATION_ID, mNotification); } + else { + removeNotification(); + } } private void createNotificationChannel() { @@ -275,6 +298,17 @@ public class HotseatEduController { } } + void showEdu() { + // hotseat is already empty and does not require migration. show edu tip + if (mHotseat.getShortcutsAndWidgets().getChildCount() == 0) { + new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_auto_enrolled), + mHotseat.getTop()); + finishOnboarding(); + } else { + showDialog(); + } + } + void showDialog() { if (mPredictedApps == null || mPredictedApps.isEmpty()) { return; @@ -291,7 +325,7 @@ public class HotseatEduController { ActivityTracker.SchedulerCallback { @Override public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) { - activity.getHotseatPredictionController().showEduDialog(); + activity.getHotseatPredictionController().showEdu(); return true; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java index f7db9eaa5b..ffc1e291be 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java @@ -16,7 +16,8 @@ package com.android.launcher3.hybridhotseat; import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType + .HYBRID_HOTSEAT_CANCELED; import android.animation.PropertyValuesHolder; import android.content.Context; @@ -27,9 +28,7 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.TextView; -import android.widget.Toast; -import com.android.launcher3.ArrowTipView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; @@ -108,18 +107,16 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable private void onAccept(View v) { mHotseatEduController.migrate(); handleClose(true); + + mHotseatEduController.moveHotseatItems(); mHotseatEduController.finishOnboarding(); //TODO: pass actual page index here. // Temporarily we're passing 1 for folder migration and 2 for page migration logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2); - int toastStringRes = !FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() - ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt; - Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show(); } private void onDismiss(View v) { - int top = mLauncher.getHotseat().getTop(); - new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top); + mHotseatEduController.showDimissTip(); mHotseatEduController.finishOnboarding(); logUserAction(false, -1); handleClose(true); @@ -165,6 +162,7 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable target.rank = MIGRATION_EXPERIMENT_IDENTIFIER; // encoding migration type on pageIndex target.pageIndex = pageIndex; + target.cardinality = HotseatPredictionController.MAX_ITEMS_FOR_MIGRATION; LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index d82e9f0115..d3bb4f9062 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -16,6 +16,9 @@ package com.android.launcher3.hybridhotseat; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; +import static com.android.launcher3.logging.LoggerUtils.newAction; +import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; +import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent; import android.animation.Animator; import android.animation.AnimatorSet; @@ -28,6 +31,7 @@ import android.app.prediction.AppTargetEvent; import android.app.prediction.AppTargetId; import android.content.ComponentName; import android.os.Bundle; +import android.provider.DeviceConfig; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -48,6 +52,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.allapps.AllAppsStore; @@ -58,9 +63,11 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.icons.IconCache; import com.android.launcher3.logging.FileLog; +import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.touch.ItemLongClickListener; +import com.android.launcher3.uioverrides.DeviceFlag; import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.uioverrides.QuickstepLauncher; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -84,6 +91,9 @@ public class HotseatPredictionController implements DragController.DragListener, private static final String TAG = "PredictiveHotseat"; private static final boolean DEBUG = false; + public static final int MAX_ITEMS_FOR_MIGRATION = DeviceConfig.getInt( + DeviceFlag.NAMESPACE_LAUNCHER, "max_homepage_items_for_migration", 5); + //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) private static final int APPTARGET_ACTION_UNPIN = 4; @@ -112,8 +122,8 @@ public class HotseatPredictionController implements DragController.DragListener, private HotseatEduController mHotseatEduController; - private List mOutlineDrawings = new ArrayList<>(); + private List mOutlineDrawings = new ArrayList<>(); private final View.OnLongClickListener mPredictionLongClickListener = v -> { if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; @@ -146,12 +156,12 @@ public class HotseatPredictionController implements DragController.DragListener, } /** - * Transitions to NORMAL workspace mode and shows edu dialog + * Transitions to NORMAL workspace mode and shows edu */ - public void showEduDialog() { + public void showEdu() { if (mHotseatEduController == null) return; mLauncher.getStateManager().goToState(LauncherState.NORMAL, true, - () -> mHotseatEduController.showDialog()); + () -> mHotseatEduController.showEdu()); } @Override @@ -277,7 +287,7 @@ public class HotseatPredictionController implements DragController.DragListener, mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(), this::setPredictedApps); setPauseUIUpdate(false); - + performBetaCheck(); if (!isReady()) { mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor); } @@ -325,7 +335,7 @@ public class HotseatPredictionController implements DragController.DragListener, mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); } predictionLog.append("]"); - if (false) FileLog.d(TAG, predictionLog.toString()); + if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString()); updateDependencies(); if (isReady()) { fillGapsWithPrediction(); @@ -589,6 +599,39 @@ public class HotseatPredictionController implements DragController.DragListener, } } + private void performBetaCheck() { + if (isReady()) return; + int hotseatItemsCount = mHotseat.getShortcutsAndWidgets().getChildCount(); + + // -1 to exclude smart space + int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId( + Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1; + + // opt user into the feature without onboarding tip or migration if they don't have any + // open spots in their hotseat and have more than maxItems in their hotseat + workspace + + if (hotseatItemsCount == mHotSeatItemsCount && workspaceItemCount + hotseatItemsCount + > MAX_ITEMS_FOR_MIGRATION) { + mLauncher.getSharedPrefs().edit().putBoolean(HotseatEduController.KEY_HOTSEAT_EDU_SEEN, + true).apply(); + + LauncherLogProto.Action action = newAction(LauncherLogProto.Action.Type.TOUCH); + LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.TIP); + action.touch = LauncherLogProto.Action.Touch.TAP; + target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT; + target.controlType = LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED; + + // temporarily encode details in log target (go/hotseat_migration) + target.rank = 2; + target.cardinality = MAX_ITEMS_FOR_MIGRATION; + target.pageIndex = (workspaceItemCount * 1000) + hotseatItemsCount; + LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); + UserEventDispatcher.newInstance(mLauncher).dispatchUserEvent(event, null); + + + } + } + /** * Fill in predicted_rank field based on app prediction. * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 30a4e508d9..9352acef12 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -184,8 +184,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { onStateOrResumeChanged(); } - if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null - && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) { + if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0 + || (changeBits & getActivityFlags() & ACTIVITY_STATE_DEFERRED_RESUMED) != 0)) { mHotseatPredictionController.setPauseUIUpdate(false); } } diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index b55b042dda..31a9bdf47b 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -78,16 +78,21 @@ Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder. - - Your hotseat items have been moved up to your homescreen - Your hotseat items have been moved to a folder - - Drag apps off the bottom row to see app suggestions Get app suggestions No thanks + + Settings + + + Most-used apps appear here, and change based on routines + + Drag apps off the bottom row to get app suggestions + + App suggestions added to empty space. + Try the back gesture