diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto index 7e8e51e100..d1185bd5bc 100644 --- a/protos/launcher_atom.proto +++ b/protos/launcher_atom.proto @@ -54,6 +54,7 @@ message ContainerInfo { ShortcutsContainer shortcuts_container = 8; SettingsContainer settings_container = 9; PredictedHotseatContainer predicted_hotseat_container = 10; + TaskSwitcherContainer task_switcher_container = 11; } } @@ -82,6 +83,9 @@ message ShortcutsContainer { message SettingsContainer { } +message TaskSwitcherContainer { +} + enum Attribute { UNKNOWN = 0; DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index b6a8206ef0..01135700db 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -18,6 +18,7 @@ package com.android.launcher3.appprediction; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALL_APPS_RANKED; import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; @@ -37,6 +38,8 @@ import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; +import com.android.launcher3.logger.LauncherAtom; +import com.android.launcher3.logging.InstanceId; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.shortcuts.ShortcutKey; @@ -48,6 +51,7 @@ import com.android.launcher3.util.MainThreadInitializedObject; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.OptionalInt; import java.util.stream.IntStream; /** @@ -301,6 +305,41 @@ public class PredictionUiStateManager implements StateListener, return mCurrentState; } + /** + * Logs ranking info for launched app within all apps prediction. + * Only applicable when {@link ItemInfo#itemType} is one of the followings: + * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, + * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} + */ + public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) { + if (itemInfo.getTargetComponent() == null || itemInfo.user == null + || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT + && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) { + return; + } + + Launcher launcher = Launcher.getLauncher(mAppsView.getContext()); + final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); + final List predictedApps = getCurrentState().apps; + OptionalInt rank = IntStream.range(0, predictedApps.size()) + .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) + .findFirst(); + if (!rank.isPresent()) { + return; + } + + LauncherAtom.ItemInfo.Builder atomBuilder = LauncherAtom.ItemInfo.newBuilder(); + atomBuilder.setRank(rank.getAsInt()); + atomBuilder.setContainerInfo( + LauncherAtom.ContainerInfo.newBuilder().setPredictionContainer( + LauncherAtom.PredictionContainer.newBuilder().build()).build()); + launcher.getStatsLogManager().log(LAUNCHER_ALL_APPS_RANKED, instanceId, + atomBuilder.build()); + } + + /** * Fill in predicted_rank field based on app prediction. * Only applicable when {@link ItemInfo#itemType} is one of the followings: @@ -310,6 +349,7 @@ public class PredictionUiStateManager implements StateListener, */ public static void fillInPredictedRank( @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { + final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate(); if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION 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 f1ce72e708..5d807d33f8 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 @@ -15,6 +15,9 @@ */ package com.android.launcher3.hybridhotseat; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent + .LAUNCHER_HOTSEAT_EDU_ONLY_TIP; + import android.content.Intent; import android.view.View; @@ -47,12 +50,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 mPredictedApps; private HotseatEduDialog mActiveDialog; @@ -71,14 +74,17 @@ public class HotseatEduController { * Checks what type of migration should be used and migrates hotseat */ void migrate() { - mRestoreHelper.createBackup(); + 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 +229,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 +248,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); @@ -265,6 +265,7 @@ public class HotseatEduController { requiresMigration ? R.string.hotseat_tip_no_empty_slots : R.string.hotseat_auto_enrolled), mHotseat.getTop()); + mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_ONLY_TIP); finishOnboarding(); } } 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 700765430f..7e2403b530 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 @@ -15,9 +15,10 @@ */ 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.logging.StatsLogManager.LauncherEvent + .LAUNCHER_HOTSEAT_EDU_ACCEPT; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_DENY; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_EDU_SEEN; import android.animation.PropertyValuesHolder; import android.content.Context; @@ -29,15 +30,14 @@ 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; import com.android.launcher3.Launcher; import com.android.launcher3.R; -import com.android.launcher3.Workspace; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -111,15 +111,13 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable 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); + mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_ACCEPT); } private void onDismiss(View v) { mHotseatEduController.showDimissTip(); mHotseatEduController.finishOnboarding(); - logUserAction(false, -1); + mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_DENY); handleClose(true); } @@ -163,39 +161,6 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable } } - private void logUserAction(boolean migrated, int pageIndex) { - LauncherLogProto.Action action = new LauncherLogProto.Action(); - LauncherLogProto.Target target = new LauncherLogProto.Target(); - - int hotseatItemsCount = mLauncher.getHotseat().getShortcutsAndWidgets().getChildCount(); - // -1 to exclude smart space - int workspaceItemCount = mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets().getChildCount() - 1; - - action.type = LauncherLogProto.Action.Type.TOUCH; - action.touch = LauncherLogProto.Action.Touch.TAP; - target.containerType = LauncherLogProto.ContainerType.TIP; - target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT; - target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED - : HYBRID_HOTSEAT_CANCELED; - target.rank = MIGRATION_EXPERIMENT_IDENTIFIER; - // encoding migration type on pageIndex - target.pageIndex = pageIndex; - target.cardinality = (workspaceItemCount * 1000) + hotseatItemsCount; - LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); - UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null); - } - - private void logOnBoardingSeen() { - LauncherLogProto.Action action = new LauncherLogProto.Action(); - LauncherLogProto.Target target = new LauncherLogProto.Target(); - action.type = LauncherLogProto.Action.Type.TIP; - target.containerType = LauncherLogProto.ContainerType.TIP; - target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT; - LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target); - UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null); - } - private void animateOpen() { if (mIsOpen || mOpenCloseAnimator.isRunning()) { return; @@ -244,8 +209,9 @@ public class HotseatEduDialog extends AbstractSlideInView implements Insettable || mHotseatEduController == null) { return; } + AbstractFloatingView.closeAllOpenViews(mLauncher); attachToContainer(); - logOnBoardingSeen(); + mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_EDU_SEEN); animateOpen(); populatePreview(predictions); } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java new file mode 100644 index 0000000000..c15a5963f6 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java @@ -0,0 +1,129 @@ +/* + * 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.hybridhotseat; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; + +import com.android.launcher3.logging.FileLog; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.MainThreadInitializedObject; + +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.text.DateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * Helper class to allow hot seat file logging + */ +public class HotseatFileLog { + + public static final int LOG_DAYS = 10; + private static final String FILE_NAME_PREFIX = "hotseat-log-"; + private static final DateFormat DATE_FORMAT = + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); + public static final MainThreadInitializedObject INSTANCE = + new MainThreadInitializedObject<>(HotseatFileLog::new); + + + private final Handler mHandler = new Handler( + Executors.createAndStartNewLooper("hotseat-logger")); + private final File mLogsDir; + private PrintWriter mCurrentWriter; + private String mFileName; + + private HotseatFileLog(Context context) { + mLogsDir = context.getFilesDir(); + } + + /** + * Prints log values to disk + */ + public void log(String tag, String msg) { + String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg); + + mHandler.post(() -> { + synchronized (this) { + PrintWriter writer = getWriter(); + if (writer != null) { + writer.println(out); + } + } + }); + } + + private PrintWriter getWriter() { + String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10); + if (fName.equals(mFileName)) return mCurrentWriter; + + Calendar cal = Calendar.getInstance(); + + boolean append = false; + File logFile = new File(mLogsDir, fName); + if (logFile.exists()) { + Calendar modifiedTime = Calendar.getInstance(); + modifiedTime.setTimeInMillis(logFile.lastModified()); + + // If the file was modified more that 36 hours ago, purge the file. + // We use instead of 24 to account for day-365 followed by day-1 + modifiedTime.add(Calendar.HOUR, 36); + append = cal.before(modifiedTime); + } + + + if (mCurrentWriter != null) { + mCurrentWriter.close(); + } + try { + mCurrentWriter = new PrintWriter(new FileWriter(logFile, append)); + mFileName = fName; + } catch (Exception ex) { + Log.e("HotseatLogs", "Error writing logs to file", ex); + closeWriter(); + } + return mCurrentWriter; + } + + + private synchronized void closeWriter() { + mFileName = null; + if (mCurrentWriter != null) { + mCurrentWriter.close(); + } + mCurrentWriter = null; + } + + + /** + * Returns a list of all log files + */ + public synchronized File[] getLogFiles() { + File[] files = new File[LOG_DAYS + FileLog.LOG_DAYS]; + //include file log files here + System.arraycopy(FileLog.getLogFiles(), 0, files, 0, FileLog.LOG_DAYS); + + closeWriter(); + for (int i = 0; i < LOG_DAYS; i++) { + files[FileLog.LOG_DAYS + i] = new File(mLogsDir, FILE_NAME_PREFIX + i); + } + return files; + } +} 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 bd4d7139e9..30a34e46eb 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 @@ -17,6 +17,8 @@ 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 static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED; import android.animation.Animator; import android.animation.AnimatorSet; @@ -27,6 +29,8 @@ 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.os.Process; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -50,7 +54,8 @@ import com.android.launcher3.appprediction.DynamicItemCache; 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.logger.LauncherAtom; +import com.android.launcher3.logging.InstanceId; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; @@ -64,6 +69,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 +114,6 @@ public class HotseatPredictionController implements DragController.DragListener, private boolean mIsCacheEmpty; private boolean mIsDestroyed = false; - private HotseatEduController mHotseatEduController; - private List mOutlineDrawings = new ArrayList<>(); @@ -146,11 +151,47 @@ 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 + */ + public boolean hasPredictions() { + return !mComponentKeyMappers.isEmpty(); } @Override @@ -250,10 +291,6 @@ public class HotseatPredictionController implements DragController.DragListener, if (mAppPredictor != null) { mAppPredictor.destroy(); } - if (mHotseatEduController != null) { - mHotseatEduController.destroy(); - mHotseatEduController = null; - } } /** @@ -299,20 +336,20 @@ public class HotseatPredictionController implements DragController.DragListener, mAppPredictor.requestPredictionUpdate(); }); setPauseUIUpdate(false); - if (!isEduSeen()) { - mHotseatEduController = new HotseatEduController(mLauncher, mRestoreHelper, - this::createPredictor); - } } /** * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items. */ - public void showCachedItems(List apps, IntArray ranks) { + public void showCachedItems(List apps, IntArray ranks) { + if (hasPredictions() && mAppPredictor != null) { + mAppPredictor.requestPredictionUpdate(); + fillGapsWithPrediction(); + return; + } mIsCacheEmpty = apps.isEmpty(); int count = Math.min(ranks.size(), apps.size()); List items = new ArrayList<>(count); - mComponentKeyMappers.clear(); for (int i = 0; i < count; i++) { WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i)); ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user); @@ -324,6 +361,7 @@ public class HotseatPredictionController implements DragController.DragListener, updateDependencies(); bindItems(items, false, null); } + private void setPredictedApps(List appTargets) { mComponentKeyMappers.clear(); if (appTargets.isEmpty()) { @@ -347,12 +385,11 @@ public class HotseatPredictionController implements DragController.DragListener, mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); } predictionLog.append("]"); - if (Utilities.IS_DEBUG_DEVICE) FileLog.d(TAG, predictionLog.toString()); + if (Utilities.IS_DEBUG_DEVICE) { + HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString()); + } updateDependencies(); fillGapsWithPrediction(); - if (!isEduSeen() && mHotseatEduController != null) { - mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers)); - } cachePredictionComponentKeysIfNecessary(componentKeys); } @@ -602,6 +639,48 @@ public class HotseatPredictionController implements DragController.DragListener, mHotseat.fillInLogContainerData(childInfo, child, parents); } + /** + * Logs rank info based on current list of predicted items + */ + public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) { + if (Utilities.IS_DEBUG_DEVICE) { + final String pkg = itemInfo.getTargetComponent() != null + ? itemInfo.getTargetComponent().getPackageName() : "unknown"; + HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent", + "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null + && !Process.myUserHandle().equals(itemInfo.user)) + + ",launchLocation:" + itemInfo.container); + } + + final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); + + final List predictedApps = new ArrayList<>(mComponentKeyMappers); + OptionalInt rank = IntStream.range(0, predictedApps.size()) + .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) + .findFirst(); + if (!rank.isPresent()) { + return; + } + LauncherAtom.PredictedHotseatContainer.Builder containerBuilder = + LauncherAtom.PredictedHotseatContainer.newBuilder(); + LauncherAtom.ItemInfo.Builder atomBuilder = LauncherAtom.ItemInfo.newBuilder(); + int cardinality = 0; + for (PredictedAppIcon icon : getPredictedIcons()) { + ItemInfo info = (ItemInfo) icon.getTag(); + cardinality |= 1 << info.screenId; + } + containerBuilder.setCardinality(cardinality); + atomBuilder.setRank(rank.getAsInt()); + if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { + containerBuilder.setIndex(rank.getAsInt()); + } + atomBuilder.setContainerInfo( + LauncherAtom.ContainerInfo.newBuilder().setPredictedHotseatContainer( + containerBuilder).build()); + mLauncher.getStatsLogManager().log(LAUNCHER_HOTSEAT_RANKED, instanceId, + atomBuilder.build()); + } + private class PinPrediction extends SystemShortcut { private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) { 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 39e0f88c40..138e51d91c 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 @@ -29,7 +29,6 @@ import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import android.content.Intent; import android.content.res.Configuration; -import android.os.Bundle; import android.util.Log; import android.view.View; @@ -42,10 +41,12 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.hybridhotseat.HotseatEduController; import com.android.launcher3.hybridhotseat.HotseatPredictionController; +import com.android.launcher3.logging.InstanceId; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -88,11 +89,10 @@ 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) { - super.onCreate(savedInstanceState); + protected void setupViews() { + super.setupViews(); if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) { mHotseatPredictionController = new HotseatPredictionController(this); mHotseatPredictionController.createPredictor(); @@ -113,6 +113,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + @Override + protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { + super.logAppLaunch(info, instanceId); + if (mHotseatPredictionController != null) { + mHotseatPredictionController.logLaunchedAppRankingInfo(info, instanceId); + } + PredictionUiStateManager.INSTANCE.get(this).logLaunchedAppRankingInfo(info, instanceId); + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -168,13 +177,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. */ diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java index d5b06871b9..fc0dcd5119 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java @@ -20,6 +20,7 @@ import android.graphics.Rect; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.quickstep.views.RecentsView; @@ -56,6 +57,17 @@ public class OverviewModalTaskState extends OverviewState { return 1.0f; } + @Override + public void onBackPressed(Launcher launcher) { + launcher.getStateManager().goToState(LauncherState.OVERVIEW); + RecentsView recentsView = launcher.getOverviewPanel(); + if (recentsView != null) { + recentsView.resetModalVisuals(); + } else { + super.onBackPressed(launcher); + } + } + public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) { Rect out = new Rect(); activity.getOverviewPanel().getTaskSize(out); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index 79dc3e25c3..ba8656db76 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -156,6 +156,7 @@ public class QuickstepAtomicAnimationFactory extends if (toState == NORMAL && fromState == OVERVIEW) { config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL); config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL); + config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL); config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f)); config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL); config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7); @@ -210,6 +211,7 @@ public class QuickstepAtomicAnimationFactory extends } } config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); + config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2); config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2); config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2); Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get() diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index b5fb31a9de..f5c587405a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -32,7 +32,6 @@ import androidx.annotation.CallSuper; import androidx.annotation.UiThread; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.VibratorWrapper; @@ -96,8 +95,8 @@ public abstract class BaseSwipeUpHandler, Q extend * depend on proper class initialization. */ protected void initAfterSubclassConstructor() { - initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext) - .getDeviceProfile(mContext)); + initTransitionEndpoints( + mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile()); } protected void performHapticFeedback() { @@ -144,13 +143,14 @@ public abstract class BaseSwipeUpHandler, Q extend TaskView nextTask = mRecentsView.getTaskView(taskId); if (nextTask != null) { mGestureState.updateLastStartedTaskId(taskId); + boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds() + .contains(taskId); nextTask.launchTask(false /* animate */, true /* freezeTaskList */, success -> { resultCallback.accept(success); if (success) { - if (mRecentsView.indexOfChild(nextTask) - == getLastAppearedTaskIndex()) { - onRestartLastAppearedTask(); + if (hasTaskPreviouslyAppeared) { + onRestartPreviouslyAppearedTask(); } } else { mActivityInterface.onLaunchTaskFailed(); @@ -171,7 +171,7 @@ public abstract class BaseSwipeUpHandler, Q extend * start A again to ensure it stays on top. */ @CallSuper - protected void onRestartLastAppearedTask() { + protected void onRestartPreviouslyAppearedTask() { // Finish the controller here, since we won't get onTaskAppeared() for a task that already // appeared. if (mRecentsAnimationController != null) { @@ -205,7 +205,7 @@ public abstract class BaseSwipeUpHandler, Q extend mRecentsAnimationController = recentsAnimationController; mRecentsAnimationTargets = targets; mTransformParams.setTargetSet(mRecentsAnimationTargets); - DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext); + DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile(); RemoteAnimationTargetCompat runningTaskTarget = targets.findTask( mGestureState.getRunningTaskId()); @@ -300,8 +300,7 @@ public abstract class BaseSwipeUpHandler, Q extend if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2"); } - initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext) - .getDeviceProfile(mContext)); + initTransitionEndpoints(createdActivity.getDeviceProfile()); } return true; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java index 044467809e..e825c5f5b8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java @@ -1122,8 +1122,8 @@ public abstract class BaseSwipeUpHandlerV2, Q exte } @Override - protected void onRestartLastAppearedTask() { - super.onRestartLastAppearedTask(); + protected void onRestartPreviouslyAppearedTask() { + super.onRestartPreviouslyAppearedTask(); reset(); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java index a28dabc6a3..ebc83c6bad 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -37,7 +37,7 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { case TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT: { final float swipeHeight = LayoutUtils.getShelfTrackingDistance(mContext, mDeviceProfile, - PagedOrientationHandler.HOME_ROTATED); + PagedOrientationHandler.PORTRAIT); response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight); return response; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index 852a51a9ce..170102047b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -139,13 +139,12 @@ public final class RecentsActivity extends StatefulActivity { */ protected DeviceProfile createDeviceProfile() { DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this); - DeviceProfile dp1 = InvariantDeviceProfile.INSTANCE.get(this).getDeviceProfile(this); // In case we are reusing IDP, create a copy so that we don't conflict with Launcher // activity. return (mRecentsRootView != null) && isInMultiWindowMode() ? dp.getMultiWindowProfile(this, getMultiWindowDisplaySize()) - : dp1.copy(this); + : dp.copy(this); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java index b17730ba6d..dc8f1c54c8 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -83,15 +83,15 @@ public abstract class SwipeUpAnimationLogic { mGestureState = gestureState; mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface()); mTransformParams = transformParams; + + mTaskViewSimulator.setLayoutRotation( + mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation()); } protected void initTransitionEndpoints(DeviceProfile dp) { mDp = dp; mTaskViewSimulator.setDp(dp); - mTaskViewSimulator.setLayoutRotation( - mDeviceState.getCurrentActiveRotation(), - mDeviceState.getDisplayRotation()); mTransitionDragLength = mGestureState.getActivityInterface().getSwipeUpDestinationAndLength( dp, mContext, TEMP_RECT, mTaskViewSimulator.getOrientationState().getOrientationHandler()); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index 97cd11b4f5..177f9a0b68 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java @@ -16,6 +16,8 @@ package com.android.quickstep; +import static android.view.Surface.ROTATION_0; + import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import android.annotation.SuppressLint; @@ -24,6 +26,7 @@ import android.graphics.Insets; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Build; +import android.view.View; import android.widget.Toast; import androidx.annotation.RequiresApi; @@ -31,9 +34,12 @@ import androidx.annotation.RequiresApi; import com.android.launcher3.BaseActivity; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; +import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.TaskThumbnailView; import com.android.quickstep.views.TaskView; @@ -58,6 +64,19 @@ public class TaskOverlayFactory implements ResourceBasedOverride { shortcuts.add(shortcut); } } + RecentsOrientedState orientedState = taskView.getRecentsView().getPagedViewOrientedState(); + boolean canLauncherRotate = orientedState.canLauncherRotate(); + boolean isInLandscape = orientedState.getTouchRotation() != ROTATION_0; + + // Add overview actions to the menu when in in-place rotate landscape mode. + if (!canLauncherRotate && isInLandscape) { + for (TaskShortcutFactory actionMenuOption : ACTION_MENU_OPTIONS) { + SystemShortcut shortcut = actionMenuOption.getShortcut(activity, taskView); + if (shortcut != null) { + shortcuts.add(shortcut); + } + } + } return shortcuts; } @@ -85,6 +104,11 @@ public class TaskOverlayFactory implements ResourceBasedOverride { TaskShortcutFactory.WELLBEING }; + private static final TaskShortcutFactory[] ACTION_MENU_OPTIONS = new TaskShortcutFactory[]{ + TaskShortcutFactory.SCREENSHOT, + TaskShortcutFactory.MODAL + }; + /** * Overlay on each task handling Overview Action Buttons. */ @@ -94,10 +118,14 @@ public class TaskOverlayFactory implements ResourceBasedOverride { protected final TaskThumbnailView mThumbnailView; private T mActionsView; + private ImageActionsApi mImageApi; + private boolean mIsAllowedByPolicy; protected TaskOverlay(TaskThumbnailView taskThumbnailView) { mApplicationContext = taskThumbnailView.getContext().getApplicationContext(); mThumbnailView = taskThumbnailView; + mImageApi = new ImageActionsApi( + mApplicationContext, mThumbnailView::getThumbnail); } protected T getActionsView() { @@ -112,15 +140,12 @@ public class TaskOverlayFactory implements ResourceBasedOverride { * Called when the current task is interactive for the user */ public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix) { - ImageActionsApi imageApi = new ImageActionsApi( - mApplicationContext, mThumbnailView::getThumbnail); final boolean isAllowedByPolicy = thumbnail.isRealSnapshot; - getActionsView().setCallbacks(new OverlayUICallbacks() { @Override public void onShare() { if (isAllowedByPolicy) { - imageApi.startShareActivity(); + mImageApi.startShareActivity(); } else { showBlockedByPolicyMessage(); } @@ -129,16 +154,23 @@ public class TaskOverlayFactory implements ResourceBasedOverride { @SuppressLint("NewApi") @Override public void onScreenshot() { - if (isAllowedByPolicy) { - imageApi.saveScreenshot(mThumbnailView.getThumbnail(), - getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key); - } else { - showBlockedByPolicyMessage(); - } + saveScreenshot(task); } }); } + /** + * Called to save screenshot of the task thumbnail. + */ + @SuppressLint("NewApi") + private void saveScreenshot(Task task) { + if (mThumbnailView.isRealSnapshot()) { + mImageApi.saveScreenshot(mThumbnailView.getThumbnail(), + getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key); + } else { + showBlockedByPolicyMessage(); + } + } /** * Called when the overlay is no longer used. @@ -146,6 +178,26 @@ public class TaskOverlayFactory implements ResourceBasedOverride { public void reset() { } + /** + * Called when the system wants to reset the modal visuals. + */ + public void resetModalVisuals() { + } + + /** + * Gets the modal state system shortcut. + */ + public SystemShortcut getModalStateSystemShortcut(WorkspaceItemInfo itemInfo) { + return null; + } + + /** + * Gets the system shortcut for the screenshot that will be added to the task menu. + */ + public SystemShortcut getScreenshotShortcut(BaseDraggingActivity activity, + ItemInfo iteminfo) { + return new ScreenshotSystemShortcut(activity, iteminfo); + } /** * Gets the task snapshot as it is displayed on the screen. * @@ -175,6 +227,22 @@ public class TaskOverlayFactory implements ResourceBasedOverride { R.string.blocked_by_policy, Toast.LENGTH_LONG).show(); } + + private class ScreenshotSystemShortcut extends SystemShortcut { + + private final BaseDraggingActivity mActivity; + + ScreenshotSystemShortcut(BaseDraggingActivity activity, ItemInfo itemInfo) { + super(R.drawable.ic_screenshot, R.string.action_screenshot, activity, itemInfo); + mActivity = activity; + } + + @Override + public void onClick(View view) { + saveScreenshot(mThumbnailView.getTaskView().getTask()); + dismissTaskMenuView(mActivity); + } + } } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java index 3623e671f1..4eae437f8d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java @@ -18,29 +18,26 @@ package com.android.quickstep; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP; import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; import android.app.Activity; import android.app.ActivityOptions; -import android.content.ComponentName; -import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; -import android.os.UserHandle; import android.view.View; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; -import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.StatsLogManager.LauncherEvent; import com.android.launcher3.model.WellbeingModel; -import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.popup.SystemShortcut.AppInfo; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -69,28 +66,7 @@ public interface TaskShortcutFactory { SystemShortcut getShortcut(BaseDraggingActivity activity, TaskView view); - static WorkspaceItemInfo dummyInfo(TaskView view) { - Task task = view.getTask(); - - WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo(){ - /** - * Helps to log events as {@link LauncherAtom.Task} - * instead of {@link LauncherAtom.ItemInfo}. - */ - @Override - public LauncherAtom.ItemInfo buildProto() { - return view.buildProto(); - } - }; - dummyInfo.intent = new Intent(); - ComponentName component = task.getTopComponent(); - dummyInfo.getIntent().setComponent(component); - dummyInfo.user = UserHandle.of(task.key.userId); - dummyInfo.title = TaskUtils.getTitle(view.getContext(), task); - return dummyInfo; - } - - TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, dummyInfo(view)); + TaskShortcutFactory APP_INFO = (activity, view) -> new AppInfo(activity, view.getItemInfo()); abstract class MultiWindowFactory implements TaskShortcutFactory { @@ -134,7 +110,7 @@ public interface TaskShortcutFactory { public MultiWindowSystemShortcut(int iconRes, int textRes, BaseDraggingActivity activity, TaskView taskView, MultiWindowFactory factory, LauncherEvent launcherEvent) { - super(iconRes, textRes, activity, dummyInfo(taskView)); + super(iconRes, textRes, activity, taskView.getItemInfo()); mLauncherEvent = launcherEvent; mHandler = new Handler(Looper.getMainLooper()); mTaskView = taskView; @@ -220,7 +196,7 @@ public interface TaskShortcutFactory { WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( future, animStartedListener, mHandler, true /* scaleUp */, taskKey.displayId); - mTarget.getStatsLogManager().log(mLauncherEvent, mTaskView.buildProto()); + mTarget.getStatsLogManager().log(mLauncherEvent, mTaskView.getItemInfo()); } } } @@ -304,7 +280,7 @@ public interface TaskShortcutFactory { private final TaskView mTaskView; public PinSystemShortcut(BaseDraggingActivity target, TaskView tv) { - super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, dummyInfo(tv)); + super(R.drawable.ic_pin, R.string.recent_task_option_pin, target, tv.getItemInfo()); mTaskView = tv; } @@ -321,15 +297,30 @@ public interface TaskShortcutFactory { mTaskView.launchTask(true, resultCallback, Executors.MAIN_EXECUTOR.getHandler()); dismissTaskMenuView(mTarget); mTarget.getStatsLogManager().log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP, - mTaskView.buildProto()); + mTaskView.getItemInfo()); } } TaskShortcutFactory INSTALL = (activity, view) -> InstantAppResolver.newInstance(activity).isInstantApp(activity, view.getTask().getTopComponent().getPackageName()) - ? new SystemShortcut.Install(activity, dummyInfo(view)) : null; + ? new SystemShortcut.Install(activity, view.getItemInfo()) : null; TaskShortcutFactory WELLBEING = (activity, view) -> - WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, dummyInfo(view)); + WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, view.getItemInfo()); + + TaskShortcutFactory SCREENSHOT = (activity, tv) -> { + if (ENABLE_OVERVIEW_ACTIONS.get()) { + return tv.getThumbnail().getTaskOverlay() + .getScreenshotShortcut(activity, tv.getItemInfo()); + } + return null; + }; + + TaskShortcutFactory MODAL = (activity, tv) -> { + if (ENABLE_OVERVIEW_ACTIONS.get() && ENABLE_OVERVIEW_SELECTIONS.get()) { + return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(tv.getItemInfo()); + } + return null; + }; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 4ca4e4c942..37314eaa3d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -539,6 +539,8 @@ public class TouchInteractionService extends Service implements PluginListener mAM.getRunningTask(false /* filterOnlyVisibleRecents */))); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 14215a1f67..a9f138edbb 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -267,10 +267,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (!mPassedSlopOnThisGesture && passedSlop) { mPassedSlopOnThisGesture = true; } - // Until passing slop, we don't know what direction we're going, so assume we might - // be quick switching to avoid translating recents away when continuing the gesture. - boolean isLikelyToStartNewTask = !mPassedSlopOnThisGesture - || horizontalDist > upDist; if (!mPassedPilferInputSlop) { if (passedSlop) { @@ -304,6 +300,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } if (mDeviceState.isFullyGesturalNavMode()) { + // Until passing slop, we don't know what direction we're going, so assume + // we're quick switching to avoid translating recents away when continuing + // the gesture. + boolean haveNotPassedSlopOnContinuedGesture = + !mPassedSlopOnThisGesture && mPassedPilferInputSlop; + boolean isLikelyToStartNewTask = haveNotPassedSlopOnContinuedGesture + || horizontalDist > upDist; mMotionPauseDetector.setDisallowPause(upDist < mMotionPauseMinDisplacement || isLikelyToStartNewTask); mMotionPauseDetector.addPosition(ev); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java index 2805f623ac..9bfe84f298 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java @@ -36,8 +36,6 @@ import com.android.quickstep.util.ActiveGestureLog; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; -import java.util.function.Predicate; - /** * Input consumer for handling touch on the recents/Launcher activity. */ @@ -50,8 +48,6 @@ public class OverviewInputConsumer> private final InputMonitorCompat mInputMonitor; private final int[] mLocationOnScreen = new int[2]; - private final boolean mProxyTouch; - private final Predicate mEventReceiver; private final boolean mStartingInActivityBounds; private boolean mTargetHandledTouch; @@ -64,15 +60,7 @@ public class OverviewInputConsumer> mActivityInterface = gestureState.getActivityInterface(); mTarget = activity.getDragLayer(); - if (startingInActivityBounds) { - mEventReceiver = mTarget::dispatchTouchEvent; - mProxyTouch = true; - } else { - // Only proxy touches to controllers if we are starting touch from nav bar. - mEventReceiver = mTarget::proxyTouchEvent; - mTarget.getLocationOnScreen(mLocationOnScreen); - mProxyTouch = mTarget.prepareProxyEventStarting(); - } + mTarget.getLocationOnScreen(mLocationOnScreen); } @Override @@ -87,10 +75,6 @@ public class OverviewInputConsumer> @Override public void onMotionEvent(MotionEvent ev) { - if (!mProxyTouch) { - return; - } - int flags = ev.getEdgeFlags(); if (!mStartingInActivityBounds) { ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR); @@ -99,7 +83,7 @@ public class OverviewInputConsumer> if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.PAUSE_NOT_DETECTED, "OverviewInputConsumer"); } - boolean handled = mEventReceiver.test(ev); + boolean handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds); ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]); ev.setEdgeFlags(flags); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index 98784efdc1..2066d52c36 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -30,6 +30,7 @@ import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; import static com.android.launcher3.anim.Interpolators.ACCEL; +import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; import static com.android.launcher3.anim.Interpolators.LINEAR; @@ -607,6 +608,17 @@ public abstract class RecentsView extends PagedView impl } } + /** + * Whether the Clear All button is hidden or fully visible. Used to determine if center + * displayed page is a task or the Clear All button. + * + * @return True = Clear All button not fully visible, center page is a task. False = Clear All + * button fully visible, center page is Clear All button. + */ + public boolean isClearAllHidden() { + return mClearAllButton.getAlpha() != 1f; + } + @Override protected void onPageBeginTransition() { super.onPageBeginTransition(); @@ -616,7 +628,7 @@ public abstract class RecentsView extends PagedView impl @Override protected void onPageEndTransition() { super.onPageEndTransition(); - if (getScrollX() == getScrollForPage(getPageNearestToCenterOfScreen())) { + if (isClearAllHidden()) { LayoutUtils.setViewEnabled(mActionsView, true); } if (getNextPage() > 0) { @@ -1336,7 +1348,7 @@ public abstract class RecentsView extends PagedView impl mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( endState.logAction, Direction.UP, index, compKey); mActivity.getStatsLogManager().log( - LAUNCHER_TASK_DISMISS_SWIPE_UP, taskView.buildProto()); + LAUNCHER_TASK_DISMISS_SWIPE_UP, taskView.getItemInfo()); } } @@ -1720,7 +1732,7 @@ public abstract class RecentsView extends PagedView impl private void updatePageOffsets() { float offset = mAdjacentPageOffset * getWidth(); - float modalOffset = mTaskModalness * getWidth(); + float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth(); if (mIsRtl) { offset = -offset; modalOffset = -modalOffset; @@ -1748,6 +1760,16 @@ public abstract class RecentsView extends PagedView impl return Math.max(getWidth(), 1); } + /** + * Resets the visuals when exit modal state. + */ + public void resetModalVisuals() { + TaskView taskView = getCurrentPageTaskView(); + if (taskView != null) { + taskView.getThumbnail().getTaskOverlay().resetModalVisuals(); + } + } + private void updateDeadZoneRects() { // Get the deadzone rect surrounding the clear all button to not dismiss overview to home mClearAllButtonDeadZoneRect.setEmpty(); @@ -1919,7 +1941,7 @@ public abstract class RecentsView extends PagedView impl endState.logAction, Direction.DOWN, indexOfChild(tv), TaskUtils.getLaunchComponentKeyForTask(task.key)); mActivity.getStatsLogManager().log( - LAUNCHER_TASK_LAUNCH_SWIPE_DOWN, tv.buildProto()); + LAUNCHER_TASK_LAUNCH_SWIPE_DOWN, tv.getItemInfo()); } } else { onTaskLaunched(false); @@ -2089,6 +2111,12 @@ public abstract class RecentsView extends PagedView impl return mClearAllButton; } + @Override + protected boolean onOverscroll(int amount) { + // overscroll should only be accepted on -1 direction (for clear all button) + if ((amount > 0 && !mIsRtl) || (amount < 0 && mIsRtl)) return false; + return super.onOverscroll(amount); + } /** * @return How many pixels the running task is offset on the currently laid out dominant axis. @@ -2182,6 +2210,11 @@ public abstract class RecentsView extends PagedView impl if (getCurrentPageTaskView() != null) { getCurrentPageTaskView().setModalness(modalness); } + // Only show actions view when it's modal for in-place landscape mode. + boolean inPlaceLandscape = !mOrientationState.canLauncherRotate() + && mOrientationState.getTouchRotation() != ROTATION_0; + mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape); + LayoutUtils.setViewEnabled(mActionsView, true); } @Nullable diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java index a371dd21ff..26fb563b82 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java @@ -447,6 +447,16 @@ public class TaskThumbnailView extends View implements PluginListener No thanks - - Settings + + Settings Most-used apps appear here, and change based on routines @@ -86,7 +86,9 @@ App suggestions added to empty space - App suggestions Enabled + App suggestions enabled + + App suggestions are disabled Predicted app: %1$s diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 48743070d8..d2e0339325 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -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); } diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java index ebe9e26c02..2b08dcd6ed 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -32,6 +32,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.states.StateAnimationConfig; +import com.android.systemui.shared.system.BlurUtils; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SurfaceControlCompat; import com.android.systemui.shared.system.TransactionCompat; @@ -110,6 +111,10 @@ public class DepthController implements StateHandler { } private void ensureDependencies() { + if (mWallpaperManager == null) { + mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); + mWallpaperManager = new WallpaperManagerCompat(mLauncher); + } if (mLauncher.getRootView() != null && mOnAttachListener == null) { mOnAttachListener = new View.OnAttachStateChangeListener() { @Override @@ -127,11 +132,6 @@ public class DepthController implements StateHandler { }; mLauncher.getRootView().addOnAttachStateChangeListener(mOnAttachListener); } - if (mWallpaperManager != null) { - return; - } - mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius); - mWallpaperManager = new WallpaperManagerCompat(mLauncher); } /** @@ -205,7 +205,8 @@ public class DepthController implements StateHandler { return; } - if (mSurface == null || !mSurface.isValid()) { + boolean supportsBlur = BlurUtils.supportsBlursOnWindows(); + if (supportsBlur && (mSurface == null || !mSurface.isValid())) { return; } mDepth = depthF; @@ -214,17 +215,20 @@ public class DepthController implements StateHandler { if (windowToken != null) { mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth); } - final int blur; - if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) { - // All apps has a solid background. We don't need to draw blurs after it's fully - // visible. This will take us out of GPU composition, saving battery and increasing - // performance. - blur = 0; - } else { - blur = (int) (mDepth * mMaxBlurRadius); + + if (supportsBlur) { + final int blur; + if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) { + // All apps has a solid background. We don't need to draw blurs after it's fully + // visible. This will take us out of GPU composition, saving battery and increasing + // performance. + blur = 0; + } else { + blur = (int) (mDepth * mMaxBlurRadius); + } + new TransactionCompat() + .setBackgroundBlurRadius(mSurface, blur) + .apply(); } - new TransactionCompat() - .setBackgroundBlurRadius(mSurface, blur) - .apply(); } } diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index 188072a042..00b5eb9880 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -30,6 +30,8 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; /** * Manages the state for an active system gesture, listens for events from the system and Launcher, @@ -128,6 +130,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL private ActivityManager.RunningTaskInfo mRunningTask; private GestureEndTarget mEndTarget; private RemoteAnimationTargetCompat mLastAppearedTaskTarget; + private Set mPreviouslyAppearedTaskIds = new HashSet<>(); private int mLastStartedTaskId = -1; public GestureState(OverviewComponentObserver componentObserver, int gestureId) { @@ -147,6 +150,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL mRunningTask = other.mRunningTask; mEndTarget = other.mEndTarget; mLastAppearedTaskTarget = other.mLastAppearedTaskTarget; + mPreviouslyAppearedTaskIds = other.mPreviouslyAppearedTaskIds; mLastStartedTaskId = other.mLastStartedTaskId; } @@ -234,6 +238,9 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL */ public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) { mLastAppearedTaskTarget = lastAppearedTaskTarget; + if (lastAppearedTaskTarget != null) { + mPreviouslyAppearedTaskIds.add(lastAppearedTaskTarget.taskId); + } } /** @@ -243,6 +250,14 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL return mLastAppearedTaskTarget != null ? mLastAppearedTaskTarget.taskId : -1; } + public void updatePreviouslyAppearedTaskIds(Set previouslyAppearedTaskIds) { + mPreviouslyAppearedTaskIds = previouslyAppearedTaskIds; + } + + public Set getPreviouslyAppearedTaskIds() { + return mPreviouslyAppearedTaskIds; + } + /** * Updates the last task that we started via startActivityFromRecents() during this gesture. */ diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index be8eb48f25..e820b3f6c5 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -64,6 +64,9 @@ public class StatsLogCompatManager extends StatsLogManager { private static Context sContext; private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0); + // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates + // from nano to lite, bake constant to prevent robo test failure. + private static final int DEFAULT_PAGE_INDEX = -2; private static final int FOLDER_HIERARCHY_OFFSET = 100; public StatsLogCompatManager(Context context) { @@ -75,7 +78,7 @@ public class StatsLogCompatManager extends StatsLogManager { */ @Override public void log(EventEnum event) { - log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance()); + log(event, DEFAULT_INSTANCE_ID, (ItemInfo) null); } /** @@ -83,26 +86,62 @@ public class StatsLogCompatManager extends StatsLogManager { */ @Override public void log(EventEnum event, InstanceId instanceId) { - log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance()); + log(event, instanceId, (ItemInfo) null); } /** * Logs an event and accompanying {@link ItemInfo}. */ @Override - public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo info) { + public void log(EventEnum event, @Nullable ItemInfo info) { log(event, DEFAULT_INSTANCE_ID, info); } + /** + * Logs an event. + * + * @param event an enum implementing EventEnum interface. + * @param atomInfo item typically containing app or task launch related information. + */ + public void log(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo) { + LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask( + new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, + AllAppsList apps) { + write(event, instanceId, atomInfo, null, + LAUNCHER_UICHANGED__DST_STATE__HOME, + LAUNCHER_UICHANGED__DST_STATE__BACKGROUND); + } + }); + } + + /** + * Logs an event. + * + * @param event an enum implementing EventEnum interface. + * @param atomItemInfo item typically containing app or task launch related information. + */ + @Override + public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo atomItemInfo, int srcState, + int dstState) { + write(event, DEFAULT_INSTANCE_ID, + atomItemInfo == null ? LauncherAtom.ItemInfo.getDefaultInstance() : atomItemInfo, + null, + srcState, + dstState); + } + /** * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. */ @Override public void log(EventEnum event, InstanceId instanceId, - @Nullable LauncherAtom.ItemInfo info) { + @Nullable ItemInfo info) { logInternal(event, instanceId, info, LAUNCHER_UICHANGED__DST_STATE__HOME, - LAUNCHER_UICHANGED__DST_STATE__BACKGROUND); + LAUNCHER_UICHANGED__DST_STATE__BACKGROUND, + DEFAULT_PAGE_INDEX); } /** @@ -124,38 +163,67 @@ public class StatsLogCompatManager extends StatsLogManager { */ @Override public void log(EventEnum event, int srcState, int dstState, int pageIndex) { - LauncherAtom.ItemInfo info = LauncherAtom.ItemInfo.getDefaultInstance(); - if (srcState == LAUNCHER_UICHANGED__DST_STATE__HOME - || dstState == LAUNCHER_UICHANGED__SRC_STATE__HOME) { - info = LauncherAtom.ItemInfo.newBuilder().setContainerInfo( - LauncherAtom.ContainerInfo.newBuilder().setWorkspace( - LauncherAtom.WorkspaceContainer.newBuilder().setPageIndex(pageIndex) - )).build(); - } - logInternal(event, DEFAULT_INSTANCE_ID, info, srcState, dstState); + logInternal(event, DEFAULT_INSTANCE_ID, null, srcState, dstState, pageIndex); } /** - * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. + * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}. */ private void logInternal(EventEnum event, InstanceId instanceId, - @Nullable LauncherAtom.ItemInfo info, int srcState, int dstState) { - info = info == null ? LauncherAtom.ItemInfo.getDefaultInstance() : info; + @Nullable ItemInfo info, int srcState, int dstState, int pageIndex) { - if (IS_VERBOSE) { - String name = (event instanceof LauncherEvent) ? ((LauncherEvent) event).name() : - event.getId() + ""; + LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask( + new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, + AllAppsList apps) { + writeEvent(event, instanceId, info, srcState, dstState, pageIndex, + dataModel.folders); + } + }); + } - Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID - ? String.format("\n%s (State:%s->%s) \n%s", name, getStateString(srcState), - getStateString(dstState), info) - : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s", name, instanceId, - getStateString(srcState), getStateString(dstState), info)); - } + private static void writeEvent(EventEnum event, InstanceId instanceId, + @Nullable ItemInfo info, int srcState, int dstState, int pageIndex, + IntSparseArrayMap folders) { if (!Utilities.ATLEAST_R) { return; } + LauncherAtom.ItemInfo atomInfo = LauncherAtom.ItemInfo.getDefaultInstance(); + if (info != null) { + if (info.container >= 0) { + atomInfo = info.buildProto(folders.get(info.container)); + } else { + atomInfo = info.buildProto(); + } + } else { + if (srcState == LAUNCHER_UICHANGED__DST_STATE__HOME + || dstState == LAUNCHER_UICHANGED__SRC_STATE__HOME) { + atomInfo = LauncherAtom.ItemInfo.newBuilder().setContainerInfo( + LauncherAtom.ContainerInfo.newBuilder().setWorkspace( + LauncherAtom.WorkspaceContainer.newBuilder().setPageIndex(pageIndex) + )).build(); + } + } + write(event, instanceId, atomInfo, info, srcState, dstState); + } + + private static void write(EventEnum event, InstanceId instanceId, + LauncherAtom.ItemInfo atomInfo, + @Nullable ItemInfo info, + int srcState, int dstState) { + if (IS_VERBOSE) { + String name = (event instanceof Enum) ? ((Enum) event).name() : + event.getId() + ""; + + Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID + ? String.format("\n%s (State:%s->%s) \n%s\n%s", name, getStateString(srcState), + getStateString(dstState), info, atomInfo) + : String.format("\n%s (State:%s->%s) (InstanceId:%s)\n%s\n%s", name, + getStateString(srcState), getStateString(dstState), instanceId, info, + atomInfo)); + } SysUiStatsLog.write( SysUiStatsLog.LAUNCHER_EVENT, @@ -165,24 +233,24 @@ public class StatsLogCompatManager extends StatsLogManager { null /* launcher extensions, deprecated */, false /* quickstep_enabled, deprecated */, event.getId() /* event_id */, - info.getItemCase().getNumber() /* target_id */, + atomInfo.getItemCase().getNumber() /* target_id */, instanceId.getId() /* instance_id TODO */, 0 /* uid TODO */, - getPackageName(info) /* package_name */, - getComponentName(info) /* component_name */, - getGridX(info, false) /* grid_x */, - getGridY(info, false) /* grid_y */, - getPageId(info, false) /* page_id */, - getGridX(info, true) /* grid_x_parent */, - getGridY(info, true) /* grid_y_parent */, - getPageId(info, true) /* page_id_parent */, - getHierarchy(info) /* hierarchy */, - info.getIsWork() /* is_work_profile */, - info.getRank() /* rank */, - info.getFolderIcon().getFromLabelState().getNumber() /* fromState */, - info.getFolderIcon().getToLabelState().getNumber() /* toState */, - info.getFolderIcon().getLabelInfo() /* edittext */, - getCardinality(info) /* cardinality */); + getPackageName(atomInfo) /* package_name */, + getComponentName(atomInfo) /* component_name */, + getGridX(atomInfo, false) /* grid_x */, + getGridY(atomInfo, false) /* grid_y */, + getPageId(atomInfo, false) /* page_id */, + getGridX(atomInfo, true) /* grid_x_parent */, + getGridY(atomInfo, true) /* grid_y_parent */, + getPageId(atomInfo, true) /* page_id_parent */, + getHierarchy(atomInfo) /* hierarchy */, + atomInfo.getIsWork() /* is_work_profile */, + atomInfo.getRank() /* rank */, + atomInfo.getFolderIcon().getFromLabelState().getNumber() /* fromState */, + atomInfo.getFolderIcon().getToLabelState().getNumber() /* toState */, + atomInfo.getFolderIcon().getLabelInfo() /* edittext */, + getCardinality(atomInfo) /* cardinality */); } /** @@ -333,7 +401,7 @@ public class StatsLogCompatManager extends StatsLogManager { } private static String getStateString(int state) { - switch(state) { + switch (state) { case LAUNCHER_UICHANGED__DST_STATE__BACKGROUND: return "BACKGROUND"; case LAUNCHER_UICHANGED__DST_STATE__HOME: diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java index 7e8222c228..1abe903d3d 100644 --- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java +++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java @@ -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() { + 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() { diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index fe5a6c90b1..8bd2281c46 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -26,8 +26,8 @@ import static android.view.Surface.ROTATION_90; import static com.android.launcher3.logging.LoggerUtils.extractObjectNameAndAddress; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; - import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; + import static java.lang.annotation.RetentionPolicy.SOURCE; import android.content.ContentResolver; @@ -49,6 +49,7 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; @@ -197,7 +198,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mPreviousRotation = touchRotation; if (mLauncherRotation == mTouchRotation || canLauncherRotate()) { - mOrientationHandler = PagedOrientationHandler.HOME_ROTATED; + mOrientationHandler = PagedOrientationHandler.PORTRAIT; if (DEBUG) { Log.d(TAG, "current RecentsOrientedState: " + this); } @@ -526,4 +527,15 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre + " mFlags=" + mFlags + "]"; } + + /** + * Returns the device profile based on expected launcher rotation + */ + public DeviceProfile getLauncherDeviceProfile() { + InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext); + // TODO also check the natural orientation is landscape or portrait + return (mLauncherRotation == ROTATION_90 || mLauncherRotation == ROTATION_270) + ? idp.landscapeProfile + : idp.portraitProfile; + } } diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index f5498c9961..bec3050dad 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -23,6 +23,7 @@ import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; +import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW; import android.content.Context; import android.graphics.Canvas; @@ -187,6 +188,7 @@ public class ShelfScrimView extends ScrimView mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset; } updateColors(); + updateSysUiColors(); updateDragHandleAlpha(); invalidate(); } @@ -240,6 +242,18 @@ public class ShelfScrimView extends ScrimView } } + @Override + protected void updateSysUiColors() { + // Use a light system UI (dark icons) if all apps is behind at least half of the + // status bar. + boolean forceChange = mShelfTop <= mLauncher.getDeviceProfile().getInsets().top / 2f; + if (forceChange) { + mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark); + } else { + mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0); + } + } + @Override protected boolean shouldDragHandleBeVisible() { boolean needsAllAppsEdu = mIsTwoZoneSwipeModel diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index a726052e27..a343e7c31c 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -37,7 +37,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import android.app.Instrumentation; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -58,10 +57,8 @@ import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.FailureRewriterRule; import com.android.launcher3.util.rule.FailureWatcher; -import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import com.android.quickstep.views.RecentsView; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -95,7 +92,8 @@ public class FallbackRecentsTest { mDevice = UiDevice.getInstance(instrumentation); mDevice.setOrientationNatural(); mLauncher = new LauncherInstrumentation(); - mLauncher.enableCheckEventsForSuccessfulGestures(); + // b/143488140 + //mLauncher.enableCheckEventsForSuccessfulGestures(); if (TestHelpers.isInLauncherProcess()) { Utilities.enableRunningInTestHarnessForTests(); @@ -132,9 +130,9 @@ public class FallbackRecentsTest { } } - @NavigationModeSwitch + // b/143488140 + //@NavigationModeSwitch @Test - @Ignore // b/143488140 public void goToOverviewFromHome() { mDevice.pressHome(); assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg( @@ -143,9 +141,9 @@ public class FallbackRecentsTest { mLauncher.getBackground().switchToOverview(); } - @NavigationModeSwitch + // b/143488140 + //@NavigationModeSwitch @Test - @Ignore // b/143488140 public void goToOverviewFromApp() { startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); @@ -178,9 +176,9 @@ public class FallbackRecentsTest { return mLauncher.getBackground().switchToOverview(); } - @NavigationModeSwitch + // b/143488140 + //@NavigationModeSwitch @Test - @Ignore // b/143488140 public void testOverview() { startAppFast(getAppPackageName()); startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java index ca2ae3a961..9732cdc64c 100644 --- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java +++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java @@ -45,6 +45,8 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { @Before public void setUp() throws Exception { super.setUp(); + // b/143488140 + mLauncher.pressHome(); // Start an activity where the gestures start. startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); } @@ -98,7 +100,6 @@ public class StartLauncherViaGestureTests extends AbstractQuickStepTest { // The test action. mLauncher.getBackground().switchToOverview(); } - // Workaround for b/157099707 mLauncher.pressHome(); } } diff --git a/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml index 087e45a4dd..0ec9981220 100644 --- a/res/layout/arrow_toast.xml +++ b/res/layout/arrow_toast.xml @@ -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" @@ -58,6 +60,5 @@ android:elevation="2dp" android:layout_width="10dp" android:layout_height="8dp" - android:layout_marginTop="-2dp" - android:layout_gravity="center_horizontal"/> + android:layout_marginTop="-2dp"/> diff --git a/res/values/strings.xml b/res/values/strings.xml index 2efa66f2c1..935bb40fb3 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -172,8 +172,10 @@ Folder closed Folder renamed to %1$s - - Folder: %1$s + + Folder: %1$s, %2$d items + + Folder: %1$s, %2$d or more items diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java index 7ca416d520..8f3a83ed07 100644 --- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java @@ -89,6 +89,22 @@ public class DefaultLayoutProviderTest { assertEquals(3, ((FolderInfo) info).contents.size()); } + @Test + public void testCustomProfileLoaded_with_folder_custom_title() throws Exception { + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder("CustomFolder") + .addApp(TEST_PACKAGE, TEST_PACKAGE) + .addApp(TEST_PACKAGE, TEST_PACKAGE) + .addApp(TEST_PACKAGE, TEST_PACKAGE) + .build()); + + // Verify folder + assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size()); + ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType); + assertEquals(3, ((FolderInfo) info).contents.size()); + assertEquals("CustomFolder", info.title.toString()); + } + @Test public void testCustomProfileLoaded_with_widget() throws Exception { String pendingAppPkg = "com.test.pending"; diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java index d3659ebdcd..4e21dce021 100644 --- a/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java +++ b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java @@ -47,6 +47,7 @@ public class LauncherLayoutBuilder { private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_CLASS_NAME = "className"; private static final String ATTR_TITLE = "title"; + private static final String ATTR_TITLE_TEXT = "titleText"; private static final String ATTR_SCREEN = "screen"; // x and y can be specified as negative integers, in which case -1 represents the @@ -145,8 +146,17 @@ public class LauncherLayoutBuilder { } public FolderBuilder putFolder(int titleResId) { - FolderBuilder folderBuilder = new FolderBuilder(); items.put(ATTR_TITLE, Integer.toString(titleResId)); + return putFolder(); + } + + public FolderBuilder putFolder(String title) { + items.put(ATTR_TITLE_TEXT, title); + return putFolder(); + } + + private FolderBuilder putFolder() { + FolderBuilder folderBuilder = new FolderBuilder(); items.put(ATTR_CHILDREN, folderBuilder.mChildren); mNodes.add(Pair.create(TAG_FOLDER, items)); return folderBuilder; diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 572615ff8a..cd27a2ddca 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -174,7 +174,8 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch targetInfo.first, TYPE_WINDOW_STATE_CHANGED, targetInfo.second); if (mIsOpen) { - performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); + getAccessibilityInitialFocusView().performAccessibilityAction( + AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null); } ActivityContext.lookupContext(getContext()).getDragLayer() .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED); @@ -184,6 +185,11 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch return null; } + /** Returns the View that Accessibility services should focus on first. */ + protected View getAccessibilityInitialFocusView() { + return this; + } + /** * Returns a view matching FloatingViewType */ diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 5971a028ca..432073ebaf 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -132,6 +132,7 @@ public class AutoInstallsLayout { private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_CLASS_NAME = "className"; private static final String ATTR_TITLE = "title"; + private static final String ATTR_TITLE_TEXT = "titleText"; private static final String ATTR_SCREEN = "screen"; // x and y can be specified as negative integers, in which case -1 represents the @@ -585,7 +586,8 @@ public class AutoInstallsLayout { if (titleResId != 0) { title = mSourceRes.getString(titleResId); } else { - title = ""; + String titleText = getAttributeValue(parser, ATTR_TITLE_TEXT); + title = TextUtils.isEmpty(titleText) ? "" : titleText; } mValues.put(Favorites.TITLE, title); diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index 268b910dd9..61ecdd7da6 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -43,6 +43,8 @@ import android.widget.Toast; import androidx.annotation.Nullable; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -188,7 +190,8 @@ public abstract class BaseDraggingActivity extends BaseActivity } getUserEventDispatcher().logAppLaunch(v, intent, user); if (item != null) { - getStatsLogManager().log(LAUNCHER_APP_LAUNCH_TAP, item.buildProto()); + InstanceId instanceId = new InstanceIdSequence().newInstanceId(); + logAppLaunch(item, instanceId); } return true; } catch (NullPointerException|ActivityNotFoundException|SecurityException e) { @@ -198,6 +201,10 @@ public abstract class BaseDraggingActivity extends BaseActivity return false; } + protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { + getStatsLogManager().log(LAUNCHER_APP_LAUNCH_TAP, instanceId, info); + } + private void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info, @Nullable String sourceContainer) { try { diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 208d565d2e..5512654390 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -163,6 +163,7 @@ public class LauncherSettings { public static final int CONTAINER_SEARCH_RESULTS = -106; public static final int CONTAINER_SHORTCUTS = -107; public static final int CONTAINER_SETTINGS = -108; + public static final int CONTAINER_TASKSWITCHER = -109; public static final String containerToString(int container) { switch (container) { @@ -249,6 +250,12 @@ public class LauncherSettings { */ public static final int ITEM_TYPE_DEEP_SHORTCUT = 6; + /** + * Type of the item is recents task. + * TODO(hyunyoungs): move constants not related to Favorites DB to a better location. + */ + public static final int ITEM_TYPE_TASK = 7; + /** * The appWidgetId of the widget * diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 8e33406ed5..bf637886b7 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -606,6 +606,7 @@ public final class Utilities { outObj[0] = activityInfo; return activityInfo.getFullResIcon(appState.getIconCache()); } + if (info.getIntent() == null || info.getIntent().getPackage() == null) return null; List si = ShortcutKey.fromItemInfo(info) .buildRequest(launcher) .query(ShortcutRequest.ALL); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index f6c392b536..4198e9f6e9 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -418,10 +418,7 @@ public class Workspace extends PagedView mStatsLogManager.log( LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED, dragObject.logInstanceId, - dragObject.dragSource instanceof Folder - ? dragObject.originalDragInfo - .buildProto(((Folder) dragObject.dragSource).mInfo) - : dragObject.originalDragInfo.buildProto() + dragObject.originalDragInfo ); } @@ -1652,7 +1649,7 @@ public class Workspace extends PagedView mStatsLogManager.log( LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED, d.logInstanceId, - destInfo.buildProto(null)); + destInfo); FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]); destInfo.cellX = -1; @@ -1693,7 +1690,7 @@ public class Workspace extends PagedView mStatsLogManager.log( LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, - fi.mInfo.buildProto(null)); + fi.mInfo); fi.onDrop(d, false /* itemReturnedOnFailedDrop */); // if the drag started here, we need to remove it from the workspace @@ -1899,7 +1896,7 @@ public class Workspace extends PagedView mStatsLogManager.log( LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, - d.dragInfo.buildProto(null)); + d.dragInfo); } if (d.stateAnnouncer != null && !droppedOnOriginalCell) { @@ -2440,7 +2437,7 @@ public class Workspace extends PagedView mStatsLogManager.log( LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, - d.dragInfo.buildProto(null)); + d.dragInfo); } }; boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET @@ -2532,7 +2529,7 @@ public class Workspace extends PagedView mStatsLogManager.log( LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, - d.dragInfo.buildProto(null)); + d.dragInfo); } } diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 82603369ec..1dd81e8305 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -14,7 +14,6 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FA import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE; import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS; -import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -37,7 +36,6 @@ import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.statemanager.StateManager.StateHandler; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; -import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; import com.android.systemui.plugins.AllAppsSearchPlugin; import com.android.systemui.plugins.PluginListener; @@ -75,7 +73,6 @@ public class AllAppsTransitionController implements StateHandler, private ScrimView mScrimView; private final Launcher mLauncher; - private final boolean mIsDarkTheme; private boolean mIsVerticalLayout; // Animation in this class is controlled by a single variable {@link mProgress}. @@ -98,7 +95,6 @@ public class AllAppsTransitionController implements StateHandler, mShiftRange = mLauncher.getDeviceProfile().heightPx; mProgress = 1f; - mIsDarkTheme = Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark); mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout(); mLauncher.addOnDeviceProfileChangeListener(this); } @@ -137,16 +133,6 @@ public class AllAppsTransitionController implements StateHandler, if (mPlugin != null) { mPlugin.setProgress(progress); } - - // Use a light system UI (dark icons) if all apps is behind at least half of the - // status bar. - boolean forceChange = Math.min(shiftCurrent, mScrimView.getVisualTop()) - <= mLauncher.getDeviceProfile().getInsets().top / 2f; - if (forceChange) { - mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, !mIsDarkTheme); - } else { - mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0); - } } public float getProgress() { diff --git a/src/com/android/launcher3/anim/FlingSpringAnim.java b/src/com/android/launcher3/anim/FlingSpringAnim.java index 06d0f1c463..6ea38ec8ab 100644 --- a/src/com/android/launcher3/anim/FlingSpringAnim.java +++ b/src/com/android/launcher3/anim/FlingSpringAnim.java @@ -35,6 +35,7 @@ public class FlingSpringAnim { private final FlingAnimation mFlingAnim; private SpringAnimation mSpringAnim; + private final boolean mSkipFlingAnim; private float mTargetPosition; @@ -57,6 +58,10 @@ public class FlingSpringAnim { .setMaxValue(maxValue); mTargetPosition = targetPosition; + // We are already past the fling target, so skip it to avoid losing a frame of the spring. + mSkipFlingAnim = startPosition <= minValue && startVelocity < 0 + || startPosition >= maxValue && startVelocity > 0; + mFlingAnim.addEndListener(((animation, canceled, value, velocity) -> { mSpringAnim = new SpringAnimation(object, property) .setStartValue(value) @@ -84,6 +89,9 @@ public class FlingSpringAnim { public void start() { mFlingAnim.start(); + if (mSkipFlingAnim) { + mFlingAnim.cancel(); + } } public void end() { diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index fdf0ea46f2..530010e948 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -745,6 +745,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo : getContext().getString(R.string.folder_closed)); } + @Override + protected View getAccessibilityInitialFocusView() { + return mContent.getFirstItem(); + } + private void closeComplete(boolean wasAnimated) { // TODO: Clear all active animations. DragLayer parent = (DragLayer) getParent(); @@ -1333,7 +1338,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo d.stateAnnouncer.completeAction(R.string.item_moved); } mStatsLogManager - .log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo.buildProto(mInfo)); + .log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo); } // This is used so the item doesn't immediately appear in the folder when added. In one case @@ -1438,7 +1443,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (hasFocus) { startEditingFolderName(); } else { - mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo.buildProto()); + mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo); logFolderLabelState(); mFolderName.dispatchBackKey(); } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 153d6bceb1..2be5883e4a 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -201,8 +201,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel icon.mActivity = activity; icon.mDotRenderer = grid.mDotRendererWorkSpace; - icon.setContentDescription( - group.getContext().getString(R.string.folder_name_format, folderInfo.title)); + icon.setContentDescription(icon.getAccessiblityTitle(folderInfo.title)); // Keep the notification dot up to date with the sum of all the content's dots. FolderDotInfo folderDotInfo = new FolderDotInfo(); @@ -450,7 +449,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } mInfo.setTitle(nameInfos[0].getLabel()); StatsLogManager.newInstance(getContext()) - .log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo.buildProto()); + .log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo); onTitleChanged(mInfo.title); mFolder.mFolderName.setText(mInfo.title); mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo); @@ -665,6 +664,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item)); boolean isDotted = mDotInfo.hasDot(); updateDotScale(wasDotted, isDotted); + setContentDescription(getAccessiblityTitle(mInfo.title)); invalidate(); requestLayout(); } @@ -675,13 +675,14 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item)); boolean isDotted = mDotInfo.hasDot(); updateDotScale(wasDotted, isDotted); + setContentDescription(getAccessiblityTitle(mInfo.title)); invalidate(); requestLayout(); } public void onTitleChanged(CharSequence title) { mFolderName.setText(title); - setContentDescription(getContext().getString(R.string.folder_name_format, title)); + setContentDescription(getAccessiblityTitle(title)); } @Override @@ -775,4 +776,17 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel public void getWorkspaceVisualDragBounds(Rect bounds) { getPreviewBounds(bounds); } + + /** + * Returns a formatted accessibility title for folder + */ + public String getAccessiblityTitle(CharSequence title) { + int size = mInfo.contents.size(); + if (size < MAX_NUM_ITEMS_IN_PREVIEW) { + return getContext().getString(R.string.folder_name_format_exact, title, size); + } else { + return getContext().getString(R.string.folder_name_format_overflow, title, + MAX_NUM_ITEMS_IN_PREVIEW); + } + } } diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java index edf2c705e9..6038a053d6 100644 --- a/src/com/android/launcher3/folder/FolderNameEditText.java +++ b/src/com/android/launcher3/folder/FolderNameEditText.java @@ -70,8 +70,11 @@ public class FolderNameEditText extends ExtendedEditText { for (int i = 0; i < cnt; i++) { cInfo[i] = new CompletionInfo(i, i, suggestList.get(i)); } - post(() -> getContext().getSystemService(InputMethodManager.class) - .displayCompletions(this, cInfo)); + // post it to future frame so that onSelectionChanged, onFocusChanged, all other + // TextView flag change and IME animation has settled. Ideally, there should be IMM + // callback to notify when the IME animation and state handling is finished. + postDelayed(() -> getContext().getSystemService(InputMethodManager.class) + .displayCompletions(this, cInfo), 40 /* 2~3 frame delay */); } /** diff --git a/src/com/android/launcher3/logging/InstanceIdSequence.java b/src/com/android/launcher3/logging/InstanceIdSequence.java index a4b795352a..ee6a5a42ba 100644 --- a/src/com/android/launcher3/logging/InstanceIdSequence.java +++ b/src/com/android/launcher3/logging/InstanceIdSequence.java @@ -44,6 +44,13 @@ public class InstanceIdSequence { mInstanceIdMax = min(max(1, instanceIdMax), InstanceId.INSTANCE_ID_MAX); } + /** + * Constructs a sequence with identifiers [1, InstanceId.INSTANCE_ID_MAX]. + */ + public InstanceIdSequence() { + this(InstanceId.INSTANCE_ID_MAX); + } + /** * Gets the next instance from the sequence. Safe for concurrent use. * @return new InstanceId diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index f216f8129b..c84b9fe487 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -21,8 +21,9 @@ import androidx.annotation.Nullable; import com.android.launcher3.LauncherState; import com.android.launcher3.R; -import com.android.launcher3.logger.LauncherAtom.ItemInfo; +import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logging.StatsLogUtils.LogStateProvider; +import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.util.ResourceBasedOverride; /** @@ -127,9 +128,29 @@ public class StatsLogManager implements ResourceBasedOverride { LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP(522), @UiEvent(doc = "User is shown All Apps education view.") - LAUNCHER_ALL_APPS_EDU_SHOWN(523); - // ADD MORE + LAUNCHER_ALL_APPS_EDU_SHOWN(523), + @UiEvent(doc = "User opened a folder.") + LAUNCHER_FOLDER_OPEN(551), + + @UiEvent(doc = "Hotseat education half sheet seen") + LAUNCHER_HOTSEAT_EDU_SEEN(479), + + @UiEvent(doc = "Hotseat migration accepted") + LAUNCHER_HOTSEAT_EDU_ACCEPT(480), + + @UiEvent(doc = "Hotseat migration denied") + LAUNCHER_HOTSEAT_EDU_DENY(481), + + @UiEvent(doc = "Hotseat education tip shown") + LAUNCHER_HOTSEAT_EDU_ONLY_TIP(482), + + @UiEvent(doc = "App launch ranking logged for all apps predictions") + LAUNCHER_ALL_APPS_RANKED(552), + + @UiEvent(doc = "App launch ranking logged for hotseat predictions)") + LAUNCHER_HOTSEAT_RANKED(553); + // ADD MORE private final int mId; LauncherEvent(int id) { @@ -177,25 +198,56 @@ public class StatsLogManager implements ResourceBasedOverride { } /** - * Logs a {@link EventEnum}. + * Logs an event. + * + * @param event an enum implementing EventEnum interface. */ public void log(EventEnum event) { } /** - * Logs an event and accompanying {@link InstanceId}. + * Logs an event. + * + * @param event an enum implementing EventEnum interface. + * @param instanceId an identifier obtained from an InstanceIdSequence. */ public void log(EventEnum event, InstanceId instanceId) { } /** - * Logs an event and accompanying {@link ItemInfo}. + * Logs an event. + * + * @param event an enum implementing EventEnum interface. + * @param itemInfo item typically containing app or task launch related information. */ public void log(EventEnum event, @Nullable ItemInfo itemInfo) { } /** - * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}. + * Logs an event. + * + * @param event an enum implementing EventEnum interface. + * @param atomInfo item typically containing app or task launch related information. + */ + public void log(EventEnum event, InstanceId instanceId, LauncherAtom.ItemInfo atomInfo) { + } + + /** + * Logs an event and accompanying {@link LauncherState}s. + * + * @param event an enum implementing EventEnum interface. + * @param launcherAtomItemInfo item typically containing app or task launch related information. + */ + public void log(EventEnum event, @Nullable LauncherAtom.ItemInfo launcherAtomItemInfo, + int srcState, int dstState) { + } + + /** + * Logs an event. + * + * @param event an enum implementing EventEnum interface. + * @param instanceId an identifier obtained from an InstanceIdSequence. + * @param itemInfo item typically containing app or task launch related information. */ public void log(EventEnum event, InstanceId instanceId, @Nullable ItemInfo itemInfo) { } @@ -204,7 +256,7 @@ public class StatsLogManager implements ResourceBasedOverride { * Log an event with ranked-choice information along with package. Does nothing if event.getId() * <= 0. * - * @param rankingEvent an enum implementing UiEventEnum interface. + * @param rankingEvent an enum implementing EventEnum interface. * @param instanceId An identifier obtained from an InstanceIdSequence. * @param packageName the package name of the relevant app, if known (null otherwise). * @param position the position picked. diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java index 7818ff55fd..e094cab82f 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -48,7 +48,6 @@ import androidx.annotation.Nullable; import com.android.launcher3.DropTarget; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.userevent.LauncherLogProto; @@ -143,14 +142,6 @@ public class UserEventDispatcher implements ResourceBasedOverride { fillIntentInfo(itemTarget, intent, userHandle); } LauncherEvent event = newLauncherEvent(action, targets); - ItemInfo info = v == null ? null : (ItemInfo) v.getTag(); - if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) { - final String pkg = info.getTargetComponent() != null - ? info.getTargetComponent().getPackageName() : "unknown"; - FileLog.d(TAG, "appLaunch: packageName:" + pkg - + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals( - userHandle)) + ",launchLocation:" + info.container); - } dispatchUserEvent(event, intent); mAppOrTaskLaunch = true; } diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java index 5ba2c8e3f5..25a2c695f1 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java @@ -59,7 +59,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; /** * This class takes care of shrinking the workspace (by maximum of one row and one column), as a @@ -248,12 +247,25 @@ public class GridSizeMigrationTaskV2 { /** Return what's in the src but not in the dest */ private static List calcDiff(List src, List dest) { - Set destSet = dest.parallelStream().map(DbEntry::getIntentStr).collect( - Collectors.toSet()); + Set destIntentSet = new HashSet<>(); + Set> destFolderIntentSet = new HashSet<>(); + for (DbEntry entry : dest) { + if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { + destFolderIntentSet.add(entry.mFolderItems.keySet()); + } else { + destIntentSet.add(entry.mIntent); + } + } List diff = new ArrayList<>(); for (DbEntry entry : src) { - if (!destSet.contains(entry.mIntent)) { - diff.add(entry); + if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { + if (!destFolderIntentSet.contains(entry.mFolderItems.keySet())) { + diff.add(entry); + } + } else { + if (!destIntentSet.contains(entry.mIntent)) { + diff.add(entry); + } } } return diff; diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index d52b7ebdd6..66c3cbbe27 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -24,11 +24,13 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICT import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SEARCH_RESULTS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SETTINGS; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_TASKSWITCHER; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK; import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.CONTAINER_NOT_SET; import android.content.ComponentName; @@ -49,6 +51,7 @@ import com.android.launcher3.logger.LauncherAtom.PredictionContainer; import com.android.launcher3.logger.LauncherAtom.SearchResultContainer; import com.android.launcher3.logger.LauncherAtom.SettingsContainer; import com.android.launcher3.logger.LauncherAtom.ShortcutsContainer; +import com.android.launcher3.logger.LauncherAtom.TaskSwitcherContainer; import com.android.launcher3.util.ContentWriter; import java.util.Optional; @@ -298,6 +301,12 @@ public class ItemInfo { .setSpanX(spanX) .setSpanY(spanY)); break; + case ITEM_TYPE_TASK: + itemBuilder + .setTask(LauncherAtom.Task.newBuilder() + .setComponentName(getTargetComponent().flattenToShortString()) + .setIndex(screenId)); + break; default: break; } @@ -378,6 +387,11 @@ public class ItemInfo { return ContainerInfo.newBuilder() .setSettingsContainer(SettingsContainer.getDefaultInstance()) .build(); + case CONTAINER_TASKSWITCHER: + return ContainerInfo.newBuilder() + .setTaskSwitcherContainer(TaskSwitcherContainer.getDefaultInstance()) + .build(); + } return ContainerInfo.getDefaultInstance(); } diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java index fa1bdfbb46..f1b63f28f9 100644 --- a/src/com/android/launcher3/notification/NotificationInfo.java +++ b/src/com/android/launcher3/notification/NotificationInfo.java @@ -108,7 +108,7 @@ public class NotificationInfo implements View.OnClickListener { intent.send(null, 0, null, null, null, null, activityOptions); launcher.getUserEventDispatcher().logNotificationLaunch(view, intent); launcher.getStatsLogManager() - .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP, mItemInfo.buildProto()); + .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP, mItemInfo); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index 5b0c388332..d5b32fccd1 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -389,6 +389,11 @@ public abstract class ArrowPopup extends Abstrac return Pair.create(this, ""); } + @Override + protected View getAccessibilityInitialFocusView() { + return getChildCount() > 0 ? getChildAt(0) : this; + } + private void animateOpen() { setVisibility(View.VISIBLE); diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java index 58251e8967..8e60c27e66 100644 --- a/src/com/android/launcher3/popup/RemoteActionShortcut.java +++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java @@ -78,7 +78,7 @@ public class RemoteActionShortcut extends SystemShortcut { public void onClick(View view) { AbstractFloatingView.closeAllOpenViews(mTarget); mTarget.getStatsLogManager() - .log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP, mItemInfo.buildProto()); + .log(LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP, mItemInfo); final String actionIdentity = mAction.getTitle() + ", " + mItemInfo.getTargetComponent().getPackageName(); diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index ea8caf55de..59d24dee77 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -119,9 +119,7 @@ public abstract class SystemShortcut extends Ite widgetsBottomSheet.populateAndShow(mItemInfo); mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, ControlType.WIDGETS_BUTTON, view); - // TODO(thiruram): Fix missing container info when item is inside folder. - mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP, - mItemInfo.buildProto()); + mTarget.getStatsLogManager().log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP, mItemInfo); } } @@ -142,9 +140,8 @@ public abstract class SystemShortcut extends Ite mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle()); mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP, ControlType.APPINFO_TARGET, view); - // TODO(thiruram): Fix missing container info when item is inside folder. mTarget.getStatsLogManager() - .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP, mItemInfo.buildProto()); + .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP, mItemInfo); } } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 2c21609fa6..171c5ee4e4 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -310,6 +310,9 @@ public abstract class AbstractStateChangeTouchController } protected void updateProgress(float fraction) { + if (mCurrentAnimation == null) { + return; + } mCurrentAnimation.setPlayFraction(fraction); if (mAtomicComponentsController != null) { // Make sure we don't divide by 0, and have at least a small runway. diff --git a/src/com/android/launcher3/touch/HomeRotatedPageHandler.java b/src/com/android/launcher3/touch/HomeRotatedPageHandler.java deleted file mode 100644 index db5c659e04..0000000000 --- a/src/com/android/launcher3/touch/HomeRotatedPageHandler.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.touch; - -import android.graphics.RectF; -import android.view.Surface; -import android.widget.LinearLayout; - -public class HomeRotatedPageHandler extends PortraitPagedViewHandler { - @Override - public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) { - if (launcherRotation == Surface.ROTATION_0) { - super.offsetTaskRect(rect, value, displayRotation, launcherRotation); - } else if (launcherRotation == Surface.ROTATION_90) { - if (displayRotation == Surface.ROTATION_0) { - rect.offset(0, value); - } else if (displayRotation == Surface.ROTATION_90) { - rect.offset(value, 0); - } else if (displayRotation == Surface.ROTATION_180) { - rect.offset(-value, 0); - } else { - rect.offset(-value, 0); - } - } else if (launcherRotation == Surface.ROTATION_270) { - if (displayRotation == Surface.ROTATION_0) { - rect.offset(0, -value); - } else if (displayRotation == Surface.ROTATION_90) { - rect.offset(value, 0); - } else if (displayRotation == Surface.ROTATION_180) { - rect.offset(0, -value); - } else { - rect.offset(value, 0); - } - } // TODO (b/149609488) handle 180 case as well - } - - @Override - public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) { - return taskMenuLayout.getOrientation(); - } -} diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index 6abca767ae..de16941864 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -17,6 +17,7 @@ package com.android.launcher3.touch; import static com.android.launcher3.Launcher.REQUEST_BIND_PENDING_APPWIDGET; import static com.android.launcher3.Launcher.REQUEST_RECONFIGURE_APPWIDGET; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN; import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_BY_PUBLISHER; import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; @@ -45,6 +46,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; @@ -111,6 +113,7 @@ public class ItemClickHandler { if (!folder.isOpen() && !folder.isDestroyed()) { // Open the requested folder folder.animateOpen(); + StatsLogManager.newInstance(v.getContext()).log(LAUNCHER_FOLDER_OPEN, folder.mInfo); } } diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java index d02c731de3..48c773413f 100644 --- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java @@ -180,19 +180,6 @@ public class LandscapePagedViewHandler implements PagedOrientationHandler { return Surface.ROTATION_90; } - @Override - public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) { - if (displayRotation == Surface.ROTATION_0) { - rect.offset(0, value); - } else if (displayRotation == Surface.ROTATION_90) { - rect.offset(value, 0); - } else if (displayRotation == Surface.ROTATION_180) { - rect.offset(0, -value); - } else { - rect.offset(-value, 0); - } - } - @Override public int getChildStart(View view) { return view.getTop(); diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java index 2e0268dc79..65b1a7a77e 100644 --- a/src/com/android/launcher3/touch/PagedOrientationHandler.java +++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java @@ -43,7 +43,6 @@ public interface PagedOrientationHandler { PagedOrientationHandler PORTRAIT = new PortraitPagedViewHandler(); PagedOrientationHandler LANDSCAPE = new LandscapePagedViewHandler(); PagedOrientationHandler SEASCAPE = new SeascapePagedViewHandler(); - PagedOrientationHandler HOME_ROTATED = new HomeRotatedPageHandler(); interface Int2DAction { void call(T target, int x, int y); @@ -82,7 +81,6 @@ public interface PagedOrientationHandler { boolean getRecentsRtlSetting(Resources resources); float getDegreesRotated(); int getRotation(); - void offsetTaskRect(RectF rect, float value, int delta, int launcherRotation); int getPrimaryValue(int x, int y); int getSecondaryValue(int x, int y); void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll); diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java index 2fc7a9f40e..79e5c87785 100644 --- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java +++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java @@ -178,19 +178,6 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { return Surface.ROTATION_0; } - @Override - public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) { - if (displayRotation == Surface.ROTATION_0) { - rect.offset(value, 0); - } else if (displayRotation == Surface.ROTATION_90) { - rect.offset(0, -value); - } else if (displayRotation == Surface.ROTATION_180) { - rect.offset(-value, 0); - } else { - rect.offset(0, value); - } - } - @Override public int getChildStart(View view) { return view.getLeft(); @@ -250,7 +237,7 @@ public class PortraitPagedViewHandler implements PagedOrientationHandler { @Override public int getTaskMenuLayoutOrientation(LinearLayout taskMenuLayout) { - return LinearLayout.VERTICAL; + return taskMenuLayout.getOrientation(); } @Override diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java index 4c1700ee9a..d5ae2dcb16 100644 --- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java +++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java @@ -18,7 +18,6 @@ package com.android.launcher3.touch; import android.content.res.Resources; import android.graphics.PointF; -import android.graphics.RectF; import android.view.Surface; import android.view.View; @@ -41,19 +40,6 @@ public class SeascapePagedViewHandler extends LandscapePagedViewHandler { return Utilities.isRtl(resources); } - @Override - public void offsetTaskRect(RectF rect, float value, int displayRotation, int launcherRotation) { - if (displayRotation == Surface.ROTATION_0) { - rect.offset(0, value); - } else if (displayRotation == Surface.ROTATION_90) { - rect.offset(value, 0); - } else if (displayRotation == Surface.ROTATION_180) { - rect.offset(0, -value); - } else { - rect.offset(-value, 0); - } - } - @Override public float getDegreesRotated() { return 270; diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java index 162028991d..90a1c82661 100644 --- a/src/com/android/launcher3/util/OnboardingPrefs.java +++ b/src/com/android/launcher3/util/OnboardingPrefs.java @@ -37,13 +37,14 @@ public class OnboardingPrefs { 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 { 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 { 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); } diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java index 53cc157682..275c0246e2 100644 --- a/src/com/android/launcher3/util/SystemUiController.java +++ b/src/com/android/launcher3/util/SystemUiController.java @@ -30,7 +30,7 @@ public class SystemUiController { // Various UI states in increasing order of priority public static final int UI_STATE_BASE_WINDOW = 0; - public static final int UI_STATE_ALL_APPS = 1; + public static final int UI_STATE_SCRIM_VIEW = 1; public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2; public static final int UI_STATE_OVERVIEW = 3; @@ -61,25 +61,38 @@ public class SystemUiController { // Apply the state flags in priority order int newFlags = oldFlags; for (int stateFlag : mStates) { - if (Utilities.ATLEAST_OREO) { - if ((stateFlag & FLAG_LIGHT_NAV) != 0) { - newFlags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; - } else if ((stateFlag & FLAG_DARK_NAV) != 0) { - newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); - } - } - - if ((stateFlag & FLAG_LIGHT_STATUS) != 0) { - newFlags |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } else if ((stateFlag & FLAG_DARK_STATUS) != 0) { - newFlags &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); - } + newFlags = getSysUiVisibilityFlags(stateFlag, newFlags); } if (newFlags != oldFlags) { mWindow.getDecorView().setSystemUiVisibility(newFlags); } } + /** + * Returns the sysui visibility for the base layer + */ + public int getBaseSysuiVisibility() { + return getSysUiVisibilityFlags( + mStates[UI_STATE_BASE_WINDOW], mWindow.getDecorView().getSystemUiVisibility()); + } + + private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) { + if (Utilities.ATLEAST_OREO) { + if ((stateFlag & FLAG_LIGHT_NAV) != 0) { + currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } else if ((stateFlag & FLAG_DARK_NAV) != 0) { + currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); + } + } + + if ((stateFlag & FLAG_LIGHT_STATUS) != 0) { + currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else if ((stateFlag & FLAG_DARK_STATUS) != 0) { + currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + return currentVisibility; + } + @Override public String toString() { return "mStates=" + Arrays.toString(mStates); diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java index a7575d12ba..b4a6b141ce 100644 --- a/src/com/android/launcher3/views/ArrowTipView.java +++ b/src/com/android/launcher3/views/ArrowTipView.java @@ -125,11 +125,35 @@ public class ArrowTipView extends AbstractFloatingView { * Show Tip with specified string and Y location */ public ArrowTipView show(String text, int top) { + return show(text, Gravity.CENTER_HORIZONTAL, 0, top); + } + + /** + * Show the ArrowTipView (tooltip) center, start, or end aligned. + * + * @param text The text to be shown in the tooltip. + * @param gravity The gravity aligns the tooltip center, start, or end. + * @param arrowMarginStart The margin from start to place arrow (ignored if center) + * @param top The Y coordinate of the bottom of tooltip. + * @return The tooltip. + */ + public ArrowTipView show(String text, int gravity, int arrowMarginStart, int top) { ((TextView) findViewById(R.id.text)).setText(text); - mActivity.getDragLayer().addView(this); + ViewGroup parent = mActivity.getDragLayer(); + parent.addView(this); DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams(); - params.gravity = Gravity.CENTER_HORIZONTAL; + params.gravity = gravity; + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) findViewById( + R.id.arrow).getLayoutParams(); + lp.gravity = gravity; + if (gravity == Gravity.END) { + lp.setMarginEnd(parent.getMeasuredWidth() - arrowMarginStart); + } else if (gravity == Gravity.START) { + lp.setMarginStart(arrowMarginStart); + } + requestLayout(); + params.leftMargin = mActivity.getDeviceProfile().workspacePadding.left; params.rightMargin = mActivity.getDeviceProfile().workspacePadding.right; post(() -> setY(top - getHeight())); diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index f54edc223f..b010b4b94b 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -90,13 +90,23 @@ public abstract class BaseDragLayer } }; - // Touch is being dispatched through the normal view dispatch system - private static final int TOUCH_DISPATCHING_VIEW = 1 << 0; + // Touch coming from normal view system is being dispatched. + private static final int TOUCH_DISPATCHING_FROM_VIEW = 1 << 0; // Touch is being dispatched through the normal view dispatch system, and started at the - // system gesture region - private static final int TOUCH_DISPATCHING_GESTURE = 1 << 1; - // Touch is being dispatched through a proxy from InputMonitor - private static final int TOUCH_DISPATCHING_PROXY = 1 << 2; + // system gesture region. In this case we prevent internal gesture handling and only allow + // normal view event handling. + private static final int TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION = 1 << 1; + // Touch coming from InputMonitor proxy is being dispatched 'only to gestures'. Note that both + // this and view-system can be active at the same time where view-system would go to the views, + // and this would go to the gestures. + // Note that this is not set when events are coming from proxy, but going through full dispatch + // process (both views and gestures) to allow view-system to easily take over in case it + // comes later. + private static final int TOUCH_DISPATCHING_FROM_PROXY = 1 << 2; + // ACTION_DOWN has been dispatched to child views and ACTION_UP or ACTION_CANCEL is pending. + // Note that the event source can either be view-dispatching or proxy-dispatching based on if + // TOUCH_DISPATCHING_VIEW is present or not. + private static final int TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS = 1 << 3; protected final float[] mTmpXY = new float[2]; protected final float[] mTmpRectPoints = new float[4]; @@ -204,7 +214,8 @@ public abstract class BaseDragLayer protected boolean findActiveController(MotionEvent ev) { mActiveController = null; - if ((mTouchDispatchState & (TOUCH_DISPATCHING_GESTURE | TOUCH_DISPATCHING_PROXY)) == 0) { + if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION + | TOUCH_DISPATCHING_FROM_PROXY)) == 0) { // Only look for controllers if we are not dispatching from gesture area and proxy is // not active mActiveController = findControllerToHandleTouch(ev); @@ -283,19 +294,28 @@ public abstract class BaseDragLayer public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case ACTION_DOWN: { - mTouchDispatchState |= TOUCH_DISPATCHING_VIEW; + if ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0) { + // Cancel the previous touch + int action = ev.getAction(); + ev.setAction(ACTION_CANCEL); + super.dispatchTouchEvent(ev); + ev.setAction(action); + } + mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW + | TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; if (isEventInLauncher(ev)) { - mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE; + mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION; } else { - mTouchDispatchState |= TOUCH_DISPATCHING_GESTURE; + mTouchDispatchState |= TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION; } break; } case ACTION_CANCEL: case ACTION_UP: - mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE; - mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW; + mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION; + mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_VIEW; + mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; break; } super.dispatchTouchEvent(ev); @@ -304,44 +324,54 @@ public abstract class BaseDragLayer return true; } - /** - * Called before we are about to receive proxy events. - * - * @return false if we can't handle proxy at this time - */ - public boolean prepareProxyEventStarting() { - mProxyTouchController = null; - if ((mTouchDispatchState & TOUCH_DISPATCHING_VIEW) != 0 && mActiveController != null) { - // We are already dispatching using view system and have an active controller, we can't - // handle another controller. - - // This flag was already cleared in proxy ACTION_UP or ACTION_CANCEL. Added here just - // to be safe - mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY; - return false; - } - - mTouchDispatchState |= TOUCH_DISPATCHING_PROXY; - return true; - } - /** * Proxies the touch events to the gesture handlers */ - public boolean proxyTouchEvent(MotionEvent ev) { - boolean handled; - if (mProxyTouchController != null) { - handled = mProxyTouchController.onControllerTouchEvent(ev); + public boolean proxyTouchEvent(MotionEvent ev, boolean allowViewDispatch) { + int actionMasked = ev.getActionMasked(); + boolean isViewDispatching = (mTouchDispatchState & TOUCH_DISPATCHING_FROM_VIEW) != 0; + + // Only do view dispatch if another view-dispatching is not running, or we already started + // proxy-dispatching before. Note that view-dispatching can always take over the proxy + // dispatching at anytime, but not vice-versa. + allowViewDispatch = allowViewDispatch && !isViewDispatching + && (actionMasked == ACTION_DOWN + || ((mTouchDispatchState & TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS) != 0)); + + if (allowViewDispatch) { + mTouchDispatchState |= TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; + super.dispatchTouchEvent(ev); + + if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) { + mTouchDispatchState &= ~TOUCH_DISPATCHING_TO_VIEW_IN_PROGRESS; + mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY; + } + return true; } else { - mProxyTouchController = findControllerToHandleTouch(ev); - handled = mProxyTouchController != null; + boolean handled; + if (mProxyTouchController != null) { + handled = mProxyTouchController.onControllerTouchEvent(ev); + } else { + if (actionMasked == ACTION_DOWN) { + if (isViewDispatching && mActiveController != null) { + // A controller is already active, we can't initiate our own controller + mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY; + } else { + // We will control the handler via proxy + mTouchDispatchState |= TOUCH_DISPATCHING_FROM_PROXY; + } + } + if ((mTouchDispatchState & TOUCH_DISPATCHING_FROM_PROXY) != 0) { + mProxyTouchController = findControllerToHandleTouch(ev); + } + handled = mProxyTouchController != null; + } + if (actionMasked == ACTION_UP || actionMasked == ACTION_CANCEL) { + mProxyTouchController = null; + mTouchDispatchState &= ~TOUCH_DISPATCHING_FROM_PROXY; + } + return handled; } - int action = ev.getAction(); - if (action == ACTION_UP || action == ACTION_CANCEL) { - mProxyTouchController = null; - mTouchDispatchState &= ~TOUCH_DISPATCHING_PROXY; - } - return handled; } /** diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index a2c7d14d52..22faf97cc4 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -27,6 +27,7 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; +import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -53,6 +54,7 @@ import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeL import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.graphics.ColorUtils; import androidx.core.view.ViewCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; @@ -77,7 +79,6 @@ import com.android.launcher3.widget.WidgetsFullSheet; import java.util.List; - /** * Simple scrim which draws a flat color */ @@ -115,6 +116,7 @@ public class ScrimView extends View implements Insettable, O private final WallpaperColorInfo mWallpaperColorInfo; private final AccessibilityManager mAM; protected final int mEndScrim; + protected final boolean mIsScrimDark; private final StateListener mAccessibilityLauncherStateListener = new StateListener() { @@ -156,6 +158,7 @@ public class ScrimView extends View implements Insettable, O mLauncher = Launcher.cast(Launcher.getLauncher(context)); mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context); mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor); + mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f; mMaxScrimAlpha = 0.7f; @@ -233,6 +236,7 @@ public class ScrimView extends View implements Insettable, O mProgress = progress; stopDragHandleEducationAnim(); updateColors(); + updateSysUiColors(); updateDragHandleAlpha(); invalidate(); } @@ -245,6 +249,17 @@ public class ScrimView extends View implements Insettable, O mEndFlatColor, Math.round((1 - mProgress) * mEndFlatColorAlpha)); } + protected void updateSysUiColors() { + // Use a light system UI (dark icons) if all apps is behind at least half of the + // status bar. + boolean forceChange = mProgress <= 0.1f; + if (forceChange) { + mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, !mIsScrimDark); + } else { + mLauncher.getSystemUiController().updateUiState(UI_STATE_SCRIM_VIEW, 0); + } + } + protected void updateDragHandleAlpha() { if (mDragHandle != null) { mDragHandle.setAlpha(mDragHandleAlpha);