diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto index cac2d8f5a7..f1db144604 100644 --- a/protos/launcher_atom.proto +++ b/protos/launcher_atom.proto @@ -48,12 +48,16 @@ message ContainerInfo { HotseatContainer hotseat = 2; FolderContainer folder = 3; AllAppsContainer all_apps_container = 4; + WidgetsContainer widgets_container = 5; } } message AllAppsContainer { } +message WidgetsContainer { +} + enum Origin { UNKNOWN = 0; DEFAULT_LAYOUT = 1; // icon automatically placed in workspace, folder, hotseat @@ -95,7 +99,18 @@ message Task { // Represents folder in a closed state. message FolderIcon { + // Number of items inside folder. optional int32 cardinality = 1; + + // State of the folder label before the event. + optional FromState from_label_state = 2; + + // State of the folder label after the event. + optional ToState to_label_state = 3; + + // Details about actual folder label. + // Populated when folder label is not a PII. + optional string label_info = 4; } ////////////////////////////////////////////// @@ -120,3 +135,78 @@ message FolderContainer { HotseatContainer hotseat = 5; } } + +// Represents state of EditText field before update. +enum FromState { + // Default value. + // Used when a FromState is not applicable, for example, during folder creation. + FROM_STATE_UNSPECIFIED = 0; + + // EditText was empty. + // Eg: When a folder label is updated from empty string. + FROM_EMPTY = 1; + + // EditText was non-empty and manually entered by the user. + // Eg: When a folder label is updated from a user-entered value. + FROM_CUSTOM = 2; + + // EditText was non-empty and one of the suggestions. + // Eg: When a folder label is updated from a suggested value. + FROM_SUGGESTED = 3; +} + +// Represents state of EditText field after update. +enum ToState { + // Default value. + // Used when ToState is not applicable, for example, when folder label is updated to a different + // value when folder label suggestion feature is disabled. + TO_STATE_UNSPECIFIED = 0; + + // User attempted to change the EditText, but was not changed. + UNCHANGED = 1; + + // New label matches with primary(aka top) suggestion. + TO_SUGGESTION0 = 2; + + // New value matches with second top suggestion even though the top suggestion was non-empty. + TO_SUGGESTION1_WITH_VALID_PRIMARY = 3; + + // New value matches with second top suggestion given that top suggestion was empty. + TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 4; + + // New value matches with third top suggestion even though the top suggestion was non-empty. + TO_SUGGESTION2_WITH_VALID_PRIMARY = 5; + + // New value matches with third top suggestion given that top suggestion was empty. + TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 6; + + // New value matches with 4th top suggestion even though the top suggestion was non-empty. + TO_SUGGESTION3_WITH_VALID_PRIMARY = 7; + + // New value matches with 4th top suggestion given that top suggestion was empty. + TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 8; + + // New value is empty even though the top suggestion was non-empty. + TO_EMPTY_WITH_VALID_PRIMARY = 9; + + // New value is empty given that top suggestion was empty. + TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 10; + + // New value is empty given that no suggestions were provided. + TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 11; + + // New value is empty given that suggestions feature was disabled. + TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 12; + + // New value is non-empty and does not match with any of the suggestions even though the top suggestion was non-empty. + TO_CUSTOM_WITH_VALID_PRIMARY = 13; + + // New value is non-empty and not match with any suggestions given that top suggestion was empty. + TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 14; + + // New value is non-empty and also no suggestions were provided. + TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 15; + + // New value is non-empty and also suggestions feature was disable. + TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 16; +} diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 04506b5647..b2286f1bd7 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -95,12 +95,6 @@ android:clearTaskOnLaunch="true" android:exported="false" /> - - com.android.quickstep.QuickstepProcessInitializer com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension + + com.android.launcher3.hybridhotseat.HotseatPredictionModel 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 ab3c71ae86..b6a8206ef0 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 @@ -211,8 +211,11 @@ public class PredictionUiStateManager implements StateListener, } private void dispatchOnChange(boolean changed) { - PredictionState newState = changed ? parseLastState() : - (mPendingState == null ? mCurrentState : mPendingState); + PredictionState newState = changed + ? parseLastState() + : mPendingState != null && canApplyPredictions(mPendingState) + ? mPendingState + : mCurrentState; if (changed && mAppsView != null && !canApplyPredictions(newState)) { scheduleApplyPredictedApps(newState); } else { 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 7f8f0a0576..e4d0adf0c0 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 @@ -121,7 +121,7 @@ public class HotseatEduController { if (!putIntoFolder.isEmpty()) { ItemInfo firstItem = putIntoFolder.get(0); FolderInfo folderInfo = new FolderInfo(); - folderInfo.title = ""; + folderInfo.setTitle(""); mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container, firstItem.screenId, firstItem.cellX, firstItem.cellY); folderInfo.contents.addAll(putIntoFolder); 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 e9f35345ca..1fe364386e 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 @@ -25,10 +25,7 @@ import android.app.prediction.AppPredictionManager; import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.app.prediction.AppTargetEvent; -import android.app.prediction.AppTargetId; import android.content.ComponentName; -import android.os.Bundle; -import android.os.Process; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -36,8 +33,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.CellLayout; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.Hotseat; @@ -48,7 +43,6 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.Workspace; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.appprediction.ComponentKeyMapper; @@ -57,12 +51,10 @@ 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.model.PredictionModel; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.model.data.LauncherAppWidgetInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; @@ -77,7 +69,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.OptionalInt; import java.util.stream.IntStream; @@ -93,17 +84,6 @@ public class HotseatPredictionController implements DragController.DragListener, private static final String TAG = "PredictiveHotseat"; private static final boolean DEBUG = false; - //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543) - private static final int APPTARGET_ACTION_UNPIN = 4; - - private static final String APP_LOCATION_HOTSEAT = "hotseat"; - private static final String APP_LOCATION_WORKSPACE = "workspace"; - - private static final String BUNDLE_KEY_HOTSEAT = "hotseat_apps"; - private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps"; - - private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; - private static final String PREDICTION_CLIENT = "hotseat"; private DropTarget.DragObject mDragObject; private int mHotSeatItemsCount; @@ -116,13 +96,14 @@ public class HotseatPredictionController implements DragController.DragListener, private DynamicItemCache mDynamicItemCache; - private final PredictionModel mPredictionModel; + private final HotseatPredictionModel mPredictionModel; private AppPredictor mAppPredictor; private AllAppsStore mAllAppsStore; private AnimatorSet mIconRemoveAnimators; private boolean mUIUpdatePaused = false; private boolean mRequiresCacheUpdate = true; private boolean mIsCacheEmpty; + private boolean mIsDestroyed = false; private HotseatEduController mHotseatEduController; @@ -141,13 +122,13 @@ public class HotseatPredictionController implements DragController.DragListener, mLauncher = launcher; mHotseat = launcher.getHotseat(); mAllAppsStore = mLauncher.getAppsView().getAppsStore(); - mPredictionModel = LauncherAppState.INSTANCE.get(launcher).getPredictionModel(); + LauncherAppState appState = LauncherAppState.getInstance(launcher); + mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel(); mAllAppsStore.addUpdateListener(this); mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction); mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); - mIsCacheEmpty = mPredictionModel.getPredictionComponentKeys().isEmpty(); if (mHotseat.isAttachedToWindow()) { onViewAttachedToWindow(mHotseat); } @@ -260,6 +241,7 @@ public class HotseatPredictionController implements DragController.DragListener, * Unregisters callbacks and frees resources */ public void destroy() { + mIsDestroyed = true; mAllAppsStore.removeUpdateListener(this); mLauncher.getDeviceProfile().inv.removeOnChangeListener(this); mHotseat.removeOnAttachStateChangeListener(this); @@ -292,100 +274,54 @@ public class HotseatPredictionController implements DragController.DragListener, } if (mAppPredictor != null) { mAppPredictor.destroy(); + mAppPredictor = null; } - mAppPredictor = apm.createAppPredictionSession( - new AppPredictionContext.Builder(mLauncher) - .setUiSurface(PREDICTION_CLIENT) - .setPredictedTargetCount(mHotSeatItemsCount) - .setExtras(getAppPredictionContextExtra()) - .build()); WeakReference controllerRef = new WeakReference<>(this); - mAppPredictor.registerPredictionUpdates(mLauncher.getApplicationContext().getMainExecutor(), - list -> { - if (controllerRef.get() != null) { - controllerRef.get().setPredictedApps(list); - } - }); + + mPredictionModel.createBundle(bundle -> { + if (mIsDestroyed) return; + mAppPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(mLauncher) + .setUiSurface(PREDICTION_CLIENT) + .setPredictedTargetCount(mHotSeatItemsCount) + .setExtras(bundle) + .build()); + mAppPredictor.registerPredictionUpdates( + mLauncher.getApplicationContext().getMainExecutor(), + list -> { + if (controllerRef.get() != null) { + controllerRef.get().setPredictedApps(list); + } + }); + mAppPredictor.requestPredictionUpdate(); + }); setPauseUIUpdate(false); if (!isEduSeen()) { mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor); } - mAppPredictor.requestPredictionUpdate(); } /** * 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) { + 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); preparePredictionInfo(item, ranks.get(i)); items.add(item); - } - mComponentKeyMappers.clear(); - for (ComponentKey key : mPredictionModel.getPredictionComponentKeys()) { - mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache)); + + mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache)); } updateDependencies(); bindItems(items, false, null); } - private Bundle getAppPredictionContextExtra() { - Bundle bundle = new Bundle(); - - //TODO: remove this way of reporting items - bundle.putParcelableArrayList(BUNDLE_KEY_HOTSEAT, - getPinnedAppTargetsInViewGroup((mHotseat.getShortcutsAndWidgets()))); - bundle.putParcelableArrayList(BUNDLE_KEY_WORKSPACE, getPinnedAppTargetsInViewGroup( - mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets())); - - ArrayList pinEvents = new ArrayList<>(); - getPinEventsForViewGroup(pinEvents, mHotseat.getShortcutsAndWidgets(), - APP_LOCATION_HOTSEAT); - getPinEventsForViewGroup(pinEvents, mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(), APP_LOCATION_WORKSPACE); - bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, pinEvents); - - return bundle; - } - - private ArrayList getPinEventsForViewGroup(ArrayList pinEvents, - ViewGroup views, String root) { - for (int i = 0; i < views.getChildCount(); i++) { - View child = views.getChildAt(i); - final AppTargetEvent event; - if (child.getTag() instanceof ItemInfo && getAppTargetFromInfo( - (ItemInfo) child.getTag()) != null) { - ItemInfo info = (ItemInfo) child.getTag(); - event = wrapAppTargetWithLocation(getAppTargetFromInfo(info), - AppTargetEvent.ACTION_PIN, info); - } else { - CellLayout.LayoutParams params = (CellLayout.LayoutParams) views.getLayoutParams(); - event = wrapAppTargetWithLocation(getBlockAppTarget(), AppTargetEvent.ACTION_PIN, - root, 0, params.cellX, params.cellY, params.cellHSpan, params.cellVSpan); - } - pinEvents.add(event); - } - return pinEvents; - } - - - private ArrayList getPinnedAppTargetsInViewGroup(ViewGroup viewGroup) { - ArrayList pinnedApps = new ArrayList<>(); - for (int i = 0; i < viewGroup.getChildCount(); i++) { - View child = viewGroup.getChildAt(i); - if (isPinnedIcon(child)) { - WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) child.getTag(); - pinnedApps.add(getAppTargetFromItemInfo(itemInfo)); - } - } - return pinnedApps; - } - private void setPredictedApps(List appTargets) { mComponentKeyMappers.clear(); StringBuilder predictionLog = new StringBuilder("predictedApps: [\n"); @@ -443,8 +379,11 @@ public class HotseatPredictionController implements DragController.DragListener, workspaceItemInfo.cellX, workspaceItemInfo.cellY); ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start(); icon.pin(workspaceItemInfo); - AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo); - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); + AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo); + if (appTarget != null) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_PIN, workspaceItemInfo)); + } mRequiresCacheUpdate = true; } @@ -524,10 +463,9 @@ public class HotseatPredictionController implements DragController.DragListener, mIconRemoveAnimators.start(); } - private void notifyItemAction(AppTarget target, String location, int action) { + private void notifyItemAction(AppTargetEvent event) { if (mAppPredictor != null) { - mAppPredictor.notifyAppTargetEvent(new AppTargetEvent.Builder(target, - action).setLaunchLocation(location).build()); + mAppPredictor.notifyAppTargetEvent(event); } } @@ -545,36 +483,35 @@ public class HotseatPredictionController implements DragController.DragListener, /** * Unpins pinned app when it's converted into a folder */ - public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) { - if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - return; + public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { + AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); + AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); + if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, + AppTargetEvent.ACTION_PIN, folderInfo)); } - AppTarget target = getAppTargetFromItemInfo(info); - ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets(); - ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(); - - if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains( - target)) { - notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); - } else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup( - firstScreenVG).contains(target)) { - notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); + // using folder info with isTrackedForPrediction as itemInfo.container is already changed + // to folder by this point + if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, + AppTargetEvent.ACTION_UNPIN, folderInfo + )); } } /** * Pins workspace item created when all folder items are removed but one */ - public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) { - if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - return; + public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) { + AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo); + AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo); + if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget, + AppTargetEvent.ACTION_UNPIN, folderInfo)); } - AppTarget target = getAppTargetFromItemInfo(info); - if (isInHotseat(info)) { - notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); - } else if (isInFirstPage(info)) { - notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); + if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget, + AppTargetEvent.ACTION_PIN, itemInfo)); } } @@ -585,29 +522,18 @@ public class HotseatPredictionController implements DragController.DragListener, } ItemInfo dragInfo = mDragObject.dragInfo; - ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets(); - ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId( - Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets(); - - if (dragInfo instanceof WorkspaceItemInfo - && dragInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && dragInfo.getTargetComponent() != null) { - AppTarget appTarget = getAppTargetFromItemInfo(dragInfo); - if (!isInHotseat(dragInfo) && isInHotseat(mDragObject.originalDragInfo)) { - if (!getPinnedAppTargetsInViewGroup(hotseatVG).contains(appTarget)) { - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN); - } + if (mDragObject.isMoved()) { + AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo); + //always send pin event first to prevent AiAi from predicting an item moved within + // the same page + if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_PIN, dragInfo)); } - if (!isInFirstPage(dragInfo) && isInFirstPage(mDragObject.originalDragInfo)) { - if (!getPinnedAppTargetsInViewGroup(firstScreenVG).contains(appTarget)) { - notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN); - } - } - if (isInHotseat(dragInfo) && !isInHotseat(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN); - } - if (isInFirstPage(dragInfo) && !isInFirstPage(mDragObject.originalDragInfo)) { - notifyItemAction(appTarget, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN); + if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction( + mDragObject.originalDragInfo)) { + notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget, + AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo)); } } mDragObject = null; @@ -615,6 +541,7 @@ public class HotseatPredictionController implements DragController.DragListener, mRequiresCacheUpdate = true; } + @Nullable @Override public SystemShortcut getShortcut(QuickstepLauncher activity, @@ -711,77 +638,4 @@ public class HotseatPredictionController implements DragController.DragListener, && ((WorkspaceItemInfo) view.getTag()).container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; } - - private static boolean isPinnedIcon(View view) { - if (!(view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo)) { - return false; - } - ItemInfo info = (ItemInfo) view.getTag(); - return info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION && ( - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION); - } - - private static boolean isInHotseat(ItemInfo itemInfo) { - return itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; - } - - private static boolean isInFirstPage(ItemInfo itemInfo) { - return itemInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP - && itemInfo.screenId == Workspace.FIRST_SCREEN_ID; - } - - private static AppTarget getAppTargetFromItemInfo(ItemInfo info) { - if (info.getTargetComponent() == null) return null; - ComponentName cn = info.getTargetComponent(); - return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } - - private AppTarget getAppTargetFromInfo(ItemInfo info) { - if (info == null) return null; - if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET - && info instanceof LauncherAppWidgetInfo - && ((LauncherAppWidgetInfo) info).providerName != null) { - ComponentName cn = ((LauncherAppWidgetInfo) info).providerName; - return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && info.getTargetComponent() != null) { - ComponentName cn = info.getTargetComponent(); - return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), - cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT - && info instanceof WorkspaceItemInfo) { - ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info); - //TODO: switch to using full shortcut info - return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()), - shortcutKey.componentName.getPackageName(), shortcutKey.user).build(); - } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { - return new AppTarget.Builder(new AppTargetId("folder:" + info.id), - mLauncher.getPackageName(), info.user).build(); - } - return null; - } - - private AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) { - return wrapAppTargetWithLocation(target, action, - info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT - ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE, info.screenId, info.cellX, - info.cellY, info.spanX, info.spanY); - } - - private AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, String root, - int screenId, int x, int y, int spanX, int spanY) { - return new AppTargetEvent.Builder(target, action).setLaunchLocation( - String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]", root, screenId, x, y, spanX, - spanY)).build(); - } - - /** - * A helper method to generate an AppTarget that's used to communicate workspace layout - */ - private AppTarget getBlockAppTarget() { - return new AppTarget.Builder(new AppTargetId("block"), - mLauncher.getPackageName(), Process.myUserHandle()).build(); - } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java new file mode 100644 index 0000000000..5a038d27af --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java @@ -0,0 +1,136 @@ +/* + * 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR; + +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.app.prediction.AppTargetId; +import android.content.ComponentName; +import android.content.Context; +import android.os.Bundle; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.Workspace; +import com.android.launcher3.model.AllAppsList; +import com.android.launcher3.model.BaseModelUpdateTask; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.PredictionModel; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.LauncherAppWidgetInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.shortcuts.ShortcutKey; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.function.Consumer; + +/** + * Model helper for app predictions in workspace + */ +public class HotseatPredictionModel extends PredictionModel { + private static final String APP_LOCATION_HOTSEAT = "hotseat"; + private static final String APP_LOCATION_WORKSPACE = "workspace"; + + private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events"; + private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items"; + + + public HotseatPredictionModel(Context context) { } + + /** + * Creates and returns bundle using workspace items and cached items + */ + public void createBundle(Consumer cb) { + LauncherAppState appState = LauncherAppState.getInstance(mContext); + appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + Bundle bundle = new Bundle(); + ArrayList events = new ArrayList<>(); + ArrayList workspaceItems = new ArrayList<>(dataModel.workspaceItems); + workspaceItems.addAll(dataModel.appWidgets); + for (ItemInfo item : workspaceItems) { + AppTarget target = getAppTargetFromInfo(item); + if (target != null && !isTrackedForPrediction(item)) continue; + events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item)); + } + ArrayList currentTargets = new ArrayList<>(); + for (ItemInfo itemInfo : dataModel.cachedPredictedItems) { + AppTarget target = getAppTargetFromInfo(itemInfo); + if (target != null) currentTargets.add(target); + } + bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events); + bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets); + MAIN_EXECUTOR.execute(() -> cb.accept(bundle)); + } + }); + } + + /** + * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null + * if item is not supported prediction + */ + public AppTarget getAppTargetFromInfo(ItemInfo info) { + if (info == null) return null; + if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET + && info instanceof LauncherAppWidgetInfo + && ((LauncherAppWidgetInfo) info).providerName != null) { + ComponentName cn = ((LauncherAppWidgetInfo) info).providerName; + return new AppTarget.Builder(new AppTargetId("widget:" + cn.getPackageName()), + cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); + } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && info.getTargetComponent() != null) { + ComponentName cn = info.getTargetComponent(); + return new AppTarget.Builder(new AppTargetId("app:" + cn.getPackageName()), + cn.getPackageName(), info.user).setClassName(cn.getClassName()).build(); + } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT + && info instanceof WorkspaceItemInfo) { + ShortcutKey shortcutKey = ShortcutKey.fromItemInfo(info); + //TODO: switch to using full shortcut info + return new AppTarget.Builder(new AppTargetId("shortcut:" + shortcutKey.getId()), + shortcutKey.componentName.getPackageName(), shortcutKey.user).build(); + } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) { + return new AppTarget.Builder(new AppTargetId("folder:" + info.id), + mContext.getPackageName(), info.user).build(); + } + return null; + } + + + /** + * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item + * location using {@link ItemInfo} + */ + public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) { + String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]", + info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE, + info.screenId, info.cellX, info.cellY, info.spanX, info.spanY); + return new AppTargetEvent.Builder(target, action).setLaunchLocation(location).build(); + } + + /** + * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors + */ + public static boolean isTrackedForPrediction(ItemInfo info) { + return info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT || ( + info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP + && info.screenId == Workspace.FIRST_SCREEN_ID); + } +} 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 70addb5a39..ed831ab03a 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 @@ -101,7 +101,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - onStateOrResumeChanged(); + onStateOrResumeChanging(false /* inTransition */); } @Override @@ -116,11 +116,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override protected void onActivityFlagsChanged(int changeBits) { super.onActivityFlagsChanged(changeBits); - if ((changeBits & (ACTIVITY_STATE_DEFERRED_RESUMED | ACTIVITY_STATE_STARTED - | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0 - && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) { - onStateOrResumeChanged(); + | ACTIVITY_STATE_USER_ACTIVE | ACTIVITY_STATE_TRANSITION_ACTIVE)) != 0) { + onStateOrResumeChanging((getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0); } if (mHotseatPredictionController != null && ((changeBits & ACTIVITY_STATE_STARTED) != 0 @@ -165,14 +163,16 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { /** * Recents logic that triggers when launcher state changes or launcher activity stops/resumes. */ - private void onStateOrResumeChanged() { + private void onStateOrResumeChanging(boolean inTransition) { LauncherState state = getStateManager().getState(); DeviceProfile profile = getDeviceProfile(); - boolean visible = (state == NORMAL || state == OVERVIEW) && isUserActive() + boolean willUserBeActive = (getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0; + boolean visible = (state == NORMAL || state == OVERVIEW) + && (willUserBeActive || isUserActive()) && !profile.isVerticalBarLayout(); UiThreadHelper.runAsyncCommand(this, SET_SHELF_HEIGHT, visible ? 1 : 0, profile.hotseatBarSizePx); - if (state == NORMAL) { + if (state == NORMAL && !inTransition) { ((RecentsView) getOverviewPanel()).setSwipeDownShouldLaunchApp(false); } } @@ -218,7 +218,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { break; } case OVERVIEW_STATE_ORDINAL: { - DiscoveryBounce.showForOverviewIfNeeded(this); + RecentsView recentsView = getOverviewPanel(); + DiscoveryBounce.showForOverviewIfNeeded(this, + recentsView.getPagedOrientationHandler()); RecentsView rv = getOverviewPanel(); sendCustomAccessibilityEvent( rv.getPageAt(rv.getCurrentPage()), TYPE_VIEW_FOCUSED, null); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java index a1cc60ec71..085b9b3af9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java @@ -71,7 +71,7 @@ public final class RecentsViewStateController extends builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData); mRecentsView.updateEmptyMessage(); } else { - builder.getAnim().addListener( + builder.addListener( AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals)); } 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 0be248691e..d5b06871b9 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 @@ -16,7 +16,6 @@ package com.android.launcher3.uioverrides.states; import android.content.Context; -import android.content.res.Resources; import android.graphics.Rect; import com.android.launcher3.BaseDraggingActivity; @@ -58,8 +57,6 @@ public class OverviewModalTaskState extends OverviewState { } public static float[] getOverviewScaleAndOffsetForModalState(BaseDraggingActivity activity) { - Resources res = activity.getResources(); - Rect out = new Rect(); activity.getOverviewPanel().getTaskSize(out); int taskHeight = out.height(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index 9f31608ad7..9b4e8906a1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -19,6 +19,8 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL_2; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; +import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; +import static com.android.quickstep.SysUINavigationMode.getMode; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; import android.content.Context; @@ -127,7 +129,11 @@ public class OverviewState extends LauncherState { @Override public int getVisibleElements(Launcher launcher) { - if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) { + RecentsView recentsView = launcher.getOverviewPanel(); + boolean hideShelfTwoButtonLandscape = getMode(launcher) == TWO_BUTTONS && + !recentsView.getPagedOrientationHandler().isLayoutNaturalToLauncher(); + if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) || + hideShelfTwoButtonLandscape) { return OVERVIEW_BUTTONS; } else if (launcher.getDeviceProfile().isVerticalBarLayout()) { return VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java index 1dd5fb7a57..dc8fb9e124 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -18,49 +18,47 @@ package com.android.quickstep; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; +import static com.android.launcher3.anim.Interpolators.clampToProgress; import static com.android.launcher3.statehandlers.DepthController.DEPTH; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; -import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.Animator; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.graphics.Rect; import android.util.Log; -import android.view.View; +import android.view.animation.Interpolator; import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statehandlers.DepthController; -import com.android.quickstep.util.AppWindowAnimationHelper; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.quickstep.util.RemoteAnimationProvider; +import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; -import com.android.systemui.shared.system.TransactionCompat; /** * Provider for the atomic (for 3-button mode) remote window animation from the app to the overview. * * @param activity that contains the overview */ -final class AppToOverviewAnimationProvider extends +final class AppToOverviewAnimationProvider> extends RemoteAnimationProvider { private static final long RECENTS_LAUNCH_DURATION = 250; private static final String TAG = "AppToOverviewAnimationProvider"; - private final BaseActivityInterface mActivityInterface; + private final BaseActivityInterface mActivityInterface; // The id of the currently running task that is transitioning to overview. private final int mTargetTaskId; private T mActivity; private RecentsView mRecentsView; - AppToOverviewAnimationProvider(BaseActivityInterface activityInterface, int targetTaskId) { + AppToOverviewAnimationProvider( + BaseActivityInterface activityInterface, int targetTaskId) { mActivityInterface = activityInterface; mTargetTaskId = targetTaskId; } @@ -96,32 +94,25 @@ final class AppToOverviewAnimationProvider exten @Override public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { - if (mRecentsView != null) { - mRecentsView.setRunningTaskIconScaledDown(true); + PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); + if (mActivity == null) { + Log.e(TAG, "Animation created, before activity"); + return pa.buildAnim(); } - AnimatorSet anim = new AnimatorSet(); - anim.addListener(new AnimationSuccessListener() { + mRecentsView.setRunningTaskIconScaledDown(true); + pa.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { mActivityInterface.onSwipeUpToRecentsComplete(); - if (mRecentsView != null) { - mRecentsView.animateUpRunningTaskIconScale(); - } + mRecentsView.animateUpRunningTaskIconScale(); } }); - if (mActivity == null) { - Log.e(TAG, "Animation created, before activity"); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)); - return anim; - } DepthController depthController = mActivityInterface.getDepthController(); if (depthController != null) { - anim.play(ObjectAnimator.ofFloat(depthController, DEPTH, - BACKGROUND_APP.getDepth(mActivity), - OVERVIEW.getDepth(mActivity)) - .setDuration(RECENTS_LAUNCH_DURATION)); + pa.addFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mActivity), + OVERVIEW.getDepth(mActivity), TOUCH_RESPONSE_INTERPOLATOR); } RemoteAnimationTargets targets = new RemoteAnimationTargets(appTargets, @@ -131,53 +122,39 @@ final class AppToOverviewAnimationProvider exten RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mTargetTaskId); if (runningTaskTarget == null) { Log.e(TAG, "No closing app"); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)); - return anim; + return pa.buildAnim(); } - final AppWindowAnimationHelper clipHelper = new AppWindowAnimationHelper( - mRecentsView.getPagedViewOrientedState(), mActivity); - - // At this point, the activity is already started and laid-out. Get the home-bounds - // relative to the screen using the rootView of the activity. - int loc[] = new int[2]; - View rootView = mActivity.getRootView(); - rootView.getLocationOnScreen(loc); - Rect homeBounds = new Rect(loc[0], loc[1], - loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight()); - clipHelper.updateSource(homeBounds, runningTaskTarget); - - Rect targetRect = new Rect(); - mActivityInterface.getSwipeUpDestinationAndLength(mActivity.getDeviceProfile(), mActivity, - targetRect); - clipHelper.updateTargetRect(targetRect); - clipHelper.prepareAnimation(mActivity.getDeviceProfile()); + TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy()); + tsv.setDp(mActivity.getDeviceProfile()); + tsv.setPreview(runningTaskTarget); + tsv.setLayoutRotation(mRecentsView.getPagedViewOrientedState().getTouchRotation(), + mRecentsView.getPagedViewOrientedState().getDisplayRotation()); TransformParams params = new TransformParams() - .setSyncTransactionApplier(new SyncRtSurfaceTransactionApplierCompat(rootView)); - ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); - valueAnimator.setDuration(RECENTS_LAUNCH_DURATION); - valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR); - valueAnimator.addUpdateListener((v) -> { - params.setProgress((float) v.getAnimatedValue()).setTargetSet(targets); - clipHelper.applyTransform(params); - }); + .setTargetSet(targets) + .setSyncTransactionApplier( + new SyncRtSurfaceTransactionApplierCompat(mActivity.getRootView())); + AnimatedFloat recentsAlpha = new AnimatedFloat(() -> { }); + params.setBaseAlphaCallback((t, a) -> recentsAlpha.value); + + Interpolator taskInterpolator; if (targets.isAnimatingHome()) { - // If we are animating home, fade in the opening targets - RemoteAnimationTargets openingSet = new RemoteAnimationTargets(appTargets, - wallpaperTargets, MODE_OPENING); - - TransactionCompat transaction = new TransactionCompat(); - valueAnimator.addUpdateListener((v) -> { - for (RemoteAnimationTargetCompat app : openingSet.apps) { - transaction.setAlpha(app.leash, (float) v.getAnimatedValue()); - } - transaction.apply(); - }); + taskInterpolator = TOUCH_RESPONSE_INTERPOLATOR; + pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, TOUCH_RESPONSE_INTERPOLATOR); + } else { + // When animation from app to recents, the recents layer is drawn on top of the app. To + // prevent the overlap, we animate the task first and then quickly fade in the recents. + taskInterpolator = clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0, 0.8f); + pa.addFloat(recentsAlpha, AnimatedFloat.VALUE, 0, 1, + clampToProgress(TOUCH_RESPONSE_INTERPOLATOR, 0.8f, 1)); } - anim.play(valueAnimator); - return anim; + + pa.addFloat(params, TransformParams.PROGRESS, 0, 1, taskInterpolator); + tsv.addAppToOverviewAnim(pa, taskInterpolator); + pa.addOnFrameCallback(() -> tsv.apply(params)); + return pa.buildAnim(); } /** 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 76c6060e8b..614ba46503 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -15,8 +15,6 @@ */ package com.android.quickstep; -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; @@ -25,8 +23,6 @@ import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; @@ -36,6 +32,7 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; +import android.util.Log; import android.view.MotionEvent; import android.view.animation.Interpolator; @@ -44,23 +41,25 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.statemanager.StatefulActivity; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.VibratorWrapper; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.TransformParams.BuilderProxy; -import com.android.quickstep.util.WindowSizeStrategy; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; @@ -76,7 +75,7 @@ import java.util.function.Consumer; * Base class for swipe up handler with some utility methods */ @TargetApi(Build.VERSION_CODES.Q) -public abstract class BaseSwipeUpHandler +public abstract class BaseSwipeUpHandler, Q extends RecentsView> implements RecentsAnimationListener { private static final String TAG = "BaseSwipeUpHandler"; @@ -97,7 +96,7 @@ public abstract class BaseSwipeUpHandler mActivityInterface; + protected final BaseActivityInterface mActivityInterface; protected final InputConsumerController mInputConsumer; protected final TaskViewSimulator mTaskViewSimulator; @@ -132,15 +131,14 @@ public abstract class BaseSwipeUpHandler t * mDragLengthFactor); - anim.play(ObjectAnimator.ofFloat(mTaskViewSimulator.recentsViewScale, - AnimatedFloat.VALUE, - mTaskViewSimulator.getFullScreenScale(), 1)); - anim.play(ObjectAnimator.ofFloat(mTaskViewSimulator.fullScreenProgress, - AnimatedFloat.VALUE, - BACKGROUND_APP.getOverviewFullscreenProgress(), - OVERVIEW.getOverviewFullscreenProgress())); - mWindowTransitionController = - AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2); + PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2); + mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor); + mWindowTransitionController = pa.createPlaybackController(); } /** @@ -400,7 +389,16 @@ public abstract class BaseSwipeUpHandler { +public final class FallbackActivityInterface extends + BaseActivityInterface { - public FallbackActivityInterface() { } + public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface(); - @Override - public void onTransitionCancelled(boolean activityVisible) { - // TODO: + private FallbackActivityInterface() { + super(false); } @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) { - FALLBACK_RECENTS_SIZE_STRATEGY.calculateTaskSize(context, dp, outRect); + calculateTaskSize(context, dp, outRect); if (dp.isVerticalBarLayout() && SysUINavigationMode.INSTANCE.get(context).getMode() != NO_BUTTON) { Rect targetInsets = dp.getInsets(); @@ -69,17 +70,6 @@ public final class FallbackActivityInterface implements } } - @Override - public void onSwipeUpToRecentsComplete() { - RecentsActivity activity = getCreatedActivity(); - if (activity == null) { - return; - } - RecentsView recentsView = activity.getOverviewPanel(); - recentsView.getClearAllButton().setVisibilityAlpha(1); - recentsView.setDisallowScrollToClearAll(false); - } - @Override public void onAssistantVisibilityChanged(float visibility) { // This class becomes active when the screen is locked. @@ -198,11 +188,14 @@ public final class FallbackActivityInterface implements } @Override - public void onLaunchTaskSuccess() { - RecentsActivity activity = getCreatedActivity(); - if (activity == null) { - return; - } - activity.onTaskLaunched(); + public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) { + out.set(dp.widthPx, dp.heightPx); + } + + @Override + protected float getExtraSpace(Context context, DeviceProfile dp) { + return showOverviewActions(context) + ? context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height) + : 0; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java index 77e50caae7..db41bd6fd4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -24,7 +24,6 @@ import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID; import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL; -import static com.android.quickstep.util.WindowSizeStrategy.FALLBACK_RECENTS_SIZE_STRATEGY; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; import android.animation.Animator; @@ -114,7 +113,7 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler { +public final class LauncherActivityInterface extends + BaseActivityInterface { + + public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface(); + + private LauncherActivityInterface() { + super(true); + } @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) { - LAUNCHER_ACTIVITY_SIZE_STRATEGY.calculateTaskSize(context, dp, outRect); + calculateTaskSize(context, dp, outRect); if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) { Rect targetInsets = dp.getInsets(); int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right; @@ -83,25 +95,15 @@ public final class LauncherActivityInterface implements BaseActivityInterface implements Runnable { + private class RecentsActivityCommand> implements Runnable { - protected final BaseActivityInterface mActivityInterface; + protected final BaseActivityInterface mActivityInterface; private final long mCreateTime; private final AppToOverviewAnimationProvider mAnimationProvider; 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 03d522e85d..a4670fd40b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -261,10 +261,6 @@ public final class RecentsActivity extends StatefulActivity { AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL); } - public void onTaskLaunched() { - mFallbackRecentsView.resetTaskVisuals(); - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 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 258d60c208..8bffc7805c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -63,6 +63,7 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.provider.RestoreDbTask; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.tracing.nano.LauncherTraceProto; @@ -539,8 +540,6 @@ public class TouchInteractionService extends Service implements PluginListener activityInterface = + final BaseActivityInterface activityInterface = mOverviewComponentObserver.getActivityInterface(); final Intent overviewIntent = new Intent( mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState()); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java index 1ab317b9eb..f958e6d734 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -17,7 +17,6 @@ package com.android.quickstep.fallback; import static com.android.quickstep.fallback.RecentsState.DEFAULT; import static com.android.quickstep.fallback.RecentsState.MODAL_TASK; -import static com.android.quickstep.util.WindowSizeStrategy.FALLBACK_RECENTS_SIZE_STRATEGY; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; @@ -26,6 +25,7 @@ import android.os.Build; import android.util.AttributeSet; import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.quickstep.FallbackActivityInterface; import com.android.quickstep.RecentsActivity; import com.android.quickstep.views.OverviewActionsView; import com.android.quickstep.views.RecentsView; @@ -45,7 +45,7 @@ public class FallbackRecentsView extends RecentsView } public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr, FALLBACK_RECENTS_SIZE_STRATEGY); + super(context, attrs, defStyleAttr, FallbackActivityInterface.INSTANCE); mActivity.getStateManager().addStateListener(this); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index adf19df39f..abe4af4705 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -25,43 +25,45 @@ import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVI import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; -import android.content.ComponentName; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.content.Context; import android.content.Intent; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; -import android.graphics.Rect; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import com.android.launcher3.R; -import com.android.launcher3.Utilities; +import com.android.launcher3.anim.Interpolators; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.DefaultDisplay; +import com.android.quickstep.AnimatedFloat; import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; -import com.android.quickstep.LockScreenRecentsActivity; import com.android.quickstep.MultiStateCallback; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationDeviceState; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.TaskAnimationManager; -import com.android.quickstep.util.AppWindowAnimationHelper; import com.android.quickstep.util.TransformParams; +import com.android.quickstep.util.TransformParams.BuilderProxy; import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder; /** * A dummy input consumer used when the device is still locked, e.g. from secure camera. */ public class DeviceLockedInputConsumer implements InputConsumer, - RecentsAnimationCallbacks.RecentsAnimationListener { - - private static final float SCALE_DOWN = 0.75f; + RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy { private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null; private static int getFlagForIndex(int index, String name) { @@ -84,18 +86,20 @@ public class DeviceLockedInputConsumer implements InputConsumer, private final InputMonitorCompat mInputMonitorCompat; private final PointF mTouchDown = new PointF(); - private final AppWindowAnimationHelper mAppWindowAnimationHelper; private final TransformParams mTransformParams; - private final Point mDisplaySize; private final MultiStateCallback mStateCallback; + private final Point mDisplaySize; + private final Matrix mMatrix = new Matrix(); + private final float mMaxTranslationY; + private VelocityTracker mVelocityTracker; - private float mProgress; + private final AnimatedFloat mProgress = new AnimatedFloat(this::applyTransform); private boolean mThresholdCrossed = false; + private boolean mHomeLaunched = false; private RecentsAnimationController mRecentsAnimationController; - private RecentsAnimationTargets mRecentsAnimationTargets; public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, @@ -105,9 +109,10 @@ public class DeviceLockedInputConsumer implements InputConsumer, mTaskAnimationManager = taskAnimationManager; mGestureState = gestureState; mTouchSlopSquared = squaredTouchSlop(context); - mAppWindowAnimationHelper = new AppWindowAnimationHelper(context); mTransformParams = new TransformParams(); mInputMonitorCompat = inputMonitorCompat; + mMaxTranslationY = context.getResources().getDimensionPixelSize( + R.dimen.device_locked_y_offset); // Do not use DeviceProfile as the user data might be locked mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize; @@ -158,9 +163,7 @@ public class DeviceLockedInputConsumer implements InputConsumer, } } else { float dy = Math.max(mTouchDown.y - y, 0); - mProgress = dy / mDisplaySize.y; - mTransformParams.setProgress(mProgress); - mAppWindowAnimationHelper.applyTransform(mTransformParams); + mProgress.updateValue(dy / mDisplaySize.y); } break; } @@ -176,7 +179,6 @@ public class DeviceLockedInputConsumer implements InputConsumer, * the animation can still be running. */ private void finishTouchTracking(MotionEvent ev) { - mStateCallback.setState(STATE_HANDLER_INVALIDATED); if (mThresholdCrossed && ev.getAction() == ACTION_UP) { mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity()); @@ -190,14 +192,29 @@ public class DeviceLockedInputConsumer implements InputConsumer, // Is fling dismissTask = velocityY < 0; } else { - dismissTask = mProgress >= (1 - MIN_PROGRESS_FOR_OVERVIEW); - } - if (dismissTask) { - // For now, just start the home intent so user is prompted to unlock the device. - mContext.startActivity(new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_HOME) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + dismissTask = mProgress.value >= (1 - MIN_PROGRESS_FOR_OVERVIEW); } + + // Animate back to fullscreen before finishing + ObjectAnimator animator = mProgress.animateToValue(mProgress.value, 0); + animator.setDuration(100); + animator.setInterpolator(Interpolators.ACCEL); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (dismissTask) { + // For now, just start the home intent so user is prompted to unlock the device. + mContext.startActivity(new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + mHomeLaunched = true; + } + mStateCallback.setState(STATE_HANDLER_INVALIDATED); + } + }); + animator.start(); + } else { + mStateCallback.setState(STATE_HANDLER_INVALIDATED); } mVelocityTracker.recycle(); mVelocityTracker = null; @@ -205,13 +222,11 @@ public class DeviceLockedInputConsumer implements InputConsumer, private void startRecentsTransition() { mThresholdCrossed = true; + mHomeLaunched = false; TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers"); mInputMonitorCompat.pilferPointers(); - Intent intent = new Intent(Intent.ACTION_MAIN) - .addCategory(Intent.CATEGORY_DEFAULT) - .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK) + Intent intent = mGestureState.getHomeIntent() .putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId()); mTaskAnimationManager.startRecentsAnimation(mGestureState, intent, this); } @@ -220,37 +235,40 @@ public class DeviceLockedInputConsumer implements InputConsumer, public void onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets) { mRecentsAnimationController = controller; - mRecentsAnimationTargets = targets; - - Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y); - RemoteAnimationTargetCompat targetCompat = targets.findTask( - mGestureState.getRunningTaskId()); - if (targetCompat != null) { - mAppWindowAnimationHelper.updateSource(displaySize, targetCompat); - } - - Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN); - displaySize.offsetTo(displaySize.left, 0); - mTransformParams.setTargetSet(mRecentsAnimationTargets); - mAppWindowAnimationHelper.updateTargetRect(displaySize); - mAppWindowAnimationHelper.applyTransform(mTransformParams); - + mTransformParams.setTargetSet(targets); + applyTransform(); mStateCallback.setState(STATE_TARGET_RECEIVED); } @Override public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { mRecentsAnimationController = null; - mRecentsAnimationTargets = null; + mTransformParams.setTargetSet(null); } private void endRemoteAnimation() { - if (mRecentsAnimationController != null) { + if (mHomeLaunched) { + ActivityManagerWrapper.getInstance().cancelRecentsAnimation(false); + } else if (mRecentsAnimationController != null) { mRecentsAnimationController.finishController( false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */); } } + private void applyTransform() { + mTransformParams.setProgress(mProgress.value); + if (mTransformParams.getTargetSet() != null) { + mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); + } + } + + @Override + public void onBuildTargetParams( + Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { + mMatrix.setTranslate(0, mProgress.value * mMaxTranslationY); + builder.withMatrix(mMatrix); + } + @Override public void onConsumerAboutToBeSwitched() { mStateCallback.setState(STATE_HANDLER_INVALIDATED); 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 1f6c506d3a..6b0d7a3e87 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 @@ -26,6 +26,7 @@ import static android.view.MotionEvent.INVALID_POINTER_ID; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS; +import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; @@ -430,6 +431,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC @Override public boolean allowInterceptByParent() { - return !mPassedPilferInputSlop; + return !mPassedPilferInputSlop || mGestureState.hasState(STATE_OVERSCROLL_WINDOW_CREATED); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java index c49b8f2d14..fb420a272a 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java @@ -24,12 +24,15 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; +import static java.lang.Math.abs; + import android.content.Context; import android.graphics.PointF; -import android.view.GestureDetector; +import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.BaseDraggingActivity; @@ -44,24 +47,36 @@ import com.android.systemui.shared.system.InputMonitorCompat; * Input consumer for handling events to pass to an {@code OverscrollPlugin}. */ public class OverscrollInputConsumer extends DelegateInputConsumer { - private static final String TAG = "OverscrollInputConsumer"; + private static final boolean DEBUG_LOGS_ENABLED = false; + private static void debugPrint(String log) { + if (DEBUG_LOGS_ENABLED) { + Log.v(TAG, log); + } + } private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); private final PointF mStartDragPos = new PointF(); private final int mAngleThreshold; - private final float mFlingThresholdPx; + private final int mFlingDistanceThresholdPx; + private final int mFlingVelocityThresholdPx; private int mActivePointerId = -1; private boolean mPassedSlop = false; - + // True if we set ourselves as active, meaning we no longer pass events to the delegate. + private boolean mPassedActiveThreshold = false; + // When a gesture crosses this length, this recognizer will attempt to interpret touch events. private final float mSquaredSlop; + // When a gesture crosses this length, this recognizer will become the sole active recognizer. + private final float mSquaredActiveThreshold; + // When a gesture crosses this length, the overscroll view should be shown. + private final float mSquaredFinishThreshold; + private boolean mThisDownIsIgnored = false; private final GestureState mGestureState; @Nullable private final OverscrollPlugin mPlugin; - private final GestureDetector mGestureDetector; @Nullable private RecentsView mRecentsView; @@ -72,15 +87,24 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { mAngleThreshold = context.getResources() .getInteger(R.integer.assistant_gesture_corner_deg_threshold); - mFlingThresholdPx = context.getResources() - .getDimension(R.dimen.gestures_overscroll_fling_threshold); + mFlingDistanceThresholdPx = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_fling_threshold); + mFlingVelocityThresholdPx = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); mGestureState = gestureState; mPlugin = plugin; float slop = ViewConfiguration.get(context).getScaledTouchSlop(); mSquaredSlop = slop * slop; - mGestureDetector = new GestureDetector(context, new FlingGestureListener()); + + + float finishGestureThreshold = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_finish_threshold); + mSquaredFinishThreshold = finishGestureThreshold * finishGestureThreshold; + + float activeThreshold = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_active_threshold); + mSquaredActiveThreshold = activeThreshold * activeThreshold; } @Override @@ -90,12 +114,26 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { @Override public void onMotionEvent(MotionEvent ev) { + if (mPlugin == null) { + return; + } + + debugPrint("got event, underlying activity is " + getUnderlyingActivity()); switch (ev.getActionMasked()) { case ACTION_DOWN: { + debugPrint("ACTION_DOWN"); mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); - + if (mPlugin.blockOtherGestures()) { + debugPrint("mPlugin.blockOtherGestures(), becoming active on ACTION_DOWN"); + // Otherwise, if an appear gesture is performed when the Activity is visible, + // the Activity will dismiss its keyboard. + mPassedActiveThreshold = true; + mPassedSlop = true; + mStartDragPos.set(mLastPos.x, mLastPos.y); + setActive(ev); + } break; } case ACTION_POINTER_DOWN: { @@ -121,57 +159,61 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { if (mState == STATE_DELEGATE_ACTIVE) { break; } + if (!mDelegate.allowInterceptByParent()) { mState = STATE_DELEGATE_ACTIVE; break; } + + // Update last touch position. int pointerIndex = ev.findPointerIndex(mActivePointerId); if (pointerIndex == -1) { break; } mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); - if (!mPassedSlop) { - // Normal gesture, ensure we pass the slop before we start tracking the gesture - if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) - > mSquaredSlop) { - - mPassedSlop = true; - mStartDragPos.set(mLastPos.x, mLastPos.y); - if (isOverscrolled()) { - setActive(ev); - - if (mPlugin != null) { - mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity()); - } - } else { - mState = STATE_DELEGATE_ACTIVE; - } - } + float squaredDist = squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y); + if ((!mPassedSlop) && (squaredDist > mSquaredSlop)) { + mPassedSlop = true; + mStartDragPos.set(mLastPos.x, mLastPos.y); + mGestureState.setState(GestureState.STATE_OVERSCROLL_WINDOW_CREATED); } - if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled() - && mPlugin != null) { - mPlugin.onTouchTraveled(getDistancePx()); + boolean becomeActive = mPassedSlop && !mPassedActiveThreshold && isOverscrolled() + && (squaredDist > mSquaredActiveThreshold); + if (becomeActive) { + debugPrint("Past slop and past threshold, set active"); + mPassedActiveThreshold = true; + setActive(ev); + } + + if (mPassedActiveThreshold) { + debugPrint("ACTION_MOVE Relaying touch event"); + mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(), + (int) Math.sqrt(mSquaredFinishThreshold), mFlingDistanceThresholdPx, + mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity()); } break; } case ACTION_CANCEL: case ACTION_UP: - if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) { - mPlugin.onTouchEnd(getDistancePx()); + debugPrint("ACTION_UP"); + if (mPassedActiveThreshold) { + debugPrint("ACTION_UP Relaying touch event"); + + mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(), + (int) Math.sqrt(mSquaredFinishThreshold), mFlingDistanceThresholdPx, + mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity()); } + mPassedSlop = false; + mPassedActiveThreshold = false; mState = STATE_INACTIVE; break; } - if (mState != STATE_DELEGATE_ACTIVE) { - mGestureDetector.onTouchEvent(ev); - } - if (mState != STATE_ACTIVE) { mDelegate.onMotionEvent(ev); } @@ -192,15 +234,20 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { maxIndex = 1; } - boolean atRightMostApp = (mRecentsView == null - || mRecentsView.getRunningTaskIndex() <= maxIndex); + boolean atRightMostApp = mRecentsView == null + || (mRecentsView.getRunningTaskIndex() <= maxIndex); // Check if the gesture is within our angle threshold of horizontal - float deltaY = Math.abs(mLastPos.y - mDownPos.y); - float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left - boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold; + float deltaY = abs(mLastPos.y - mDownPos.y); + float deltaX = mLastPos.x - mDownPos.x; - return atRightMostApp && angleInBounds; + boolean angleInBounds = (Math.toDegrees(Math.atan2(deltaY, abs(deltaX))) < mAngleThreshold); + + boolean overscrollVisible = mPlugin.blockOtherGestures(); + boolean overscrollInvisibleAndLeftSwipe = !overscrollVisible && deltaX < 0; + boolean gestureDirectionMatchesVisibility = overscrollVisible + || overscrollInvisibleAndLeftSwipe; + return atRightMostApp && angleInBounds && gestureDirectionMatchesVisibility; } private String getDeviceState() { @@ -219,35 +266,22 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { return deviceState; } - private int getDistancePx() { - return (int) Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y); + private int getHorizontalDistancePx() { + return (int) (mLastPos.x - mDownPos.x); } - private String getUnderlyingActivity() { + private int getVerticalDistancePx() { + return (int) (mLastPos.y - mDownPos.y); + } + + private @NonNull String getUnderlyingActivity() { + // Overly defensive, got guidance on code review that something in the chain of + // `mGestureState.getRunningTask().topActivity` can be null and thus cause a null pointer + // exception to be thrown, but we aren't sure which part can be null. + if ((mGestureState == null) || (mGestureState.getRunningTask() == null) + || (mGestureState.getRunningTask().topActivity == null)) { + return ""; + } return mGestureState.getRunningTask().topActivity.flattenToString(); } - - private class FlingGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (isValidAngle(velocityX, -velocityY) - && getDistancePx() >= mFlingThresholdPx - && mState != STATE_DELEGATE_ACTIVE) { - - if (mPlugin != null) { - mPlugin.onFling(-velocityX); - } - } - return true; - } - - private boolean isValidAngle(float deltaX, float deltaY) { - float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX)); - // normalize so that angle is measured clockwise from horizontal in the bottom right - // corner and counterclockwise from horizontal in the bottom left corner - - angle = angle > 90 ? 180 - angle : angle; - return (angle < mAngleThreshold); - } - } } 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 b7af8acb7a..2805f623ac 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 @@ -24,8 +24,8 @@ import android.view.MotionEvent; import androidx.annotation.Nullable; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.Utilities; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.views.BaseDragLayer; @@ -41,11 +41,11 @@ import java.util.function.Predicate; /** * Input consumer for handling touch on the recents/Launcher activity. */ -public class OverviewInputConsumer +public class OverviewInputConsumer> implements InputConsumer { private final T mActivity; - private final BaseActivityInterface mActivityInterface; + private final BaseActivityInterface mActivityInterface; private final BaseDragLayer mTarget; private final InputMonitorCompat mInputMonitor; diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java index 110370a722..b743d3fa0b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java @@ -77,7 +77,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { private final Matrix mTmpMatrix = new Matrix(); private final Rect mTmpRect = new Rect(); private final RectF mTmpRectF = new RectF(); - private final RectF mCurrentRectWithInsets = new RectF(); private RecentsOrientedState mOrientedState; // Corner radius of windows, in pixels private final float mWindowCornerRadius; @@ -88,9 +87,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { // Whether or not to actually use the rounded cornders on windows private boolean mUseRoundedCornersOnWindows; - // Corner radius currently applied to transformed window. - private float mCurrentCornerRadius; - public AppWindowAnimationHelper(RecentsOrientedState orientedState, Context context) { Resources res = context.getResources(); mOrientedState = orientedState; @@ -100,24 +96,11 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { mUseRoundedCornersOnWindows = mSupportsRoundedCornersOnWindows; } - public AppWindowAnimationHelper(Context context) { - this(null, context); - } - private void updateSourceStack(RemoteAnimationTargetCompat target) { mSourceInsets.set(target.contentInsets); mSourceStackBounds.set(target.screenSpaceBounds); } - public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) { - updateSourceStack(target); - updateHomeBounds(homeStackBounds); - } - - public void updateHomeBounds(Rect homeStackBounds) { - mHomeStackBounds.set(homeStackBounds); - } - public void updateTargetRect(Rect targetRect) { mSourceRect.set(mSourceInsets.left, mSourceInsets.top, mSourceStackBounds.width() - mSourceInsets.right, @@ -202,7 +185,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { cornerRadius = mapRange(boundToRange(params.getProgress(), 0, 1), windowCornerRadius, mTaskCornerRadius); } - mCurrentCornerRadius = cornerRadius; } builder.withMatrix(mTmpMatrix) @@ -238,11 +220,6 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress); } - public RectF getCurrentRectWithInsets() { - mTmpMatrix.mapRect(mCurrentRectWithInsets, mCurrentClipRectF); - return mCurrentRectWithInsets; - } - public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv, @Nullable RemoteAnimationTargetCompat target) { BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext()); @@ -314,8 +291,4 @@ public class AppWindowAnimationHelper implements TransformParams.BuilderProxy { return mTargetRect; } - public float getCurrentCornerRadius() { - return mCurrentCornerRadius; - } - } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java index 32fc0de0a8..3e0daaf2ea 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -212,12 +212,13 @@ public class StaggeredWorkspaceAnim { } private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) { - PendingAnimation builder = new PendingAnimation(duration, mAnimators); + PendingAnimation builder = new PendingAnimation(duration); launcher.getWorkspace().getStateTransitionAnimation().setScrim(builder, state); builder.setFloat( launcher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS, state.getOverviewScrimAlpha(launcher), ACCEL_DEACCEL); + mAnimators.play(builder.buildAnim()); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java index a204aaeb66..d7a7e4c25e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java @@ -20,6 +20,7 @@ import static com.android.launcher3.touch.PagedOrientationHandler.MATRIX_POST_TR import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation; import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN; +import android.animation.TimeInterpolator; import android.content.Context; import android.graphics.Matrix; import android.graphics.PointF; @@ -29,8 +30,10 @@ import android.graphics.RectF; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.AnimatedFloat; +import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.views.RecentsView.ScrollState; import com.android.quickstep.views.TaskThumbnailView.PreviewPositionHelper; import com.android.quickstep.views.TaskView; @@ -50,7 +53,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final RecentsOrientedState mOrientationState; private final Context mContext; - private final WindowSizeStrategy mSizeStrategy; + private final BaseActivityInterface mSizeStrategy; private final Rect mTaskRect = new Rect(); private final PointF mPivot = new PointF(); @@ -79,7 +82,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private boolean mLayoutValid = false; private boolean mScrollValid = false; - public TaskViewSimulator(Context context, WindowSizeStrategy sizeStrategy) { + public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) { mContext = context; mSizeStrategy = sizeStrategy; @@ -141,6 +144,14 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } } + /** + * Adds animation for all the components corresponding to transition from an app to overview + */ + public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) { + pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator); + pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1, interpolator); + } + /** * Returns the current clipped/visible window bounds in the window coordinate space */ @@ -247,14 +258,11 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } @Override - public void onBuildParams(Builder builder, RemoteAnimationTargetCompat app, - int targetMode, TransformParams params) { - if (app.mode == targetMode - && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { - builder.withMatrix(mMatrix) - .withWindowCrop(mTmpCropRect) - .withCornerRadius(getCurrentCornerRadius()); - } + public void onBuildTargetParams( + Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { + builder.withMatrix(mMatrix) + .withWindowCrop(mTmpCropRect) + .withCornerRadius(getCurrentCornerRadius()); } /** diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java index 83b64db024..d837e54835 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java @@ -16,6 +16,7 @@ package com.android.quickstep.util; import android.graphics.RectF; +import android.util.FloatProperty; import androidx.annotation.Nullable; @@ -29,6 +30,19 @@ import com.android.systemui.shared.system.TransactionCompat; public class TransformParams { + public static FloatProperty PROGRESS = + new FloatProperty("progress") { + @Override + public void setValue(TransformParams params, float v) { + params.setProgress(v); + } + + @Override + public Float get(TransformParams params) { + return params.getProgress(); + } + }; + private float mProgress; private @Nullable RectF mCurrentRect; private float mTargetAlpha; @@ -176,10 +190,6 @@ public class TransformParams { return mTargetSet; } - public SyncRtSurfaceTransactionApplierCompat getSyncTransactionApplier() { - return mSyncTransactionApplier; - } - public void applySurfaceParams(SurfaceParams[] params) { if (mSyncTransactionApplier != null) { mSyncTransactionApplier.scheduleApply(params); @@ -199,7 +209,15 @@ public class TransformParams { public interface BuilderProxy { - void onBuildParams(SurfaceParams.Builder builder, - RemoteAnimationTargetCompat app, int targetMode, TransformParams params); + default void onBuildParams(SurfaceParams.Builder builder, + RemoteAnimationTargetCompat app, int targetMode, TransformParams params) { + if (app.mode == targetMode + && app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { + onBuildTargetParams(builder, app, params); + } + } + + default void onBuildTargetParams(SurfaceParams.Builder builder, + RemoteAnimationTargetCompat app, TransformParams params) { } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java index 250c78bdac..3d894033d1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java @@ -25,7 +25,6 @@ import static com.android.launcher3.LauncherState.SPRING_LOADED; import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; @@ -47,6 +46,7 @@ import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.ScrimView; +import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.util.TransformParams; import com.android.systemui.plugins.PluginListener; @@ -89,7 +89,7 @@ public class LauncherRecentsView extends RecentsView } public LauncherRecentsView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr, LAUNCHER_ACTIVITY_SIZE_STRATEGY); + super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE); mActivity.getStateManager().addStateListener(this); } 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 54ed40f3e9..3fc235c2f0 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 @@ -115,6 +115,7 @@ import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.OverScroller; import com.android.launcher3.util.Themes; import com.android.launcher3.util.ViewPool; +import com.android.quickstep.BaseActivityInterface; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.RecentsModel; @@ -128,7 +129,6 @@ import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.SplitScreenBounds; import com.android.quickstep.util.TransformParams; -import com.android.quickstep.util.WindowSizeStrategy; import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; import com.android.systemui.shared.recents.model.Task; @@ -209,7 +209,7 @@ public abstract class RecentsView extends PagedView impl }; protected final RecentsOrientedState mOrientationState; - protected final WindowSizeStrategy mSizeStrategy; + protected final BaseActivityInterface mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; protected RecentsAnimationTargets mRecentsAnimationTargets; protected AppWindowAnimationHelper mAppWindowAnimationHelper; @@ -381,7 +381,7 @@ public abstract class RecentsView extends PagedView impl }; public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, - WindowSizeStrategy sizeStrategy) { + BaseActivityInterface sizeStrategy) { super(context, attrs, defStyleAttr); setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing)); setEnableFreeScroll(true); @@ -2190,6 +2190,10 @@ public abstract class RecentsView extends PagedView impl */ public void setModalStateEnabled(boolean isModalState) { } + public BaseActivityInterface getSizeStrategy() { + return mSizeStrategy; + } + /** * Used to register callbacks for when our empty message state changes. * diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 72275c81b1..f1ea6bbeb7 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -79,8 +79,13 @@ 28dp + + -80dp + 40dp + 80dp + 136dp 40dp diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index 629a74b87a..0968d8e4ad 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -304,4 +304,8 @@ public abstract class BaseQuickstepLauncher extends Launcher public ShelfPeekAnim getShelfPeekAnim() { return mShelfPeekAnim; } + + public void setHintUserWillBeActive() { + addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); + } } diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index 43328b6885..f29f0ffe30 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -15,23 +15,34 @@ */ package com.android.quickstep; +import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; +import static com.android.quickstep.SysUINavigationMode.getMode; +import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; + import android.annotation.TargetApi; import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; import android.view.MotionEvent; -import android.view.View; import android.view.animation.Interpolator; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.statehandlers.DepthController; +import com.android.launcher3.statemanager.BaseState; +import com.android.launcher3.statemanager.StatefulActivity; +import com.android.launcher3.util.WindowBounds; +import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.ShelfPeekAnim; +import com.android.quickstep.util.SplitScreenBounds; +import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -42,84 +53,234 @@ import java.util.function.Predicate; * Utility class which abstracts out the logical differences between Launcher and RecentsActivity. */ @TargetApi(Build.VERSION_CODES.P) -public interface BaseActivityInterface { +public abstract class BaseActivityInterface, + ACTIVITY_TYPE extends StatefulActivity> { - void onTransitionCancelled(boolean activityVisible); + private final PointF mTempPoint = new PointF(); + public final boolean rotationSupportedByActivity; - int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect); + protected BaseActivityInterface(boolean rotationSupportedByActivity) { + this.rotationSupportedByActivity = rotationSupportedByActivity; + } - void onSwipeUpToRecentsComplete(); + public void onTransitionCancelled(boolean activityVisible) { + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + STATE_TYPE startState = activity.getStateManager().getRestState(); + activity.getStateManager().goToState(startState, activityVisible); + } - default void onSwipeUpToHomeComplete() { } - void onAssistantVisibilityChanged(float visibility); + public abstract int getSwipeUpDestinationAndLength( + DeviceProfile dp, Context context, Rect outRect); - AnimationFactory prepareRecentsUI( + public void onSwipeUpToRecentsComplete() { + // Re apply state in case we did something funky during the transition. + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + activity.getStateManager().reapplyState(); + } + + public void onSwipeUpToHomeComplete() { } + + public abstract void onAssistantVisibilityChanged(float visibility); + + public abstract AnimationFactory prepareRecentsUI( boolean activityVisible, Consumer callback); - ActivityInitListener createActivityInitListener(Predicate onInitListener); + public abstract ActivityInitListener createActivityInitListener( + Predicate onInitListener); /** * Sets a callback to be run when an activity launch happens while launcher is not yet resumed. */ - default void setOnDeferredActivityLaunchCallback(Runnable r) {} + public void setOnDeferredActivityLaunchCallback(Runnable r) {} @Nullable - T getCreatedActivity(); + public abstract ACTIVITY_TYPE getCreatedActivity(); @Nullable - default DepthController getDepthController() { + public DepthController getDepthController() { return null; } - default boolean isResumed() { - BaseDraggingActivity activity = getCreatedActivity(); + public final boolean isResumed() { + ACTIVITY_TYPE activity = getCreatedActivity(); return activity != null && activity.hasBeenResumed(); } - default boolean isStarted() { - BaseDraggingActivity activity = getCreatedActivity(); + public final boolean isStarted() { + ACTIVITY_TYPE activity = getCreatedActivity(); return activity != null && activity.isStarted(); } @UiThread @Nullable - T getVisibleRecentsView(); + public abstract T getVisibleRecentsView(); @UiThread - boolean switchToRecentsIfVisible(Runnable onCompleteCallback); + public abstract boolean switchToRecentsIfVisible(Runnable onCompleteCallback); - Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target); + public abstract Rect getOverviewWindowBounds( + Rect homeBounds, RemoteAnimationTargetCompat target); - boolean allowMinimizeSplitScreen(); + public abstract boolean allowMinimizeSplitScreen(); - default boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { + public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { return true; } /** * Updates the prediction state to the overview state. */ - default void updateOverviewPredictionState() { - // By default overview predictions are not supported + public void updateOverviewPredictionState() { + // By public overview predictions are not supported } /** * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher} */ - int getContainerType(); + public abstract int getContainerType(); - boolean isInLiveTileMode(); + public abstract boolean isInLiveTileMode(); - void onLaunchTaskFailed(); + public abstract void onLaunchTaskFailed(); - void onLaunchTaskSuccess(); + public void onLaunchTaskSuccess() { + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + activity.getStateManager().moveToRestState(); + } - default void closeOverlay() { } + public void closeOverlay() { } - default void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, - Runnable runnable) {} + public void switchRunningTaskViewToScreenshot(ThumbnailData thumbnailData, Runnable runnable) { + ACTIVITY_TYPE activity = getCreatedActivity(); + if (activity == null) { + return; + } + RecentsView recentsView = activity.getOverviewPanel(); + if (recentsView == null) { + if (runnable != null) { + runnable.run(); + } + return; + } + recentsView.switchToScreenshot(thumbnailData, runnable); + } - interface AnimationFactory { + public void setHintUserWillBeActive() {} + + /** + * Sets the expected window size in multi-window mode + */ + public abstract void getMultiWindowSize(Context context, DeviceProfile dp, PointF out); + + /** + * Calculates the taskView size for the provided device configuration + */ + public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) { + calculateTaskSize(context, dp, getExtraSpace(context, dp), outRect); + } + + protected abstract float getExtraSpace(Context context, DeviceProfile dp); + + private void calculateTaskSize( + Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect) { + Resources res = context.getResources(); + final boolean showLargeTaskSize = showOverviewActions(context); + + final int paddingResId; + if (dp.isMultiWindowMode) { + paddingResId = R.dimen.multi_window_task_card_horz_space; + } else if (dp.isVerticalBarLayout()) { + paddingResId = R.dimen.landscape_task_card_horz_space; + } else if (showLargeTaskSize) { + paddingResId = R.dimen.portrait_task_card_horz_space_big_overview; + } else { + paddingResId = R.dimen.portrait_task_card_horz_space; + } + float paddingHorz = res.getDimension(paddingResId); + float paddingVert = showLargeTaskSize + ? 0 : res.getDimension(R.dimen.task_card_vert_space); + + calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, + res.getDimension(R.dimen.task_thumbnail_top_margin), outRect); + } + + private void calculateTaskSizeInternal(Context context, DeviceProfile dp, + float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin, + Rect outRect) { + float taskWidth, taskHeight; + Rect insets = dp.getInsets(); + if (dp.isMultiWindowMode) { + WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context); + taskWidth = bounds.availableSize.x; + taskHeight = bounds.availableSize.y; + } else { + taskWidth = dp.availableWidthPx; + taskHeight = dp.availableHeightPx; + } + + // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless + // we override the insets ourselves. + int launcherVisibleWidth = dp.widthPx - insets.left - insets.right; + int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom; + + float availableHeight = launcherVisibleHeight + - topIconMargin - extraVerticalSpace - paddingVert; + float availableWidth = launcherVisibleWidth - paddingHorz; + + float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight); + float outWidth = scale * taskWidth; + float outHeight = scale * taskHeight; + + // Center in the visible space + float x = insets.left + (launcherVisibleWidth - outWidth) / 2; + float y = insets.top + Math.max(topIconMargin, + (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2); + outRect.set(Math.round(x), Math.round(y), + Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight)); + } + + /** + * Calculates the modal taskView size for the provided device configuration + */ + public void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) { + float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode + ? R.dimen.multi_window_task_card_horz_space + : dp.isVerticalBarLayout() + ? R.dimen.landscape_task_card_horz_space + : R.dimen.portrait_modal_task_card_horz_space); + float extraVerticalSpace = getOverviewActionsHeight(context); + float paddingVert = 0; + float topIconMargin = 0; + calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, + topIconMargin, outRect); + } + + /** Gets the space that the overview actions will take, including margins. */ + public float getOverviewActionsHeight(Context context) { + Resources res = context.getResources(); + float actionsBottomMargin = 0; + if (getMode(context) == Mode.THREE_BUTTONS) { + actionsBottomMargin = res.getDimensionPixelSize( + R.dimen.overview_actions_bottom_margin_three_button); + } else { + actionsBottomMargin = res.getDimensionPixelSize( + R.dimen.overview_actions_bottom_margin_gesture); + } + float overviewActionsHeight = actionsBottomMargin + + res.getDimensionPixelSize(R.dimen.overview_actions_height); + return overviewActionsHeight; + } + + public interface AnimationFactory { default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { } @@ -137,4 +298,8 @@ public interface BaseActivityInterface { */ default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { } } + + protected static boolean showOverviewActions(Context context) { + return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context); + } } diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index 9b515ae788..016ffea2d7 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -17,10 +17,12 @@ package com.android.quickstep; import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; +import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Intent; +import android.os.Build; -import com.android.launcher3.BaseDraggingActivity; +import com.android.launcher3.statemanager.StatefulActivity; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -32,6 +34,7 @@ import java.util.ArrayList; * Manages the state for an active system gesture, listens for events from the system and Launcher, * and fires events when the states change. */ +@TargetApi(Build.VERSION_CODES.R) public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationListener { /** @@ -106,6 +109,10 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL public static final int STATE_RECENTS_ANIMATION_ENDED = getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED"); + // Called when we create an overscroll window when swiping right to left on the most recent app + public static final int STATE_OVERSCROLL_WINDOW_CREATED = + getFlagForIndex("STATE_OVERSCROLL_WINDOW_CREATED"); + // Called when RecentsView stops scrolling and settles on a TaskView. public static final int STATE_RECENTS_SCROLLING_FINISHED = getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED"); @@ -189,7 +196,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL /** * @return the interface to the activity handing the UI updates for this gesture. */ - public BaseActivityInterface getActivityInterface() { + public > BaseActivityInterface getActivityInterface() { return mActivityInterface; } @@ -315,7 +322,7 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL pw.println(" gestureID=" + mGestureId); pw.println(" runningTask=" + mRunningTask); pw.println(" endTarget=" + mEndTarget); - pw.println(" lastAppearedTaskTarget=" + mLastAppearedTaskTarget); + pw.println(" lastAppearedTaskTargetId=" + getLastAppearedTaskId()); pw.println(" lastStartedTaskId=" + mLastStartedTaskId); pw.println(" isRecentsAnimationRunning=" + isRecentsAnimationRunning()); } diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java index 879fd1d64f..4cf7aab4ab 100644 --- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java +++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java @@ -317,13 +317,6 @@ class OrientationTouchTransformer { private class OrientationRectF extends RectF { - /** - * Delta to subtract width and height by because if we report the translated touch - * bounds as the width and height, calling {@link RectF#contains(float, float)} will - * be false - */ - private float maxDelta = 0.001f; - private int mRotation; private float mHeight; private float mWidth; @@ -331,8 +324,8 @@ class OrientationTouchTransformer { OrientationRectF(float left, float top, float right, float bottom, int rotation) { super(left, top, right, bottom); this.mRotation = rotation; - mHeight = bottom - maxDelta; - mWidth = right - maxDelta; + mHeight = bottom; + mWidth = right; } @Override @@ -342,6 +335,13 @@ class OrientationTouchTransformer { return s; } + @Override + public boolean contains(float x, float y) { + // Mark bottom right as included in the Rect (copied from Rect src, added "=" in "<=") + return left < right && top < bottom // check for empty first + && x >= left && x <= right && y >= top && y <= bottom; + } + boolean applyTransform(MotionEvent event, boolean forceTransform) { mTmpMatrix.reset(); postDisplayRotation(deltaRotation(mCurrentDisplayRotation, mRotation), diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java index 231ee72f1c..0449d0ca2a 100644 --- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java +++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java @@ -140,7 +140,7 @@ public final class OverviewComponentObserver { if (!mDeviceState.isHomeDisabled() && (defaultHome == null || mIsDefaultHome)) { // User default home is same as out home app. Use Overview integrated in Launcher. - mActivityInterface = new LauncherActivityInterface(); + mActivityInterface = LauncherActivityInterface.INSTANCE; mIsHomeAndOverviewSame = true; mOverviewIntent = mMyHomeIntent; mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent()); @@ -150,7 +150,7 @@ public final class OverviewComponentObserver { } else { // The default home app is a different launcher. Use the fallback Overview instead. - mActivityInterface = new FallbackActivityInterface(); + mActivityInterface = FallbackActivityInterface.INSTANCE; mIsHomeAndOverviewSame = false; mOverviewIntent = mFallbackIntent; mCurrentHomeIntent.setComponent(defaultHome); diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java index 8ac15e803f..a892ddcccd 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java @@ -73,8 +73,6 @@ public class RecentsAnimationDeviceState implements NavigationModeChangeListener, DefaultDisplay.DisplayInfoChangeListener { - private static final String TAG = "RecentsAnimationDeviceState"; - private final Context mContext; private final SysUINavigationMode mSysUiNavMode; private final DefaultDisplay mDefaultDisplay; @@ -97,7 +95,6 @@ public class RecentsAnimationDeviceState implements @Override public void onReceive(Context context, Intent intent) { if (ACTION_USER_UNLOCKED.equals(intent.getAction())) { - Log.d(TAG, "User Unlocked Broadcast Received"); mIsUserUnlocked = true; notifyUserUnlocked(); } diff --git a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java index 5fa6bc79e1..f90df4563f 100644 --- a/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java +++ b/quickstep/src/com/android/quickstep/RemoteAnimationTargets.java @@ -68,7 +68,7 @@ public class RemoteAnimationTargets { } public boolean isAnimatingHome() { - for (RemoteAnimationTargetCompat target : apps) { + for (RemoteAnimationTargetCompat target : unfilteredApps) { if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { return true; } diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index a98aad191e..88895602dc 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -64,24 +64,46 @@ public class StatsLogCompatManager extends StatsLogManager { } /** - * Logs an event and accompanying {@link ItemInfo} + * Logs a {@link LauncherEvent}. */ + @Override + public void log(LauncherEvent event) { + log(event, DEFAULT_INSTANCE_ID, LauncherAtom.ItemInfo.getDefaultInstance()); + } + + /** + * Logs an event and accompanying {@link InstanceId}. + */ + @Override + public void log(LauncherEvent event, InstanceId instanceId) { + log(event, instanceId, LauncherAtom.ItemInfo.getDefaultInstance()); + } + + /** + * Logs an event and accompanying {@link ItemInfo}. + */ + @Override public void log(LauncherEvent event, LauncherAtom.ItemInfo itemInfo) { log(event, DEFAULT_INSTANCE_ID, itemInfo); } /** - * Logs an event and accompanying {@link LauncherAtom.ItemInfo} + * Logs an event and accompanying {@link InstanceId} and {@link LauncherAtom.ItemInfo}. */ @Override public void log(LauncherEvent event, InstanceId instanceId, LauncherAtom.ItemInfo itemInfo) { if (IS_VERBOSE) { - Log.d(TAG, String.format("\n%s\n%s", event.name(), itemInfo)); + Log.d(TAG, instanceId == DEFAULT_INSTANCE_ID + ? String.format("\n%s\n%s", event.name(), itemInfo) + : String.format("%s(InstanceId:%s)\n%s", event.name(), instanceId, itemInfo)); } + if (!Utilities.ATLEAST_R) { return; } - SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, + + SysUiStatsLog.write( + SysUiStatsLog.LAUNCHER_EVENT, SysUiStatsLog.LAUNCHER_UICHANGED__ACTION__DEFAULT_ACTION /* deprecated */, SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME /* TODO */, SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND /* TODO */, @@ -118,6 +140,7 @@ public class StatsLogCompatManager extends StatsLogManager { } private class SnapshotWorker extends BaseModelUpdateTask { + @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { IntSparseArrayMap folders = dataModel.folders.clone(); @@ -140,6 +163,7 @@ public class StatsLogCompatManager extends StatsLogManager { } } } + private static void writeSnapshot(LauncherAtom.ItemInfo itemInfo) { if (IS_VERBOSE) { Log.d(TAG, "\nwriteSnapshot:" + itemInfo); diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java index fa53be2b08..c1b276aa5a 100644 --- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java +++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java @@ -17,7 +17,6 @@ package com.android.quickstep.util; import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; -import static com.android.quickstep.util.WindowSizeStrategy.LAUNCHER_ACTIVITY_SIZE_STRATEGY; import android.content.Context; import android.graphics.Rect; @@ -26,6 +25,7 @@ import android.view.ViewGroup; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; +import com.android.quickstep.LauncherActivityInterface; import com.android.quickstep.SysUINavigationMode; public class LayoutUtils { @@ -45,7 +45,7 @@ public class LayoutUtils { // Track the bottom of the window. if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) { Rect taskSize = new Rect(); - LAUNCHER_ACTIVITY_SIZE_STRATEGY.calculateTaskSize(context, dp, taskSize); + LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize); return (dp.heightPx - taskSize.height()) / 2; } int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom; diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java index e7ff48f52b..4c47d7fe6f 100644 --- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java +++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java @@ -27,6 +27,7 @@ import static com.android.launcher3.logging.LoggerUtils.extractObjectNameAndAddr 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; @@ -52,6 +53,8 @@ import com.android.launcher3.Utilities; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.WindowBounds; +import com.android.quickstep.BaseActivityInterface; +import com.android.quickstep.SysUINavigationMode; import java.lang.annotation.Retention; import java.util.function.IntConsumer; @@ -117,11 +120,14 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE | FLAG_SYSTEM_ROTATION_ALLOWED | FLAG_ROTATION_WATCHER_SUPPORTED | FLAG_ROTATION_WATCHER_ENABLED; + private SysUINavigationMode.NavigationModeChangeListener mNavModeChangeListener = + newMode -> setFlag(FLAG_ROTATION_WATCHER_SUPPORTED, newMode != TWO_BUTTONS); + private final Context mContext; private final ContentResolver mContentResolver; private final SharedPreferences mSharedPrefs; private final OrientationEventListener mOrientationListener; - private final WindowSizeStrategy mSizeStrategy; + private final BaseActivityInterface mSizeStrategy; private final Matrix mTmpMatrix = new Matrix(); @@ -133,7 +139,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre * is enabled * @see #setRotationWatcherEnabled(boolean) */ - public RecentsOrientedState(Context context, WindowSizeStrategy sizeStrategy, + public RecentsOrientedState(Context context, BaseActivityInterface sizeStrategy, IntConsumer rotationChangeListener) { mContext = context; mContentResolver = context.getContentResolver(); @@ -162,13 +168,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre if (isFixedRotationTransformEnabled(context)) { mFlags |= FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_FLAG; } - if (mOrientationListener.canDetectOrientation()) { - mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED; - } - - // initialize external flags - updateAutoRotateSetting(); - updateHomeRotationSetting(); + initFlags(); } /** @@ -272,6 +272,18 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY, false)); } + private void initFlags() { + SysUINavigationMode.Mode currentMode = SysUINavigationMode.getMode(mContext); + if (mOrientationListener.canDetectOrientation() && + currentMode != TWO_BUTTONS) { + mFlags |= FLAG_ROTATION_WATCHER_SUPPORTED; + } + + // initialize external flags + updateAutoRotateSetting(); + updateHomeRotationSetting(); + } + /** * Initializes any system values and registers corresponding change listeners. It must be * paired with {@link #destroyListeners()} call @@ -282,9 +294,11 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre mContentResolver.registerContentObserver( Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), false, mSystemAutoRotateObserver); + SysUINavigationMode.Mode currentMode = + SysUINavigationMode.INSTANCE.get(mContext) + .addModeChangeListener(mNavModeChangeListener); } - updateAutoRotateSetting(); - updateHomeRotationSetting(); + initFlags(); } /** @@ -294,6 +308,8 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre if (isMultipleOrientationSupportedByDevice()) { mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this); mContentResolver.unregisterContentObserver(mSystemAutoRotateObserver); + SysUINavigationMode.INSTANCE.get(mContext) + .removeModeChangeListener(mNavModeChangeListener); } setRotationWatcherEnabled(false); } diff --git a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java b/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java deleted file mode 100644 index b088ba8e58..0000000000 --- a/quickstep/src/com/android/quickstep/util/WindowSizeStrategy.java +++ /dev/null @@ -1,185 +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.quickstep.util; - -import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS; -import static com.android.quickstep.SysUINavigationMode.getMode; -import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; -import static com.android.quickstep.util.LayoutUtils.getDefaultSwipeHeight; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.os.Build; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.R; -import com.android.launcher3.util.WindowBounds; -import com.android.quickstep.SysUINavigationMode.Mode; - -/** - * Utility class to wrap different layout behavior for Launcher and RecentsView - * TODO: Merge is with {@link com.android.quickstep.BaseActivityInterface} once we remove the - * state dependent members from {@link com.android.quickstep.LauncherActivityInterface} - */ -@TargetApi(Build.VERSION_CODES.R) -public abstract class WindowSizeStrategy { - - public final boolean rotationSupportedByActivity; - - private WindowSizeStrategy(boolean rotationSupportedByActivity) { - this.rotationSupportedByActivity = rotationSupportedByActivity; - } - - /** - * Calculates the taskView size for the provided device configuration - */ - public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect) { - calculateTaskSize(context, dp, getExtraSpace(context, dp), outRect); - } - - abstract float getExtraSpace(Context context, DeviceProfile dp); - - private void calculateTaskSize( - Context context, DeviceProfile dp, float extraVerticalSpace, Rect outRect) { - Resources res = context.getResources(); - final boolean showLargeTaskSize = showOverviewActions(context); - - final int paddingResId; - if (dp.isMultiWindowMode) { - paddingResId = R.dimen.multi_window_task_card_horz_space; - } else if (dp.isVerticalBarLayout()) { - paddingResId = R.dimen.landscape_task_card_horz_space; - } else if (showLargeTaskSize) { - paddingResId = R.dimen.portrait_task_card_horz_space_big_overview; - } else { - paddingResId = R.dimen.portrait_task_card_horz_space; - } - float paddingHorz = res.getDimension(paddingResId); - float paddingVert = showLargeTaskSize - ? 0 : res.getDimension(R.dimen.task_card_vert_space); - - calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, - res.getDimension(R.dimen.task_thumbnail_top_margin), outRect); - } - - private void calculateTaskSizeInternal(Context context, DeviceProfile dp, - float extraVerticalSpace, float paddingHorz, float paddingVert, float topIconMargin, - Rect outRect) { - float taskWidth, taskHeight; - Rect insets = dp.getInsets(); - if (dp.isMultiWindowMode) { - WindowBounds bounds = SplitScreenBounds.INSTANCE.getSecondaryWindowBounds(context); - taskWidth = bounds.availableSize.x; - taskHeight = bounds.availableSize.y; - } else { - taskWidth = dp.availableWidthPx; - taskHeight = dp.availableHeightPx; - } - - // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless - // we override the insets ourselves. - int launcherVisibleWidth = dp.widthPx - insets.left - insets.right; - int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom; - - float availableHeight = launcherVisibleHeight - - topIconMargin - extraVerticalSpace - paddingVert; - float availableWidth = launcherVisibleWidth - paddingHorz; - - float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight); - float outWidth = scale * taskWidth; - float outHeight = scale * taskHeight; - - // Center in the visible space - float x = insets.left + (launcherVisibleWidth - outWidth) / 2; - float y = insets.top + Math.max(topIconMargin, - (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2); - outRect.set(Math.round(x), Math.round(y), - Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight)); - } - - /** - * Calculates the modal taskView size for the provided device configuration - */ - public void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) { - float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode - ? R.dimen.multi_window_task_card_horz_space - : dp.isVerticalBarLayout() - ? R.dimen.landscape_task_card_horz_space - : R.dimen.portrait_modal_task_card_horz_space); - float extraVerticalSpace = getOverviewActionsHeight(context); - float paddingVert = 0; - float topIconMargin = 0; - calculateTaskSizeInternal(context, dp, extraVerticalSpace, paddingHorz, paddingVert, - topIconMargin, outRect); - } - - /** Gets the space that the overview actions will take, including margins. */ - public float getOverviewActionsHeight(Context context) { - Resources res = context.getResources(); - float actionsBottomMargin = 0; - if (getMode(context) == Mode.THREE_BUTTONS) { - actionsBottomMargin = res.getDimensionPixelSize( - R.dimen.overview_actions_bottom_margin_three_button); - } else { - actionsBottomMargin = res.getDimensionPixelSize( - R.dimen.overview_actions_bottom_margin_gesture); - } - float overviewActionsHeight = actionsBottomMargin - + res.getDimensionPixelSize(R.dimen.overview_actions_height); - return overviewActionsHeight; - } - - public static final WindowSizeStrategy LAUNCHER_ACTIVITY_SIZE_STRATEGY = - new WindowSizeStrategy(true) { - - @Override - float getExtraSpace(Context context, DeviceProfile dp) { - if (dp.isVerticalBarLayout()) { - return 0; - } else { - Resources res = context.getResources(); - if (showOverviewActions(context)) { - //TODO: this needs to account for the swipe gesture height and accessibility - // UI when shown. - return getOverviewActionsHeight(context); - } else { - return getDefaultSwipeHeight(context, dp) + dp.workspacePageIndicatorHeight - + res.getDimensionPixelSize( - R.dimen.dynamic_grid_hotseat_extra_vertical_size) - + res.getDimensionPixelSize( - R.dimen.dynamic_grid_hotseat_bottom_padding); - } - } - } - }; - - public static final WindowSizeStrategy FALLBACK_RECENTS_SIZE_STRATEGY = - new WindowSizeStrategy(false) { - - @Override - float getExtraSpace(Context context, DeviceProfile dp) { - return showOverviewActions(context) - ? context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height) - : 0; - } - }; - - static boolean showOverviewActions(Context context) { - return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context); - } -} diff --git a/res/values/config.xml b/res/values/config.xml index 603dc91619..4cbc597f6f 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -69,6 +69,7 @@ + diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 7de44a3fce..7d80d816dd 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -107,10 +107,15 @@ public abstract class BaseActivity extends Activity implements LogStateProvider, */ public static final int ACTIVITY_STATE_USER_ACTIVE = 1 << 4; + /** + * State flag indicating if the user will be active shortly. + */ + public static final int ACTIVITY_STATE_USER_WILL_BE_ACTIVE = 1 << 5; + /** * State flag indicating that a state transition is in progress */ - public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 5; + public static final int ACTIVITY_STATE_TRANSITION_ACTIVE = 1 << 6; @Retention(SOURCE) @IntDef( @@ -180,6 +185,7 @@ public abstract class BaseActivity extends Activity implements LogStateProvider, @Override protected void onResume() { addActivityFlags(ACTIVITY_STATE_RESUMED | ACTIVITY_STATE_USER_ACTIVE); + removeActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE); super.onResume(); } diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index 0b0983cbee..c1aed9812c 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -110,6 +110,18 @@ public interface DropTarget { return res; } + + + /** + * This is used to determine if an object is dropped at a different location than it was + * dragged from + */ + public boolean isMoved() { + return dragInfo.cellX != originalDragInfo.cellX + || dragInfo.cellY != originalDragInfo.cellY + || dragInfo.screenId != originalDragInfo.screenId + || dragInfo.container != originalDragInfo.container; + } } /** diff --git a/src/com/android/launcher3/InsettableFrameLayout.java b/src/com/android/launcher3/InsettableFrameLayout.java index faa18b8050..9a66d32524 100644 --- a/src/com/android/launcher3/InsettableFrameLayout.java +++ b/src/com/android/launcher3/InsettableFrameLayout.java @@ -91,6 +91,9 @@ public class InsettableFrameLayout extends FrameLayout implements Insettable { @Override public void onViewAdded(View child) { super.onViewAdded(child); + if (!isAttachedToWindow()) { + return; + } setFrameLayoutChildInsets(child, mInsets, new Rect()); } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 14e604d9ed..53e5274c0c 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -129,7 +129,7 @@ public class LauncherAppState { mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext)); - mPredictionModel = new PredictionModel(mContext); + mPredictionModel = PredictionModel.newInstance(mContext); } protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 87ead9e7b5..535c9e6c70 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -153,12 +153,16 @@ public class LauncherSettings { public static final int CONTAINER_PREDICTION = -102; public static final int CONTAINER_HOTSEAT_PREDICTION = -103; public static final int CONTAINER_ALL_APPS = -104; + public static final int CONTAINER_WIDGETS_TRAY = -105; + public static final String containerToString(int container) { switch (container) { case CONTAINER_DESKTOP: return "desktop"; case CONTAINER_HOTSEAT: return "hotseat"; case CONTAINER_PREDICTION: return "prediction"; + case CONTAINER_ALL_APPS: return "all_apps"; + case CONTAINER_WIDGETS_TRAY: return "widgets_tray"; default: return String.valueOf(container); } } diff --git a/src/com/android/launcher3/PendingAddItemInfo.java b/src/com/android/launcher3/PendingAddItemInfo.java index 29c9d935ae..be994ee138 100644 --- a/src/com/android/launcher3/PendingAddItemInfo.java +++ b/src/com/android/launcher3/PendingAddItemInfo.java @@ -18,12 +18,15 @@ package com.android.launcher3; import android.content.ComponentName; +import androidx.annotation.Nullable; + import com.android.launcher3.model.data.ItemInfo; +import java.util.Optional; + /** - * Meta data that is used for deferred binding. - * e.g., this object is used to pass information on draggable targets when they are dropped onto - * the workspace from another container. + * Meta data that is used for deferred binding. e.g., this object is used to pass information on + * draggable targets when they are dropped onto the workspace from another container. */ public class PendingAddItemInfo extends ItemInfo { @@ -36,4 +39,22 @@ public class PendingAddItemInfo extends ItemInfo { protected String dumpProperties() { return super.dumpProperties() + " componentName=" + componentName; } + + /** + * Returns shallow copy of the object. + */ + @Override + public ItemInfo makeShallowCopy() { + PendingAddItemInfo itemInfo = new PendingAddItemInfo(); + itemInfo.copyFrom(this); + itemInfo.componentName = this.componentName; + return itemInfo; + } + + @Nullable + @Override + public ComponentName getTargetComponent() { + return Optional.ofNullable(super.getTargetComponent()).orElse(componentName); + } + } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 286b522636..6b660c1ab8 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -2438,6 +2438,10 @@ public class Workspace extends PagedView // widgets/shortcuts/folders in a slightly different way mLauncher.addPendingItem(pendingInfo, container, screenId, mTargetCell, item.spanX, item.spanY); + mStatsLogManager.log( + LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, + d.logInstanceId, + d.dragInfo.buildProto(null)); } }; boolean isWidget = pendingInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET @@ -2526,11 +2530,12 @@ public class Workspace extends PagedView mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, view, this); resetTransitionTransform(); } + mStatsLogManager.log( + LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, + d.logInstanceId, + d.dragInfo.buildProto(null)); } - mStatsLogManager.log( - LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, - d.logInstanceId, - d.dragInfo.buildProto(null)); + } public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) { diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java index 539794230a..b4ff5ea19a 100644 --- a/src/com/android/launcher3/allapps/DiscoveryBounce.java +++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java @@ -34,6 +34,7 @@ import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.statemanager.StateManager.StateListener; +import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.OnboardingPrefs; /** @@ -153,16 +154,19 @@ public class DiscoveryBounce extends AbstractFloatingView { new DiscoveryBounce(launcher, 0).show(HOTSEAT); } - public static void showForOverviewIfNeeded(Launcher launcher) { - showForOverviewIfNeeded(launcher, true); + public static void showForOverviewIfNeeded(Launcher launcher, + PagedOrientationHandler orientationHandler) { + showForOverviewIfNeeded(launcher, true, orientationHandler); } - private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay) { + private static void showForOverviewIfNeeded(Launcher launcher, boolean withDelay, + PagedOrientationHandler orientationHandler) { OnboardingPrefs onboardingPrefs = launcher.getOnboardingPrefs(); if (!launcher.isInState(OVERVIEW) || !launcher.hasBeenResumed() || launcher.isForceInvisible() || launcher.getDeviceProfile().isVerticalBarLayout() + || !orientationHandler.isLayoutNaturalToLauncher() || onboardingPrefs.getBoolean(OnboardingPrefs.SHELF_BOUNCE_SEEN) || launcher.getSystemService(UserManager.class).isDemoUser() || Utilities.IS_RUNNING_IN_TEST_HARNESS) { @@ -170,7 +174,8 @@ public class DiscoveryBounce extends AbstractFloatingView { } if (withDelay) { - new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false), DELAY_MS); + new Handler().postDelayed(() -> showForOverviewIfNeeded(launcher, false, + orientationHandler), DELAY_MS); return; } else if (AbstractFloatingView.getTopOpenView(launcher) != null) { // TODO: Move these checks to the top and call this method after invalidate handler. diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java index 80b6a5ab96..a6bc6cf59f 100644 --- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java @@ -59,6 +59,14 @@ public class LauncherAllAppsContainerView extends AllAppsContainerView { return super.onInterceptTouchEvent(ev); } + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mLauncher.isInState(LauncherState.ALL_APPS)) { + return false; + } + return super.onTouchEvent(ev); + } + @Override public void setInsets(Rect insets) { super.setInsets(insets); diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java index 740f7f2469..0f04104ca8 100644 --- a/src/com/android/launcher3/anim/PendingAnimation.java +++ b/src/com/android/launcher3/anim/PendingAnimation.java @@ -18,6 +18,7 @@ package com.android.launcher3.anim; import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur; import android.animation.Animator; +import android.animation.Animator.AnimatorListener; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; @@ -51,12 +52,8 @@ public class PendingAnimation implements PropertySetter { private ValueAnimator mProgressAnimator; public PendingAnimation(long duration) { - this(duration, new AnimatorSet()); - } - - public PendingAnimation(long duration, AnimatorSet targetSet) { mDuration = duration; - mAnim = targetSet; + mAnim = new AnimatorSet(); } /** @@ -129,13 +126,32 @@ public class PendingAnimation implements PropertySetter { public void addOnFrameCallback(Runnable runnable) { if (mProgressAnimator == null) { mProgressAnimator = ValueAnimator.ofFloat(0, 1).setDuration(mDuration); - add(mProgressAnimator); } mProgressAnimator.addUpdateListener(anim -> runnable.run()); } - public AnimatorSet getAnim() { + /** + * @see AnimatorSet#addListener(AnimatorListener) + */ + public void addListener(Animator.AnimatorListener listener) { + mAnim.addListener(listener); + } + + /** + * Creates and returns the underlying AnimatorSet + */ + public AnimatorSet buildAnim() { + // Add progress animation to the end, so that frame callback is called after all the other + // animation update. + if (mProgressAnimator != null) { + add(mProgressAnimator); + mProgressAnimator = null; + } + if (mAnimHolders.isEmpty()) { + // Add a dummy animation to that the duration is respected + add(ValueAnimator.ofFloat(0, 1)); + } return mAnim; } @@ -143,7 +159,7 @@ public class PendingAnimation implements PropertySetter { * Creates a controller for this animation */ public AnimatorPlaybackController createPlaybackController() { - return new AnimatorPlaybackController(mAnim, mDuration, mAnimHolders); + return new AnimatorPlaybackController(buildAnim(), mDuration, mAnimHolders); } /** diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java index 770df034fb..a9702b4784 100644 --- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java +++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java @@ -18,7 +18,6 @@ package com.android.launcher3.anim; import static com.android.launcher3.anim.Interpolators.LINEAR; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.util.FloatProperty; @@ -197,9 +196,9 @@ public class SpringAnimationBuilder { animator.setDuration(getDuration()).setInterpolator(LINEAR); animator.addUpdateListener(anim -> property.set(target, getInterpolatedValue(anim.getAnimatedFraction()))); - animator.addListener(new AnimatorListenerAdapter() { + animator.addListener(new AnimationSuccessListener() { @Override - public void onAnimationEnd(Animator animation) { + public void onAnimationSuccess(Animator animation) { property.set(target, mEndValue); } }); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 78d194bd98..69193399f7 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -110,6 +110,9 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag( "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture"); + public static final BooleanFlag ENABLE_QUICK_CAPTURE_WINDOW = getDebugFlag( + "ENABLE_QUICK_CAPTURE_WINDOW", false, "Use window to host quick capture"); + public static final BooleanFlag FORCE_LOCAL_OVERSCROLL_PLUGIN = getDebugFlag( "FORCE_LOCAL_OVERSCROLL_PLUGIN", false, "Use a launcher-provided OverscrollPlugin if available"); diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 9a36b3ed7c..f7fe535a48 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -18,23 +18,17 @@ package com.android.launcher3.folder; import static android.text.TextUtils.isEmpty; -import static androidx.core.util.Preconditions.checkNotNull; - import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY; -import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS; import static com.android.launcher3.logging.LoggerUtils.newContainerTarget; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED; import static com.android.launcher3.model.data.FolderInfo.FLAG_MANUAL_FOLDER_NAME; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED; -import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED; import static java.util.Arrays.asList; -import static java.util.Arrays.stream; import static java.util.Optional.ofNullable; import android.animation.Animator; @@ -94,12 +88,6 @@ import com.android.launcher3.model.data.FolderInfo.FolderListener; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; -import com.android.launcher3.userevent.LauncherLogProto.Action; -import com.android.launcher3.userevent.LauncherLogProto.ContainerType; -import com.android.launcher3.userevent.LauncherLogProto.ItemType; -import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent; -import com.android.launcher3.userevent.LauncherLogProto.Target; -import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.Executors; import com.android.launcher3.util.Thunk; @@ -111,10 +99,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; -import java.util.Optional; -import java.util.OptionalInt; import java.util.stream.Collectors; -import java.util.stream.IntStream; /** * Represents a set of icons chosen by the user or generated by the system. @@ -213,8 +198,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Thunk int mScrollHintDir = SCROLL_NONE; @Thunk int mCurrentScrollDir = SCROLL_NONE; - private String mPreviousLabel; - private boolean mIsPreviousLabelSuggested; + private StatsLogManager mStatsLogManager; /** * Used to inflate the Workspace from XML. @@ -227,10 +211,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo setAlwaysDrawnWithCacheEnabled(false); mLauncher = Launcher.getLauncher(context); + mStatsLogManager = StatsLogManager.newInstance(context); // We need this view to be focusable in touch mode so that when text editing of the folder // name is complete, we have something to focus on, thus hiding the cursor and giving // reliable behavior when clicking the text field (since it will always gain focus on click). setFocusableInTouchMode(true); + } @Override @@ -348,9 +334,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (DEBUG) { Log.d(TAG, "onBackKey newTitle=" + newTitle); } - - mInfo.title = newTitle; - mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !getAcceptedSuggestionIndex().isPresent(), + mInfo.setTitle(newTitle); + mInfo.fromCustom = mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME); + mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, !mInfo.getAcceptedSuggestionIndex().isPresent(), mLauncher.getModelWriter()); mFolderIcon.onTitleChanged(newTitle); mLauncher.getModelWriter().updateItemInDatabase(mInfo); @@ -441,8 +427,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } mItemsInvalidated = true; mInfo.addListener(this); - Optional.ofNullable(mInfo.title).ifPresent(title -> mPreviousLabel = title.toString()); - mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME); if (!isEmpty(mInfo.title)) { mFolderName.setText(mInfo.title); @@ -1347,10 +1331,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (d.stateAnnouncer != null) { d.stateAnnouncer.completeAction(R.string.item_moved); } - StatsLogManager.newInstance(getContext()) - .log(StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED, - d.logInstanceId, - d.dragInfo.buildProto(mInfo)); + mStatsLogManager + .log(LAUNCHER_ITEM_DROP_COMPLETED, d.logInstanceId, d.dragInfo.buildProto(mInfo)); } // This is used so the item doesn't immediately appear in the folder when added. In one case @@ -1455,7 +1437,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo if (hasFocus) { startEditingFolderName(); } else { - logCurrentFolderLabelState(); + mStatsLogManager.log(LAUNCHER_FOLDER_LABEL_UPDATED, mInfo.buildProto()); + logFolderLabelState(); mFolderName.dispatchBackKey(); } } @@ -1654,147 +1637,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return mContent; } - protected void logCurrentFolderLabelState() { - LauncherEvent launcherEvent = LauncherEvent.newBuilder() - .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD)) - .addSrcTarget(newEditTextTargetBuilder() - .setFromFolderLabelState(getFromFolderLabelState()) - .setToFolderLabelState(getToFolderLabelState())) - .addSrcTarget(newFolderTargetBuilder()) - .addSrcTarget(newParentContainerTarget()) - .build(); - mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent); - mPreviousLabel = mFolderName.getText().toString(); - mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME); - } - - private Target.FromFolderLabelState getFromFolderLabelState() { - return mPreviousLabel == null - ? FROM_FOLDER_LABEL_STATE_UNSPECIFIED - : mPreviousLabel.isEmpty() - ? FROM_EMPTY - : mIsPreviousLabelSuggested - ? FROM_SUGGESTED - : FROM_CUSTOM; - } - - private Target.ToFolderLabelState getToFolderLabelState() { - String newLabel = - checkNotNull(mFolderName.getText().toString(), - "Expected valid folder label, but found null"); - if (newLabel.equals(mPreviousLabel)) { - return Target.ToFolderLabelState.UNCHANGED; - } - - if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) { - return newLabel.isEmpty() - ? ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED - : ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED; - } - - Optional suggestedLabels = getSuggestedLabels(); - boolean isEmptySuggestions = suggestedLabels - .map(labels -> stream(labels).allMatch(TextUtils::isEmpty)) - .orElse(true); - if (isEmptySuggestions) { - return newLabel.isEmpty() - ? ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS - : ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS; - } - - boolean hasValidPrimary = suggestedLabels - .map(labels -> !isEmpty(labels[0])) - .orElse(false); - if (newLabel.isEmpty()) { - return hasValidPrimary ? ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; - } - - OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex(); - if (!accepted_suggestion_index.isPresent()) { - return hasValidPrimary ? ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; - } - - switch (accepted_suggestion_index.getAsInt()) { - case 0: - return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY; - case 1: - return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY; - case 2: - return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY; - case 3: - return hasValidPrimary ? ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY - : ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY; - default: - // fall through - } - return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED; - - } - - private Optional getSuggestedLabels() { - return ofNullable(mInfo) - .map(info -> info.suggestedFolderNames) - .map( - folderNames -> - (FolderNameInfo[]) - folderNames.getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS)) - .map( - folderNameInfoArray -> - stream(folderNameInfoArray) - .filter(Objects::nonNull) - .map(FolderNameInfo::getLabel) - .filter(Objects::nonNull) - .map(CharSequence::toString) - .toArray(String[]::new)); - } - - private OptionalInt getAcceptedSuggestionIndex() { - String newLabel = checkNotNull(mFolderName.getText().toString(), - "Expected valid folder label, but found null"); - return getSuggestedLabels() - .map(suggestionsArray -> - IntStream.range(0, suggestionsArray.length) - .filter( - index -> !isEmpty(suggestionsArray[index]) - && newLabel.equalsIgnoreCase(suggestionsArray[index])) - .sequential() - .findFirst() - ).orElse(OptionalInt.empty()); - - } - - - private Target.Builder newEditTextTargetBuilder() { - return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT); - } - - private Target.Builder newFolderTargetBuilder() { - return Target.newBuilder() - .setType(Target.Type.CONTAINER) - .setContainerType(ContainerType.FOLDER) - .setPageIndex(mInfo.screenId) - .setGridX(mInfo.cellX) - .setGridY(mInfo.cellY) - .setCardinality(mInfo.contents.size()); - } - - private Target.Builder newParentContainerTarget() { - Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER); - switch (mInfo.container) { - case CONTAINER_HOTSEAT: - return builder.setContainerType(ContainerType.HOTSEAT); - case CONTAINER_DESKTOP: - return builder.setContainerType(ContainerType.WORKSPACE); - default: - throw new AssertionError(String - .format("Expected container to be either %s or %s but found %s.", - CONTAINER_HOTSEAT, - CONTAINER_DESKTOP, - mInfo.container)); - } + /** + * Logs current folder label info. + * + * @deprecated This method is only used for log validation and soon will be removed. + */ + @Deprecated + public void logFolderLabelState() { + mLauncher.getUserEventDispatcher() + .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent()); } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 93208d4a09..153d6bceb1 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -20,6 +20,7 @@ import static android.text.TextUtils.isEmpty; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -62,6 +63,8 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DraggableView; import com.android.launcher3.icons.DotRenderer; +import com.android.launcher3.logging.InstanceId; +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.FolderInfo.FolderListener; @@ -410,10 +413,10 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel Executors.UI_HELPER_EXECUTOR.post(() -> { d.folderNameProvider.getSuggestedFolderName( getContext(), mInfo.contents, nameInfos); - showFinalView(finalIndex, item, nameInfos); + showFinalView(finalIndex, item, nameInfos, d.logInstanceId); }); } else { - showFinalView(finalIndex, item, nameInfos); + showFinalView(finalIndex, item, nameInfos, d.logInstanceId); } } else { addItem(item); @@ -421,12 +424,12 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } private void showFinalView(int finalIndex, final WorkspaceItemInfo item, - FolderNameInfo[] nameInfos) { + FolderNameInfo[] nameInfos, InstanceId instanceId) { postDelayed(() -> { mPreviewItemManager.hidePreviewItem(finalIndex, false); mFolder.showItem(item); - setLabelSuggestion(nameInfos); - mFolder.logCurrentFolderLabelState(); + setLabelSuggestion(nameInfos, instanceId); + mFolder.logFolderLabelState(); invalidate(); }, DROP_IN_ANIMATION_DURATION); } @@ -434,7 +437,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel /** * Set the suggested folder name. */ - public void setLabelSuggestion(FolderNameInfo[] nameInfos) { + public void setLabelSuggestion(FolderNameInfo[] nameInfos, InstanceId instanceId) { if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) { return; } @@ -445,7 +448,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) { return; } - mInfo.title = nameInfos[0].getLabel(); + mInfo.setTitle(nameInfos[0].getLabel()); + StatsLogManager.newInstance(getContext()) + .log(LAUNCHER_FOLDER_LABEL_UPDATED, instanceId, mInfo.buildProto()); onTitleChanged(mInfo.title); mFolder.mFolderName.setText(mInfo.title); mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo); diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index c62f308367..350f221c97 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -81,10 +81,12 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { binderDied(); } + SurfaceControlViewHost.SurfacePackage surfacePackage; try { mSurfaceControlViewHost = MAIN_EXECUTOR .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken)) .get(5, TimeUnit.SECONDS); + surfacePackage = mSurfaceControlViewHost.getSurfacePackage(); mHostToken.linkToDeath(this, 0); } catch (Exception e) { e.printStackTrace(); @@ -92,6 +94,14 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { } MAIN_EXECUTOR.execute(() -> { + // If mSurfaceControlViewHost is null due to any reason (e.g. binder died, + // happening when user leaves the preview screen before preview rendering finishes), + // we should return here. + SurfaceControlViewHost host = mSurfaceControlViewHost; + if (host == null) { + return; + } + View view = new LauncherPreviewRenderer(mContext, mIdp).getRenderedView(); // This aspect scales the view to fit in the surface and centers it final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), @@ -107,14 +117,14 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { .setInterpolator(new AccelerateDecelerateInterpolator()) .setDuration(FADE_IN_ANIMATION_DURATION) .start(); - mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), + host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight()); }); Bundle result = new Bundle(); - result.putParcelable(KEY_SURFACE_PACKAGE, mSurfaceControlViewHost.getSurfacePackage()); + result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage); - Handler handler = new Handler(Looper.getMainLooper(), Loopermessage -> { + Handler handler = new Handler(Looper.getMainLooper(), message -> { binderDied(); return true; }); @@ -128,8 +138,10 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { @Override public void binderDied() { if (mSurfaceControlViewHost != null) { - mSurfaceControlViewHost.release(); - mSurfaceControlViewHost = null; + MAIN_EXECUTOR.execute(() -> { + mSurfaceControlViewHost.release(); + mSurfaceControlViewHost = null; + }); } mHostToken.unlinkToDeath(this, 0); } diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java index 9455bd3fa0..475305f65d 100644 --- a/src/com/android/launcher3/logging/StatsLogManager.java +++ b/src/com/android/launcher3/logging/StatsLogManager.java @@ -16,10 +16,8 @@ package com.android.launcher3.logging; import android.content.Context; -import android.util.Log; import com.android.launcher3.R; -import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ItemInfo; import com.android.launcher3.logging.StatsLogUtils.LogStateProvider; import com.android.launcher3.util.ResourceBasedOverride; @@ -32,61 +30,65 @@ import com.android.launcher3.util.ResourceBasedOverride; */ public class StatsLogManager implements ResourceBasedOverride { - private static final String TAG = "StatsLogManager"; - interface EventEnum { int getId(); } public enum LauncherEvent implements EventEnum { - @LauncherUiEvent(doc = "App launched from workspace, hotseat or folder in launcher") + @UiEvent(doc = "App launched from workspace, hotseat or folder in launcher") LAUNCHER_APP_LAUNCH_TAP(338), - @LauncherUiEvent(doc = "Task launched from overview using TAP") + @UiEvent(doc = "Task launched from overview using TAP") LAUNCHER_TASK_LAUNCH_TAP(339), - @LauncherUiEvent(doc = "Task launched from overview using SWIPE DOWN") + @UiEvent(doc = "Task launched from overview using SWIPE DOWN") LAUNCHER_TASK_LAUNCH_SWIPE_DOWN(340), - @LauncherUiEvent(doc = "TASK dismissed from overview using SWIPE UP") + @UiEvent(doc = "TASK dismissed from overview using SWIPE UP") LAUNCHER_TASK_DISMISS_SWIPE_UP(341), - @LauncherUiEvent(doc = "User dragged a launcher item") + @UiEvent(doc = "User dragged a launcher item") LAUNCHER_ITEM_DRAG_STARTED(383), - @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped") + @UiEvent(doc = "A dragged launcher item is successfully dropped") LAUNCHER_ITEM_DROP_COMPLETED(385), - @LauncherUiEvent(doc = "A dragged launcher item is successfully dropped on another item " + @UiEvent(doc = "A dragged launcher item is successfully dropped on another item " + "resulting in a new folder creation") LAUNCHER_ITEM_DROP_FOLDER_CREATED(386), - @LauncherUiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar") + @UiEvent(doc = "User action resulted in or manually updated the folder label to " + + "new/same value.") + LAUNCHER_FOLDER_LABEL_UPDATED(460), + + @UiEvent(doc = "A dragged item is dropped on 'Remove' button in the target bar") LAUNCHER_ITEM_DROPPED_ON_REMOVE(465), - @LauncherUiEvent(doc = "A dragged item is dropped on 'Cancel' button in the target bar") + @UiEvent(doc = "A dragged item is dropped on 'Cancel' button in the target bar") LAUNCHER_ITEM_DROPPED_ON_CANCEL(466), - @LauncherUiEvent(doc = "A predicted item is dragged and dropped on 'Don't suggest app'" + @UiEvent(doc = "A predicted item is dragged and dropped on 'Don't suggest app'" + " button in the target bar") LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST(467), - @LauncherUiEvent(doc = "A dragged item is dropped on 'Uninstall' button in target bar") + @UiEvent(doc = "A dragged item is dropped on 'Uninstall' button in target bar") LAUNCHER_ITEM_DROPPED_ON_UNINSTALL(468), - @LauncherUiEvent(doc = "User completed uninstalling the package after dropping on " + @UiEvent(doc = "User completed uninstalling the package after dropping on " + "the icon onto 'Uninstall' button in the target bar") LAUNCHER_ITEM_UNINSTALL_COMPLETED(469), - @LauncherUiEvent(doc = "User cancelled uninstalling the package after dropping on " + @UiEvent(doc = "User cancelled uninstalling the package after dropping on " + "the icon onto 'Uninstall' button in the target bar") LAUNCHER_ITEM_UNINSTALL_CANCELLED(470); // ADD MORE private final int mId; + LauncherEvent(int id) { mId = id; } + public int getId() { return mId; } @@ -109,22 +111,32 @@ public class StatsLogManager implements ResourceBasedOverride { } /** - * Logs an event and accompanying {@link ItemInfo} + * Logs a {@link LauncherEvent}. */ - public void log(LauncherEvent event, InstanceId instanceId) { - Log.d(TAG, String.format("%s(InstanceId:%s)", event.name(), instanceId)); - // Call StatsLog method + public void log(LauncherEvent event) { } /** - * Logs an event and accompanying {@link LauncherAtom.ItemInfo} + * Logs an event and accompanying {@link InstanceId}. */ - public void log(LauncherEvent event, InstanceId instanceId, LauncherAtom.ItemInfo itemInfo) { } - public void log(LauncherEvent event, LauncherAtom.ItemInfo itemInfo) { } + public void log(LauncherEvent event, InstanceId instanceId) { + } + /** + * Logs an event and accompanying {@link ItemInfo}. + */ + public void log(LauncherEvent event, ItemInfo itemInfo) { + } + + /** + * Logs an event and accompanying {@link InstanceId} and {@link ItemInfo}. + */ + public void log(LauncherEvent event, InstanceId instanceId, ItemInfo itemInfo) { + } /** * Logs snapshot, or impression of the current workspace. */ - public void logSnapshot() { } + public void logSnapshot() { + } } diff --git a/src/com/android/launcher3/logging/LauncherUiEvent.java b/src/com/android/launcher3/logging/UiEvent.java similarity index 80% rename from src/com/android/launcher3/logging/LauncherUiEvent.java rename to src/com/android/launcher3/logging/UiEvent.java index 4507ff7d76..20d6c72e41 100644 --- a/src/com/android/launcher3/logging/LauncherUiEvent.java +++ b/src/com/android/launcher3/logging/UiEvent.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.launcher3.logging; import static java.lang.annotation.ElementType.FIELD; @@ -23,8 +24,11 @@ import java.lang.annotation.Target; @Retention(SOURCE) @Target(FIELD) -public @interface LauncherUiEvent { - /** An explanation, suitable for Android analysts, of the UI event that this log represents. */ +// Copy of frameworks/base/core/java/com/android/internal/logging/UiEvent.java +public @interface UiEvent { + + /** + * An explanation, suitable for Android analysts, of the UI event that this log represents. + */ String doc(); } - diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index 1465100b83..ab921eaad9 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -253,8 +253,8 @@ public abstract class BaseLoaderResults { } private void bindPredictedItems(IntArray ranks, final Executor executor) { - executeCallbacksTask( - c -> c.bindPredictedItems(mBgDataModel.cachedPredictedItems, ranks), executor); + ArrayList items = new ArrayList<>(mBgDataModel.cachedPredictedItems); + executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor); } protected void executeCallbacksTask(CallbackTask task, Executor executor) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 9e6282e270..d05d70b044 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -45,6 +45,8 @@ import android.util.LongSparseArray; import android.util.MutableInt; import android.util.TimingLogger; +import androidx.annotation.WorkerThread; + import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; @@ -850,12 +852,11 @@ public class LoaderTask implements Runnable { } } - private List loadCachedPredictions() { + @WorkerThread + private void loadCachedPredictions() { synchronized (mBgDataModel) { List componentKeys = mApp.getPredictionModel().getPredictionComponentKeys(); - List results = new ArrayList<>(); - if (componentKeys == null) return results; List l; mBgDataModel.cachedPredictedItems.clear(); for (ComponentKey key : componentKeys) { @@ -866,7 +867,6 @@ public class LoaderTask implements Runnable { mBgDataModel.cachedPredictedItems.add(info); mIconCache.getTitleAndIcon(info, false); } - return results; } } diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java index 6aa41eb760..1429843b7e 100644 --- a/src/com/android/launcher3/model/PredictionModel.java +++ b/src/com/android/launcher3/model/PredictionModel.java @@ -14,80 +14,111 @@ * limitations under the License. */ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; +import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.os.UserHandle; +import androidx.annotation.AnyThread; +import androidx.annotation.WorkerThread; + +import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.ResourceBasedOverride; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; /** - * Model helper for app predictions in workspace + * Model Helper for app predictions */ -public class PredictionModel { +public class PredictionModel implements ResourceBasedOverride { + private static final String CACHED_ITEMS_KEY = "predicted_item_keys"; private static final int MAX_CACHE_ITEMS = 5; - private final Context mContext; - private final SharedPreferences mDevicePrefs; - private ArrayList mCachedComponentKeys; + protected Context mContext; + private SharedPreferences mDevicePrefs; + private UserCache mUserCache; - public PredictionModel(Context context) { - mContext = context; - mDevicePrefs = Utilities.getDevicePrefs(mContext); + + /** + * Retrieve instance of this object that can be overridden in runtime based on the build + * variant of the application. + */ + public static PredictionModel newInstance(Context context) { + PredictionModel model = Overrides.getObject(PredictionModel.class, context, + R.string.prediction_model_class); + model.init(context); + return model; } + protected void init(Context context) { + mContext = context; + mDevicePrefs = Utilities.getDevicePrefs(mContext); + mUserCache = UserCache.INSTANCE.get(mContext); + + } /** * Formats and stores a list of component key in device preferences. */ + @AnyThread public void cachePredictionComponentKeys(List componentKeys) { - StringBuilder builder = new StringBuilder(); - int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS); - for (int i = 0; i < count; i++) { - builder.append(componentKeys.get(i)); - builder.append("\n"); - } - mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); - mCachedComponentKeys = null; + MODEL_EXECUTOR.execute(() -> { + StringBuilder builder = new StringBuilder(); + int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS); + for (int i = 0; i < count; i++) { + builder.append(serializeComponentKeyToString(componentKeys.get(i))); + builder.append("\n"); + } + mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply(); + }); } /** * parses and returns ComponentKeys saved by * {@link PredictionModel#cachePredictionComponentKeys(List)} */ + @WorkerThread public List getPredictionComponentKeys() { - if (mCachedComponentKeys == null) { - mCachedComponentKeys = new ArrayList<>(); - - String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, ""); - for (String line : cachedBlob.split("\n")) { - ComponentKey key = ComponentKey.fromString(line); - if (key != null) { - mCachedComponentKeys.add(key); - } + Preconditions.assertWorkerThread(); + ArrayList items = new ArrayList<>(); + String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, ""); + for (String line : cachedBlob.split("\n")) { + ComponentKey key = getComponentKeyFromSerializedString(line); + if (key != null) { + items.add(key); } + } - return mCachedComponentKeys; + return items; } - /** - * Remove uninstalled applications from model - */ - public void removePackage(String pkgName, UserHandle user, ArrayList ids) { - for (int i = ids.size() - 1; i >= 0; i--) { - AppInfo info = ids.get(i); - if (info.user.equals(user) && pkgName.equals(info.componentName.getPackageName())) { - ids.remove(i); - } + private String serializeComponentKeyToString(ComponentKey componentKey) { + long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user); + return componentKey.componentName.flattenToString() + "#" + userSerialNumber; + } + + private ComponentKey getComponentKeyFromSerializedString(String str) { + int sep = str.indexOf('#'); + if (sep < 0 || (sep + 1) >= str.length()) { + return null; + } + ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep)); + if (componentName == null) { + return null; + } + try { + long serialNumber = Long.parseLong(str.substring(sep + 1)); + UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber); + return userHandle != null ? new ComponentKey(componentName, userHandle) : null; + } catch (NumberFormatException ex) { + return null; } - cachePredictionComponentKeys(getPredictionComponentKeys().stream() - .filter(cn -> !(cn.user.equals(user) && cn.componentName.getPackageName().equals( - pkgName))).collect(Collectors.toList())); } } diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java index 3ac6a2213b..096743a8c8 100644 --- a/src/com/android/launcher3/model/data/FolderInfo.java +++ b/src/com/android/launcher3/model/data/FolderInfo.java @@ -16,16 +16,45 @@ package com.android.launcher3.model.data; +import static android.text.TextUtils.isEmpty; + +import static androidx.core.util.Preconditions.checkNotNull; + +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED; +import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED; + +import static java.util.Arrays.stream; +import static java.util.Optional.ofNullable; + import android.content.Intent; import android.os.Process; +import android.text.TextUtils; import com.android.launcher3.LauncherSettings; import com.android.launcher3.Utilities; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.folder.FolderNameInfo; import com.android.launcher3.logger.LauncherAtom; +import com.android.launcher3.logger.LauncherAtom.FromState; +import com.android.launcher3.logger.LauncherAtom.ToState; import com.android.launcher3.model.ModelWriter; +import com.android.launcher3.userevent.LauncherLogProto; +import com.android.launcher3.userevent.LauncherLogProto.Target; +import com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState; +import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState; import com.android.launcher3.util.ContentWriter; import java.util.ArrayList; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.StringJoiner; +import java.util.stream.IntStream; + /** * Represents a folder containing shortcuts or apps. @@ -57,6 +86,20 @@ public class FolderInfo extends ItemInfo { public Intent suggestedFolderNames; + // Represents the title before current. + // Primarily used for logging purpose. + private CharSequence mPreviousTitle; + + // True if the title before was manually entered, suggested otherwise. + // Primarily used for logging purpose. + public boolean fromCustom; + + /** + * Used for separating {@link #mPreviousTitle} and {@link #title} when concatenating them + * for logging. + */ + private static final CharSequence FOLDER_LABEL_DELIMITER = "=>"; + /** * The apps and shortcuts */ @@ -160,9 +203,20 @@ public class FolderInfo extends ItemInfo { @Override public LauncherAtom.ItemInfo buildProto(FolderInfo fInfo) { return getDefaultItemInfoBuilder() - .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size())) - .setContainerInfo(getContainerInfo()) - .build(); + .setFolderIcon(LauncherAtom.FolderIcon.newBuilder().setCardinality(contents.size())) + .setRank(rank) + .setContainerInfo(getContainerInfo()) + .build(); + } + + @Override + public void setTitle(CharSequence title) { + mPreviousTitle = this.title; + this.title = title; + } + + public CharSequence getPreviousTitle() { + return mPreviousTitle; } @Override @@ -172,4 +226,244 @@ public class FolderInfo extends ItemInfo { folderInfo.contents = this.contents; return folderInfo; } + + /** + * Returns {@link LauncherAtom.FolderIcon} wrapped as {@link LauncherAtom.ItemInfo} for logging. + */ + @Override + public LauncherAtom.ItemInfo buildProto() { + FromState fromFolderLabelState = getFromFolderLabelState(); + ToState toFolderLabelState = getToFolderLabelState(); + LauncherAtom.FolderIcon.Builder folderIconBuilder = LauncherAtom.FolderIcon.newBuilder() + .setCardinality(contents.size()) + .setFromLabelState(fromFolderLabelState) + .setToLabelState(toFolderLabelState); + + // If the folder label is suggested, it is logged to improve prediction model. + // When both old and new labels are logged together delimiter is used. + StringJoiner labelInfoBuilder = new StringJoiner(FOLDER_LABEL_DELIMITER); + if (fromFolderLabelState.equals(FromState.FROM_SUGGESTED)) { + labelInfoBuilder.add(mPreviousTitle); + } + if (toFolderLabelState.toString().startsWith("TO_SUGGESTION")) { + labelInfoBuilder.add(title); + } + if (labelInfoBuilder.length() > 0) { + folderIconBuilder.setLabelInfo(labelInfoBuilder.toString()); + } + + return getDefaultItemInfoBuilder() + .setFolderIcon(folderIconBuilder) + .setContainerInfo(getContainerInfo()) + .build(); + } + + /** + * Returns index of the accepted suggestion. + */ + public OptionalInt getAcceptedSuggestionIndex() { + String newLabel = checkNotNull(title, + "Expected valid folder label, but found null").toString(); + return getSuggestedLabels() + .map(suggestionsArray -> + IntStream.range(0, suggestionsArray.length) + .filter( + index -> !isEmpty(suggestionsArray[index]) + && newLabel.equalsIgnoreCase( + suggestionsArray[index])) + .sequential() + .findFirst() + ).orElse(OptionalInt.empty()); + + } + + private LauncherAtom.ToState getToFolderLabelState() { + if (title == null) { + return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; + } + + if (title.equals(mPreviousTitle)) { + return LauncherAtom.ToState.UNCHANGED; + } + + if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) { + return title.length() > 0 + ? LauncherAtom.ToState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED + : LauncherAtom.ToState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED; + } + + Optional suggestedLabels = getSuggestedLabels(); + boolean isEmptySuggestions = suggestedLabels + .map(labels -> stream(labels).allMatch(TextUtils::isEmpty)) + .orElse(true); + if (isEmptySuggestions) { + return title.length() > 0 + ? LauncherAtom.ToState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS + : LauncherAtom.ToState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS; + } + + boolean hasValidPrimary = suggestedLabels + .map(labels -> !isEmpty(labels[0])) + .orElse(false); + if (title.length() == 0) { + return hasValidPrimary ? LauncherAtom.ToState.TO_EMPTY_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + } + + OptionalInt accepted_suggestion_index = getAcceptedSuggestionIndex(); + if (!accepted_suggestion_index.isPresent()) { + return hasValidPrimary ? LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + } + + switch (accepted_suggestion_index.getAsInt()) { + case 0: + return LauncherAtom.ToState.TO_SUGGESTION0; + case 1: + return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION1_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY; + case 2: + return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION2_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY; + case 3: + return hasValidPrimary ? LauncherAtom.ToState.TO_SUGGESTION3_WITH_VALID_PRIMARY + : LauncherAtom.ToState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY; + default: + // fall through + } + return LauncherAtom.ToState.TO_STATE_UNSPECIFIED; + + } + + private LauncherAtom.FromState getFromFolderLabelState() { + return mPreviousTitle == null + ? LauncherAtom.FromState.FROM_STATE_UNSPECIFIED + : mPreviousTitle.length() == 0 + ? LauncherAtom.FromState.FROM_EMPTY + : fromCustom + ? LauncherAtom.FromState.FROM_CUSTOM + : LauncherAtom.FromState.FROM_SUGGESTED; + } + + private Optional getSuggestedLabels() { + return ofNullable(suggestedFolderNames) + .map(folderNames -> + (FolderNameInfo[]) + folderNames.getParcelableArrayExtra(EXTRA_FOLDER_SUGGESTIONS)) + .map(folderNameInfoArray -> + stream(folderNameInfoArray) + .filter(Objects::nonNull) + .map(FolderNameInfo::getLabel) + .filter(Objects::nonNull) + .map(CharSequence::toString) + .toArray(String[]::new)); + } + + /** + * Returns {@link LauncherLogProto.LauncherEvent} to log current folder label info. + * + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent() { + return LauncherLogProto.LauncherEvent.newBuilder() + .setAction(LauncherLogProto.Action + .newBuilder() + .setType(LauncherLogProto.Action.Type.SOFT_KEYBOARD)) + .addSrcTarget(Target + .newBuilder() + .setType(Target.Type.ITEM) + .setItemType(LauncherLogProto.ItemType.EDITTEXT) + .setFromFolderLabelState(convertFolderLabelState(getFromFolderLabelState())) + .setToFolderLabelState(convertFolderLabelState(getToFolderLabelState()))) + .addSrcTarget(Target.newBuilder() + .setType(Target.Type.CONTAINER) + .setContainerType(LauncherLogProto.ContainerType.FOLDER) + .setPageIndex(screenId) + .setGridX(cellX) + .setGridY(cellY) + .setCardinality(contents.size())) + .addSrcTarget(newParentContainerTarget()) + .build(); + } + + /** + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + private Target.Builder newParentContainerTarget() { + Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER); + switch (container) { + case CONTAINER_HOTSEAT: + return builder.setContainerType(LauncherLogProto.ContainerType.HOTSEAT); + case CONTAINER_DESKTOP: + return builder.setContainerType(LauncherLogProto.ContainerType.WORKSPACE); + default: + throw new AssertionError(String + .format("Expected container to be either %s or %s but found %s.", + CONTAINER_HOTSEAT, + CONTAINER_DESKTOP, + container)); + } + } + + /** + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + private static FromFolderLabelState convertFolderLabelState(FromState fromState) { + switch (fromState) { + case FROM_EMPTY: + return FROM_EMPTY; + case FROM_SUGGESTED: + return FROM_SUGGESTED; + case FROM_CUSTOM: + return FROM_CUSTOM; + default: + return FROM_FOLDER_LABEL_STATE_UNSPECIFIED; + } + } + + /** + * @deprecated This method is used only for validation purpose and soon will be removed. + */ + @Deprecated + private static ToFolderLabelState convertFolderLabelState(ToState toState) { + switch (toState) { + case UNCHANGED: + return ToFolderLabelState.UNCHANGED; + case TO_SUGGESTION0: + return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY; + case TO_SUGGESTION1_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY; + case TO_SUGGESTION1_WITH_EMPTY_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY; + case TO_SUGGESTION2_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY; + case TO_SUGGESTION2_WITH_EMPTY_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY; + case TO_SUGGESTION3_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY; + case TO_SUGGESTION3_WITH_EMPTY_PRIMARY: + return ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY; + case TO_EMPTY_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY; + case TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY: + return ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + case TO_EMPTY_WITH_EMPTY_SUGGESTIONS: + return ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS; + case TO_EMPTY_WITH_SUGGESTIONS_DISABLED: + return ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED; + case TO_CUSTOM_WITH_VALID_PRIMARY: + return ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY; + case TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY: + return ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY; + case TO_CUSTOM_WITH_EMPTY_SUGGESTIONS: + return ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS; + case TO_CUSTOM_WITH_SUGGESTIONS_DISABLED: + return ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED; + default: + return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED; + } + } } diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index 7611ee74b4..a97d52969b 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APP import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION; +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; @@ -249,6 +250,13 @@ public class ItemInfo { public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) { } + /** + * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. + */ + public LauncherAtom.ItemInfo buildProto() { + return buildProto(null); + } + /** * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info. */ @@ -335,6 +343,11 @@ public class ItemInfo { .setAllAppsContainer( AllAppsContainer.getDefaultInstance()) .build(); + case CONTAINER_WIDGETS_TRAY: + return ContainerInfo.newBuilder() + .setWidgetsContainer( + LauncherAtom.WidgetsContainer.getDefaultInstance()) + .build(); } return ContainerInfo.getDefaultInstance(); } @@ -345,4 +358,8 @@ public class ItemInfo { itemInfo.copyFrom(this); return itemInfo; } + + public void setTitle(CharSequence title) { + this.title = title; + } } diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java index 44471660af..97dc0524f1 100644 --- a/src/com/android/launcher3/statemanager/StateManager.java +++ b/src/com/android/launcher3/statemanager/StateManager.java @@ -239,7 +239,7 @@ public class StateManager> { ? fromState.getTransitionDuration(mActivity) : state.getTransitionDuration(mActivity); prepareForAtomicAnimation(fromState, state, mConfig); - AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).getAnim(); + AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim(); if (onCompleteRunnable != null) { animation.addListener(AnimationSuccessListener.forRunnable(onCompleteRunnable)); } @@ -267,7 +267,7 @@ public class StateManager> { for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) { handler.setStateWithAnimation(toState, config, builder); } - return builder.getAnim(); + return builder.buildAnim(); } /** @@ -309,7 +309,7 @@ public class StateManager> { for (StateHandler handler : getStateHandlers()) { handler.setStateWithAnimation(state, mConfig, builder); } - builder.getAnim().addListener(new AnimationSuccessListener() { + builder.addListener(new AnimationSuccessListener() { @Override public void onAnimationStart(Animator animation) { @@ -325,7 +325,7 @@ public class StateManager> { onStateTransitionEnd(state); } }); - mConfig.setAnimation(builder.getAnim(), state); + mConfig.setAnimation(builder.buildAnim(), state); return builder; } diff --git a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java index 6e21a41b27..96016526a7 100644 --- a/src/com/android/launcher3/widget/PendingAddShortcutInfo.java +++ b/src/com/android/launcher3/widget/PendingAddShortcutInfo.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; + import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.pm.ShortcutConfigActivityInfo; @@ -32,5 +34,6 @@ public class PendingAddShortcutInfo extends PendingAddItemInfo { componentName = activityInfo.getComponent(); user = activityInfo.getUser(); itemType = activityInfo.getItemType(); + this.container = CONTAINER_WIDGETS_TRAY; } } diff --git a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java index bc404842eb..bef9a08f14 100644 --- a/src/com/android/launcher3/widget/PendingAddWidgetInfo.java +++ b/src/com/android/launcher3/widget/PendingAddWidgetInfo.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.widget; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY; + import android.appwidget.AppWidgetHostView; import android.os.Bundle; @@ -50,6 +52,7 @@ public class PendingAddWidgetInfo extends PendingAddItemInfo { spanY = i.spanY; minSpanX = i.minSpanX; minSpanY = i.minSpanY; + this.container = CONTAINER_WIDGETS_TRAY; } public WidgetAddFlowHandler getHandler() { diff --git a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java index 28a9193bec..a434d078bc 100644 --- a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java +++ b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java @@ -15,6 +15,8 @@ */ package com.android.systemui.plugins; +import android.view.MotionEvent; + import com.android.systemui.plugins.annotations.ProvidesInterface; /** @@ -28,7 +30,7 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; public interface OverscrollPlugin extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL"; - int VERSION = 3; + int VERSION = 4; String DEVICE_STATE_LOCKED = "Locked"; String DEVICE_STATE_LAUNCHER = "Launcher"; @@ -41,33 +43,33 @@ public interface OverscrollPlugin extends Plugin { boolean isActive(); /** - * Called when a touch is down and has been recognized as an overscroll gesture. - * A call of this method will always result in `onTouchUp` being called, and possibly - * `onFling` as well. - * + * Called when a touch has been recognized as an overscroll gesture. + * @param horizontalDistancePx Horizontal distance from the last finger location to the finger + * location when it first touched the screen. + * @param verticalDistancePx Horizontal distance from the last finger location to the finger + * location when it first touched the screen. + * @param thresholdPx Minimum distance for gesture. + * @param flingDistanceThresholdPx Minimum distance for gesture by fling. + * @param flingVelocityThresholdPx Minimum velocity for gesture by fling. * @param deviceState String representing the current device state * @param underlyingActivity String representing the currently active Activity */ - void onTouchStart(String deviceState, String underlyingActivity); + void onTouchEvent(MotionEvent event, + int horizontalDistancePx, + int verticalDistancePx, + int thresholdPx, + int flingDistanceThresholdPx, + int flingVelocityThresholdPx, + String deviceState, + String underlyingActivity); /** - * Called when a touch that was previously recognized has moved. - * - * @param px distance between the position of touch on this update and the position of the - * touch when it was initially recognized. + * @return `true` if overscroll gesture handling should override all other gestures. */ - void onTouchTraveled(int px); + boolean blockOtherGestures(); /** - * Called when a touch that was previously recognized has ended. - * - * @param px distance between the position of touch on this update and the position of the - * touch when it was initially recognized. + * @return `true` if the overscroll gesture can pan the underlying app. */ - void onTouchEnd(int px); - - /** - * Called when the user starts Compose with a fling. `onTouchUp` will also be called. - */ - void onFling(float velocity); + boolean allowsUnderlyingActivityOverscroll(); } diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index c51b87e5f6..f0b7fae58b 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -362,7 +362,7 @@ public final class LauncherInstrumentation { public void checkForAnomaly() { final String systemAnomalyMessage = getSystemAnomalyMessage(); if (systemAnomalyMessage != null) { - Assert.fail(formatSystemHealthMessage(closeEvents( + Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( "http://go/tapl : Tests are broken by a non-Launcher system error: " + systemAnomalyMessage, false))); } @@ -424,7 +424,7 @@ public final class LauncherInstrumentation { return message; } - private String closeEvents(String message, boolean checkEvents) { + private String formatErrorWithEvents(String message, boolean checkEvents) { if (sCheckingEvents) { sCheckingEvents = false; if (checkEvents) { @@ -436,9 +436,12 @@ public final class LauncherInstrumentation { sEventChecker.finishNoWait(); } } - // b/156287114 + try { - log("Input: " + mDevice.executeShellCommand("dumpsys input")); + Log.e("b/156287114", "Input:"); + for (String line : mDevice.executeShellCommand("dumpsys input").split("\\n")) { + Log.d("b/156287114", line); + } } catch (IOException e) { e.printStackTrace(); } @@ -451,7 +454,7 @@ public final class LauncherInstrumentation { private void fail(String message) { checkForAnomaly(); - Assert.fail(formatSystemHealthMessage(closeEvents( + Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( "http://go/tapl : " + getContextDescription() + message + " (visible state: " + getVisibleStateMessage() + ")", true))); } @@ -629,8 +632,6 @@ public final class LauncherInstrumentation { * @return the Workspace object. */ public Workspace pressHome() { - mInstrumentation.getUiAutomation().setOnAccessibilityEventListener( - e -> Log.d("b/155926212", e.toString())); try (LauncherInstrumentation.Closable e = eventsCheck()) { waitForLauncherInitialized(); // Click home, then wait for any accessibility event, then wait until accessibility @@ -639,9 +640,7 @@ public final class LauncherInstrumentation { // otherwise waitForIdle may return immediately in case when there was a big enough // pause in accessibility events prior to pressing Home. final String action; - Log.d("b/155926212", "Before isLauncherVisible()"); final boolean launcherWasVisible = isLauncherVisible(); - Log.d("b/155926212", "After isLauncherVisible(): " + launcherWasVisible); if (getNavigationModel() == NavigationModel.ZERO_BUTTON) { checkForAnomaly(); @@ -697,8 +696,6 @@ public final class LauncherInstrumentation { "performed action to switch to Home - " + action)) { return getWorkspace(); } - } finally { - mInstrumentation.getUiAutomation().setOnAccessibilityEventListener(null); } } @@ -1312,7 +1309,8 @@ public final class LauncherInstrumentation { if (mOnLauncherCrashed != null) mOnLauncherCrashed.run(); checkForAnomaly(); Assert.fail( - formatSystemHealthMessage(closeEvents("Launcher crashed", false))); + formatSystemHealthMessage( + formatErrorWithEvents("Launcher crashed", false))); } if (sCheckingEvents) { @@ -1320,6 +1318,16 @@ public final class LauncherInstrumentation { if (mCheckEventsForSuccessfulGestures) { final String message = sEventChecker.verify(WAIT_TIME_MS, true); if (message != null) { + try { + Log.e("b/156287114", "Input:"); + for (String line : mDevice.executeShellCommand("dumpsys input").split( + "\\n")) { + Log.d("b/156287114", line); + } + } catch (IOException e) { + e.printStackTrace(); + } + checkForAnomaly(); Assert.fail(formatSystemHealthMessage( "http://go/tapl : successful gesture produced " + message));