Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 76dd6673 authored by Samuel Fufa's avatar Samuel Fufa Committed by Android (Google) Code Review
Browse files

Merge changes I51e949fa,Ic5202caf,I1111fa15,I11ffa080,I8d2f5f59 into rvc-dev

* changes:
  Beta specific hotseat opt in behavior
  Personalize hotseat education
  Remove Edu notification if predictions are empty
  Resume UI update on user active
  enable prediction logging
parents 644e1a6f 59affa42
Loading
Loading
Loading
Loading
+40 −6
Original line number Diff line number Diff line
@@ -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<WorkspaceItemInfo> 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<ItemInfo> 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<WorkspaceItemInfo> 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<QuickstepLauncher> {
        @Override
        public boolean init(QuickstepLauncher activity, boolean alreadyOnHome) {
            activity.getHotseatPredictionController().showEduDialog();
            activity.getHotseatPredictionController().showEdu();
            return true;
        }
    }
+6 −8
Original line number Diff line number Diff line
@@ -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);
    }
+49 −6
Original line number Diff line number Diff line
@@ -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<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();

    private List<PredictedAppIcon.PredictedIconOutlineDrawing> 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
+2 −2
Original line number Diff line number Diff line
@@ -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);
        }
    }
+10 −5
Original line number Diff line number Diff line
@@ -78,16 +78,21 @@
    <string name="hotseat_edu_message_migrate">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. </string>
    <string name="hotseat_edu_message_migrate_alt">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.</string>

    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
    <string name="hotseat_items_migrated">Your hotseat items have been moved up to your homescreen</string>
    <string name="hotseat_items_migrated_alt">Your hotseat items have been moved to a folder</string>
    <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
    <string name="hotseat_no_migration">Drag apps off the bottom row to see app suggestions</string>
    <!-- Button text to opt in for fully predicted hotseat -->
    <string name="hotseat_edu_accept">Get app suggestions</string>
    <!-- Button text to dismiss opt in for fully predicted hotseat -->
    <string name="hotseat_edu_dismiss">No thanks</string>

    <!-- action shown to turn of predictions after onboarding -->
    <string name="hotseat_turn_off">Settings</string>

    <!-- tip shown if user has no items in hotseat to migrate -->
    <string name="hotseat_auto_enrolled">Most-used apps appear here, and change based on routines</string>
    <!-- tip shown if user declines migration and has some open spots for prediction -->
    <string name="hotseat_tip_no_empty_slots">Drag apps off the bottom row to get app suggestions</string>
    <!-- tip shown if user declines migration and has no open spots for prediction -->
    <string name="hotseat_tip_gaps_filled">App suggestions added to empty space.</string>


    <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
    <string name="back_gesture_tutorial_playground_title_swipe_inward_right_edge" translatable="false">Try the back gesture</string>