diff --git a/Android.bp b/Android.bp index e132854e62..4265a44b7c 100644 --- a/Android.bp +++ b/Android.bp @@ -19,6 +19,7 @@ android_library { "androidx.test.runner", "androidx.test.rules", "androidx.test.uiautomator_uiautomator", + "androidx.preference_preference", "SystemUISharedLib", ], srcs: [ diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8e01f82018..4664c9315a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -49,7 +49,7 @@ android:stateNotNeeded="true" android:windowSoftInputMode="adjustPan" android:screenOrientation="unspecified" - android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize" + android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:resizeableActivity="true" android:resumeWhilePausing="true" android:taskAffinity="" diff --git a/OWNERS b/OWNERS index 3069afaedc..1d6ad8cf8d 100644 --- a/OWNERS +++ b/OWNERS @@ -28,6 +28,7 @@ tracyzhou@google.com peanutbutter@google.com xuqiu@google.com sreyasr@google.com +thiruram@google.com per-file FeatureFlags.java, globs = set noparent per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, zakcohen@google.com, mrcasey@google.com, adamcohen@google.com, hyunyoungs@google.com diff --git a/quickstep/AndroidManifest-launcher.xml b/quickstep/AndroidManifest-launcher.xml index 53910e344b..19f85e4c1f 100644 --- a/quickstep/AndroidManifest-launcher.xml +++ b/quickstep/AndroidManifest-launcher.xml @@ -49,7 +49,7 @@ android:stateNotNeeded="true" android:windowSoftInputMode="adjustPan" android:screenOrientation="unspecified" - android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize" + android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:resizeableActivity="true" android:resumeWhilePausing="true" android:taskAffinity="" diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 4e7c3fa75b..5be32a89b4 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -59,7 +59,7 @@ android:stateNotNeeded="true" android:theme="@style/LauncherTheme" android:screenOrientation="unspecified" - android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize" + android:configChanges="keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenSize|screenLayout|smallestScreenSize|uiMode" android:resizeableActivity="true" android:resumeWhilePausing="true" android:taskAffinity=""/> diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/recents_ui_overrides/res/values/override.xml index 6aa9619cc9..1937164b7d 100644 --- a/quickstep/recents_ui_overrides/res/values/override.xml +++ b/quickstep/recents_ui_overrides/res/values/override.xml @@ -25,8 +25,11 @@ com.android.quickstep.QuickstepProcessInitializer - com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension + com.android.quickstep.logging.UserEventDispatcherExtension com.android.launcher3.hybridhotseat.HotseatPredictionModel + + com.android.launcher3.model.QuickstepModelDelegate + diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index e11c701640..55384af96b 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -30,7 +30,6 @@ import android.graphics.Rect; import android.os.Build; import android.util.AttributeSet; import android.util.IntProperty; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.animation.Interpolator; @@ -44,19 +43,15 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherState; import com.android.launcher3.R; -import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; import com.android.launcher3.anim.AlphaUpdateListener; import com.android.launcher3.anim.PropertySetter; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.keyboard.FocusIndicatorHelper; import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper; import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider; -import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -67,15 +62,12 @@ import com.android.launcher3.util.Themes; import com.android.quickstep.AnimatedFloat; import java.util.ArrayList; -import java.util.Collections; import java.util.List; @TargetApi(Build.VERSION_CODES.P) public class PredictionRowView extends LinearLayout implements LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow { - private static final String TAG = "PredictionRowView"; - private static final IntProperty TEXT_ALPHA = new IntProperty("textAlpha") { @Override @@ -93,16 +85,14 @@ public class PredictionRowView extends LinearLayout implements (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f; private final Launcher mLauncher; - private final PredictionUiStateManager mPredictionUiStateManager; private int mNumPredictedAppsPerRow; - // The set of predicted app component names - private final List mPredictedAppComponents = new ArrayList<>(); - // The set of predicted apps resolved from the component names and the current set of apps - private final ArrayList mPredictedApps = new ArrayList<>(); // Helper to drawing the focus indicator. private final FocusIndicatorHelper mFocusHelper; + // The set of predicted apps resolved from the component names and the current set of apps + private final List mPredictedApps = new ArrayList<>(); + private final int mIconTextColor; private final int mIconFullTextAlpha; private int mIconLastSetTextAlpha; @@ -134,8 +124,6 @@ public class PredictionRowView extends LinearLayout implements mLauncher = Launcher.getLauncher(context); mLauncher.addOnDeviceProfileChangeListener(this); - mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(context); - mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary); mIconFullTextAlpha = Color.alpha(mIconTextColor); mIconCurrentTextAlpha = mIconFullTextAlpha; @@ -146,24 +134,9 @@ public class PredictionRowView extends LinearLayout implements @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - - mPredictionUiStateManager.setTargetAppsView(mLauncher.getAppsView()); - getAppsStore().registerIconContainer(this); AllAppsTipView.scheduleShowIfNeeded(mLauncher); } - private AllAppsStore getAppsStore() { - return mLauncher.getAppsView().getAppsStore(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - mPredictionUiStateManager.setTargetAppsView(null); - getAppsStore().unregisterIconContainer(this); - } - public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { mParent = parent; } @@ -205,7 +178,7 @@ public class PredictionRowView extends LinearLayout implements * Returns the predicted apps. */ public List getPredictedApps() { - return mPredictedApps; + return new ArrayList<>(mPredictedApps); } /** @@ -217,12 +190,12 @@ public class PredictionRowView extends LinearLayout implements * If the number of predicted apps is the same as the previous list of predicted apps, * we can optimize by swapping them in place. */ - public void setPredictedApps(List apps) { - mPredictedAppComponents.clear(); - mPredictedAppComponents.addAll(apps); - + public void setPredictedApps(List items) { mPredictedApps.clear(); - mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents)); + items.stream() + .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo) + .map(itemInfo -> (WorkspaceItemInfo) itemInfo) + .forEach(mPredictedApps::add); applyPredictionApps(); } @@ -264,11 +237,7 @@ public class PredictionRowView extends LinearLayout implements icon.reset(); if (predictionCount > i) { icon.setVisibility(View.VISIBLE); - if (mPredictedApps.get(i) instanceof AppInfo) { - icon.applyFromApplicationInfo((AppInfo) mPredictedApps.get(i)); - } else if (mPredictedApps.get(i) instanceof WorkspaceItemInfo) { - icon.applyFromWorkspaceItem((WorkspaceItemInfo) mPredictedApps.get(i)); - } + icon.applyFromWorkspaceItem(mPredictedApps.get(i)); icon.setTextColor(iconColor); } else { icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE); @@ -284,33 +253,6 @@ public class PredictionRowView extends LinearLayout implements mParent.onHeightUpdated(); } - private List processPredictedAppComponents( - List components) { - if (getAppsStore().getApps().length == 0) { - // Apps have not been bound yet. - return Collections.emptyList(); - } - - List predictedApps = new ArrayList<>(); - for (ComponentKeyMapper mapper : components) { - ItemInfoWithIcon info = mapper.getApp(getAppsStore()); - if (info != null) { - ItemInfoWithIcon predictedApp = info.clone(); - predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION; - predictedApps.add(predictedApp); - } else { - if (FeatureFlags.IS_STUDIO_BUILD) { - Log.e(TAG, "Predicted app not found: " + mapper); - } - } - // Stop at the number of predicted apps - if (predictedApps.size() == mNumPredictedAppsPerRow) { - break; - } - } - return predictedApps; - } - @Override public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child, ArrayList parents) { 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 deleted file mode 100644 index 830c1032ba..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2019 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.appprediction; - -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; -import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; -import static com.android.launcher3.LauncherState.BACKGROUND_APP; -import static com.android.launcher3.LauncherState.OVERVIEW; - -import android.app.prediction.AppPredictor; -import android.app.prediction.AppTarget; -import android.content.ComponentName; -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; -import com.android.launcher3.Launcher; -import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.LauncherState; -import com.android.launcher3.Utilities; -import com.android.launcher3.allapps.AllAppsContainerView; -import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener; -import com.android.launcher3.hybridhotseat.HotseatPredictionController; -import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.ItemInfoWithIcon; -import com.android.launcher3.shortcuts.ShortcutKey; -import com.android.launcher3.statemanager.StateManager.StateListener; -import com.android.launcher3.userevent.nano.LauncherLogProto; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.MainThreadInitializedObject; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.OptionalInt; -import java.util.stream.IntStream; - -/** - * Handler responsible to updating the UI due to predicted apps changes. Operations: - * 1) Pushes the predicted apps to all-apps. If all-apps is visible, waits until it becomes - * invisible again before applying the changes. This ensures that the UI does not change abruptly - * in front of the user, even if an app launched and user pressed back button to return to the - * all-apps UI again. - * 2) Prefetch high-res icons for predicted apps. This ensures that we have the icons in memory - * even if all-apps is not opened as they are shown in search UI as well - * 3) Load instant app if it is not already in memory. As predictions are persisted on disk, - * instant app will not be in memory when launcher starts. - * 4) Maintains the current active client id (for the predictions) and all updates are performed on - * that client id. - */ -public class PredictionUiStateManager implements StateListener, - ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener { - - public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; - - // TODO (b/129421797): Update the client constants - public enum Client { - HOME("home"); - - public final String id; - - Client(String id) { - this.id = id; - } - } - - public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(PredictionUiStateManager::new); - - private final Context mContext; - - private final DynamicItemCache mDynamicItemCache; - private List mPredictionServicePredictions = Collections.emptyList(); - - private int mMaxIconsPerRow; - - private AllAppsContainerView mAppsView; - - private PredictionState mPendingState; - private PredictionState mCurrentState; - - private boolean mGettingValidPredictionResults; - - private PredictionUiStateManager(Context context) { - mContext = context; - - mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated); - - InvariantDeviceProfile idp = LauncherAppState.getIDP(context); - mMaxIconsPerRow = idp.numColumns; - - idp.addOnChangeListener(this); - mGettingValidPredictionResults = Utilities.getDevicePrefs(context) - .getBoolean(LAST_PREDICTION_ENABLED_STATE, true); - - // Call this last - mCurrentState = parseLastState(); - } - - @Override - public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { - mMaxIconsPerRow = profile.numColumns; - } - - public void setTargetAppsView(AllAppsContainerView appsView) { - if (mAppsView != null) { - mAppsView.getAppsStore().removeUpdateListener(this); - } - mAppsView = appsView; - if (mAppsView != null) { - mAppsView.getAppsStore().addUpdateListener(this); - } - if (mPendingState != null) { - applyState(mPendingState); - mPendingState = null; - } else { - applyState(mCurrentState); - } - updateDependencies(mCurrentState); - } - - @Override - public void reapplyItemInfo(ItemInfoWithIcon info) { } - - @Override - public void onStateTransitionComplete(LauncherState state) { - if (mAppsView == null) { - return; - } - if (mPendingState != null && canApplyPredictions(mPendingState)) { - applyState(mPendingState); - mPendingState = null; - } - if (mPendingState == null) { - Launcher.getLauncher(mAppsView.getContext()).getStateManager() - .removeStateListener(this); - } - } - - private void scheduleApplyPredictedApps(PredictionState state) { - boolean registerListener = mPendingState == null; - mPendingState = state; - if (registerListener) { - // Add a listener and wait until appsView is invisible again. - Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this); - } - } - - private void applyState(PredictionState state) { - mCurrentState = state; - if (mAppsView != null) { - mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class) - .setPredictedApps(mCurrentState.apps); - } - } - - private void updatePredictionStateAfterCallback() { - boolean validResults = mPredictionServicePredictions != null - && !mPredictionServicePredictions.isEmpty(); - if (validResults != mGettingValidPredictionResults) { - mGettingValidPredictionResults = validResults; - Utilities.getDevicePrefs(mContext).edit() - .putBoolean(LAST_PREDICTION_ENABLED_STATE, true) - .apply(); - } - dispatchOnChange(true); - } - - public AppPredictor.Callback appPredictorCallback(Client client) { - return targets -> { - mPredictionServicePredictions = targets; - updatePredictionStateAfterCallback(); - }; - } - - private void dispatchOnChange(boolean changed) { - PredictionState newState = changed - ? parseLastState() - : mPendingState != null && canApplyPredictions(mPendingState) - ? mPendingState - : mCurrentState; - if (changed && mAppsView != null && !canApplyPredictions(newState)) { - scheduleApplyPredictedApps(newState); - } else { - applyState(newState); - } - } - - private PredictionState parseLastState() { - PredictionState state = new PredictionState(); - state.isEnabled = mGettingValidPredictionResults; - if (!state.isEnabled) { - state.apps = Collections.EMPTY_LIST; - return state; - } - - state.apps = new ArrayList<>(); - - List appTargets = mPredictionServicePredictions; - if (!appTargets.isEmpty()) { - for (AppTarget appTarget : appTargets) { - ComponentKey key; - if (appTarget.getShortcutInfo() != null) { - key = ShortcutKey.fromInfo(appTarget.getShortcutInfo()); - } else { - key = new ComponentKey(new ComponentName(appTarget.getPackageName(), - appTarget.getClassName()), appTarget.getUser()); - } - state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache)); - } - } - updateDependencies(state); - return state; - } - - private void updateDependencies(PredictionState state) { - if (!state.isEnabled || mAppsView == null) { - return; - } - mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this, - mMaxIconsPerRow); - } - - @Override - public void onAppsUpdated() { - dispatchOnChange(false); - } - - private boolean canApplyPredictions(PredictionState newState) { - if (mAppsView == null) { - // If there is no apps view, no need to schedule. - return true; - } - Launcher launcher = Launcher.getLauncher(mAppsView.getContext()); - PredictionRowView predictionRow = mAppsView.getFloatingHeaderView(). - findFixedRowByType(PredictionRowView.class); - if (!predictionRow.isShown() || predictionRow.getAlpha() == 0 || - launcher.isForceInvisible()) { - return true; - } - - if (mCurrentState.isEnabled != newState.isEnabled - || mCurrentState.apps.isEmpty() != newState.apps.isEmpty()) { - // If the visibility of the prediction row is changing, apply immediately. - return true; - } - - if (launcher.getDeviceProfile().isVerticalBarLayout()) { - // If we are here & mAppsView.isShown() = true, we are probably in all-apps or mid way - return false; - } - if (!launcher.isInState(OVERVIEW) && !launcher.isInState(BACKGROUND_APP)) { - // Just a fallback as we dont need to apply instantly, if we are not in the swipe-up UI - return false; - } - - // Instead of checking against 1, we should check against (1 + delta), where delta accounts - // for the nav-bar height (as app icon can still be visible under the nav-bar). Checking - // against 1, keeps the logic simple :) - return launcher.getAllAppsController().getProgress() > 1; - } - - public PredictionState getCurrentState() { - return mCurrentState; - } - - /** - * Returns ranking info for the app within all apps prediction. - * Only applicable when {@link ItemInfo#itemType} is one of the followings: - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} - */ - public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) { - if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) { - return OptionalInt.empty(); - } - - if (itemInfo.itemType == ITEM_TYPE_APPLICATION - || itemInfo.itemType == ITEM_TYPE_SHORTCUT - || itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) { - ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), - itemInfo.user); - final List apps = getCurrentState().apps; - return IntStream.range(0, apps.size()) - .filter(index -> key.equals(apps.get(index).getComponentKey())) - .findFirst(); - } - - return OptionalInt.empty(); - } - - /** - * Fill in predicted_rank field based on app prediction. - * Only applicable when {@link ItemInfo#itemType} is one of the followings: - * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT}, - * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT} - */ - public static void fillInPredictedRank( - @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { - - final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate(); - if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null - || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT - && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) { - return; - } - if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) { - HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target); - return; - } - - final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); - final List predictedApps = manager.getCurrentState().apps; - IntStream.range(0, predictedApps.size()) - .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) - .findFirst() - .ifPresent((rank) -> target.predictedRank = 0 - rank); - } - - public static class PredictionState { - - public boolean isEnabled; - public List apps; - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java index c15a5963f6..20e1edc05f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java @@ -71,10 +71,9 @@ public class HotseatFileLog { } private PrintWriter getWriter() { - String fName = FILE_NAME_PREFIX + (LOG_DAYS % 10); - if (fName.equals(mFileName)) return mCurrentWriter; - Calendar cal = Calendar.getInstance(); + String fName = FILE_NAME_PREFIX + (cal.get(Calendar.DAY_OF_YEAR) % 10); + if (fName.equals(mFileName)) return mCurrentWriter; boolean append = false; File logFile = new File(mLogsDir, fName); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java index 8c1db4e2e7..9e7c9fba6d 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java @@ -30,7 +30,6 @@ import com.android.launcher3.provider.LauncherDbUtils; */ public class HotseatRestoreHelper { private final Launcher mLauncher; - private boolean mBackupRestored = false; HotseatRestoreHelper(Launcher context) { mLauncher = context; @@ -62,7 +61,6 @@ public class HotseatRestoreHelper { * Finds and restores a previously saved snapshow of Favorites table */ public void restoreBackup() { - if (mBackupRestored) return; MODEL_EXECUTOR.execute(() -> { try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction) LauncherSettings.Settings.call( @@ -78,7 +76,6 @@ public class HotseatRestoreHelper { idp.numRows); backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true); transaction.commit(); - mBackupRestored = true; mLauncher.getModel().forceReload(); } }); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java similarity index 65% rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java rename to quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java index 8cabe3d80b..8e4c43f464 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/AppEventProducer.java @@ -13,175 +13,76 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.appprediction; +package com.android.launcher3.model; -import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP; -import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; -import android.app.prediction.AppPredictionContext; -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.content.Context; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; -import android.util.Log; import androidx.annotation.AnyThread; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; -import com.android.launcher3.InvariantDeviceProfile; -import com.android.launcher3.appprediction.PredictionUiStateManager.Client; import com.android.launcher3.logger.LauncherAtom; import com.android.launcher3.logger.LauncherAtom.ContainerInfo; import com.android.launcher3.logger.LauncherAtom.FolderContainer; import com.android.launcher3.logger.LauncherAtom.HotseatContainer; import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer; import com.android.launcher3.logging.StatsLogManager.EventEnum; -import com.android.launcher3.model.AppLaunchTracker; import com.android.launcher3.pm.UserCache; -import com.android.quickstep.logging.StatsLogCompatManager; import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer; import java.util.Locale; +import java.util.function.Consumer; import java.util.function.Predicate; /** - * Subclass of app tracker which publishes the data to the prediction engine and gets back results. + * Utility class to track stats log and emit corresponding app events */ -@TargetApi(Build.VERSION_CODES.Q) -public class PredictionAppTracker extends AppLaunchTracker implements StatsLogConsumer { +@TargetApi(Build.VERSION_CODES.R) +public class AppEventProducer implements StatsLogConsumer { - private static final String TAG = "PredictionAppTracker"; - private static final boolean DBG = false; + private static final int MSG_LAUNCH = 0; - private static final int MSG_INIT = 0; - private static final int MSG_DESTROY = 1; - private static final int MSG_LAUNCH = 2; - private static final int MSG_PREDICT = 3; - - protected final Context mContext; + private final Context mContext; private final Handler mMessageHandler; + private final Consumer mCallback; - // Accessed only on worker thread - private AppPredictor mHomeAppPredictor; - - public PredictionAppTracker(Context context) { + public AppEventProducer(Context context, Consumer callback) { mContext = context; - mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage); - InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged); - - mMessageHandler.sendEmptyMessage(MSG_INIT); - } - - @UiThread - private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { - if ((changeFlags & CHANGE_FLAG_GRID) != 0) { - // Reinitialize everything - mMessageHandler.sendEmptyMessage(MSG_INIT); - } - } - - @WorkerThread - private void destroy() { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.destroy(); - mHomeAppPredictor = null; - } - StatsLogCompatManager.LOGS_CONSUMER.remove(this); - } - - @WorkerThread - private AppPredictor createPredictor(Client client, int count) { - AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class); - - if (apm == null) { - return null; - } - - AppPredictor predictor = apm.createAppPredictionSession( - new AppPredictionContext.Builder(mContext) - .setUiSurface(client.id) - .setPredictedTargetCount(count) - .setExtras(getAppPredictionContextExtras(client)) - .build()); - predictor.registerPredictionUpdates(mContext.getMainExecutor(), - PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client)); - predictor.requestPredictionUpdate(); - return predictor; - } - - /** - * Override to add custom extras. - */ - @WorkerThread - @Nullable - public Bundle getAppPredictionContextExtras(Client client) { - return null; + mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage); + mCallback = callback; } @WorkerThread private boolean handleMessage(Message msg) { switch (msg.what) { - case MSG_INIT: { - // Destroy any existing clients - destroy(); - - // Initialize the clients - int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns; - mHomeAppPredictor = createPredictor(Client.HOME, count); - StatsLogCompatManager.LOGS_CONSUMER.add(this); - return true; - } - case MSG_DESTROY: { - destroy(); - return true; - } case MSG_LAUNCH: { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj); - } - return true; - } - case MSG_PREDICT: { - if (mHomeAppPredictor != null) { - mHomeAppPredictor.requestPredictionUpdate(); - } + mCallback.accept((AppTargetEvent) msg.obj); return true; } } return false; } - @Override - @UiThread - public void onReturnedToHome() { - String client = Client.HOME.id; - mMessageHandler.removeMessages(MSG_PREDICT, client); - Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget(); - if (DBG) { - Log.d(TAG, String.format("Sent immediate message to update %s", client)); - } - } - @AnyThread private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) { AppTarget target = toAppTarget(atomInfo); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java new file mode 100644 index 0000000000..721e2bebd5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java @@ -0,0 +1,117 @@ +/* + * 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.model; + +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE; +import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER; + +import android.app.prediction.AppTarget; +import android.content.ComponentName; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.os.UserHandle; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.Utilities; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.model.data.AppInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Task to update model as a result of predicted apps update + */ +public class PredictionUpdateTask extends BaseModelUpdateTask { + + private final List mTargets; + private final int mContainerId; + + PredictionUpdateTask(int containerId, List targets) { + mContainerId = containerId; + mTargets = targets; + } + + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + // TODO: persist the whole list + Utilities.getDevicePrefs(app.getContext()).edit() + .putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply(); + + FixedContainerItems fci; + synchronized (dataModel) { + fci = dataModel.extraItems.get(mContainerId); + if (fci == null) { + return; + } + } + + Set usersForChangedShortcuts = new HashSet<>(fci.items.stream() + .filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT) + .map(info -> info.user) + .collect(Collectors.toSet())); + fci.items.clear(); + + for (AppTarget target : mTargets) { + WorkspaceItemInfo itemInfo; + ShortcutInfo si = target.getShortcutInfo(); + if (si != null) { + usersForChangedShortcuts.add(si.getUserHandle()); + itemInfo = new WorkspaceItemInfo(si, app.getContext()); + app.getIconCache().getShortcutIcon(itemInfo, si); + } else { + String className = target.getClassName(); + if (COMPONENT_CLASS_MARKER.equals(className)) { + // TODO: Implement this + continue; + } + ComponentName cn = new ComponentName(target.getPackageName(), className); + UserHandle user = target.getUser(); + itemInfo = apps.data.stream() + .filter(info -> user.equals(info.user) && cn.equals(info.componentName)) + .map(AppInfo::makeWorkspaceItem) + .findAny() + .orElseGet(() -> { + LauncherActivityInfo lai = app.getContext() + .getSystemService(LauncherApps.class) + .resolveActivity(AppInfo.makeLaunchIntent(cn), user); + if (lai == null) { + return null; + } + AppInfo ai = new AppInfo(app.getContext(), lai, user); + app.getIconCache().getTitleAndIcon(ai, lai, false); + return ai.makeWorkspaceItem(); + }); + + if (itemInfo == null) { + continue; + } + } + + itemInfo.container = mContainerId; + fci.items.add(itemInfo); + } + + bindExtraContainerItems(fci); + usersForChangedShortcuts.forEach( + u -> dataModel.updateShortcutPinnedState(app.getContext(), u)); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java new file mode 100644 index 0000000000..b5164695d5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java @@ -0,0 +1,138 @@ +/* + * 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.model; + +import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION; + +import android.app.prediction.AppPredictionContext; +import android.app.prediction.AppPredictionManager; +import android.app.prediction.AppPredictor; +import android.app.prediction.AppTarget; +import android.app.prediction.AppTargetEvent; +import android.content.Context; + +import androidx.annotation.WorkerThread; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; +import com.android.launcher3.util.Executors; +import com.android.quickstep.logging.StatsLogCompatManager; + +import java.util.List; + +/** + * Model delegate which loads prediction items + */ +public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener { + + public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; + + private final InvariantDeviceProfile mIDP; + private final AppEventProducer mAppEventProducer; + + private AppPredictor mAllAppsPredictor; + private boolean mActive = false; + + public QuickstepModelDelegate(Context context) { + mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent); + + mIDP = InvariantDeviceProfile.INSTANCE.get(context); + mIDP.addOnChangeListener(this); + StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer); + } + + @Override + public void loadItems() { + // TODO: Implement caching and preloading + super.loadItems(); + mDataModel.extraItems.put( + CONTAINER_PREDICTION, new FixedContainerItems(CONTAINER_PREDICTION)); + + mActive = true; + recreatePredictors(); + } + + @Override + public void validateData() { + super.validateData(); + if (mAllAppsPredictor != null) { + mAllAppsPredictor.requestPredictionUpdate(); + } + } + + @Override + public void destroy() { + super.destroy(); + mActive = false; + StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer); + + destroyPredictors(); + mIDP.removeOnChangeListener(this); + } + + private void destroyPredictors() { + if (mAllAppsPredictor != null) { + mAllAppsPredictor.destroy(); + mAllAppsPredictor = null; + } + } + + @WorkerThread + private void recreatePredictors() { + destroyPredictors(); + if (!mActive) { + return; + } + + Context context = mApp.getContext(); + AppPredictionManager apm = context.getSystemService(AppPredictionManager.class); + if (apm == null) { + return; + } + + int count = mIDP.numAllAppsColumns; + + mAllAppsPredictor = apm.createAppPredictionSession( + new AppPredictionContext.Builder(context) + .setUiSurface("home") + .setPredictedTargetCount(count) + .build()); + mAllAppsPredictor.registerPredictionUpdates( + Executors.MODEL_EXECUTOR, this::onAllAppsPredictionChanged); + mAllAppsPredictor.requestPredictionUpdate(); + } + + private void onAllAppsPredictionChanged(List targets) { + mApp.getModel().enqueueModelUpdateTask( + new PredictionUpdateTask(CONTAINER_PREDICTION, targets)); + } + + @Override + public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) { + if ((changeFlags & CHANGE_FLAG_GRID) != 0) { + // Reinitialize everything + Executors.MODEL_EXECUTOR.execute(this::recreatePredictors); + } + } + + private void onAppTargetEvent(AppTargetEvent event) { + if (mAllAppsPredictor != null) { + mAllAppsPredictor.notifyAppTargetEvent(event); + } + } +} 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 bdbd1e955d..cf86cd5324 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 @@ -17,6 +17,9 @@ package com.android.launcher3.uioverrides; import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK; @@ -37,16 +40,18 @@ import android.view.View; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherState; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; -import com.android.launcher3.appprediction.PredictionUiStateManager; +import com.android.launcher3.appprediction.PredictionRowView; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.hybridhotseat.HotseatPredictionController; import com.android.launcher3.logging.InstanceId; import com.android.launcher3.logging.StatsLogManager.StatsLogger; +import com.android.launcher3.model.BgDataModel.FixedContainerItems; import com.android.launcher3.model.data.AppInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; @@ -79,7 +84,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import java.util.OptionalInt; +import java.util.Objects; import java.util.stream.Stream; public class QuickstepLauncher extends BaseQuickstepLauncher { @@ -91,6 +96,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2); + private FixedContainerItems mAllAppsPredictions; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -111,8 +118,23 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { protected void logAppLaunch(ItemInfo info, InstanceId instanceId) { StatsLogger logger = getStatsLogManager() .logger().withItemInfo(info).withInstanceId(instanceId); - OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info); - allAppsRank.ifPresent(logger::withRank); + + if (mAllAppsPredictions != null + && (info.itemType == ITEM_TYPE_APPLICATION + || info.itemType == ITEM_TYPE_SHORTCUT + || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) { + int count = mAllAppsPredictions.items.size(); + for (int i = 0; i < count; i++) { + ItemInfo targetInfo = mAllAppsPredictions.items.get(i); + if (targetInfo.itemType == info.itemType + && targetInfo.user.equals(info.user) + && Objects.equals(targetInfo.getIntent(), info.getIntent())) { + logger.withRank(i); + break; + } + + } + } logger.log(LAUNCHER_APP_LAUNCH_TAP); if (mHotseatPredictionController != null) { @@ -200,6 +222,15 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { } } + @Override + public void bindExtraContainerItems(FixedContainerItems item) { + if (item.containerId == Favorites.CONTAINER_PREDICTION) { + mAllAppsPredictions = item; + getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class) + .setPredictedApps(item.items); + } + } + @Override public void onDestroy() { super.onDestroy(); @@ -216,10 +247,8 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { switch (state.ordinal) { case HINT_STATE_ORDINAL: { Workspace workspace = getWorkspace(); - boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE; - getStateManager().goToState(NORMAL, true, - willMoveScreens ? null : getScrimView()::startDragHandleEducationAnim); - if (willMoveScreens) { + getStateManager().goToState(NORMAL); + if (workspace.getNextPage() != Workspace.DEFAULT_PAGE) { workspace.post(workspace::moveToDefaultScreen); } break; diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java index a0af79743b..131fcbfbf1 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java @@ -17,7 +17,6 @@ package com.android.launcher3.uioverrides.states; import static android.view.View.VISIBLE; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.HINT_STATE; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; @@ -52,6 +51,7 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_S import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE; import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON; import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import android.animation.Animator; import android.animation.AnimatorSet; @@ -212,7 +212,7 @@ public class QuickstepAtomicAnimationFactory extends // Scale up the recents, if it is not coming from the side RecentsView overview = mActivity.getOverviewPanel(); if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) { - SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); + RECENTS_SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); } } config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java index c1a585ea9d..e45fa9d513 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java @@ -159,8 +159,7 @@ public class NavBarToHomeTouchController implements TouchController, builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET, -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR); if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - builder.addOnFrameCallback( - () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */)); + builder.addOnFrameCallback(recentsView::redrawLiveTile); } } else if (mStartState == ALL_APPS) { AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index 1b439d1d31..821ada400e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -15,7 +15,6 @@ */ package com.android.launcher3.uioverrides.touchcontrollers; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherState.HOTSEAT_ICONS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; @@ -47,6 +46,7 @@ import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE; import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK; import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET; import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS; +import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; @@ -244,7 +244,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, final LauncherState toState = OVERVIEW; // Set RecentView's initial properties. - SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]); + RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]); ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f); mRecentsView.setContentAlpha(1); mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress()); @@ -266,7 +266,8 @@ public class NoButtonQuickSwitchTouchController implements TouchController, // - RecentsView scale // - RecentsView fullscreenProgress PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2)); - yAnim.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], SCALE_DOWN_INTERPOLATOR); + yAnim.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0], + SCALE_DOWN_INTERPOLATOR); yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS, toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR); mYOverviewAnim = yAnim.createPlaybackController(); diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java index 0ee5d047c6..fce019b3e4 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java @@ -16,7 +16,6 @@ package com.android.launcher3.uioverrides.touchcontrollers; import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE; -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE; import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE; @@ -262,11 +261,6 @@ public abstract class TaskViewTouchController mCurrentAnimation.setPlayFraction(Utilities.boundToRange( totalDisplacement * mProgressMultiplier, 0, 1)); - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - if (mRecentsView.getCurrentPage() != 0 || isGoingUp) { - mRecentsView.redrawLiveTile(true); - } - } return true; } @@ -297,13 +291,6 @@ public abstract class TaskViewTouchController } mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction)); - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> { - if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) { - mRecentsView.redrawLiveTile(true); - } - }); - } mCurrentAnimation.startWithVelocity(mActivity, goingToEnd, velocity, mEndDisplacement, animationDuration); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java similarity index 82% rename from quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java rename to quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java index 1e98a6b61d..3a5c027698 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -15,6 +15,8 @@ */ package com.android.quickstep; +import static android.widget.Toast.LENGTH_SHORT; + import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.anim.Interpolators.DEACCEL; @@ -29,7 +31,9 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT; import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; +import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; +import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; import static com.android.quickstep.GestureState.GestureEndTarget.HOME; import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK; @@ -51,13 +55,17 @@ import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.graphics.PointF; +import android.graphics.Rect; import android.os.Build; import android.os.SystemClock; +import android.util.Log; +import android.view.MotionEvent; import android.view.View; import android.view.View.OnApplyWindowInsetsListener; import android.view.ViewTreeObserver.OnDrawListener; import android.view.WindowInsets; import android.view.animation.Interpolator; +import android.widget.Toast; import androidx.annotation.Nullable; import androidx.annotation.UiThread; @@ -73,17 +81,24 @@ import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.logging.StatsLogManager.StatsLogger; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.statemanager.StatefulActivity; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.TraceHelper; +import com.android.launcher3.util.VibratorWrapper; +import com.android.launcher3.util.WindowBounds; import com.android.quickstep.BaseActivityInterface.AnimationFactory; import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.inputconsumers.OverviewInputConsumer; import com.android.quickstep.util.ActiveGestureLog; +import com.android.quickstep.util.ActivityInitListener; +import com.android.quickstep.util.InputConsumerProxy; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.ShelfPeekAnim; import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState; +import com.android.quickstep.util.SurfaceTransactionApplier; +import com.android.quickstep.util.TransformParams; import com.android.quickstep.views.LiveTileOverlay; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -95,17 +110,34 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.TaskInfoCompat; import com.android.systemui.shared.system.TaskStackChangeListener; +import java.util.ArrayList; +import java.util.function.Consumer; + /** * Handles the navigation gestures when Launcher is the default home activity. - * TODO: Merge this with BaseSwipeUpHandler */ -@TargetApi(Build.VERSION_CODES.O) -public abstract class BaseSwipeUpHandlerV2, Q extends RecentsView> - extends BaseSwipeUpHandler implements OnApplyWindowInsetsListener { - private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName(); +@TargetApi(Build.VERSION_CODES.R) +public abstract class AbsSwipeUpHandler, Q extends RecentsView> + extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener, + RecentsAnimationCallbacks.RecentsAnimationListener { + private static final String TAG = "AbsSwipeUpHandler"; private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null; + protected final BaseActivityInterface mActivityInterface; + protected final InputConsumerProxy mInputConsumerProxy; + protected final ActivityInitListener mActivityInitListener; + // Callbacks to be made once the recents animation starts + private final ArrayList mRecentsAnimationStartCallbacks = new ArrayList<>(); + protected RecentsAnimationController mRecentsAnimationController; + protected RecentsAnimationTargets mRecentsAnimationTargets; + protected T mActivity; + protected Q mRecentsView; + protected Runnable mGestureEndCallback; + protected MultiStateCallback mStateCallback; + protected boolean mCanceled; + private boolean mRecentsViewScrollLinked = false; + private static int getFlagForIndex(int index, String name) { if (DEBUG_STATES) { STATE_NAMES[index] = name; @@ -201,11 +233,15 @@ public abstract class BaseSwipeUpHandlerV2, Q exte private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch; - public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState, + public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, boolean continuingLastGesture, InputConsumerController inputConsumer) { - super(context, deviceState, gestureState, inputConsumer); + super(context, deviceState, gestureState, new TransformParams()); + mActivityInterface = gestureState.getActivityInterface(); + mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit); + mInputConsumerProxy = + new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler); mTaskAnimationManager = taskAnimationManager; mTouchTimeMs = touchTimeMs; mContinuingLastGesture = continuingLastGesture; @@ -267,10 +303,6 @@ public abstract class BaseSwipeUpHandlerV2, Q exte mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK, this::notifyTransitionCancelled); - mGestureState.runOnceAtState(STATE_END_TARGET_SET, - () -> mDeviceState.onEndTargetCalculated(mGestureState.getEndTarget(), - mActivityInterface)); - if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT | STATE_SCREENSHOT_VIEW_SHOWN | STATE_CAPTURE_SCREENSHOT, @@ -278,9 +310,17 @@ public abstract class BaseSwipeUpHandlerV2, Q exte } } - @Override protected boolean onActivityInit(Boolean alreadyOnHome) { - super.onActivityInit(alreadyOnHome); + T createdActivity = mActivityInterface.getCreatedActivity(); + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1"); + } + if (createdActivity != null) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2"); + } + initTransitionEndpoints(createdActivity.getDeviceProfile()); + } final T activity = mActivityInterface.getCreatedActivity(); if (mActivity == activity) { return true; @@ -319,7 +359,9 @@ public abstract class BaseSwipeUpHandlerV2, Q exte return true; } - @Override + /** + * Return true if the window should be translated horizontally if the recents view scrolls + */ protected boolean moveWindowWithRecentsScroll() { return mGestureState.getEndTarget() != HOME; } @@ -332,7 +374,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { return; } - mTaskViewSimulator.setRecentsConfiguration(mActivity.getResources().getConfiguration()); + mTaskViewSimulator.setRecentsRotation(mActivity.getDisplay().getRotation()); // If we've already ended the gesture and are going home, don't prepare recents UI, // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL. @@ -395,6 +437,11 @@ public abstract class BaseSwipeUpHandlerV2, Q exte mGestureState.getActivityInterface().setOnDeferredActivityLaunchCallback( mOnDeferredActivityLaunch); + mGestureState.runOnceAtState(STATE_END_TARGET_SET, + () -> mDeviceState.getRotationTouchHelper(). + onEndTargetCalculated(mGestureState.getEndTarget(), + mActivityInterface)); + notifyGestureStartedAsync(); } @@ -443,7 +490,9 @@ public abstract class BaseSwipeUpHandlerV2, Q exte .getHighResLoadingState().setVisible(true); } - @Override + /** + * Called when motion pause is detected + */ public void onMotionPauseChanged(boolean isPaused) { setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION); } @@ -481,7 +530,6 @@ public abstract class BaseSwipeUpHandlerV2, Q exte mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate); } - @Override public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */); } @@ -537,11 +585,14 @@ public abstract class BaseSwipeUpHandlerV2, Q exte updateLauncherTransitionProgress(); } - @Override public Intent getLaunchIntent() { return mGestureState.getOverviewIntent(); } + /** + * Called when the value of {@link #mCurrentShift} changes + */ + @UiThread @Override public void updateFinalShift() { final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; @@ -602,7 +653,41 @@ public abstract class BaseSwipeUpHandlerV2, Q exte public void onRecentsAnimationStart(RecentsAnimationController controller, RecentsAnimationTargets targets) { ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length); - super.onRecentsAnimationStart(controller, targets); + mRecentsAnimationController = controller; + mRecentsAnimationTargets = targets; + mTransformParams.setTargetSet(mRecentsAnimationTargets); + RemoteAnimationTargetCompat runningTaskTarget = targets.findTask( + mGestureState.getRunningTaskId()); + + if (runningTaskTarget != null) { + mTaskViewSimulator.setPreview(runningTaskTarget); + } + + // Only initialize the device profile, if it has not been initialized before, as in some + // configurations targets.homeContentInsets may not be correct. + if (mActivity == null) { + DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile(); + if (targets.minimizedHomeBounds != null && runningTaskTarget != null) { + Rect overviewStackBounds = mActivityInterface + .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget); + dp = dp.getMultiWindowProfile(mContext, + new WindowBounds(overviewStackBounds, targets.homeContentInsets)); + } else { + // If we are not in multi-window mode, home insets should be same as system insets. + dp = dp.copy(mContext); + } + dp.updateInsets(targets.homeContentInsets); + dp.updateIsSeascape(mContext); + initTransitionEndpoints(dp); + } + + // Notify when the animation starts + if (!mRecentsAnimationStartCallbacks.isEmpty()) { + for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) { + action.run(); + } + mRecentsAnimationStartCallbacks.clear(); + } // Only add the callback to enable the input consumer after we actually have the controller mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED, @@ -619,10 +704,14 @@ public abstract class BaseSwipeUpHandlerV2, Q exte mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); // Defer clearing the controller and the targets until after we've updated the state - super.onRecentsAnimationCanceled(thumbnailData); + mRecentsAnimationController = null; + mRecentsAnimationTargets = null; + if (mRecentsView != null) { + mRecentsView.setRecentsAnimationTargets(null, null); + } } - @Override + @UiThread public void onGestureStarted(boolean isLikelyToStartNewTask) { notifyGestureStartedAsync(); setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */); @@ -646,7 +735,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte /** * Called as a result on ACTION_CANCEL to return the UI to the start state. */ - @Override + @UiThread public void onGestureCancelled() { updateDisplacement(0); mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED); @@ -659,7 +748,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte * @param velocity The x and y components of the velocity when the gesture ends. * @param downPos The x and y value of where the gesture started. */ - @Override + @UiThread public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) { float flingThreshold = mContext.getResources() .getDimension(R.dimen.quickstep_fling_threshold_velocity); @@ -677,7 +766,10 @@ public abstract class BaseSwipeUpHandlerV2, Q exte handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */); } - @Override + /** + * Called to create a input proxy for the running task + */ + @UiThread protected InputConsumer createNewInputProxyHandler() { endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */); endLauncherTransitionController(); @@ -718,7 +810,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget()); } - @Override + /** @return Whether this was the task we were waiting to appear, and thus handled it. */ protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) { if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) { return false; @@ -1095,10 +1187,12 @@ public abstract class BaseSwipeUpHandlerV2, Q exte mActivityInterface.onSwipeUpToHomeComplete(mDeviceState); } }); + if (mRecentsAnimationTargets != null) { + mRecentsAnimationTargets.addReleaseCheck(anim); + } return anim; } - @Override public void onConsumerAboutToBeSwitched() { if (mActivity != null) { // In the off chance that the gesture ends before Launcher is started, we should clear @@ -1127,15 +1221,6 @@ public abstract class BaseSwipeUpHandlerV2, Q exte @UiThread private void startNewTask() { - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal); - } else { - startNewTaskInternal(); - } - } - - @UiThread - private void startNewTaskInternal() { TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView(); startNewTask(success -> { if (!success) { @@ -1149,9 +1234,19 @@ public abstract class BaseSwipeUpHandlerV2, Q exte }); } - @Override + /** + * Called when we successfully startNewTask() on the task that was previously running. Normally + * we call resumeLastTask() when returning to the previously running task, but this handles a + * specific edge case: if we switch from A to B, and back to A before B appears, we need to + * start A again to ensure it stays on top. + */ + @androidx.annotation.CallSuper protected void onRestartPreviouslyAppearedTask() { - super.onRestartPreviouslyAppearedTask(); + // Finish the controller here, since we won't get onTaskAppeared() for a task that already + // appeared. + if (mRecentsAnimationController != null) { + mRecentsAnimationController.finish(false, null); + } reset(); } @@ -1256,7 +1351,7 @@ public abstract class BaseSwipeUpHandlerV2, Q exte // new thumbnail finishTransitionPosted = ViewUtils.postDraw(taskView, () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED), - this::isCanceled); + this::isCanceled); } } if (!finishTransitionPosted) { @@ -1325,4 +1420,172 @@ public abstract class BaseSwipeUpHandlerV2, Q exte return app.isNotInRecents || app.activityType == ACTIVITY_TYPE_HOME; } + + /** + * To be called at the end of constructor of subclasses. This calls various methods which can + * depend on proper class initialization. + */ + protected void initAfterSubclassConstructor() { + initTransitionEndpoints( + mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile()); + } + + protected void performHapticFeedback() { + VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC); + } + + public Consumer getRecentsViewDispatcher(float navbarRotation) { + return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null; + } + + public void setGestureEndCallback(Runnable gestureEndCallback) { + mGestureEndCallback = gestureEndCallback; + } + + protected void linkRecentsViewScroll() { + SurfaceTransactionApplier.create(mRecentsView, applier -> { + mTransformParams.setSyncTransactionApplier(applier); + runOnRecentsAnimationStart(() -> + mRecentsAnimationTargets.addReleaseCheck(applier)); + }); + + mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + if (moveWindowWithRecentsScroll()) { + updateFinalShift(); + } + }); + runOnRecentsAnimationStart(() -> + mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController, + mRecentsAnimationTargets)); + mRecentsViewScrollLinked = true; + } + + protected void startNewTask(Consumer resultCallback) { + // Launch the task user scrolled to (mRecentsView.getNextPage()). + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + // We finish recents animation inside launchTask() when live tile is enabled. + mRecentsView.getNextPageTaskView().launchTask(false /* animate */, + true /* freezeTaskList */); + } else { + if (!mCanceled) { + TaskView nextTask = mRecentsView.getNextPageTaskView(); + if (nextTask != null) { + int taskId = nextTask.getTask().key.id; + mGestureState.updateLastStartedTaskId(taskId); + boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds() + .contains(taskId); + nextTask.launchTask(false /* animate */, true /* freezeTaskList */, + success -> { + resultCallback.accept(success); + if (success) { + if (hasTaskPreviouslyAppeared) { + onRestartPreviouslyAppearedTask(); + } + } else { + mActivityInterface.onLaunchTaskFailed(); + nextTask.notifyTaskLaunchFailed(TAG); + mRecentsAnimationController.finish(true /* toRecents */, null); + } + }, MAIN_EXECUTOR.getHandler()); + } else { + mActivityInterface.onLaunchTaskFailed(); + Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show(); + mRecentsAnimationController.finish(true /* toRecents */, null); + } + } + mCanceled = false; + } + } + + /** + * Runs the given {@param action} if the recents animation has already started, or queues it to + * be run when it is next started. + */ + protected void runOnRecentsAnimationStart(Runnable action) { + if (mRecentsAnimationTargets == null) { + mRecentsAnimationStartCallbacks.add(action); + } else { + action.run(); + } + } + + /** + * TODO can we remove this now that we don't finish the controller until onTaskAppeared()? + * @return whether the recents animation has started and there are valid app targets. + */ + protected boolean hasTargets() { + return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets(); + } + + @Override + public void onRecentsAnimationFinished(RecentsAnimationController controller) { + mRecentsAnimationController = null; + mRecentsAnimationTargets = null; + if (mRecentsView != null) { + mRecentsView.setRecentsAnimationTargets(null, null); + } + } + + @Override + public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) { + if (mRecentsAnimationController != null) { + if (handleTaskAppeared(appearedTaskTarget)) { + mRecentsAnimationController.finish(false /* toRecents */, + null /* onFinishComplete */); + mActivityInterface.onLaunchTaskSuccess(); + ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false); + } + } + } + + /** + * @return The index of the TaskView in RecentsView whose taskId matches the task that will + * resume if we finish the controller. + */ + protected int getLastAppearedTaskIndex() { + return mGestureState.getLastAppearedTaskId() != -1 + ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId()) + : mRecentsView.getRunningTaskIndex(); + } + + /** + * @return Whether we are continuing a gesture that already landed on a new task, + * but before that task appeared. + */ + protected boolean hasStartedNewTask() { + return mGestureState.getLastStartedTaskId() != -1; + } + + /** + * Registers a callback to run when the activity is ready. + * @param intent The intent that will be used to start the activity if it doesn't exist already. + */ + public void initWhenReady(Intent intent) { + // Preload the plan + RecentsModel.INSTANCE.get(mContext).getTasks(null); + + mActivityInitListener.register(intent); + } + + /** + * Applies the transform on the recents animation + */ + protected void applyWindowTransform() { + if (mWindowTransitionController != null) { + float progress = mCurrentShift.value / mDragLengthFactor; + mWindowTransitionController.setPlayFraction(progress); + } + if (mRecentsAnimationTargets != null) { + if (mRecentsViewScrollLinked) { + mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset()); + } + mTaskViewSimulator.apply(mTransformParams); + } + } + + public interface Factory { + + AbsSwipeUpHandler, RecentsView> newHandler( + GestureState gestureState, long touchTimeMs, boolean continuingLastGesture); + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java deleted file mode 100644 index aba5ab63e1..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright (C) 2019 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; - -import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; -import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; -import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; -import android.graphics.PointF; -import android.graphics.Rect; -import android.os.Build; -import android.util.Log; -import android.view.MotionEvent; - -import androidx.annotation.CallSuper; -import androidx.annotation.UiThread; - -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.statemanager.StatefulActivity; -import com.android.launcher3.testing.TestProtocol; -import com.android.launcher3.util.VibratorWrapper; -import com.android.launcher3.util.WindowBounds; -import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener; -import com.android.quickstep.util.ActiveGestureLog; -import com.android.quickstep.util.ActivityInitListener; -import com.android.quickstep.util.InputConsumerProxy; -import com.android.quickstep.util.RectFSpringAnim; -import com.android.quickstep.util.SurfaceTransactionApplier; -import com.android.quickstep.util.TransformParams; -import com.android.quickstep.views.RecentsView; -import com.android.quickstep.views.TaskView; -import com.android.systemui.shared.recents.model.ThumbnailData; -import com.android.systemui.shared.system.InputConsumerController; -import com.android.systemui.shared.system.RemoteAnimationTargetCompat; - -import java.util.ArrayList; -import java.util.function.Consumer; - -/** - * Base class for swipe up handler with some utility methods - */ -@TargetApi(Build.VERSION_CODES.Q) -public abstract class BaseSwipeUpHandler, Q extends RecentsView> - extends SwipeUpAnimationLogic implements RecentsAnimationListener { - - private static final String TAG = "BaseSwipeUpHandler"; - - protected final BaseActivityInterface mActivityInterface; - protected final InputConsumerProxy mInputConsumerProxy; - - protected final ActivityInitListener mActivityInitListener; - - protected RecentsAnimationController mRecentsAnimationController; - protected RecentsAnimationTargets mRecentsAnimationTargets; - - // Callbacks to be made once the recents animation starts - private final ArrayList mRecentsAnimationStartCallbacks = new ArrayList<>(); - - protected T mActivity; - protected Q mRecentsView; - - protected Runnable mGestureEndCallback; - - protected MultiStateCallback mStateCallback; - - protected boolean mCanceled; - - private boolean mRecentsViewScrollLinked = false; - - protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState, - GestureState gestureState, InputConsumerController inputConsumer) { - super(context, deviceState, gestureState, new TransformParams()); - mActivityInterface = gestureState.getActivityInterface(); - mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit); - mInputConsumerProxy = - new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler); - } - - /** - * To be called at the end of constructor of subclasses. This calls various methods which can - * depend on proper class initialization. - */ - protected void initAfterSubclassConstructor() { - initTransitionEndpoints( - mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile()); - } - - protected void performHapticFeedback() { - VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC); - } - - public Consumer getRecentsViewDispatcher(float navbarRotation) { - return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null; - } - - public void setGestureEndCallback(Runnable gestureEndCallback) { - mGestureEndCallback = gestureEndCallback; - } - - public abstract Intent getLaunchIntent(); - - protected void linkRecentsViewScroll() { - SurfaceTransactionApplier.create(mRecentsView, applier -> { - mTransformParams.setSyncTransactionApplier(applier); - runOnRecentsAnimationStart(() -> - mRecentsAnimationTargets.addReleaseCheck(applier)); - }); - - mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { - if (moveWindowWithRecentsScroll()) { - updateFinalShift(); - } - }); - runOnRecentsAnimationStart(() -> - mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController, - mRecentsAnimationTargets)); - mRecentsViewScrollLinked = true; - } - - protected void startNewTask(Consumer resultCallback) { - // Launch the task user scrolled to (mRecentsView.getNextPage()). - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - // We finish recents animation inside launchTask() when live tile is enabled. - mRecentsView.getNextPageTaskView().launchTask(false /* animate */, - true /* freezeTaskList */); - } else { - int taskId = mRecentsView.getNextPageTaskView().getTask().key.id; - if (!mCanceled) { - TaskView nextTask = mRecentsView.getTaskView(taskId); - if (nextTask != null) { - mGestureState.updateLastStartedTaskId(taskId); - boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds() - .contains(taskId); - nextTask.launchTask(false /* animate */, true /* freezeTaskList */, - success -> { - resultCallback.accept(success); - if (success) { - if (hasTaskPreviouslyAppeared) { - onRestartPreviouslyAppearedTask(); - } - } else { - mActivityInterface.onLaunchTaskFailed(); - nextTask.notifyTaskLaunchFailed(TAG); - mRecentsAnimationController.finish(true /* toRecents */, null); - } - }, MAIN_EXECUTOR.getHandler()); - } - } - mCanceled = false; - } - } - - /** - * Called when we successfully startNewTask() on the task that was previously running. Normally - * we call resumeLastTask() when returning to the previously running task, but this handles a - * specific edge case: if we switch from A to B, and back to A before B appears, we need to - * start A again to ensure it stays on top. - */ - @CallSuper - protected void onRestartPreviouslyAppearedTask() { - // Finish the controller here, since we won't get onTaskAppeared() for a task that already - // appeared. - if (mRecentsAnimationController != null) { - mRecentsAnimationController.finish(false, null); - } - } - - /** - * Runs the given {@param action} if the recents animation has already started, or queues it to - * be run when it is next started. - */ - protected void runOnRecentsAnimationStart(Runnable action) { - if (mRecentsAnimationTargets == null) { - mRecentsAnimationStartCallbacks.add(action); - } else { - action.run(); - } - } - - /** - * TODO can we remove this now that we don't finish the controller until onTaskAppeared()? - * @return whether the recents animation has started and there are valid app targets. - */ - protected boolean hasTargets() { - return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets(); - } - - @Override - public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController, - RecentsAnimationTargets targets) { - mRecentsAnimationController = recentsAnimationController; - mRecentsAnimationTargets = targets; - mTransformParams.setTargetSet(mRecentsAnimationTargets); - RemoteAnimationTargetCompat runningTaskTarget = targets.findTask( - mGestureState.getRunningTaskId()); - - if (runningTaskTarget != null) { - mTaskViewSimulator.setPreview(runningTaskTarget); - } - - // Only initialize the device profile, if it has not been initialized before, as in some - // configurations targets.homeContentInsets may not be correct. - if (mActivity == null) { - DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile(); - if (targets.minimizedHomeBounds != null && runningTaskTarget != null) { - Rect overviewStackBounds = mActivityInterface - .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget); - dp = dp.getMultiWindowProfile(mContext, - new WindowBounds(overviewStackBounds, targets.homeContentInsets)); - } else { - // If we are not in multi-window mode, home insets should be same as system insets. - dp = dp.copy(mContext); - } - dp.updateInsets(targets.homeContentInsets); - dp.updateIsSeascape(mContext); - initTransitionEndpoints(dp); - } - - // Notify when the animation starts - if (!mRecentsAnimationStartCallbacks.isEmpty()) { - for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) { - action.run(); - } - mRecentsAnimationStartCallbacks.clear(); - } - } - - @Override - public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) { - mRecentsAnimationController = null; - mRecentsAnimationTargets = null; - if (mRecentsView != null) { - mRecentsView.setRecentsAnimationTargets(null, null); - } - } - - @Override - public void onRecentsAnimationFinished(RecentsAnimationController controller) { - mRecentsAnimationController = null; - mRecentsAnimationTargets = null; - if (mRecentsView != null) { - mRecentsView.setRecentsAnimationTargets(null, null); - } - } - - @Override - public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) { - if (mRecentsAnimationController != null) { - if (handleTaskAppeared(appearedTaskTarget)) { - mRecentsAnimationController.finish(false /* toRecents */, - null /* onFinishComplete */); - mActivityInterface.onLaunchTaskSuccess(); - ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false); - } - } - } - - /** @return Whether this was the task we were waiting to appear, and thus handled it. */ - protected abstract boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget); - - /** - * @return The index of the TaskView in RecentsView whose taskId matches the task that will - * resume if we finish the controller. - */ - protected int getLastAppearedTaskIndex() { - return mGestureState.getLastAppearedTaskId() != -1 - ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId()) - : mRecentsView.getRunningTaskIndex(); - } - - /** - * @return Whether we are continuing a gesture that already landed on a new task, - * but before that task appeared. - */ - protected boolean hasStartedNewTask() { - return mGestureState.getLastStartedTaskId() != -1; - } - - /** - * Return true if the window should be translated horizontally if the recents view scrolls - */ - protected abstract boolean moveWindowWithRecentsScroll(); - - protected boolean onActivityInit(Boolean alreadyOnHome) { - T createdActivity = mActivityInterface.getCreatedActivity(); - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1"); - } - if (createdActivity != null) { - if (TestProtocol.sDebugTracing) { - Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2"); - } - initTransitionEndpoints(createdActivity.getDeviceProfile()); - } - return true; - } - - /** - * Called to create a input proxy for the running task - */ - @UiThread - protected abstract InputConsumer createNewInputProxyHandler(); - - /** - * Called when the value of {@link #mCurrentShift} changes - */ - @UiThread - public abstract void updateFinalShift(); - - /** - * Called when motion pause is detected - */ - public abstract void onMotionPauseChanged(boolean isPaused); - - @UiThread - public void onGestureStarted(boolean isLikelyToStartNewTask) { } - - @UiThread - public abstract void onGestureCancelled(); - - @UiThread - public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos); - - public abstract void onConsumerAboutToBeSwitched(); - - public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { } - - /** - * Registers a callback to run when the activity is ready. - * @param intent The intent that will be used to start the activity if it doesn't exist already. - */ - public void initWhenReady(Intent intent) { - // Preload the plan - RecentsModel.INSTANCE.get(mContext).getTasks(null); - - mActivityInitListener.register(intent); - } - - /** - * Applies the transform on the recents animation - */ - protected void applyWindowTransform() { - if (mWindowTransitionController != null) { - float progress = mCurrentShift.value / mDragLengthFactor; - mWindowTransitionController.setPlayFraction(progress); - } - if (mRecentsAnimationTargets != null) { - if (mRecentsViewScrollLinked) { - mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset()); - } - mTaskViewSimulator.apply(mTransformParams); - } - } - - @Override - protected RectFSpringAnim createWindowAnimationToHome(float startProgress, - HomeAnimationFactory homeAnimationFactory) { - RectFSpringAnim anim = - super.createWindowAnimationToHome(startProgress, homeAnimationFactory); - if (mRecentsAnimationTargets != null) { - mRecentsAnimationTargets.addReleaseCheck(anim); - } - return anim; - } - - public interface Factory { - - BaseSwipeUpHandler newHandler( - GestureState gestureState, long touchTimeMs, boolean continuingLastGesture); - } -} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java index 33b9cdee08..d1da0c1c10 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java @@ -140,7 +140,7 @@ public final class FallbackActivityInterface extends } @Override - public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) { + public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) { // no-op, fake landscape not supported for 3P } 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 fc7a119f21..ffb05dfed7 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -15,14 +15,35 @@ */ package com.android.quickstep; +import static android.content.Intent.EXTRA_COMPONENT_NAME; +import static android.content.Intent.EXTRA_USER; + +import static com.android.launcher3.GestureNavContract.EXTRA_GESTURE_CONTRACT; +import static com.android.launcher3.GestureNavContract.EXTRA_ICON_POSITION; +import static com.android.launcher3.GestureNavContract.EXTRA_ICON_SURFACE; +import static com.android.launcher3.GestureNavContract.EXTRA_REMOTE_CALLBACK; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME; import android.animation.ObjectAnimator; +import android.annotation.TargetApi; import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.ParcelUuid; +import android.os.UserHandle; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import androidx.annotation.NonNull; @@ -32,18 +53,32 @@ import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.anim.SpringAnimationBuilder; import com.android.quickstep.fallback.FallbackRecentsView; +import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TransformParams; import com.android.quickstep.util.TransformParams.BuilderProxy; +import com.android.systemui.shared.recents.model.Task.TaskKey; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; +import java.lang.ref.WeakReference; +import java.util.UUID; +import java.util.function.Consumer; + /** * Handles the navigation gestures when a 3rd party launcher is the default home activity. */ +@TargetApi(Build.VERSION_CODES.R) public class FallbackSwipeHandler extends - BaseSwipeUpHandlerV2 { + AbsSwipeUpHandler { + + /** + * Message used for receiving gesture nav contract information. We use a static messenger to + * avoid leaking too make binders in case the receiving launcher does not handle the contract + * properly. + */ + private static StaticMessageReceiver sMessageReceiver = null; private FallbackHomeAnimationFactory mActiveAnimationFactory; private final boolean mRunningOverHome; @@ -89,7 +124,9 @@ public class FallbackSwipeHandler extends protected HomeAnimationFactory createHomeAnimationFactory(long duration) { mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration); ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); - mContext.startActivity(new Intent(mGestureState.getHomeIntent()), options.toBundle()); + Intent intent = new Intent(mGestureState.getHomeIntent()); + mActiveAnimationFactory.addGestureContract(intent); + mContext.startActivity(intent, options.toBundle()); return mActiveAnimationFactory; } @@ -130,17 +167,20 @@ public class FallbackSwipeHandler extends } private class FallbackHomeAnimationFactory extends HomeAnimationFactory { - + private final Rect mTempRect = new Rect(); private final TransformParams mHomeAlphaParams = new TransformParams(); private final AnimatedFloat mHomeAlpha; private final AnimatedFloat mVerticalShiftForScale = new AnimatedFloat(); - private final AnimatedFloat mRecentsAlpha = new AnimatedFloat(); + private final RectF mTargetRect = new RectF(); + private SurfaceControl mSurfaceControl; + private final long mDuration; + + private RectFSpringAnim mSpringAnim; FallbackHomeAnimationFactory(long duration) { - super(null); mDuration = duration; if (mRunningOverHome) { @@ -162,6 +202,15 @@ public class FallbackSwipeHandler extends this::updateRecentsActivityTransformDuringHomeAnim); } + @NonNull + @Override + public RectF getWindowTargetRect() { + if (mTargetRect.isEmpty()) { + mTargetRect.set(super.getWindowTargetRect()); + } + return mTargetRect; + } + private void updateRecentsActivityTransformDuringHomeAnim(SurfaceParams.Builder builder, RemoteAnimationTargetCompat app, TransformParams params) { builder.withAlpha(mRecentsAlpha.value); @@ -218,5 +267,87 @@ public class FallbackSwipeHandler extends .start(); } } + + @Override + public void setAnimation(RectFSpringAnim anim) { + mSpringAnim = anim; + } + + private void onMessageReceived(Message msg) { + try { + Bundle data = msg.getData(); + RectF position = data.getParcelable(EXTRA_ICON_POSITION); + if (!position.isEmpty()) { + mSurfaceControl = data.getParcelable(EXTRA_ICON_SURFACE); + mTargetRect.set(position); + if (mSpringAnim != null) { + mSpringAnim.onTargetPositionChanged(); + } + } + } catch (Exception e) { + // Ignore + } + } + + @Override + public void update(RectF currentRect, float progress, float radius) { + if (mSurfaceControl != null) { + currentRect.roundOut(mTempRect); + Transaction t = new Transaction(); + t.setGeometry(mSurfaceControl, null, mTempRect, Surface.ROTATION_0); + t.apply(); + } + } + + private void addGestureContract(Intent intent) { + if (mRunningOverHome || mGestureState.getRunningTask() == null) { + return; + } + + TaskKey key = new TaskKey(mGestureState.getRunningTask()); + if (key.getComponent() != null) { + if (sMessageReceiver == null) { + sMessageReceiver = new StaticMessageReceiver(); + } + + Bundle gestureNavContract = new Bundle(); + gestureNavContract.putParcelable(EXTRA_COMPONENT_NAME, key.getComponent()); + gestureNavContract.putParcelable(EXTRA_USER, UserHandle.of(key.userId)); + gestureNavContract.putParcelable(EXTRA_REMOTE_CALLBACK, + sMessageReceiver.newCallback(this::onMessageReceived)); + intent.putExtra(EXTRA_GESTURE_CONTRACT, gestureNavContract); + } + } + } + + private static class StaticMessageReceiver implements Handler.Callback { + + private final Messenger mMessenger = + new Messenger(new Handler(Looper.getMainLooper(), this)); + + private ParcelUuid mCurrentUID = new ParcelUuid(UUID.randomUUID()); + private WeakReference> mCurrentCallback = new WeakReference<>(null); + + public Message newCallback(Consumer callback) { + mCurrentUID = new ParcelUuid(UUID.randomUUID()); + mCurrentCallback = new WeakReference<>(callback); + + Message msg = Message.obtain(); + msg.replyTo = mMessenger; + msg.obj = mCurrentUID; + return msg; + } + + @Override + public boolean handleMessage(@NonNull Message message) { + if (mCurrentUID.equals(message.obj)) { + Consumer consumer = mCurrentCallback.get(); + if (consumer != null) { + consumer.accept(message); + return true; + } + } + return false; + } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java index 6621b418e9..5a35eb5eb2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java @@ -41,7 +41,6 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.appprediction.PredictionUiStateManager; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; import com.android.launcher3.statemanager.StateManager; @@ -105,7 +104,7 @@ public final class LauncherActivityInterface extends // recents, we assume the first task is invisible, making translation off by one task. launcher.getStateManager().reapplyState(); launcher.getRootView().setForceHideBackArrow(false); - notifyRecentsOfOrientation(deviceState); + notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); } @Override @@ -120,7 +119,7 @@ public final class LauncherActivityInterface extends @Override public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState, boolean activityVisible, Consumer callback) { - notifyRecentsOfOrientation(deviceState); + notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) { @Override public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, @@ -228,7 +227,7 @@ public final class LauncherActivityInterface extends @Override - public void onExitOverview(RecentsAnimationDeviceState deviceState, Runnable exitRunnable) { + public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) { final StateManager stateManager = getCreatedActivity().getStateManager(); stateManager.addStateListener( new StateManager.StateListener() { @@ -244,11 +243,11 @@ public final class LauncherActivityInterface extends }); } - private void notifyRecentsOfOrientation(RecentsAnimationDeviceState deviceState) { + private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) { // reset layout on swipe to home RecentsView recentsView = getCreatedActivity().getOverviewPanel(); - recentsView.setLayoutRotation(deviceState.getCurrentActiveRotation(), - deviceState.getDisplayRotation()); + recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), + rotationTouchHelper.getDisplayRotation()); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java index fa7d2689f3..44114558e5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java @@ -16,6 +16,7 @@ package com.android.quickstep; import static com.android.launcher3.LauncherState.NORMAL; +import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import android.animation.AnimatorSet; import android.content.Context; @@ -28,6 +29,7 @@ import androidx.annotation.NonNull; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.views.FloatingIconView; +import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; @@ -37,7 +39,7 @@ import com.android.systemui.shared.system.InputConsumerController; * Temporary class to allow easier refactoring */ public class LauncherSwipeHandlerV2 extends - BaseSwipeUpHandlerV2 { + AbsSwipeUpHandler { public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState, TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs, @@ -72,36 +74,39 @@ public class LauncherSwipeHandlerV2 extends mActivity.getRootView().setForceHideBackArrow(true); mActivity.setHintUserWillBeActive(); - homeAnimFactory = new HomeAnimationFactory(floatingIconView) { - - @Override - public RectF getWindowTargetRect() { - if (canUseWorkspaceView) { + if (canUseWorkspaceView) { + // We want the window alpha to be 0 once this threshold is met, so that the + // FolderIconView can be seen morphing into the icon shape. + float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION; + homeAnimFactory = new LauncherHomeAnimationFactory() { + @Override + public RectF getWindowTargetRect() { return iconLocation; - } else { - return super.getWindowTargetRect(); } - } - @NonNull - @Override - public AnimatorPlaybackController createActivityAnimationToHome() { - // Return an empty APC here since we have an non-user controlled animation - // to home. - long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx); - return mActivity.getStateManager().createAnimationToNewWorkspace( - NORMAL, accuracy, 0 /* animComponents */); - } + @Override + public void setAnimation(RectFSpringAnim anim) { + anim.addAnimatorListener(floatingIconView); + floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged); + floatingIconView.setFastFinishRunnable(anim::end); + } - @Override - public void playAtomicAnimation(float velocity) { - new StaggeredWorkspaceAnim(mActivity, velocity, - true /* animateOverviewScrim */).start(); - } - }; + @Override + public void update(RectF currentRect, float progress, float radius) { + floatingIconView.update(currentRect, 1f, progress, windowAlphaThreshold, + radius, false); + } + @Override + public void onCancel() { + floatingIconView.fastFinish(); + } + }; + } else { + homeAnimFactory = new LauncherHomeAnimationFactory(); + } } else { - homeAnimFactory = new HomeAnimationFactory(null) { + homeAnimFactory = new HomeAnimationFactory() { @Override public AnimatorPlaybackController createActivityAnimationToHome() { return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); @@ -118,4 +123,22 @@ public class LauncherSwipeHandlerV2 extends mRecentsAnimationController.finish( true /* toRecents */, callback, true /* sendUserLeaveHint */); } + + private class LauncherHomeAnimationFactory extends HomeAnimationFactory { + @NonNull + @Override + public AnimatorPlaybackController createActivityAnimationToHome() { + // Return an empty APC here since we have an non-user controlled animation + // to home. + long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx); + return mActivity.getStateManager().createAnimationToNewWorkspace( + NORMAL, accuracy, 0 /* animComponents */); + } + + @Override + public void playAtomicAnimation(float velocity) { + new StaggeredWorkspaceAnim(mActivity, velocity, + true /* animateOverviewScrim */).start(); + } + } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java index 6f4d34c8c7..5026f36e08 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java @@ -58,6 +58,12 @@ public class QuickstepTestInformationHandler extends TestInformationHandler { FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()); return response; } + + case TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED: { + response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, + FeatureFlags.ENABLE_OVERVIEW_SHARE.get()); + return response; + } } return super.call(method); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java index dc8f1c54c8..e54a21c073 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java @@ -17,7 +17,6 @@ package com.android.quickstep; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; -import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import android.animation.Animator; import android.content.Context; @@ -28,7 +27,6 @@ import android.graphics.RectF; import android.view.animation.Interpolator; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.launcher3.DeviceProfile; @@ -37,7 +35,6 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.touch.PagedOrientationHandler; -import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; @@ -85,7 +82,8 @@ public abstract class SwipeUpAnimationLogic { mTransformParams = transformParams; mTaskViewSimulator.setLayoutRotation( - mDeviceState.getCurrentActiveRotation(), mDeviceState.getDisplayRotation()); + mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(), + mDeviceState.getRotationTouchHelper().getDisplayRotation()); } protected void initTransitionEndpoints(DeviceProfile dp) { @@ -148,12 +146,6 @@ public abstract class SwipeUpAnimationLogic { protected abstract class HomeAnimationFactory { - public FloatingIconView mIconView; - - public HomeAnimationFactory(@Nullable FloatingIconView iconView) { - mIconView = iconView; - } - public @NonNull RectF getWindowTargetRect() { PagedOrientationHandler orientationHandler = getOrientationHandler(); DeviceProfile dp = mDp; @@ -174,6 +166,12 @@ public abstract class SwipeUpAnimationLogic { public void playAtomicAnimation(float velocity) { // No-op } + + public void setAnimation(RectFSpringAnim anim) { } + + public void update(RectF currentRect, float progress, float radius) { } + + public void onCancel() { } } /** @@ -184,8 +182,6 @@ public abstract class SwipeUpAnimationLogic { protected RectFSpringAnim createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory) { final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); - final FloatingIconView fiv = homeAnimationFactory.mIconView; - final boolean isFloatingIconView = fiv != null; mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor); mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress)); @@ -203,11 +199,7 @@ public abstract class SwipeUpAnimationLogic { windowToHomePositionMap.mapRect(startRect); RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext); - if (isFloatingIconView) { - anim.addAnimatorListener(fiv); - fiv.setOnTargetChangeListener(anim::onTargetPositionChanged); - fiv.setFastFinishRunnable(anim::end); - } + homeAnimationFactory.setAnimation(anim); SpringAnimationRunner runner = new SpringAnimationRunner( homeAnimationFactory, cropRectF, homeToWindowPositionMap); @@ -242,32 +234,27 @@ public abstract class SwipeUpAnimationLogic { final RectF mWindowCurrentRect = new RectF(); final Matrix mHomeToWindowPositionMap; + final HomeAnimationFactory mAnimationFactory; - final FloatingIconView mFIV; final AnimatorPlaybackController mHomeAnim; final RectF mCropRectF; final float mStartRadius; final float mEndRadius; - final float mWindowAlphaThreshold; SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF, Matrix homeToWindowPositionMap) { + mAnimationFactory = factory; mHomeAnim = factory.createActivityAnimationToHome(); mCropRectF = cropRectF; mHomeToWindowPositionMap = homeToWindowPositionMap; cropRectF.roundOut(mCropRect); - mFIV = factory.mIconView; // End on a "round-enough" radius so that the shape reveal doesn't have to do too much // rounding at the end of the animation. mStartRadius = mTaskViewSimulator.getCurrentCornerRadius(); mEndRadius = cropRectF.width() / 2f; - - // We want the window alpha to be 0 once this threshold is met, so that the - // FolderIconView can be seen morphing into the icon shape. - mWindowAlphaThreshold = mFIV != null ? 1f - SHAPE_PROGRESS_DURATION : 1f; } @Override @@ -282,10 +269,7 @@ public abstract class SwipeUpAnimationLogic { .setCornerRadius(cornerRadius); mTransformParams.applySurfaceParams(mTransformParams.createSurfaceParams(this)); - if (mFIV != null) { - mFIV.update(currentRect, 1f, progress, - mWindowAlphaThreshold, mMatrix.mapRadius(cornerRadius), false); - } + mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius)); } @Override @@ -298,9 +282,7 @@ public abstract class SwipeUpAnimationLogic { @Override public void onCancel() { - if (mFIV != null) { - mFIV.fastFinish(); - } + mAnimationFactory.onCancel(); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java index e9614d1351..db512fa0f9 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java @@ -19,6 +19,7 @@ package com.android.quickstep; import static android.view.Surface.ROTATION_0; import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; +import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL; import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED; import android.annotation.SuppressLint; @@ -146,26 +147,29 @@ public class TaskOverlayFactory implements ResourceBasedOverride { */ public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix, boolean rotated) { - final boolean isAllowedByPolicy = thumbnail.isRealSnapshot; + getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null); - getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); + if (thumbnail != null) { + getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); + final boolean isAllowedByPolicy = thumbnail.isRealSnapshot; - getActionsView().setCallbacks(new OverlayUICallbacks() { - @Override - public void onShare() { - if (isAllowedByPolicy) { - mImageApi.startShareActivity(); - } else { - showBlockedByPolicyMessage(); + getActionsView().setCallbacks(new OverlayUICallbacks() { + @Override + public void onShare() { + if (isAllowedByPolicy) { + mImageApi.startShareActivity(); + } else { + showBlockedByPolicyMessage(); + } } - } - @SuppressLint("NewApi") - @Override - public void onScreenshot() { - saveScreenshot(task); - } - }); + @SuppressLint("NewApi") + @Override + public void onScreenshot() { + saveScreenshot(task); + } + }); + } } /** 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 131ebc169c..9d16f0e591 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -259,6 +259,7 @@ public class TouchInteractionService extends Service implements PluginListener mAM.getRunningTask(false /* filterOnlyVisibleRecents */))); } return gestureState; @@ -684,7 +691,7 @@ public class TouchInteractionService extends Service implements PluginListener mAM.getRunningTask(true /* filterOnlyVisibleRecents */))); ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent(); ComponentName runningComponent = @@ -714,7 +721,7 @@ public class TouchInteractionService extends Service implements PluginListener mSquaredSlop) { - mStartDragPos.set(mLastPos.x, mLastPos.y); if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle( mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y)) || (mDeviceState.isOneHandedModeActive() && isValidExitAngle( @@ -104,17 +102,16 @@ public class OneHandedModeInputConsumer extends DelegateInputConsumer { } } } else { - float distance = (float) Math.hypot(mLastPos.x - mStartDragPos.x, - mLastPos.y - mStartDragPos.y); + float distance = (float) Math.hypot(mLastPos.x - mDownPos.x, + mLastPos.y - mDownPos.y); if (distance > mDragDistThreshold && mPassedSlop) { onStopGestureDetected(); } } break; } - case ACTION_UP: - case ACTION_CANCEL: { - if (mLastPos.y >= mStartDragPos.y && mPassedSlop) { + case ACTION_UP: { + if (mLastPos.y >= mDownPos.y && mPassedSlop) { onStartGestureDetected(); } @@ -122,6 +119,10 @@ public class OneHandedModeInputConsumer extends DelegateInputConsumer { mState = STATE_INACTIVE; break; } + case ACTION_CANCEL: + mPassedSlop = false; + mState = STATE_INACTIVE; + break; } if (mState != STATE_ACTIVE) { 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 26df9c7b93..f02e5e6d61 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 @@ -53,12 +53,13 @@ import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.BaseActivityInterface; -import com.android.quickstep.BaseSwipeUpHandler; -import com.android.quickstep.BaseSwipeUpHandler.Factory; +import com.android.quickstep.AbsSwipeUpHandler; +import com.android.quickstep.AbsSwipeUpHandler.Factory; import com.android.quickstep.GestureState; import com.android.quickstep.InputConsumer; import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationDeviceState; +import com.android.quickstep.RotationTouchHelper; import com.android.quickstep.TaskAnimationManager; import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.util.CachedEventDispatcher; @@ -86,12 +87,13 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC private final NavBarPosition mNavBarPosition; private final TaskAnimationManager mTaskAnimationManager; private final GestureState mGestureState; + private final RotationTouchHelper mRotationTouchHelper; private RecentsAnimationCallbacks mActiveCallbacks; private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher(); private final InputMonitorCompat mInputMonitorCompat; private final BaseActivityInterface mActivityInterface; - private final BaseSwipeUpHandler.Factory mHandlerFactory; + private final AbsSwipeUpHandler.Factory mHandlerFactory; private final Consumer mOnCompleteCallback; private final MotionPauseDetector mMotionPauseDetector; @@ -99,7 +101,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC private VelocityTracker mVelocityTracker; - private BaseSwipeUpHandler mInteractionHandler; + private AbsSwipeUpHandler mInteractionHandler; private final boolean mIsDeferredDownTarget; private final PointF mDownPos = new PointF(); @@ -163,6 +165,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture; mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe; + mRotationTouchHelper = mDeviceState.getRotationTouchHelper(); } @Override @@ -230,7 +233,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC if (!mPassedPilferInputSlop) { // Cancel interaction in case of multi-touch interaction int ptrIdx = ev.getActionIndex(); - if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) { + if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) { forceCancelGesture(ev); } } @@ -424,7 +427,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC @Override public void notifyOrientationSetup() { - mDeviceState.onStartGesture(); + mRotationTouchHelper.onStartGesture(); } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java deleted file mode 100644 index e0008033b4..0000000000 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2018 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.logging; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.android.launcher3.appprediction.PredictionUiStateManager; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.userevent.nano.LauncherLogProto; - -import java.util.ArrayList; - -/** - * This class handles AOSP MetricsLogger function calls and logging around - * quickstep interactions and app launches. - */ -@SuppressWarnings("unused") -public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension { - - public static final int ALL_APPS_PREDICTION_TIPS = 2; - - private static final String TAG = "UserEventDispatcher"; - - public UserEventDispatcherAppPredictionExtension(Context context) { - super(context); - } - - @Override - protected void onFillInLogContainerData( - @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target, - @NonNull ArrayList targets) { - PredictionUiStateManager.fillInPredictedRank(itemInfo, target); - } -} 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 64ae1e0479..94e496d183 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 @@ -32,7 +32,6 @@ import android.graphics.RectF; import android.util.IntProperty; 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; @@ -73,6 +72,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { private final BaseActivityInterface mSizeStrategy; private final Rect mTaskRect = new Rect(); + private float mOffsetY; private final PointF mPivot = new PointF(); private DeviceProfile mDp; @@ -93,7 +93,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { public final AnimatedFloat recentsViewScale = new AnimatedFloat(); public final AnimatedFloat fullScreenProgress = new AnimatedFloat(); private final ScrollState mScrollState = new ScrollState(); - private final int mPageSpacing; // Cached calculations private boolean mLayoutValid = false; @@ -107,7 +106,6 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mOrientationState.setGestureActive(true); mCurrentFullscreenParams = new FullscreenDrawParams(context); - mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); } /** @@ -130,8 +128,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { /** * @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration) */ - public void setRecentsConfiguration(Configuration configuration) { - mOrientationState.setActivityConfiguration(configuration); + public void setRecentsRotation(int recentsRotation) { + mOrientationState.setRecentsRotation(recentsRotation); mLayoutValid = false; } @@ -178,14 +176,26 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { } } + public void setOffsetY(float offsetY) { + mOffsetY = offsetY; + } + /** - * Adds animation for all the components corresponding to transition from an app to overview + * 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); } + /** + * Adds animation for all the components corresponding to transition from overview to the app. + */ + public void addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator) { + pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 0, 1, interpolator); + pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, 1, getFullScreenScale(), interpolator); + } + /** * Returns the current clipped/visible window bounds in the window coordinate space */ @@ -262,7 +272,8 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { int start = mOrientationState.getOrientationHandler() .getPrimaryValue(mTaskRect.left, mTaskRect.top); mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize; - mScrollState.updateInterpolation(start, mPageSpacing); + mScrollState.pageParentScale = recentsViewScale.value; + mScrollState.updateInterpolation(start); mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation); } @@ -281,7 +292,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { mMatrix.postScale(scale, scale); // Apply TaskView matrix: translate, scale, scroll - mMatrix.postTranslate(mTaskRect.left, mTaskRect.top); + mMatrix.postTranslate(mTaskRect.left, mTaskRect.top + mOffsetY); mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2); mOrientationState.getOrientationHandler().set( mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll); @@ -307,7 +318,7 @@ public class TaskViewSimulator implements TransformParams.BuilderProxy { .withCornerRadius(getCurrentCornerRadius()); if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) { - builder.withRelativeLayerTo(params.getRecentsSurface(), Integer.MAX_VALUE); + builder.withRelativeLayerTo(params.getRecentsSurface(), Integer.MIN_VALUE); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java index 3d44eb613d..c06dd9c0bd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java @@ -54,6 +54,7 @@ import com.android.quickstep.util.MultiValueUpdateListener; /** * View used to educate the user on how to access All Apps when in No Nav Button navigation mode. + * Consumes all touches until after the animation is completed and the view is removed. */ public class AllAppsEduView extends AbstractFloatingView { @@ -113,9 +114,19 @@ public class AllAppsEduView extends AbstractFloatingView { return (type & TYPE_ALL_APPS_EDU) != 0; } + @Override + public boolean onBackPressed() { + return true; + } + + @Override + public boolean canInterceptEventsInSystemGestureRegion() { + return true; + } + @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - return mAnimation != null && mAnimation.isRunning(); + return true; } private void playAnimation() { 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 1034234138..f31bc19c06 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 @@ -15,7 +15,6 @@ */ package com.android.quickstep.views; -import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; @@ -39,14 +38,11 @@ import android.widget.FrameLayout; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.Hotseat; import com.android.launcher3.LauncherState; -import com.android.launcher3.anim.Interpolators; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateListener; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; -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; import com.android.systemui.plugins.RecentsExtraCard; @@ -57,8 +53,6 @@ import com.android.systemui.plugins.RecentsExtraCard; public class LauncherRecentsView extends RecentsView implements StateListener { - private final TransformParams mTransformParams = new TransformParams(); - private RecentsExtraCard mRecentsExtraCardPlugin; private RecentsExtraViewContainer mRecentsExtraViewContainer; private PluginListener mRecentsExtraCardPluginListener = @@ -108,17 +102,6 @@ public class LauncherRecentsView extends RecentsView } } - @Override - public void setTranslationY(float translationY) { - super.setTranslationY(translationY); - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - LauncherState state = mActivity.getStateManager().getState(); - if (state == OVERVIEW || state == ALL_APPS) { - redrawLiveTile(false); - } - } - } - /** * Animates adjacent tasks and translate hotseat off screen as well. */ @@ -141,28 +124,9 @@ public class LauncherRecentsView extends RecentsView } anim.play(ObjectAnimator.ofFloat( mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen)); - - ObjectAnimator dragHandleAnim = ObjectAnimator.ofInt( - mActivity.getScrimView(), ScrimView.DRAG_HANDLE_ALPHA, 0); - dragHandleAnim.setInterpolator(Interpolators.ACCEL_2); - anim.play(dragHandleAnim); - return anim; } - @Override - protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) { - if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { - if (tv.isRunningTask()) { - mTransformParams.setProgress(1 - progress) - .setSyncTransactionApplier(mSyncTransactionApplier); - // TODO: Revisit live tiles - } else { - redrawLiveTile(true); - } - } - } - @Override protected void onTaskLaunchAnimationEnd(boolean success) { if (success) { @@ -174,46 +138,6 @@ public class LauncherRecentsView extends RecentsView super.onTaskLaunchAnimationEnd(success); } - @Override - public void scrollTo(int x, int y) { - super.scrollTo(x, y); - if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) { - redrawLiveTile(true); - } - } - - @Override - public TransformParams getLiveTileParams( - boolean mightNeedToRefill) { - if (!mEnableDrawingLiveTile || mRecentsAnimationController == null - || mRecentsAnimationTargets == null) { - return null; - } - TaskView taskView = getRunningTaskView(); - if (taskView != null) { - taskView.getThumbnail().getGlobalVisibleRect(mTempRect); - int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX() - - mTempRect.width()); - int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY() - - mTempRect.height()); - if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) { - if (mTempRect.left - offsetX < 0) { - mTempRect.left -= offsetX; - } else { - mTempRect.right += offsetX; - } - } - if (mightNeedToRefill && offsetY > 0) { - mTempRect.top -= offsetY; - } - mTransformParams.setProgress(1f) - .setTargetAlpha(taskView.getAlpha()) - .setSyncTransactionApplier(mSyncTransactionApplier) - .setTargetSet(mRecentsAnimationTargets); - } - return mTransformParams; - } - @Override public void reset() { super.reset(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java index a2da39855c..79d57c55a2 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java @@ -70,12 +70,14 @@ public class OverviewActionsView extends FrameLayo @IntDef(flag = true, value = { DISABLED_SCROLLING, - DISABLED_ROTATED}) + DISABLED_ROTATED, + DISABLED_NO_THUMBNAIL}) @Retention(RetentionPolicy.SOURCE) public @interface ActionsDisabledFlags { } public static final int DISABLED_SCROLLING = 1 << 0; public static final int DISABLED_ROTATED = 1 << 1; + public static final int DISABLED_NO_THUMBNAIL = 1 << 2; private static final int INDEX_CONTENT_ALPHA = 0; private static final int INDEX_VISIBILITY_ALPHA = 1; 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 520b36e971..0335ee7748 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 @@ -22,7 +22,6 @@ import static android.view.View.MeasureSpec.makeMeasureSpec; import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; -import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; @@ -62,6 +61,7 @@ import android.graphics.Canvas; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; @@ -78,6 +78,7 @@ import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; +import android.view.TouchDelegate; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; @@ -129,6 +130,7 @@ import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.RecentsOrientedState; import com.android.quickstep.util.SplitScreenBounds; import com.android.quickstep.util.SurfaceTransactionApplier; +import com.android.quickstep.util.TaskViewSimulator; import com.android.quickstep.util.TransformParams; import com.android.systemui.plugins.ResourceProvider; import com.android.systemui.shared.recents.IPinnedStackAnimationListener; @@ -209,15 +211,37 @@ public abstract class RecentsView extends PagedView } }; + /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ + public static final FloatProperty RECENTS_SCALE_PROPERTY = + new FloatProperty("recentsScale") { + @Override + public void setValue(RecentsView view, float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + view.mLastComputedTaskPushOutDistance = null; + view.updatePageOffsets(); + } + + @Override + public Float get(RecentsView view) { + return view.getScaleX(); + } + }; + protected RecentsOrientedState mOrientationState; protected final BaseActivityInterface mSizeStrategy; protected RecentsAnimationController mRecentsAnimationController; - protected RecentsAnimationTargets mRecentsAnimationTargets; protected SurfaceTransactionApplier mSyncTransactionApplier; protected int mTaskWidth; protected int mTaskHeight; + protected final TransformParams mLiveTileParams = new TransformParams(); + protected final TaskViewSimulator mLiveTileTaskViewSimulator; + protected final Rect mLastComputedTaskSize = new Rect(); + // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot. + protected Float mLastComputedTaskPushOutDistance = null; protected boolean mEnableDrawingLiveTile = false; protected final Rect mTempRect = new Rect(); + protected final RectF mTempRectF = new RectF(); private final PointF mTempPointF = new PointF(); private static final int DISMISS_TASK_DURATION = 300; @@ -376,7 +400,7 @@ public abstract class RecentsView extends PagedView mOrientationState.setMultiWindowMode(inMultiWindowMode); setLayoutRotation(mOrientationState.getTouchRotation(), mOrientationState.getDisplayRotation()); - rotateAllChildTasks(); + updateChildTaskOrientations(); } if (!inMultiWindowMode && mOverviewStateEnabled) { // TODO: Re-enable layout transitions for addition of the unpinned task @@ -393,7 +417,8 @@ public abstract class RecentsView extends PagedView mActivity = BaseActivity.fromContext(context); mOrientationState = new RecentsOrientedState( context, mSizeStrategy, this::animateRecentsRotationInPlace); - mOrientationState.setActivityConfiguration(context.getResources().getConfiguration()); + final int rotation = mActivity.getDisplay().getRotation(); + mOrientationState.setRecentsRotation(rotation); mFastFlingVelocity = getResources() .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); @@ -429,6 +454,12 @@ public abstract class RecentsView extends PagedView // Initialize quickstep specific cache params here, as this is constructed only once mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); + + mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy()); + mLiveTileTaskViewSimulator.recentsViewScale.value = 1; + mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(), + getPagedViewOrientedState().getDisplayRotation()); + mLiveTileTaskViewSimulator.setRecentsRotation(rotation); } public OverScroller getScroller() { @@ -513,6 +544,7 @@ public abstract class RecentsView extends PagedView mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); mSyncTransactionApplier = new SurfaceTransactionApplier(this); + mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier); RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); mIdp.addOnChangeListener(this); mIPinnedStackAnimationListener.setActivity(mActivity); @@ -530,6 +562,7 @@ public abstract class RecentsView extends PagedView mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); mSyncTransactionApplier = null; + mLiveTileParams.setSyncTransactionApplier(null); RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); mIdp.removeOnChangeListener(this); SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); @@ -648,6 +681,16 @@ public abstract class RecentsView extends PagedView @Override public boolean onTouchEvent(MotionEvent ev) { super.onTouchEvent(ev); + + TaskView taskView = getCurrentPageTaskView(); + if (taskView != null) { + TouchDelegate mChildTouchDelegate = taskView.getIconTouchDelegate(ev); + if (mChildTouchDelegate != null && mChildTouchDelegate.onTouchEvent(ev)) { + // Keep consuming events to pass to delegate + return true; + } + } + final int x = (int) ev.getX(); final int y = (int) ev.getY(); switch (ev.getAction()) { @@ -806,6 +849,14 @@ public abstract class RecentsView extends PagedView taskView.setModalness(mTaskModalness); } } + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need + // to reset the params after it settles in Overview from swipe up so that we don't + // render with obsolete param values. + mLiveTileTaskViewSimulator.fullScreenProgress.value = 0; + mLiveTileTaskViewSimulator.recentsViewScale.value = 1; + mLiveTileTaskViewSimulator.setOffsetY(0); + } if (mRunningTaskTileHidden) { setRunningTaskHidden(mRunningTaskTileHidden); } @@ -847,6 +898,7 @@ public abstract class RecentsView extends PagedView public void setInsets(Rect insets) { mInsets.set(insets); resetPaddingFromTaskSize(); + mLiveTileTaskViewSimulator.setDp(mActivity.getDeviceProfile()); } private void resetPaddingFromTaskSize() { @@ -864,6 +916,7 @@ public abstract class RecentsView extends PagedView public void getTaskSize(Rect outRect) { mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect, mOrientationHandler); + mLastComputedTaskSize.set(outRect); } /** Gets the task size for modal state. */ @@ -888,6 +941,12 @@ public abstract class RecentsView extends PagedView // Update the high res thumbnail loader state mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast); + + mLiveTileTaskViewSimulator.setScroll(getScrollOffset()); + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile + && mLiveTileParams.getTargetSet() != null) { + redrawLiveTile(); + } return scrolling; } @@ -905,8 +964,8 @@ public abstract class RecentsView extends PagedView final int pageCount = getPageCount(); for (int i = 0; i < pageCount; i++) { View page = getPageAt(i); - mScrollState.updateInterpolation(mOrientationHandler.getChildStartWithTranslation(page), - mPageSpacing); + mScrollState.updateInterpolation( + mOrientationHandler.getChildStartWithTranslation(page)); ((PageCallbacks) page).onPageScroll(mScrollState); } } @@ -995,7 +1054,7 @@ public abstract class RecentsView extends PagedView mTaskListChangeId = -1; mRecentsAnimationController = null; - mRecentsAnimationTargets = null; + mLiveTileParams.setTargetSet(null); unloadVisibleTaskData(); setCurrentPage(0); @@ -1077,7 +1136,7 @@ public abstract class RecentsView extends PagedView pa.addListener(AnimationSuccessListener.forRunnable(() -> { setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); mActivity.getDragLayer().recreateControllers(); - rotateAllChildTasks(); + updateChildTaskOrientations(); setRecentsChangedOrientation(false).start(); })); pa.start(); @@ -1098,7 +1157,7 @@ public abstract class RecentsView extends PagedView } - private void rotateAllChildTasks() { + private void updateChildTaskOrientations() { for (int i = 0; i < getTaskViewCount(); i++) { getTaskViewAt(i).setOrientationState(mOrientationState); } @@ -1328,10 +1387,14 @@ public abstract class RecentsView extends PagedView /** * Updates linearInterpolation for the provided child position */ - public void updateInterpolation(float childStart, int pageSpacing) { - float pageCenter = childStart + halfPageSize; + public void updateInterpolation(float childStart) { + float scaledHalfPageSize = halfPageSize / pageParentScale; + float pageCenter = childStart + scaledHalfPageSize; float distanceFromScreenCenter = screenCenter - pageCenter; - float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing; + // How far the page has to move from the center to be offscreen, taking into account + // the EDGE_SCALE_DOWN_FACTOR that will be applied at that position. + float distanceToReachEdge = halfScreenSize + + scaledHalfPageSize * (1 - TaskView.EDGE_SCALE_DOWN_FACTOR); linearInterpolation = Math.min(1, Math.abs(distanceFromScreenCenter) / distanceToReachEdge); } @@ -1446,6 +1509,13 @@ public abstract class RecentsView extends PagedView anim.addOnFrameCallback(this::updateCurveProperties); } + if (ENABLE_QUICKSTEP_LIVE_TILE.get() && getRunningTaskView() == taskView) { + anim.addOnFrameCallback(() -> { + mLiveTileTaskViewSimulator.setOffsetY(taskView.getTranslationY()); + redrawLiveTile(); + }); + } + // Add a tiny bit of translation Z, so that it draws on top of other views if (animateTaskView) { taskView.setTranslationZ(0.1f); @@ -1648,15 +1718,20 @@ public abstract class RecentsView extends PagedView super.setVisibility(visibility); if (mActionsView != null) { mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE); + if (visibility != VISIBLE) { + mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); + } } } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - if (mOrientationState.setActivityConfiguration(newConfig)) { + final int rotation = mActivity.getDisplay().getRotation(); + if (mOrientationState.setRecentsRotation(rotation)) { updateOrientationHandler(); } + mLiveTileTaskViewSimulator.setRecentsRotation(rotation); } public void setLayoutRotation(int touchRotation, int displayRotation) { @@ -1680,10 +1755,13 @@ public abstract class RecentsView extends PagedView || mOrientationState.getRecentsActivityRotation() != ROTATION_0; mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, !mOrientationState.canRecentsActivityRotate() && isInLandscape); + updateChildTaskOrientations(); resetPaddingFromTaskSize(); requestLayout(); // Reapply the current page to update page scrolls. setCurrentPage(mCurrentPage); + mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(), + getPagedViewOrientedState().getDisplayRotation()); } public RecentsOrientedState getPagedViewOrientedState() { @@ -1765,14 +1843,15 @@ public abstract class RecentsView extends PagedView setPivotX(mTempPointF.x); setPivotY(mTempPointF.y); setTaskModalness(mTaskModalness); + mLastComputedTaskPushOutDistance = null; updatePageOffsets(); setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO : IMPORTANT_FOR_ACCESSIBILITY_AUTO); } private void updatePageOffsets() { - float offset = mAdjacentPageOffset * getWidth(); - float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth(); + float offset = mAdjacentPageOffset; + float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness); if (mIsRtl) { offset = -offset; modalOffset = -modalOffset; @@ -1781,18 +1860,89 @@ public abstract class RecentsView extends PagedView TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden ? null : getTaskView(mRunningTaskId); - int midPoint = runningTask == null ? -1 : indexOfChild(runningTask); - int currentPage = getCurrentPage(); + int midpoint = runningTask == null ? -1 : indexOfChild(runningTask); + int modalMidpoint = getCurrentPage(); + + float midpointOffsetSize = 0; + float leftOffsetSize = midpoint - 1 >= 0 + ? -getOffsetSize(midpoint - 1, midpoint, offset) + : 0; + float rightOffsetSize = midpoint + 1 < count + ? getOffsetSize(midpoint + 1, midpoint, offset) + : 0; + + float modalMidpointOffsetSize = 0; + float modalLeftOffsetSize = modalMidpoint - 1 >= 0 + ? -getOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset) + : 0; + float modalRightOffsetSize = modalMidpoint + 1 < count + ? getOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset) + : 0; for (int i = 0; i < count; i++) { - float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset); - float modalTranslation = - i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset); + float translation = i == midpoint + ? midpointOffsetSize + : i < midpoint + ? leftOffsetSize + : rightOffsetSize; + float modalTranslation = i == modalMidpoint + ? modalMidpointOffsetSize + : i < modalMidpoint + ? modalLeftOffsetSize + : modalRightOffsetSize; getChildAt(i).setTranslationX(translation + modalTranslation); } updateCurveProperties(); } + /** + * Computes the distance to offset the given child such that it is completely offscreen when + * translating away from the given midpoint. + * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. + */ + private float getOffsetSize(int childIndex, int midpointIndex, float offsetProgress) { + if (offsetProgress == 0) { + // Don't bother calculating everything below if we won't offset anyway. + return 0; + } + // First, get the position of the task relative to the midpoint. If there is no midpoint + // then we just use the normal (centered) task position. + mTempRectF.set(mLastComputedTaskSize); + RectF taskPosition = mTempRectF; + float desiredLeft = getWidth(); + float distanceToOffscreen = desiredLeft - taskPosition.left; + // Used to calculate the scale of the task view based on its new offset. + float centerToOffscreenProgress = Math.abs(offsetProgress); + if (midpointIndex > -1) { + // When there is a midpoint reference task, adjacent tasks have less distance to travel + // to reach offscreen. Offset the task position to the task's starting point. + View child = getChildAt(childIndex); + View midpointChild = getChildAt(midpointIndex); + int distanceFromMidpoint = Math.abs(mOrientationHandler.getChildStart(child) + - mOrientationHandler.getChildStart(midpointChild) + + getDisplacementFromScreenCenter(midpointIndex)); + taskPosition.offset(distanceFromMidpoint, 0); + centerToOffscreenProgress = Utilities.mapRange(centerToOffscreenProgress, + distanceFromMidpoint / distanceToOffscreen, 1); + } + // Find the task's scale based on its offscreen progress, then see how far it still needs to + // move to be completely offscreen. + Utilities.scaleRectFAboutCenter(taskPosition, + TaskView.getCurveScaleForInterpolation(centerToOffscreenProgress)); + distanceToOffscreen = desiredLeft - taskPosition.left; + // Finally, we need to account for RecentsView scale, because it moves tasks based on its + // pivot. To do this, we move the task position to where it would be offscreen at scale = 1 + // (computed above), then we apply the scale via getMatrix() to determine how much that + // moves the task from its desired position, and adjust the computed distance accordingly. + if (mLastComputedTaskPushOutDistance == null) { + taskPosition.offsetTo(desiredLeft, 0); + getMatrix().mapRect(taskPosition); + mLastComputedTaskPushOutDistance = (taskPosition.left - desiredLeft) / getScaleX(); + } + distanceToOffscreen -= mLastComputedTaskPushOutDistance; + return distanceToOffscreen * offsetProgress; + } + /** * TODO: Do not assume motion across X axis for adjacent page */ @@ -1891,7 +2041,7 @@ public abstract class RecentsView extends PagedView float toScale = getMaxScaleForFullScreen(); if (launchingCenterTask) { RecentsView recentsView = tv.getRecentsView(); - anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale)); + anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale)); anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1)); } else { // We are launching an adjacent task, so parallax the center and other adjacent task. @@ -1941,8 +2091,6 @@ public abstract class RecentsView extends PagedView ? targetSysUiFlags : 0); - onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv); - // Passing the threshold from taskview to fullscreen app will vibrate final boolean passed = animator.getAnimatedFraction() >= SUCCESS_TRANSITION_PROGRESS; @@ -1966,6 +2114,10 @@ public abstract class RecentsView extends PagedView mPendingAnimation = new PendingAnimation(duration); mPendingAnimation.add(anim); + if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { + mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator); + mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); + } mPendingAnimation.addEndListener((endState) -> { if (endState.isSuccess) { Consumer onLaunchResult = (result) -> { @@ -1991,9 +2143,6 @@ public abstract class RecentsView extends PagedView return mPendingAnimation; } - protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) { - } - protected void onTaskLaunchAnimationEnd(boolean success) { if (success) { resetTaskVisuals(); @@ -2060,13 +2209,23 @@ public abstract class RecentsView extends PagedView mEnableDrawingLiveTile = enableDrawingLiveTile; } - public void redrawLiveTile(boolean mightNeedToRefill) { } + public void redrawLiveTile() { + mLiveTileTaskViewSimulator.apply(mLiveTileParams); + } + + public TaskViewSimulator getLiveTileTaskViewSimulator() { + return mLiveTileTaskViewSimulator; + } // TODO: To be removed in a follow up CL public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets) { mRecentsAnimationController = recentsAnimationController; - mRecentsAnimationTargets = recentsAnimationTargets; + if (recentsAnimationTargets != null) { + mLiveTileTaskViewSimulator.setPreview( + recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]); + mLiveTileParams.setTargetSet(recentsAnimationTargets); + } } public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) { @@ -2202,11 +2361,6 @@ public abstract class RecentsView extends PagedView }; } - public TransformParams getLiveTileParams( - boolean mightNeedToRefill) { - return null; - } - private void updateEnabledOverlays() { int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1; int taskCount = getTaskViewCount(); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java index ef66b7a396..8b49f2c212 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java @@ -169,7 +169,9 @@ public class TaskMenuView extends AbstractFloatingView { } if (mIsOpen) { mOptionLayout.removeAllViews(); - populateAndLayoutMenu(); + if (!populateAndLayoutMenu()) { + close(false); + } } } @@ -186,14 +188,22 @@ public class TaskMenuView extends AbstractFloatingView { } mActivity.getDragLayer().addView(this); mTaskView = taskView; - populateAndLayoutMenu(); + if (!populateAndLayoutMenu()) { + return false; + } post(this::animateOpen); return true; } - private void populateAndLayoutMenu() { + /** @return true if successfully able to populate task view menu, false otherwise */ + private boolean populateAndLayoutMenu() { + if (mTaskView.getTask().icon == null) { + // Icon may not be loaded + return false; + } addMenuOptions(mTaskView); orientAroundTaskView(mTaskView); + return true; } private void addMenuOptions(TaskView taskView) { @@ -240,8 +250,10 @@ public class TaskMenuView extends AbstractFloatingView { setLayoutParams(params); setScaleX(taskView.getScaleX()); setScaleY(taskView.getScaleY()); + boolean canActivityRotate = taskView.getRecentsView() + .mOrientationState.canRecentsActivityRotate(); mOptionLayout.setOrientation(orientationHandler - .getTaskMenuLayoutOrientation(mOptionLayout)); + .getTaskMenuLayoutOrientation(canActivityRotate, mOptionLayout)); setPosition(sTempRect.left - insets.left, sTempRect.top - insets.top, taskView.getPagedOrientationHandler()); } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java index b2f937f8c2..a8d6442135 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java @@ -59,7 +59,6 @@ import com.android.systemui.plugins.OverviewScreenshotActions; import com.android.systemui.plugins.PluginListener; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; -import com.android.systemui.shared.system.ConfigurationCompat; /** * A task in the Recents view. @@ -357,7 +356,7 @@ public class TaskThumbnailView extends View implements PluginListener SYSTEM_GESTURE_EXCLUSION_RECT = Collections.singletonList(new Rect()); @@ -185,6 +199,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private int mStackHeight; private View mContextualChipWrapper; private View mContextualChip; + private final float[] mIconCenterCoords = new float[2]; public TaskView(Context context) { this(context, null); @@ -245,6 +260,26 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { super.onFinishInflate(); mSnapshotView = findViewById(R.id.snapshot); mIconView = findViewById(R.id.icon); + mIconTouchDelegate = new TransformingTouchDelegate(mIconView); + } + + public TouchDelegate getIconTouchDelegate(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + computeAndSetIconTouchDelegate(); + } + return mIconTouchDelegate; + } + + private void computeAndSetIconTouchDelegate() { + float iconHalfSize = mIconView.getWidth() / 2f; + mIconCenterCoords[0] = mIconCenterCoords[1] = iconHalfSize; + getDescendantCoordRelativeToAncestor(mIconView, mActivity.getDragLayer(), mIconCenterCoords, + false); + mIconTouchDelegate.setBounds( + (int) (mIconCenterCoords[0] - iconHalfSize), + (int) (mIconCenterCoords[1] - iconHalfSize), + (int) (mIconCenterCoords[0] + iconHalfSize), + (int) (mIconCenterCoords[1] + iconHalfSize)); } /** @@ -466,18 +501,18 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin); LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams(); switch (orientationHandler.getRotation()) { - case Surface.ROTATION_90: + case ROTATION_90: iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL; iconParams.rightMargin = -thumbnailPadding; iconParams.leftMargin = 0; iconParams.topMargin = snapshotParams.topMargin / 2; break; - case Surface.ROTATION_180: + case ROTATION_180: iconParams.gravity = BOTTOM | CENTER_HORIZONTAL; iconParams.bottomMargin = -thumbnailPadding; iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0; break; - case Surface.ROTATION_270: + case ROTATION_270: iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL; iconParams.leftMargin = -thumbnailPadding; iconParams.rightMargin = 0; diff --git a/quickstep/res/drawable/bg_circle.xml b/quickstep/res/drawable/bg_circle.xml new file mode 100644 index 0000000000..506177b6a1 --- /dev/null +++ b/quickstep/res/drawable/bg_circle.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml index 459d65faf7..43bf0ea966 100644 --- a/quickstep/res/layout/gesture_tutorial_fragment.xml +++ b/quickstep/res/layout/gesture_tutorial_fragment.xml @@ -24,6 +24,14 @@ android:layout_height="match_parent" android:background="@drawable/gesture_tutorial_ripple"/> + + + android:padding="18dp" + android:src="@drawable/gesture_tutorial_close_button" + android:tint="?android:attr/textColorPrimary"/> + android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"/> + android:layout_marginTop="10dp" + android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"/> + android:layout_marginBottom="10dp"/>