Show discovery tip for hybrid hotseat

Doc: go/hybrid-hotseat-tips

Issue 157683315: for fully populated hotseat, count returns to home screen and show discovery tip if Tip action was not tapped.
Issue 158301717: Don't use cached items if client has predicted items.

Test: Manual
Change-Id: I4747a1148caa62a6262fb6592d5185bdf216ede6
This commit is contained in:
Samuel Fufa 2020-05-28 16:55:26 -07:00
parent c11a808b99
commit 5b2da14e72
9 changed files with 102 additions and 43 deletions

View File

@ -47,12 +47,12 @@ public class HotseatEduController {
public static final String KEY_HOTSEAT_EDU_SEEN = "hotseat_edu_seen";
public static final String HOTSEAT_EDU_ACTION =
"com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
private static final String SETTINGS_ACTION =
public static final String SETTINGS_ACTION =
"android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
private final Launcher mLauncher;
private final Hotseat mHotseat;
private final HotseatRestoreHelper mRestoreHelper;
private HotseatRestoreHelper mRestoreHelper;
private List<WorkspaceItemInfo> mPredictedApps;
private HotseatEduDialog mActiveDialog;
@ -71,14 +71,17 @@ public class HotseatEduController {
* Checks what type of migration should be used and migrates hotseat
*/
void migrate() {
if (mRestoreHelper != null) {
mRestoreHelper.createBackup();
}
if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
migrateToFolder();
} else {
migrateHotseatWhole();
}
Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled, R.string.hotseat_turn_off,
null, () -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_enabled,
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
}
/**
@ -223,15 +226,15 @@ public class HotseatEduController {
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)));
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
} else {
new ArrowTipView(mLauncher).show(
mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
@ -242,12 +245,6 @@ public class HotseatEduController {
mPredictedApps = predictedApps;
}
void destroy() {
if (mActiveDialog != null) {
mActiveDialog.setHotseatEduController(null);
}
}
void showEdu() {
int childCount = mHotseat.getShortcutsAndWidgets().getChildCount();
CellLayout cellLayout = mLauncher.getWorkspace().getScreenWithId(Workspace.FIRST_SCREEN_ID);

View File

@ -29,6 +29,7 @@ import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
@ -245,6 +246,7 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable
|| mHotseatEduController == null) {
return;
}
AbstractFloatingView.closeAllOpenViews(mLauncher);
attachToContainer();
logOnBoardingSeen();
animateOpen();

View File

@ -17,6 +17,7 @@ package com.android.launcher3.hybridhotseat;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.hybridhotseat.HotseatEduController.SETTINGS_ACTION;
import android.animation.Animator;
import android.animation.AnimatorSet;
@ -27,6 +28,7 @@ import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.content.ComponentName;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@ -64,6 +66,8 @@ import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.views.ArrowTipView;
import com.android.launcher3.views.Snackbar;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@ -107,8 +111,6 @@ public class HotseatPredictionController implements DragController.DragListener,
private boolean mIsCacheEmpty;
private boolean mIsDestroyed = false;
private HotseatEduController mHotseatEduController;
private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
@ -146,11 +148,48 @@ public class HotseatPredictionController implements DragController.DragListener,
}
/**
* Transitions to NORMAL workspace mode and shows edu
* Shows appropriate hotseat education based on prediction enabled and migration states.
*/
public void showEdu() {
if (mHotseatEduController == null) return;
mHotseatEduController.showEdu();
if (mComponentKeyMappers.isEmpty()) {
// launcher has empty predictions set
Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(
new Intent(SETTINGS_ACTION)));
} else if (isEduSeen()) {
// user has already went through education
new ArrowTipView(mLauncher).show(
mLauncher.getString(R.string.hotsaet_tip_prediction_enabled),
mHotseat.getTop());
} else {
HotseatEduController eduController = new HotseatEduController(mLauncher, mRestoreHelper,
this::createPredictor);
eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
eduController.showEdu();
}
}
/**
* Shows educational tip for hotseat if user does not go through Tips app.
*/
public void showDiscoveryTip() {
if (getPredictedIcons().size() == mHotSeatItemsCount) {
new ArrowTipView(mLauncher).show(
mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
} else {
Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
R.string.hotseat_prediction_settings, null,
() -> mLauncher.startActivity(new Intent(SETTINGS_ACTION)));
}
}
/**
* Returns if hotseat client has predictions
* @return
*/
public boolean hasPredictions() {
return !mComponentKeyMappers.isEmpty();
}
@Override
@ -250,10 +289,6 @@ public class HotseatPredictionController implements DragController.DragListener,
if (mAppPredictor != null) {
mAppPredictor.destroy();
}
if (mHotseatEduController != null) {
mHotseatEduController.destroy();
mHotseatEduController = null;
}
}
/**
@ -299,10 +334,6 @@ public class HotseatPredictionController implements DragController.DragListener,
mAppPredictor.requestPredictionUpdate();
});
setPauseUIUpdate(false);
if (!isEduSeen()) {
mHotseatEduController = new HotseatEduController(mLauncher, mRestoreHelper,
this::createPredictor);
}
}
/**
@ -350,9 +381,6 @@ public class HotseatPredictionController implements DragController.DragListener,
if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString());
updateDependencies();
fillGapsWithPrediction();
if (!isEduSeen() && mHotseatEduController != null) {
mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
}
cachePredictionComponentKeysIfNecessary(componentKeys);
}

View File

@ -88,7 +88,6 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
*/
public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
private HotseatPredictionController mHotseatPredictionController;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -168,13 +167,6 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
}
}
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
/**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes.
*/
@ -195,7 +187,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher {
@Override
public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) {
super.bindPredictedItems(appInfos, ranks);
if (mHotseatPredictionController != null) {
if (mHotseatPredictionController != null
&& !mHotseatPredictionController.hasPredictions()) {
mHotseatPredictionController.showCachedItems(appInfos, ranks);
}
}

View File

@ -76,8 +76,8 @@
<!-- Button text to dismiss opt in for fully predicted hotseat -->
<string name="hotseat_edu_dismiss">No thanks</string>
<!-- action shown to turn off predictions after onboarding -->
<string name="hotseat_turn_off">Settings</string>
<!-- action shown to toggle predictions after onboarding -->
<string name="hotseat_prediction_settings">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>
@ -86,7 +86,9 @@
<!-- tip shown if user declines migration and has some open spots for prediction -->
<string name="hotseat_tip_gaps_filled">App suggestions added to empty space</string>
<!-- tip shown when user migrates and predictions are enabled in hotseat -->
<string name="hotsaet_tip_prediction_enabled">App suggestions Enabled</string>
<string name="hotsaet_tip_prediction_enabled">App suggestions enabled</string>
<!-- tip shown when hotseat edu is requested while predicions are disabled -->
<string name="hotsaet_tip_prediction_disabled">App suggestions are disabled</string>
<!-- content description for hotseat items -->
<string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>

View File

@ -31,6 +31,7 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.model.WellbeingModel;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.proxy.ProxyActivityStarter;
@ -75,6 +76,7 @@ public abstract class BaseQuickstepLauncher extends Launcher
private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
private OverviewActionsView mActionsView;
protected HotseatPredictionController mHotseatPredictionController;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -305,6 +307,13 @@ public abstract class BaseQuickstepLauncher extends Launcher
return mShelfPeekAnim;
}
/**
* Returns Prediction controller for hybrid hotseat
*/
public HotseatPredictionController getHotseatPredictionController() {
return mHotseatPredictionController;
}
public void setHintUserWillBeActive() {
addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
}

View File

@ -31,6 +31,7 @@ import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.launcher3.util.OnboardingPrefs;
@ -100,6 +101,28 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLaunc
});
}
if (!hasReachedMaxCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
stateManager.addStateListener(new StateListener<LauncherState>() {
boolean mFromAllApps = false;
@Override
public void onStateTransitionStart(LauncherState toState) {
mFromAllApps = mLauncher.getStateManager().getCurrentStableState() == ALL_APPS;
}
@Override
public void onStateTransitionComplete(LauncherState finalState) {
HotseatPredictionController client = mLauncher.getHotseatPredictionController();
if (mFromAllApps && finalState == NORMAL && client.hasPredictions()) {
if (incrementEventCount(HOTSEAT_DISCOVERY_TIP_COUNT)) {
client.showDiscoveryTip();
stateManager.removeStateListener(this);
}
}
}
});
}
if (SysUINavigationMode.getMode(launcher) == NO_BUTTON
&& FeatureFlags.ENABLE_ALL_APPS_EDU.get()) {
stateManager.addStateListener(new StateListener<LauncherState>() {

View File

@ -34,6 +34,8 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:gravity="center"
android:layout_gravity="center_vertical"
android:textColor="@android:color/white"

View File

@ -37,13 +37,14 @@ public class OnboardingPrefs<T extends Launcher> {
public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
public static final String ALL_APPS_COUNT = "launcher.all_apps_count";
public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
/**
* Events that either have happened or have not (booleans).
*/
@StringDef(value = {
HOME_BOUNCE_SEEN,
SHELF_BOUNCE_SEEN,
SHELF_BOUNCE_SEEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventBoolKey {}
@ -55,6 +56,7 @@ public class OnboardingPrefs<T extends Launcher> {
HOME_BOUNCE_COUNT,
SHELF_BOUNCE_COUNT,
ALL_APPS_COUNT,
HOTSEAT_DISCOVERY_TIP_COUNT
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventCountKey {}
@ -65,6 +67,7 @@ public class OnboardingPrefs<T extends Launcher> {
maxCounts.put(HOME_BOUNCE_COUNT, 3);
maxCounts.put(SHELF_BOUNCE_COUNT, 3);
maxCounts.put(ALL_APPS_COUNT, 5);
maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
}