Merge remote-tracking branch 'goog/ub-launcher3-master' into temp

Test: make builds

Change-Id: I4e845aa51c9247328159e5aa0d95d425bffa435c
This commit is contained in:
Hyunyoung Song 2020-08-04 10:45:53 -07:00
commit bb71582658
148 changed files with 3836 additions and 2950 deletions

View File

@ -19,6 +19,7 @@ android_library {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"androidx.preference_preference",
"SystemUISharedLib",
],
srcs: [

View File

@ -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=""

1
OWNERS
View File

@ -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

View File

@ -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=""

View File

@ -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=""/>

View File

@ -25,8 +25,11 @@
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
<string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherExtension</string>
<string name="prediction_model_class" translatable="false">com.android.launcher3.hybridhotseat.HotseatPredictionModel</string>
<string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
</resources>

View File

@ -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<PredictionRowView> TEXT_ALPHA =
new IntProperty<PredictionRowView>("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<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>();
// The set of predicted apps resolved from the component names and the current set of apps
private final ArrayList<ItemInfoWithIcon> 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<WorkspaceItemInfo> 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<ItemInfoWithIcon> 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<ComponentKeyMapper> apps) {
mPredictedAppComponents.clear();
mPredictedAppComponents.addAll(apps);
public void setPredictedApps(List<ItemInfo> 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<ItemInfoWithIcon> processPredictedAppComponents(
List<ComponentKeyMapper> components) {
if (getAppsStore().getApps().length == 0) {
// Apps have not been bound yet.
return Collections.emptyList();
}
List<ItemInfoWithIcon> 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<LauncherLogProto.Target> parents) {

View File

@ -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<LauncherState>,
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<PredictionUiStateManager> 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<AppTarget> 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<ComponentKeyMapper> 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<ComponentKeyMapper> 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<ComponentKeyMapper> apps;
}
}

View File

@ -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);

View File

@ -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();
}
});

View File

@ -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<AppTargetEvent> mCallback;
// Accessed only on worker thread
private AppPredictor mHomeAppPredictor;
public PredictionAppTracker(Context context) {
public AppEventProducer(Context context, Consumer<AppTargetEvent> 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);

View File

@ -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<AppTarget> mTargets;
private final int mContainerId;
PredictionUpdateTask(int containerId, List<AppTarget> 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<UserHandle> 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));
}
}

View File

@ -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<AppTarget> 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);
}
}
}

View File

@ -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;

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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<T extends BaseDraggingActivity>
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<T extends BaseDraggingActivity>
}
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);
}

View File

@ -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<T extends StatefulActivity<?>, Q extends RecentsView>
extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
@TargetApi(Build.VERSION_CODES.R)
public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, 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<?, T> mActivityInterface;
protected final InputConsumerProxy mInputConsumerProxy;
protected final ActivityInitListener mActivityInitListener;
// Callbacks to be made once the recents animation starts
private final ArrayList<Runnable> 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, Q exte
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
}
@Override
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
}
@ -537,11 +585,14 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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<T extends StatefulActivity<?>, 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();
}
@ -1325,4 +1420,172 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, 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<MotionEvent> 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<Boolean> 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<StatefulActivity<?>, RecentsView> newHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
}
}

View File

@ -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<T extends StatefulActivity<?>, Q extends RecentsView>
extends SwipeUpAnimationLogic implements RecentsAnimationListener {
private static final String TAG = "BaseSwipeUpHandler";
protected final BaseActivityInterface<?, T> 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<Runnable> 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<MotionEvent> 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<Boolean> 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);
}
}

View File

@ -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
}

View File

@ -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<RecentsActivity, FallbackRecentsView> {
AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
/**
* 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<Consumer<Message>> mCurrentCallback = new WeakReference<>(null);
public Message newCallback(Consumer<Message> 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<Message> consumer = mCurrentCallback.get();
if (consumer != null) {
consumer.accept(message);
return true;
}
}
return false;
}
}
}

View File

@ -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<AnimatorPlaybackController> 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<LauncherState> stateManager = getCreatedActivity().getStateManager();
stateManager.addStateListener(
new StateManager.StateListener<LauncherState>() {
@ -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

View File

@ -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<BaseQuickstepLauncher, RecentsView> {
AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
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) {
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() {
if (canUseWorkspaceView) {
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 playAtomicAnimation(float velocity) {
new StaggeredWorkspaceAnim(mActivity, velocity,
true /* animateOverviewScrim */).start();
public void setAnimation(RectFSpringAnim anim) {
anim.addAnimatorListener(floatingIconView);
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
floatingIconView.setFastFinishRunnable(anim::end);
}
@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 HomeAnimationFactory(null) {
homeAnimFactory = new LauncherHomeAnimationFactory();
}
} else {
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();
}
}
}

View File

@ -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);

View File

@ -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

View File

@ -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,9 +147,11 @@ 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);
if (thumbnail != null) {
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
getActionsView().setCallbacks(new OverlayUICallbacks() {
@Override
@ -167,6 +170,7 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
}
});
}
}
/**
* Called to save screenshot of the task thumbnail.

View File

@ -259,6 +259,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
private static boolean sConnected = false;
private static boolean sIsInitialized = false;
private RotationTouchHelper mRotationTouchHelper;
public static boolean isConnected() {
return sConnected;
@ -268,9 +269,9 @@ public class TouchInteractionService extends Service implements PluginListener<O
return sIsInitialized;
}
private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
this::createLauncherSwipeHandler;
private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
this::createFallbackSwipeHandler;
private ActivityManagerWrapper mAM;
@ -300,6 +301,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
ProtoTracer.INSTANCE.get(this).add(this);
sConnected = true;
@ -328,7 +330,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
mDeviceState.updateGestureTouchRegions();
mRotationTouchHelper.updateGestureTouchRegions();
}
/**
@ -479,9 +481,9 @@ public class TouchInteractionService extends Service implements PluginListener<O
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
}
mDeviceState.setOrientationTransformIfNeeded(event);
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
if (mDeviceState.isInSwipeUpTouchRegion(event)) {
if (mRotationTouchHelper.isInSwipeUpTouchRegion(event)) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME,
"TouchInteractionService.onInputEvent:isInSwipeUpTouchRegion");
@ -508,6 +510,11 @@ public class TouchInteractionService extends Service implements PluginListener<O
mGestureState,
InputConsumer.NO_OP, mInputMonitorCompat,
mOverviewComponentObserver.assistantGestureIsConstrained());
} else if (mDeviceState.canTriggerOneHandedAction(event)
&& !mDeviceState.isOneHandedModeActive()) {
// Consume gesture event for triggering one handed feature.
mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
InputConsumer.NO_OP, mInputMonitorCompat);
} else {
mUncheckedConsumer = InputConsumer.NO_OP;
}
@ -523,7 +530,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
// Other events
if (mUncheckedConsumer != InputConsumer.NO_OP) {
// Only transform the event if we are handling it in a proper consumer
mDeviceState.setOrientationTransformIfNeeded(event);
mRotationTouchHelper.setOrientationTransformIfNeeded(event);
}
}
@ -561,7 +568,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
gestureState.updatePreviouslyAppearedTaskIds(
previousGestureState.getPreviouslyAppearedTaskIds());
} else {
gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.0",
() -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
}
return gestureState;
@ -684,7 +691,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
if (AssistantUtilities.isExcludedAssistant(gestureState.getRunningTask())) {
// In the case where we are in the excluded assistant state, ignore it and treat the
// running activity as the task behind the assistant
gestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.assistant",
gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.assistant",
() -> mAM.getRunningTask(true /* filterOnlyVisibleRecents */)));
ComponentName homeComponent = mOverviewComponentObserver.getHomeIntent().getComponent();
ComponentName runningComponent =
@ -714,7 +721,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
MotionEvent event) {
final BaseSwipeUpHandler.Factory factory;
final AbsSwipeUpHandler.Factory factory;
if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
factory = mFallbackSwipeHandlerFactory;
} else {
@ -887,13 +894,13 @@ public class TouchInteractionService extends Service implements PluginListener<O
}
}
private BaseSwipeUpHandler createLauncherSwipeHandler(
private AbsSwipeUpHandler createLauncherSwipeHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
private BaseSwipeUpHandler createFallbackSwipeHandler(
private AbsSwipeUpHandler createFallbackSwipeHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);

View File

@ -15,7 +15,6 @@
*/
package com.android.quickstep.fallback;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
@ -25,6 +24,7 @@ import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVER
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
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.quickstep.views.RecentsView.TASK_MODALNESS;
import com.android.launcher3.anim.PendingAnimation;
@ -82,7 +82,7 @@ public class FallbackRecentsStateController implements StateHandler<RecentsState
MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));

View File

@ -99,7 +99,8 @@ public class AccessibilityInputConsumer extends DelegateInputConsumer {
case ACTION_POINTER_DOWN: {
if (mState == STATE_INACTIVE) {
int pointerIndex = ev.getActionIndex();
if (mDeviceState.isInSwipeUpTouchRegion(ev, pointerIndex)
if (mDeviceState.getRotationTouchHelper()
.isInSwipeUpTouchRegion(ev, pointerIndex)
&& mDelegate.allowInterceptByParent()) {
setActive(ev);

View File

@ -21,7 +21,7 @@ import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@ -147,7 +147,7 @@ public class DeviceLockedInputConsumer implements InputConsumer,
if (!mThresholdCrossed) {
// Cancel interaction in case of multi-touch interaction
int ptrIdx = ev.getActionIndex();
if (!mDeviceState.isInSwipeUpTouchRegion(ev, ptrIdx)) {
if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) {
int action = ev.getAction();
ev.setAction(ACTION_CANCEL);
finishTouchTracking(ev);

View File

@ -52,7 +52,6 @@ public class OneHandedModeInputConsumer extends DelegateInputConsumer {
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private final PointF mStartDragPos = new PointF();
private boolean mPassedSlop;
@ -92,7 +91,6 @@ public class OneHandedModeInputConsumer extends DelegateInputConsumer {
if (!mPassedSlop) {
if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
> 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) {

View File

@ -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<OtherActivityInputConsumer> 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

View File

@ -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<LauncherLogProto.Target> targets) {
PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
}
}

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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<BaseQuickstepLauncher>
implements StateListener<LauncherState> {
private final TransformParams mTransformParams = new TransformParams();
private RecentsExtraCard mRecentsExtraCardPlugin;
private RecentsExtraViewContainer mRecentsExtraViewContainer;
private PluginListener<RecentsExtraCard> mRecentsExtraCardPluginListener =
@ -108,17 +102,6 @@ public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
}
}
@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<BaseQuickstepLauncher>
}
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<BaseQuickstepLauncher>
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();

View File

@ -70,12 +70,14 @@ public class OverviewActionsView<T extends OverlayUICallbacks> 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;

View File

@ -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<T extends StatefulActivity> extends PagedView
}
};
/** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
new FloatProperty<RecentsView>("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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> extends PagedView
mTaskListChangeId = -1;
mRecentsAnimationController = null;
mRecentsAnimationTargets = null;
mLiveTileParams.setTargetSet(null);
unloadVisibleTaskData();
setCurrentPage(0);
@ -1077,7 +1136,7 @@ public abstract class RecentsView<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> 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<Boolean> onLaunchResult = (result) -> {
@ -1991,9 +2143,6 @@ public abstract class RecentsView<T extends StatefulActivity> 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<T extends StatefulActivity> 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<T extends StatefulActivity> extends PagedView
};
}
public TransformParams getLiveTileParams(
boolean mightNeedToRefill) {
return null;
}
private void updateEnabledOverlays() {
int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
int taskCount = getTaskViewCount();

View File

@ -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());
}

View File

@ -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<OverviewSc
}
private void updateOverlay() {
if (mOverlayEnabled && mBitmapShader != null && mThumbnailData != null) {
if (mOverlayEnabled) {
mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
mPreviewPositionHelper.mIsOrientationChanged);
} else {
@ -385,8 +384,8 @@ public class TaskThumbnailView extends View implements PluginListener<OverviewSc
if (mBitmapShader != null && mThumbnailData != null) {
mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
mThumbnailData.thumbnail.getHeight());
int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
mActivity.getResources().getConfiguration());
int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
.getRecentsActivityRotation();
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
currentRotation);

View File

@ -22,10 +22,14 @@ import static android.view.Gravity.CENTER_VERTICAL;
import static android.view.Gravity.END;
import static android.view.Gravity.START;
import static android.view.Gravity.TOP;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.widget.Toast.LENGTH_SHORT;
import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
@ -52,7 +56,9 @@ import android.os.Handler;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
@ -77,6 +83,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.TransformingTouchDelegate;
import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
@ -121,6 +128,13 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable {
public static final long SCALE_ICON_DURATION = 120;
private static final long DIM_ANIM_DURATION = 700;
/**
* This technically can be a vanilla {@link TouchDelegate} class, however that class requires
* setting the touch bounds at construction, so we'd repeatedly be created many instances
* unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
* delegated bounds only to be updated.
*/
private TransformingTouchDelegate mIconTouchDelegate;
private static final List<Rect> 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;

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FFFFFFFF" />
</shape>

View File

@ -24,6 +24,14 @@
android:layout_height="match_parent"
android:background="@drawable/gesture_tutorial_ripple"/>
<com.android.launcher3.views.ClipIconView
android:id="@+id/gesture_tutorial_fake_icon_view"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/bg_circle"
android:backgroundTint="@color/gesture_tutorial_fake_task_view_color"
android:visibility="invisible" />
<View
android:id="@+id/gesture_tutorial_fake_task_view"
android:layout_width="match_parent"
@ -41,81 +49,81 @@
android:id="@+id/gesture_tutorial_fragment_close_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="18dp"
android:layout_marginTop="30dp"
android:layout_marginStart="4dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="@android:color/transparent"
android:layout_marginStart="4dp"
android:layout_marginTop="30dp"
android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_titles_container"
android:background="@android:color/transparent"
android:contentDescription="@string/gesture_tutorial_close_button_content_description"
android:tint="?android:attr/textColorPrimary"
android:src="@drawable/gesture_tutorial_close_button"/>
android:padding="18dp"
android:src="@drawable/gesture_tutorial_close_button"
android:tint="?android:attr/textColorPrimary"/>
<LinearLayout
android:id="@+id/gesture_tutorial_fragment_titles_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="70dp"
android:layout_alignParentTop="true"
android:layout_marginTop="70dp"
android:focusable="true"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:id="@+id/gesture_tutorial_fragment_title_view"
style="@style/TextAppearance.GestureTutorial.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/gesture_tutorial_title_margin_start_end"
android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"
style="@style/TextAppearance.GestureTutorial.Title"/>
android:layout_marginEnd="@dimen/gesture_tutorial_title_margin_start_end"/>
<TextView
android:id="@+id/gesture_tutorial_fragment_subtitle_view"
style="@style/TextAppearance.GestureTutorial.Subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginStart="@dimen/gesture_tutorial_subtitle_margin_start_end"
android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"
style="@style/TextAppearance.GestureTutorial.Subtitle"/>
android:layout_marginTop="10dp"
android:layout_marginEnd="@dimen/gesture_tutorial_subtitle_margin_start_end"/>
</LinearLayout>
<TextView
android:id="@+id/gesture_tutorial_fragment_feedback_view"
style="@style/TextAppearance.GestureTutorial.Feedback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_centerHorizontal="true"
android:layout_above="@id/gesture_tutorial_fragment_action_button"
android:layout_centerHorizontal="true"
android:layout_marginStart="@dimen/gesture_tutorial_feedback_margin_start_end"
android:layout_marginEnd="@dimen/gesture_tutorial_feedback_margin_start_end"
style="@style/TextAppearance.GestureTutorial.Feedback"/>
android:layout_marginBottom="10dp"/>
<!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
of elevation and shadow) which is replaced by ripple effect in android:foreground -->
<Button
android:id="@+id/gesture_tutorial_fragment_action_button"
style="@style/TextAppearance.GestureTutorial.ButtonLabel"
android:layout_width="142dp"
android:layout_height="49dp"
android:layout_marginEnd="@dimen/gesture_tutorial_button_margin_start_end"
android:layout_marginBottom="48dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:stateListAnimator="@null"
android:layout_marginEnd="@dimen/gesture_tutorial_button_margin_start_end"
android:layout_marginBottom="48dp"
android:background="@drawable/gesture_tutorial_action_button_background"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
style="@style/TextAppearance.GestureTutorial.ButtonLabel"/>
android:stateListAnimator="@null"/>
<Button
android:id="@+id/gesture_tutorial_fragment_action_text_button"
style="@style/TextAppearance.GestureTutorial.TextButtonLabel"
android:layout_width="142dp"
android:layout_height="49dp"
android:layout_marginStart="@dimen/gesture_tutorial_button_margin_start_end"
android:layout_marginBottom="48dp"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:stateListAnimator="@null"
android:layout_marginStart="@dimen/gesture_tutorial_button_margin_start_end"
android:layout_marginBottom="48dp"
android:background="@null"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
style="@style/TextAppearance.GestureTutorial.TextButtonLabel"/>
android:stateListAnimator="@null"/>
</RelativeLayout>

View File

@ -17,9 +17,7 @@
<com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/overview_actions_height"
android:layout_gravity="center_horizontal|bottom"
android:layout_marginLeft="@dimen/overview_actions_horizontal_margin"
android:layout_marginRight="@dimen/overview_actions_horizontal_margin">
android:layout_gravity="center_horizontal|bottom">
<LinearLayout
android:id="@+id/action_buttons"

View File

@ -110,6 +110,13 @@ public abstract class BaseQuickstepLauncher extends Launcher
.getHighResLoadingState().setVisible(true);
}
@Override
protected void handleGestureContract(Intent intent) {
if (FeatureFlags.SEPARATE_RECENTS_ACTIVITY.get()) {
super.handleGestureContract(intent);
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);

View File

@ -43,9 +43,11 @@ import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
@ -57,6 +59,7 @@ import com.android.launcher3.util.SimpleBroadcastReceiver;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
/**
* Data model for digital wellbeing status of apps.
@ -72,6 +75,9 @@ public final class WellbeingModel {
private static final int MSG_FULL_REFRESH = 3;
// Welbeing contract
private static final String PATH_ACTIONS = "actions";
private static final String PATH_MINIMAL_DEVICE = "minimal_device";
private static final String METHOD_GET_MINIMAL_DEVICE_CONFIG = "get_minimal_device_config";
private static final String METHOD_GET_ACTIONS = "get_actions";
private static final String EXTRA_ACTIONS = "actions";
private static final String EXTRA_ACTION = "action";
@ -104,15 +110,22 @@ public final class WellbeingModel {
mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
// Wellbeing reports that app actions have changed.
if (DEBUG || mIsInTest) {
Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange
+ "], uri = [" + uri + "]");
Log.d(TAG, "ContentObserver.onChange() called with: selfChange = ["
+ selfChange + "], uri = [" + uri + "]");
}
Preconditions.assertUIThread();
if (uri.getPath().contains(PATH_ACTIONS)) {
// Wellbeing reports that app actions have changed.
updateWellbeingData();
} else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
// Wellbeing reports that minimal device state or config is changed.
updateLauncherModel();
}
}
};
FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, this::updateLauncherModel);
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
context.registerReceiver(
@ -146,14 +159,18 @@ public final class WellbeingModel {
private void restartObserver() {
final ContentResolver resolver = mContext.getContentResolver();
resolver.unregisterContentObserver(mContentObserver);
Uri actionsUri = apiBuilder().path("actions").build();
Uri actionsUri = apiBuilder().path(PATH_ACTIONS).build();
Uri minimalDeviceUri = apiBuilder().path(PATH_MINIMAL_DEVICE).build();
try {
resolver.registerContentObserver(
actionsUri, true /* notifyForDescendants */, mContentObserver);
resolver.registerContentObserver(
minimalDeviceUri, true /* notifyForDescendants */, mContentObserver);
} catch (Exception e) {
Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
if (mIsInTest) throw new RuntimeException(e);
}
updateWellbeingData();
}
@ -191,12 +208,42 @@ public final class WellbeingModel {
mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
}
private void updateLauncherModel() {
if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) return;
// TODO: init Launcher in minimal device / normal mode
}
private Uri.Builder apiBuilder() {
return new Uri.Builder()
.scheme(SCHEME_CONTENT)
.authority(mWellbeingProviderPkg + ".api");
}
/**
* Fetch most up-to-date minimal device config.
*/
@WorkerThread
private void runWithMinimalDeviceConfigs(Consumer<Bundle> consumer) {
if (DEBUG || mIsInTest) {
Log.d(TAG, "runWithMinimalDeviceConfigs() called");
}
Preconditions.assertNonUiThread();
final Uri contentUri = apiBuilder().build();
final Bundle remoteBundle;
try (ContentProviderClient client = mContext.getContentResolver()
.acquireUnstableContentProviderClient(contentUri)) {
remoteBundle = client.call(
METHOD_GET_MINIMAL_DEVICE_CONFIG, null /* args */, null /* extras */);
consumer.accept(remoteBundle);
} catch (Exception e) {
Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e);
if (mIsInTest) throw new RuntimeException(e);
}
if (DEBUG || mIsInTest) Log.i(TAG, "runWithMinimalDeviceConfigs(): finished");
}
private boolean updateActions(String... packageNames) {
if (packageNames.length == 0) {
return true;

View File

@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
@ -29,6 +28,7 @@ import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVER
import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
import android.util.FloatProperty;
@ -61,7 +61,7 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
@Override
public void setState(@NonNull LauncherState state) {
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]);
getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
@ -93,7 +93,7 @@ public abstract class BaseRecentsViewStateController<T extends RecentsView>
void setStateWithAnimationInternal(@NonNull final LauncherState toState,
@NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));

View File

@ -15,12 +15,11 @@
*/
package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.SysUINavigationMode.getMode;
import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@ -28,6 +27,7 @@ import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_REC
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
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 android.animation.Animator;
import android.annotation.TargetApi;
@ -150,7 +150,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
return deviceState.isInDeferredGestureRegion(ev);
}
public abstract void onExitOverview(RecentsAnimationDeviceState deviceState,
public abstract void onExitOverview(RotationTouchHelper deviceState,
Runnable exitRunnable);
/**
@ -393,7 +393,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
// Scale down recents from being full screen to being in overview.
RecentsView recentsView = activity.getOverviewPanel();
pa.addFloat(recentsView, SCALE_PROPERTY,
pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
}

View File

@ -374,7 +374,7 @@ class OrientationTouchTransformer {
StringBuilder regions = new StringBuilder(" currentTouchableRotations=");
for(int i = 0; i < mSwipeTouchRegions.size(); i++) {
OrientationRectF rectF = mSwipeTouchRegions.get(mSwipeTouchRegions.keyAt(i));
regions.append(rectF.mRotation).append(" ");
regions.append(rectF).append(" ");
}
pw.println(regions.toString());
pw.println(" mNavBarGesturalHeight=" + mNavBarGesturalHeight);

View File

@ -16,11 +16,9 @@
package com.android.quickstep;
import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@ -52,25 +50,21 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.Surface;
import androidx.annotation.BinderThread;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import com.android.launcher3.util.SecureSettingsObserver;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
import com.android.systemui.shared.system.SystemGestureExclusionListenerCompat;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.io.PrintWriter;
import java.util.ArrayList;
@ -85,11 +79,13 @@ public class RecentsAnimationDeviceState implements
DefaultDisplay.DisplayInfoChangeListener,
OneHandedModeChangeListener {
static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
private final Context mContext;
private final SysUINavigationMode mSysUiNavMode;
private final DefaultDisplay mDefaultDisplay;
private final int mDisplayId;
private int mDisplayRotation;
private final RotationTouchHelper mRotationTouchHelper;
private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
@ -115,76 +111,10 @@ public class RecentsAnimationDeviceState implements
}
};
private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
@Override
public void onRecentTaskListFrozenChanged(boolean frozen) {
mTaskListFrozen = frozen;
if (frozen || mInOverview) {
return;
}
enableMultipleRegions(false);
}
@Override
public void onActivityRotation(int displayId) {
super.onActivityRotation(displayId);
// This always gets called before onDisplayInfoChanged() so we know how to process
// the rotation in that method. This is done to avoid having a race condition between
// the sensor readings and onDisplayInfoChanged() call
if (displayId != mDisplayId) {
return;
}
mPrioritizeDeviceRotation = true;
if (mInOverview) {
// reset, launcher must be rotating
mExitOverviewRunnable.run();
}
}
};
private Runnable mExitOverviewRunnable = new Runnable() {
@Override
public void run() {
mInOverview = false;
enableMultipleRegions(false);
}
};
private OrientationTouchTransformer mOrientationTouchTransformer;
/**
* Used to listen for when the device rotates into the orientation of the current
* foreground app. For example, if a user quickswitches from a portrait to a fixed landscape
* app and then rotates rotates the device to match that orientation, this triggers calls to
* sysui to adjust the navbar.
*/
private OrientationEventListener mOrientationListener;
private int mSensorRotation = ROTATION_0;
/**
* This is the configuration of the foreground app or the app that will be in the foreground
* once a quickstep gesture finishes.
*/
private int mCurrentAppRotation = -1;
/**
* This flag is set to true when the device physically changes orientations. When true,
* we will always report the current rotation of the foreground app whenever the display
* changes, as it would indicate the user's intention to rotate the foreground app.
*/
private boolean mPrioritizeDeviceRotation = false;
private Region mExclusionRegion;
private SystemGestureExclusionListenerCompat mExclusionListener;
private final List<ComponentName> mGestureBlockedActivities;
private Runnable mOnDestroyFrozenTaskRunnable;
/**
* Set to true when user swipes to recents. In recents, we ignore the state of the recents
* task list being frozen or not to allow the user to keep interacting with nav bar rotation
* they went into recents with as opposed to defaulting to the default display rotation.
* TODO: (b/156984037) For when user rotates after entering overview
*/
private boolean mInOverview;
private boolean mTaskListFrozen;
private boolean mIsUserSetupComplete;
@ -194,6 +124,8 @@ public class RecentsAnimationDeviceState implements
mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
mDisplayId = mDefaultDisplay.getInfo().id;
runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
mRotationTouchHelper = new RotationTouchHelper(context);
runOnDestroy(mRotationTouchHelper::destroy);
// Register for user unlocked if necessary
mIsUserUnlocked = context.getSystemService(UserManager.class)
@ -215,10 +147,6 @@ public class RecentsAnimationDeviceState implements
};
runOnDestroy(mExclusionListener::unregister);
Resources resources = mContext.getResources();
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> QuickStepContract.getWindowCornerRadius(resources));
// Register for navigation mode changes
onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
@ -239,13 +167,15 @@ public class RecentsAnimationDeviceState implements
}
}
if (SystemProperties.getBoolean("ro.support_one_handed_mode", false)) {
if (SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
SecureSettingsObserver oneHandedEnabledObserver =
SecureSettingsObserver.newOneHandedSettingsObserver(
mContext, enabled -> mIsOneHandedModeEnabled = enabled);
oneHandedEnabledObserver.register();
oneHandedEnabledObserver.dispatchOnChange();
runOnDestroy(oneHandedEnabledObserver::unregister);
} else {
mIsOneHandedModeEnabled = false;
}
SecureSettingsObserver swipeBottomEnabledObserver =
@ -265,38 +195,6 @@ public class RecentsAnimationDeviceState implements
userSetupObserver.register();
runOnDestroy(userSetupObserver::unregister);
}
mOrientationListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int degrees) {
int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
mSensorRotation);
if (newRotation == mSensorRotation) {
return;
}
mSensorRotation = newRotation;
mPrioritizeDeviceRotation = true;
if (newRotation == mCurrentAppRotation) {
// When user rotates device to the orientation of the foreground app after
// quickstepping
toggleSecondaryNavBarsForRotation();
}
}
};
}
private void setupOrientationSwipeHandler() {
ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
.unregisterTaskStackListener(mFrozenTaskListener);
runOnDestroy(mOnDestroyFrozenTaskRunnable);
}
private void destroyOrientationSwipeHandlerCallback() {
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
}
private void runOnDestroy(Runnable action) {
@ -343,15 +241,6 @@ public class RecentsAnimationDeviceState implements
}
mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
mContext.getApplicationContext().getResources());
if (!mMode.hasGestures && newMode.hasGestures) {
setupOrientationSwipeHandler();
} else if (mMode.hasGestures && !newMode.hasGestures){
destroyOrientationSwipeHandlerCallback();
}
mMode = newMode;
}
@ -362,34 +251,15 @@ public class RecentsAnimationDeviceState implements
return;
}
mDisplayRotation = info.rotation;
if (!mMode.hasGestures) {
return;
}
mNavBarPosition = new NavBarPosition(mMode, info);
updateGestureTouchRegions();
mOrientationTouchTransformer.createOrAddTouchRegion(info);
mCurrentAppRotation = mDisplayRotation;
/* Update nav bars on the following:
* a) if this is coming from an activity rotation OR
* aa) we launch an app in the orientation that user is already in
* b) We're not in overview, since overview will always be portrait (w/o home rotation)
* c) We're actively in quickswitch mode
*/
if ((mPrioritizeDeviceRotation
|| mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
&& !mInOverview
&& mTaskListFrozen) {
toggleSecondaryNavBarsForRotation();
}
}
@Override
public void onOneHandedModeChanged(int newGesturalHeight) {
mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
mContext.getApplicationContext().getResources());
mRotationTouchHelper.setGesturalHeight(newGesturalHeight);
}
/**
@ -504,7 +374,7 @@ public class RecentsAnimationDeviceState implements
*/
public boolean canStartSystemGesture() {
boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
|| mTaskListFrozen;
|| mRotationTouchHelper.isTaskListFrozen();
return canStartWithNavHidden
&& (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
&& (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
@ -577,34 +447,7 @@ public class RecentsAnimationDeviceState implements
}
/**
* Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
*/
public void updateGestureTouchRegions() {
if (!mMode.hasGestures) {
return;
}
mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
}
/**
* @return whether the coordinates of the {@param event} is in the swipe up gesture region.
*/
public boolean isInSwipeUpTouchRegion(MotionEvent event) {
return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
}
/**
* @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
* is in the swipe up gesture region.
*/
public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
event.getY(pointerIndex));
}
/**
* @return whether screen pinning is enabled and active
* @return whether one-handed mode is enabled and active
*/
public boolean isOneHandedModeActive() {
return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
@ -667,7 +510,7 @@ public class RecentsAnimationDeviceState implements
public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
return mAssistantAvailable
&& !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
&& mOrientationTouchTransformer.touchInAssistantRegion(ev)
&& mRotationTouchHelper.touchInAssistantRegion(ev)
&& !isLockToAppActive()
&& !isGestureBlockedActivity(task);
}
@ -679,12 +522,16 @@ public class RecentsAnimationDeviceState implements
* @return whether the given motion event can trigger the one handed mode.
*/
public boolean canTriggerOneHandedAction(MotionEvent ev) {
if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) {
return false;
}
if (!mIsOneHandedModeEnabled && !mIsSwipeToNotificationEnabled) {
return false;
}
final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
return (mOrientationTouchTransformer.touchInOneHandedModeRegion(ev)
return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
&& displayInfo.rotation != Surface.ROTATION_90
&& displayInfo.rotation != Surface.ROTATION_270
&& displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
@ -698,96 +545,8 @@ public class RecentsAnimationDeviceState implements
return mIsSwipeToNotificationEnabled;
}
/**
* *May* apply a transform on the motion event if it lies in the nav bar region for another
* orientation that is currently being tracked as a part of quickstep
*/
void setOrientationTransformIfNeeded(MotionEvent event) {
// negative coordinates bug b/143901881
if (event.getX() < 0 || event.getY() < 0) {
event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
}
mOrientationTouchTransformer.transform(event);
}
private void enableMultipleRegions(boolean enable) {
mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
// Clear any previous state from sensor manager
mSensorRotation = mCurrentAppRotation;
mOrientationListener.enable();
} else {
mOrientationListener.disable();
}
}
public void onStartGesture() {
if (mTaskListFrozen) {
// Prioritize whatever nav bar user touches once in quickstep
// This case is specifically when user changes what nav bar they are using mid
// quickswitch session before tasks list is unfrozen
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
}
void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
BaseActivityInterface activityInterface) {
if (endTarget == GestureState.GestureEndTarget.RECENTS) {
mInOverview = true;
if (!mTaskListFrozen) {
// If we're in landscape w/o ever quickswitching, show the navbar in landscape
enableMultipleRegions(true);
}
activityInterface.onExitOverview(this, mExitOverviewRunnable);
} else if (endTarget == GestureState.GestureEndTarget.HOME) {
enableMultipleRegions(false);
} else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
// First gesture to start quickswitch
enableMultipleRegions(true);
} else {
notifySysuiOfCurrentRotation(
mOrientationTouchTransformer.getCurrentActiveRotation());
}
// A new gesture is starting, reset the current device rotation
// This is done under the assumption that the user won't rotate the phone and then
// quickswitch in the old orientation.
mPrioritizeDeviceRotation = false;
} else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
if (!mTaskListFrozen) {
// touched nav bar but didn't go anywhere and not quickswitching, do nothing
return;
}
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
}
private void notifySysuiOfCurrentRotation(int rotation) {
UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
.onQuickSwitchToNewTask(rotation));
}
/**
* Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
* notifies system UI of the primary rotation the user is interacting with
*/
private void toggleSecondaryNavBarsForRotation() {
mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
public int getCurrentActiveRotation() {
if (!mMode.hasGestures) {
// touch rotation should always match that of display for 3 button
return mDisplayRotation;
}
return mOrientationTouchTransformer.getCurrentActiveRotation();
}
public int getDisplayRotation() {
return mDisplayRotation;
public RotationTouchHelper getRotationTouchHelper() {
return mRotationTouchHelper;
}
public void dump(PrintWriter pw) {
@ -799,11 +558,9 @@ public class RecentsAnimationDeviceState implements
pw.println(" assistantAvailable=" + mAssistantAvailable);
pw.println(" assistantDisabled="
+ QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
pw.println(" displayRotation=" + getDisplayRotation());
pw.println(" isUserUnlocked=" + mIsUserUnlocked);
pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
mOrientationTouchTransformer.dump(pw);
mRotationTouchHelper.dump(pw);
}
}

View File

@ -0,0 +1,376 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep;
import static android.view.Surface.ROTATION_0;
import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
import android.content.Context;
import android.content.res.Resources;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.DefaultDisplay;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.io.PrintWriter;
import java.util.ArrayList;
public class RotationTouchHelper implements
SysUINavigationMode.NavigationModeChangeListener,
DefaultDisplay.DisplayInfoChangeListener {
private final OrientationTouchTransformer mOrientationTouchTransformer;
private final DefaultDisplay mDefaultDisplay;
private final SysUINavigationMode mSysUiNavMode;
private final int mDisplayId;
private int mDisplayRotation;
private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
private SysUINavigationMode.Mode mMode = THREE_BUTTONS;
private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
@Override
public void onRecentTaskListFrozenChanged(boolean frozen) {
mTaskListFrozen = frozen;
if (frozen || mInOverview) {
return;
}
enableMultipleRegions(false);
}
@Override
public void onActivityRotation(int displayId) {
super.onActivityRotation(displayId);
// This always gets called before onDisplayInfoChanged() so we know how to process
// the rotation in that method. This is done to avoid having a race condition between
// the sensor readings and onDisplayInfoChanged() call
if (displayId != mDisplayId) {
return;
}
mPrioritizeDeviceRotation = true;
if (mInOverview) {
// reset, launcher must be rotating
mExitOverviewRunnable.run();
}
}
};
private Runnable mExitOverviewRunnable = new Runnable() {
@Override
public void run() {
mInOverview = false;
enableMultipleRegions(false);
}
};
/**
* Used to listen for when the device rotates into the orientation of the current foreground
* app. For example, if a user quickswitches from a portrait to a fixed landscape app and then
* rotates rotates the device to match that orientation, this triggers calls to sysui to adjust
* the navbar.
*/
private OrientationEventListener mOrientationListener;
private int mSensorRotation = ROTATION_0;
/**
* This is the configuration of the foreground app or the app that will be in the foreground
* once a quickstep gesture finishes.
*/
private int mCurrentAppRotation = -1;
/**
* This flag is set to true when the device physically changes orientations. When true, we will
* always report the current rotation of the foreground app whenever the display changes, as it
* would indicate the user's intention to rotate the foreground app.
*/
private boolean mPrioritizeDeviceRotation = false;
private Runnable mOnDestroyFrozenTaskRunnable;
/**
* Set to true when user swipes to recents. In recents, we ignore the state of the recents
* task list being frozen or not to allow the user to keep interacting with nav bar rotation
* they went into recents with as opposed to defaulting to the default display rotation.
* TODO: (b/156984037) For when user rotates after entering overview
*/
private boolean mInOverview;
private boolean mTaskListFrozen;
private final Context mContext;
public RotationTouchHelper(Context context) {
mContext = context;
Resources resources = mContext.getResources();
mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
mDisplayId = mDefaultDisplay.getInfo().id;
mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
() -> QuickStepContract.getWindowCornerRadius(resources));
// Register for navigation mode changes
onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(this));
mOrientationListener = new OrientationEventListener(context) {
@Override
public void onOrientationChanged(int degrees) {
int newRotation = RecentsOrientedState.getRotationForUserDegreesRotated(degrees,
mSensorRotation);
if (newRotation == mSensorRotation) {
return;
}
mSensorRotation = newRotation;
mPrioritizeDeviceRotation = true;
if (newRotation == mCurrentAppRotation) {
// When user rotates device to the orientation of the foreground app after
// quickstepping
toggleSecondaryNavBarsForRotation();
}
}
};
}
private void setupOrientationSwipeHandler() {
ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
.unregisterTaskStackListener(mFrozenTaskListener);
runOnDestroy(mOnDestroyFrozenTaskRunnable);
}
private void destroyOrientationSwipeHandlerCallback() {
ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
}
private void runOnDestroy(Runnable action) {
mOnDestroyActions.add(action);
}
/**
* Cleans up all the registered listeners and receivers.
*/
public void destroy() {
for (Runnable r : mOnDestroyActions) {
r.run();
}
}
public boolean isTaskListFrozen() {
return mTaskListFrozen;
}
public boolean touchInAssistantRegion(MotionEvent ev) {
return mOrientationTouchTransformer.touchInAssistantRegion(ev);
}
public boolean touchInOneHandedModeRegion(MotionEvent ev) {
return mOrientationTouchTransformer.touchInOneHandedModeRegion(ev);
}
/**
* Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
*/
public void updateGestureTouchRegions() {
if (!mMode.hasGestures) {
return;
}
mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
}
/**
* @return whether the coordinates of the {@param event} is in the swipe up gesture region.
*/
public boolean isInSwipeUpTouchRegion(MotionEvent event) {
return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(), event.getY());
}
/**
* @return whether the coordinates of the {@param event} with the given {@param pointerIndex}
* is in the swipe up gesture region.
*/
public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
event.getY(pointerIndex));
}
@Override
public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
mDefaultDisplay.removeChangeListener(this);
mDefaultDisplay.addChangeListener(this);
onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
mContext.getResources());
if (!mMode.hasGestures && newMode.hasGestures) {
setupOrientationSwipeHandler();
} else if (mMode.hasGestures && !newMode.hasGestures){
destroyOrientationSwipeHandlerCallback();
}
mMode = newMode;
}
public int getDisplayRotation() {
return mDisplayRotation;
}
@Override
public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
if (info.id != mDisplayId|| flags == CHANGE_FRAME_DELAY) {
// ignore displays that aren't running launcher and frame refresh rate changes
return;
}
mDisplayRotation = info.rotation;
if (!mMode.hasGestures) {
return;
}
updateGestureTouchRegions();
mOrientationTouchTransformer.createOrAddTouchRegion(info);
mCurrentAppRotation = mDisplayRotation;
/* Update nav bars on the following:
* a) if this is coming from an activity rotation OR
* aa) we launch an app in the orientation that user is already in
* b) We're not in overview, since overview will always be portrait (w/o home rotation)
* c) We're actively in quickswitch mode
*/
if ((mPrioritizeDeviceRotation
|| mCurrentAppRotation == mSensorRotation) // switch to an app of orientation user is in
&& !mInOverview
&& mTaskListFrozen) {
toggleSecondaryNavBarsForRotation();
}
}
/**
* Sets the gestural height.
*/
void setGesturalHeight(int newGesturalHeight) {
mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
mContext.getResources());
}
/**
* *May* apply a transform on the motion event if it lies in the nav bar region for another
* orientation that is currently being tracked as a part of quickstep
*/
void setOrientationTransformIfNeeded(MotionEvent event) {
// negative coordinates bug b/143901881
if (event.getX() < 0 || event.getY() < 0) {
event.setLocation(Math.max(0, event.getX()), Math.max(0, event.getY()));
}
mOrientationTouchTransformer.transform(event);
}
private void enableMultipleRegions(boolean enable) {
mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
// Clear any previous state from sensor manager
mSensorRotation = mCurrentAppRotation;
mOrientationListener.enable();
} else {
mOrientationListener.disable();
}
}
public void onStartGesture() {
if (mTaskListFrozen) {
// Prioritize whatever nav bar user touches once in quickstep
// This case is specifically when user changes what nav bar they are using mid
// quickswitch session before tasks list is unfrozen
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
}
void onEndTargetCalculated(GestureState.GestureEndTarget endTarget,
BaseActivityInterface activityInterface) {
if (endTarget == GestureState.GestureEndTarget.RECENTS) {
mInOverview = true;
if (!mTaskListFrozen) {
// If we're in landscape w/o ever quickswitching, show the navbar in landscape
enableMultipleRegions(true);
}
activityInterface.onExitOverview(this, mExitOverviewRunnable);
} else if (endTarget == GestureState.GestureEndTarget.HOME) {
enableMultipleRegions(false);
} else if (endTarget == GestureState.GestureEndTarget.NEW_TASK) {
if (mOrientationTouchTransformer.getQuickStepStartingRotation() == -1) {
// First gesture to start quickswitch
enableMultipleRegions(true);
} else {
notifySysuiOfCurrentRotation(
mOrientationTouchTransformer.getCurrentActiveRotation());
}
// A new gesture is starting, reset the current device rotation
// This is done under the assumption that the user won't rotate the phone and then
// quickswitch in the old orientation.
mPrioritizeDeviceRotation = false;
} else if (endTarget == GestureState.GestureEndTarget.LAST_TASK) {
if (!mTaskListFrozen) {
// touched nav bar but didn't go anywhere and not quickswitching, do nothing
return;
}
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
}
private void notifySysuiOfCurrentRotation(int rotation) {
UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
.onQuickSwitchToNewTask(rotation));
}
/**
* Disables/Enables multiple nav bars on {@link OrientationTouchTransformer} and then
* notifies system UI of the primary rotation the user is interacting with
*/
private void toggleSecondaryNavBarsForRotation() {
mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
}
public int getCurrentActiveRotation() {
if (!mMode.hasGestures) {
// touch rotation should always match that of display for 3 button
return mDisplayRotation;
}
return mOrientationTouchTransformer.getCurrentActiveRotation();
}
public void dump(PrintWriter pw) {
pw.println("RotationTouchHelper:");
pw.println(" currentActiveRotation=" + getCurrentActiveRotation());
pw.println(" displayRotation=" + getDisplayRotation());
mOrientationTouchTransformer.dump(pw);
}
}

View File

@ -17,6 +17,9 @@
package com.android.quickstep;
import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_2_BUTTON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_3_BUTTON;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import android.content.BroadcastReceiver;
@ -25,6 +28,7 @@ import android.content.Intent;
import android.util.Log;
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.MainThreadInitializedObject;
@ -38,16 +42,18 @@ import java.util.List;
public class SysUINavigationMode {
public enum Mode {
THREE_BUTTONS(false, 0),
TWO_BUTTONS(true, 1),
NO_BUTTON(true, 2);
THREE_BUTTONS(false, 0, LAUNCHER_NAVIGATION_MODE_3_BUTTON),
TWO_BUTTONS(true, 1, LAUNCHER_NAVIGATION_MODE_2_BUTTON),
NO_BUTTON(true, 2, LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON);
public final boolean hasGestures;
public final int resValue;
public final LauncherEvent launcherEvent;
Mode(boolean hasGestures, int resValue) {
Mode(boolean hasGestures, int resValue, LauncherEvent launcherEvent) {
this.hasGestures = hasGestures;
this.resValue = resValue;
this.launcherEvent = launcherEvent;
}
}
@ -183,12 +189,10 @@ public class SysUINavigationMode {
}
public interface NavigationModeChangeListener {
void onNavigationModeChanged(Mode newMode);
}
public interface OneHandedModeChangeListener {
void onOneHandedModeChanged(int newGesturalHeight);
}
}

View File

@ -346,6 +346,19 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
@Override
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
Insets visibleInsets, Task.TaskKey task) {
if (mSystemUiProxy != null) {
try {
mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
visibleInsets, task);
} catch (RemoteException e) {
Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
}
}
}
@Override
public void startOneHandedMode() {
if (mSystemUiProxy != null) {
@ -368,19 +381,6 @@ public class SystemUiProxy implements ISystemUiProxy {
}
}
@Override
public void handleImageBundleAsScreenshot(Bundle screenImageBundle, Rect locationInScreen,
Insets visibleInsets, Task.TaskKey task) {
if (mSystemUiProxy != null) {
try {
mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
visibleInsets, task);
} catch (RemoteException e) {
Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
}
}
}
@Override
public void expandNotificationPanel() {
if (mSystemUiProxy != null) {

View File

@ -16,6 +16,7 @@
package com.android.quickstep.interaction;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@ -48,6 +49,7 @@ import com.android.launcher3.ResourceUtils;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.util.VibratorWrapper;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.NavBarPosition;
import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
import com.android.systemui.shared.system.QuickStepContract;
@ -74,6 +76,7 @@ public class NavBarGestureHandler implements OnTouchListener,
private final PointF mAssistantStartDragPos = new PointF();
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private final MotionPauseDetector mMotionPauseDetector;
private boolean mTouchCameFromAssistantCorner;
private boolean mTouchCameFromNavBar;
private boolean mPassedAssistantSlop;
@ -100,6 +103,7 @@ public class NavBarGestureHandler implements OnTouchListener,
new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
new NavBarPosition(Mode.NO_BUTTON, displayRotation),
null /*onInterceptTouch*/, this);
mMotionPauseDetector = new MotionPauseDetector(context);
final Resources resources = context.getResources();
mBottomGestureHeight =
@ -177,12 +181,14 @@ public class NavBarGestureHandler implements OnTouchListener,
}
mLaunchedAssistant = false;
mSwipeUpTouchTracker.init();
mMotionPauseDetector.clear();
mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
break;
case MotionEvent.ACTION_MOVE:
mLastPos.set(event.getX(), event.getY());
if (!mAssistantGestureActive) {
break;
}
mLastPos.set(event.getX(), event.getY());
if (!mPassedAssistantSlop) {
// Normal gesture, ensure we pass the slop before we start tracking the gesture
@ -213,6 +219,8 @@ public class NavBarGestureHandler implements OnTouchListener,
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mMotionPauseDetector.clear();
mMotionPauseDetector.setOnMotionPauseListener(null);
if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
mGestureCallback.onNavBarGestureAttempted(
HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF());
@ -239,9 +247,17 @@ public class NavBarGestureHandler implements OnTouchListener,
}
mSwipeUpTouchTracker.onMotionEvent(event);
mAssistantGestureDetector.onTouchEvent(event);
mMotionPauseDetector.addPosition(event);
mMotionPauseDetector.setDisallowPause(mLastPos.y >= mDisplaySize.y - mBottomGestureHeight);
return intercepted;
}
protected void onMotionPauseChanged(boolean isPaused) {
if (isPaused) {
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
}
}
/**
* Determine if angle is larger than threshold for assistant detection
*/

View File

@ -17,7 +17,8 @@ package com.android.quickstep.interaction;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
@ -110,6 +111,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
AnimatorListenerAdapter resetTaskView = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
mFakeIconView.setVisibility(View.INVISIBLE);
mFakeTaskView.setVisibility(View.INVISIBLE);
mFakeTaskView.setAlpha(1);
mRunningWindowAnim = null;
@ -131,6 +133,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
});
} else {
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
anim.setViewAlpha(mFakeIconView, 0, ACCEL);
anim.addListener(resetTaskView);
}
if (onEndRunnable != null) {
@ -202,7 +205,7 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
// derivative of the scroll interpolator at zero, ie. 2.
long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory(null) {
HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory() {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
@ -218,6 +221,24 @@ abstract class SwipeUpGestureTutorialController extends TutorialController {
fakeHomeIconLeft + fakeHomeIconSizePx,
fakeHomeIconTop + fakeHomeIconSizePx);
}
@Override
public void update(RectF rect, float progress, float radius) {
mFakeIconView.setVisibility(View.VISIBLE);
mFakeIconView.update(rect, progress,
1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
radius,
false, /* isOpening */
mFakeIconView, mDp,
false /* isVerticalBarLayout */);
mFakeIconView.setAlpha(1);
mFakeTaskView.setAlpha(getWindowAlpha(progress));
}
@Override
public void onCancel() {
mFakeIconView.setVisibility(View.INVISIBLE);
}
};
RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, homeAnimFactory);
windowAnim.start(mContext, velocityPxPerMs);

View File

@ -28,6 +28,7 @@ import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
@ -46,6 +47,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
final TextView mTitleTextView;
final TextView mSubtitleTextView;
final TextView mFeedbackView;
final ClipIconView mFakeIconView;
final View mFakeTaskView;
final View mRippleView;
final RippleDrawable mRippleDrawable;
@ -66,6 +68,7 @@ abstract class TutorialController implements BackGestureAttemptCallback,
mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
mRippleDrawable = (RippleDrawable) mRippleView.getBackground();

View File

@ -16,6 +16,10 @@
package com.android.quickstep.logging;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.formatElapsedTime;
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
@ -24,6 +28,8 @@ import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGE
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
import static java.lang.System.currentTimeMillis;
import android.content.Context;
import android.util.Log;
@ -33,6 +39,7 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.FolderContainer.ParentContainerCase;
import com.android.launcher3.logger.LauncherAtom.FolderIcon;
import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
@ -52,6 +59,7 @@ import com.android.launcher3.util.LogConfig;
import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.concurrent.CopyOnWriteArrayList;
@ -60,15 +68,17 @@ import java.util.concurrent.CopyOnWriteArrayList;
* This class calls StatsLog compile time generated methods.
*
* To see if the logs are properly sent to statsd, execute following command.
* <ul>
* $ wwdebug (to turn on the logcat printout)
* $ wwlogcat (see logcat with grep filter on)
* $ statsd_testdrive (see how ww is writing the proto to statsd buffer)
* </ul>
*/
public class StatsLogCompatManager extends StatsLogManager {
private static final String TAG = "StatsLog";
private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
// LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
// from nano to lite, bake constant to prevent robo test failure.
@ -91,23 +101,40 @@ public class StatsLogCompatManager extends StatsLogManager {
}
/**
* Logs the workspace layout information on the model thread.
* Logs impression of the current workspace with additional launcher events.
*/
@Override
public void logSnapshot() {
public void logSnapshot(List<EventEnum> extraEvents) {
LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
new SnapshotWorker());
new SnapshotWorker(extraEvents));
}
private class SnapshotWorker extends BaseModelUpdateTask {
private final InstanceId mInstanceId;
SnapshotWorker() {
mInstanceId = new InstanceIdSequence(
1 << 20 /*InstanceId.INSTANCE_ID_MAX*/).newInstanceId();
private final List<EventEnum> mExtraEvents;
SnapshotWorker(List<EventEnum> extraEvents) {
mInstanceId = new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
.newInstanceId();
this.mExtraEvents = extraEvents;
}
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
long lastSnapshotTimeMillis = getDevicePrefs(mContext)
.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
// Log snapshot only if previous snapshot was older than a day
if (currentTimeMillis() - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
if (IS_VERBOSE) {
String elapsedTime = formatElapsedTime(
(currentTimeMillis() - lastSnapshotTimeMillis) / 1000);
Log.d(TAG, String.format(
"Skipped snapshot logging since previous snapshot was %s old.",
elapsedTime));
}
return;
}
IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
@ -123,16 +150,22 @@ public class StatsLogCompatManager extends StatsLogManager {
LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
writeSnapshot(atomInfo, mInstanceId);
}
} catch (Exception e) { }
} catch (Exception e) {
}
}
for (ItemInfo info : appWidgets) {
LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
writeSnapshot(atomInfo, mInstanceId);
}
mExtraEvents
.forEach(eventName -> logger().withInstanceId(mInstanceId).log(eventName));
getDevicePrefs(mContext).edit()
.putLong(LAST_SNAPSHOT_TIME_MILLIS, currentTimeMillis()).apply();
}
}
private static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
private void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
if (IS_VERBOSE) {
Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
}
@ -335,7 +368,7 @@ public class StatsLogCompatManager extends StatsLogManager {
}
private static int getCardinality(LauncherAtom.ItemInfo info) {
switch (info.getContainerInfo().getContainerCase()){
switch (info.getContainerInfo().getContainerCase()) {
case PREDICTED_HOTSEAT_CONTAINER:
return info.getContainerInfo().getPredictedHotseatContainer().getCardinality();
case SEARCH_RESULT_CONTAINER:
@ -400,9 +433,16 @@ public class StatsLogCompatManager extends StatsLogManager {
}
private static int getPageId(LauncherAtom.ItemInfo info) {
if (info.hasTask()) {
return info.getTask().getIndex();
}
switch (info.getContainerInfo().getContainerCase()) {
case FOLDER:
return info.getContainerInfo().getFolder().getPageIndex();
case HOTSEAT:
return info.getContainerInfo().getHotseat().getIndex();
case PREDICTED_HOTSEAT_CONTAINER:
return info.getContainerInfo().getPredictedHotseatContainer().getIndex();
default:
return info.getContainerInfo().getWorkspace().getPageIndex();
}
@ -411,6 +451,10 @@ public class StatsLogCompatManager extends StatsLogManager {
private static int getParentPageId(LauncherAtom.ItemInfo info) {
switch (info.getContainerInfo().getContainerCase()) {
case FOLDER:
if (info.getContainerInfo().getFolder().getParentContainerCase()
== ParentContainerCase.HOTSEAT) {
return info.getContainerInfo().getFolder().getHotseat().getIndex();
}
return info.getContainerInfo().getFolder().getWorkspace().getPageIndex();
case SEARCH_RESULT_CONTAINER:
return info.getContainerInfo().getSearchResultContainer().getWorkspace()

View File

@ -358,19 +358,24 @@ public class MotionPauseDetector {
if (count < 3) {
// Too few samples
if (count == 2) {
switch (count) {
case 2: {
int endPos = pointPos - 1;
if (endPos < 0) {
endPos += HISTORY_SIZE;
}
float denominator = eventTime - mHistoricTimes[endPos];
if (denominator != 0) {
return (eventTime - mHistoricPos[endPos]) / denominator;
return (mHistoricPos[pointPos] - mHistoricPos[endPos]) / denominator;
}
}
// fall through
case 1:
return 0f;
default:
return null;
}
}
float Sxx = sxi2 - sxi * sxi / count;
float Sxy = sxiyi - sxi * syi / count;

View File

@ -87,20 +87,6 @@ public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLaunc
});
}
if (!hasReachedMaxCount(ALL_APPS_COUNT)) {
stateManager.addStateListener(new StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if (finalState == ALL_APPS) {
if (incrementEventCount(ALL_APPS_COUNT)) {
stateManager.removeStateListener(this);
mLauncher.getScrimView().updateDragHandleVisibility();
}
}
}
});
}
if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() && !hasReachedMaxCount(
HOTSEAT_DISCOVERY_TIP_COUNT)) {
stateManager.addStateListener(new StateListener<LauncherState>() {

View File

@ -33,7 +33,6 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Matrix;
@ -48,7 +47,6 @@ import android.view.Surface;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
@ -58,7 +56,6 @@ import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.BaseActivityInterface;
import com.android.quickstep.SysUINavigationMode;
import com.android.systemui.shared.system.ConfigurationCompat;
import java.lang.annotation.Retention;
import java.util.function.IntConsumer;
@ -91,6 +88,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
private @SurfaceRotation int mTouchRotation = ROTATION_0;
private @SurfaceRotation int mDisplayRotation = ROTATION_0;
private @SurfaceRotation int mRecentsActivityRotation = ROTATION_0;
private @SurfaceRotation int mRecentsRotation = ROTATION_0 - 1;
// Launcher activity supports multiple orientation, but fallback activity does not
private static final int FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY = 1 << 0;
@ -133,8 +131,6 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
private int mFlags;
private int mPreviousRotation = ROTATION_0;
@Nullable private Configuration mActivityConfiguration;
/**
* @param rotationChangeListener Callback for receiving rotation events when rotation watcher
* is enabled
@ -170,11 +166,11 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
}
/**
* Sets the configuration for the recents activity, which could affect the activity's rotation
* Sets the rotation for the recents activity, which could affect the appearance of task view.
* @see #update(int, int)
*/
public boolean setActivityConfiguration(Configuration activityConfiguration) {
mActivityConfiguration = activityConfiguration;
public boolean setRecentsRotation(@SurfaceRotation int recentsRotation) {
mRecentsRotation = recentsRotation;
return update(mTouchRotation, mDisplayRotation);
}
@ -231,9 +227,7 @@ public final class RecentsOrientedState implements SharedPreferences.OnSharedPre
@SurfaceRotation
private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
if (isRecentsActivityRotationAllowed()) {
return mActivityConfiguration == null
? displayRotation
: ConfigurationCompat.getWindowConfigurationRotation(mActivityConfiguration);
return mRecentsRotation < ROTATION_0 ? displayRotation : mRecentsRotation;
} else {
return ROTATION_0;
}

View File

@ -44,7 +44,6 @@ import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
@ -77,15 +76,11 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
private final float mRadius;
private final int mMaxScrimAlpha;
private final Paint mPaint;
private final OnboardingPrefs mOnboardingPrefs;
// Mid point where the alpha changes
private int mMidAlpha;
private float mMidProgress;
// The progress at which the drag handle starts moving up with the shelf.
private float mDragHandleProgress;
private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
@ -103,7 +98,6 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
private boolean mRemainingScreenPathValid = false;
private Mode mSysUINavigationMode;
private boolean mIsTwoZoneSwipeModel;
public ShelfScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
@ -112,7 +106,6 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
mEndAlpha = Color.alpha(mEndScrim);
mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOnboardingPrefs = mLauncher.getOnboardingPrefs();
// Just assume the easiest UI for now, until we have the proper layout information.
mDrawingFlatColor = true;
@ -145,11 +138,9 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
// Show the shelf more quickly before reaching overview progress.
mBeforeMidProgressColorInterpolator = ACCEL_2;
mAfterMidProgressColorInterpolator = ACCEL;
mIsTwoZoneSwipeModel = FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get();
} else {
mBeforeMidProgressColorInterpolator = ACCEL;
mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
mIsTwoZoneSwipeModel = false;
}
}
@ -164,7 +155,6 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
Context context = getContext();
if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
mDragHandleProgress = 1;
if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
&& SysUINavigationMode.removeShelfFromOverview(context)) {
// Fade in all apps background quickly to distinguish from swiping from nav bar.
@ -182,29 +172,22 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
+ hotseatPadding.bottom + hotseatPadding.top;
float dragHandleTop =
Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
mDragHandleProgress = 1 - (dragHandleTop / mShiftRange);
}
mTopOffset = dp.getInsets().top - mDragHandleSize.y;
mTopOffset = dp.getInsets().top;
mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
}
updateColors();
updateSysUiColors();
updateDragHandleAlpha();
invalidate();
}
@Override
public void updateColors() {
super.updateColors();
mDragHandleOffset = 0;
if (mDrawingFlatColor) {
return;
}
if (mProgress < mDragHandleProgress) {
mDragHandleOffset = mShiftRange * (mDragHandleProgress - mProgress);
}
if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
mShelfTop = mShiftRange * mProgress + mTopOffset;
} else {
@ -258,20 +241,8 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
}
}
@Override
protected boolean shouldDragHandleBeVisible() {
boolean needsAllAppsEdu = mIsTwoZoneSwipeModel
&& !mOnboardingPrefs.hasReachedMaxCount(OnboardingPrefs.ALL_APPS_COUNT);
return needsAllAppsEdu || super.shouldDragHandleBeVisible();
}
@Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas);
drawDragHandle(canvas);
}
private void drawBackground(Canvas canvas) {
if (mDrawingFlatColor) {
if (mCurrentFlatColor != 0) {
canvas.drawColor(mCurrentFlatColor);
@ -311,9 +282,4 @@ public class ShelfScrimView extends ScrimView<BaseQuickstepLauncher>
mPaint.setColor(mShelfColor);
canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
}
@Override
public float getVisualTop() {
return mShelfTop;
}
}

View File

@ -1,162 +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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetId;
import android.content.ComponentName;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.os.Process;
import android.view.View;
import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
import com.android.launcher3.model.AppLaunchTracker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class AppPredictionsUITests extends AbstractQuickStepTest {
private LauncherActivityInfo mSampleApp1;
private LauncherActivityInfo mSampleApp2;
private LauncherActivityInfo mSampleApp3;
private AppPredictor.Callback mCallback;
@Before
public void setUp() throws Exception {
super.setUp();
List<LauncherActivityInfo> activities = mTargetContext.getSystemService(LauncherApps.class)
.getActivityList(null, Process.myUserHandle());
mSampleApp1 = activities.get(0);
mSampleApp2 = activities.get(1);
mSampleApp3 = activities.get(2);
// Disable app tracker
AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
PredictionUiStateManager.INSTANCE.initializeForTesting(null);
mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback(
Client.HOME);
mDevice.setOrientationNatural();
}
@After
public void tearDown() throws Throwable {
AppLaunchTracker.INSTANCE.initializeForTesting(null);
PredictionUiStateManager.INSTANCE.initializeForTesting(null);
mDevice.unfreezeRotation();
}
/**
* Test that prediction UI is updated as soon as we get predictions from the system
*/
@Test
public void testPredictionExistsInAllApps() {
mLauncher.pressHome().switchToAllApps();
// Dispatch an update
sendPredictionUpdate(mSampleApp1, mSampleApp2);
// The first update should apply immediately.
waitForLauncherCondition("Predictions were not updated in loading state",
launcher -> getPredictedApp(launcher).size() == 2);
}
/**
* Test that prediction update is deferred if it is already visible
*/
@Test
public void testPredictionsDeferredUntilHome() {
mDevice.pressHome();
sendPredictionUpdate(mSampleApp1, mSampleApp2);
mLauncher.pressHome().switchToAllApps();
waitForLauncherCondition("Predictions were not updated in loading state",
launcher -> getPredictedApp(launcher).size() == 2);
// Update predictions while all-apps is visible
sendPredictionUpdate(mSampleApp1, mSampleApp2, mSampleApp3);
assertEquals(2, getFromLauncher(this::getPredictedApp).size());
// Go home and go back to all-apps
mLauncher.pressHome().switchToAllApps();
assertEquals(3, getFromLauncher(this::getPredictedApp).size());
}
@Test
public void testPredictionsDisabled() {
mDevice.pressHome();
sendPredictionUpdate();
mLauncher.pressHome().switchToAllApps();
waitForLauncherCondition("Predictions were not updated in loading state",
launcher -> launcher.getAppsView().getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class).getVisibility() == View.GONE);
assertFalse(PredictionUiStateManager.INSTANCE.get(mTargetContext)
.getCurrentState().isEnabled);
}
public ArrayList<BubbleTextView> getPredictedApp(Launcher launcher) {
PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class);
ArrayList<BubbleTextView> predictedAppViews = new ArrayList<>();
for (int i = 0; i < container.getChildCount(); i++) {
View view = container.getChildAt(i);
if (view instanceof BubbleTextView && view.getVisibility() == View.VISIBLE) {
predictedAppViews.add((BubbleTextView) view);
}
}
return predictedAppViews;
}
private void sendPredictionUpdate(LauncherActivityInfo... activities) {
getOnUiThread(() -> {
List<AppTarget> targets = new ArrayList<>(activities.length);
for (LauncherActivityInfo info : activities) {
ComponentName cn = info.getComponentName();
AppTarget target = new AppTarget.Builder(
new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
.setClassName(cn.getClassName())
.build();
targets.add(target);
}
mCallback.onTargetsAvailable(targets);
return null;
});
}
}

View File

@ -188,7 +188,7 @@ public class NavigationModeSwitchRule implements TestRule {
SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
Wait.atMost(() -> "Navigation mode didn't change to " + expectedMode,
() -> currentSysUiNavigationMode() == expectedMode, 60000 /* b/148422894 */,
() -> currentSysUiNavigationMode() == expectedMode, WAIT_TIME_MS,
launcher);
// b/139137636
// assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
@ -202,7 +202,7 @@ public class NavigationModeSwitchRule implements TestRule {
Wait.atMost(() -> "Switching nav mode: "
+ launcher.getNavigationModeMismatchError(),
() -> launcher.getNavigationModeMismatchError() == null,
60000 /* b/148422894 */, launcher);
WAIT_TIME_MS, launcher);
AbstractLauncherUiTest.checkDetectedLeaks(launcher);
return true;
}

View File

@ -36,6 +36,7 @@ import com.android.launcher3.tapl.AllAppsFromOverview;
import com.android.launcher3.tapl.Background;
import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
import com.android.launcher3.tapl.Overview;
import com.android.launcher3.tapl.OverviewActions;
import com.android.launcher3.tapl.OverviewTask;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.ui.TaplTestsLauncher3;
@ -68,11 +69,14 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
});
}
private void startTestApps() throws Exception {
public static void startTestApps() throws Exception {
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
}
private void startTestAppsWithCheck() throws Exception {
startTestApps();
executeOnLauncher(launcher -> assertTrue(
"Launcher activity is the top activity; expecting another activity to be the top "
+ "one",
@ -105,7 +109,7 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
@Test
@PortraitLandscape
public void testOverview() throws Exception {
startTestApps();
startTestAppsWithCheck();
// mLauncher.pressHome() also tests an important case of pressing home while in background.
Overview overview = mLauncher.pressHome().switchToOverview();
assertTrue("Launcher internal state didn't switch to Overview",
@ -189,6 +193,22 @@ public class TaplTestsQuickstep extends AbstractQuickStepTest {
0, getTaskCount(launcher)));
}
/**
* Smoke test for action buttons: Presses all the buttons and makes sure no crashes occur.
*/
@Test
@NavigationModeSwitch
@PortraitLandscape
public void testOverviewActions() throws Exception {
if (mLauncher.getNavigationModel() != NavigationModel.TWO_BUTTON) {
startTestAppsWithCheck();
OverviewActions actionsView =
mLauncher.pressHome().switchToOverview().getOverviewActions();
actionsView.clickAndDismissScreenshot();
actionsView.clickAndDismissShare();
}
}
private int getCurrentOverviewPage(Launcher launcher) {
return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
}

View File

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.graphics.ShadowDrawable
<com.android.launcher3.views.FloatingSurfaceView
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/drag_handle_indicator_no_shadow"
android:elevation="@dimen/vertical_drag_handle_elevation" />
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/filter_box"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/developer_options_filter_margins"
android:hint="@string/developer_options_filter_hint"
android:visibility="gone"
/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@android:id/list_container"/>
</LinearLayout>

View File

@ -28,6 +28,12 @@
android:clipToPadding="false"
android:importantForAccessibility="no">
<com.android.launcher3.views.AccessibilityActionsView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/home_screen"
/>
<!-- The workspace contains 5 screens of cells -->
<!-- DO NOT CHANGE THE ID -->
<com.android.launcher3.Workspace

View File

@ -13,17 +13,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/vertical_drag_handle_width"
android:height="@dimen/vertical_drag_handle_height"
android:viewportWidth="18.0"
android:viewportHeight="6.0"
android:tint="?attr/workspaceTextColor" >
<path
android:pathData="M17,6c-0.15,0-0.3-0.03-0.45-0.11L9,2.12L1.45,5.89c-0.5,0.25-1.09,
0.05-1.34-0.45S0.06,4.35,0.55,4.11l8-4c0.28-0.14,0.61-0.14,0.89,0l8,4c0.49,0.25,0.69,
0.85,0.45,1.34C17.72,5.8,17.37,6,17,6z"
android:fillColor="@android:color/white" />
</vector>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/section_title"
android:textSize="14sp"
android:fontFamily="@style/TextHeadline"
android:layout_width="wrap_content"
android:textColor="?android:attr/textColorPrimary"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:paddingTop="8dp"
android:layout_height="wrap_content"/>

View File

@ -70,6 +70,7 @@
<string name="test_information_handler_class" translatable="false"></string>
<string name="launcher_activity_logic_class" translatable="false"></string>
<string name="prediction_model_class" translatable="false"></string>
<string name="model_delegate_class" translatable="false"></string>
<!-- View ID to use for QSB widget -->
<item type="id" name="qsb_widget" />

View File

@ -40,14 +40,6 @@
<dimen name="workspace_page_indicator_line_height">1dp</dimen>
<dimen name="workspace_page_indicator_overlap_workspace">0dp</dimen>
<!-- Hotseat/all-apps scrim -->
<dimen name="all_apps_scrim_blur">4dp</dimen>
<dimen name="vertical_drag_handle_width">18dp</dimen>
<dimen name="vertical_drag_handle_height">6dp</dimen>
<dimen name="vertical_drag_handle_elevation">1dp</dimen>
<dimen name="vertical_drag_handle_touch_size">48dp</dimen>
<dimen name="vertical_drag_handle_padding_in_vertical_bar_layout">16dp</dimen>
<!-- Drop target bar -->
<dimen name="dynamic_grid_drop_target_size">48dp</dimen>
<dimen name="drop_target_vertical_gap">20dp</dimen>
@ -247,6 +239,9 @@
<dimen name="snackbar_min_text_size">12sp</dimen>
<dimen name="snackbar_max_text_size">14sp</dimen>
<!-- Developer Options -->
<dimen name="developer_options_filter_margins">10dp</dimen>
<!-- Theming related -->
<dimen name="default_dialog_corner_radius">8dp</dimen>

View File

@ -18,5 +18,4 @@
<drawable name="ic_remove_shadow">@drawable/ic_remove_no_shadow</drawable>
<drawable name="ic_uninstall_shadow">@drawable/ic_uninstall_no_shadow</drawable>
<drawable name="ic_block_shadow">@drawable/ic_block_no_shadow</drawable>
<drawable name="all_apps_arrow_shadow">@drawable/drag_handle_indicator_no_shadow</drawable>
</resources>

View File

@ -36,7 +36,7 @@
<!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. -->
<string name="shortcut_not_available">Shortcut isn\'t available</string>
<!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
<string name="home_screen">Home screen</string>
<string name="home_screen">Home</string>
<!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
<string name="custom_actions">Custom actions</string>
@ -67,6 +67,10 @@
<!-- Label for an icon representing any generic app. [CHAR_LIMIT=50] -->
<string name="label_application">App</string>
<!--All apps Search-->
<!-- Section title for apps [CHAR_LIMIT=50] -->
<string name="search_corpus_apps">Apps</string>
<!-- Popup items -->
<!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
<string name="notifications_header">Notifications</string>
@ -88,10 +92,6 @@
<string name="all_apps_button_personal_label">Personal apps list</string>
<string name="all_apps_button_work_label">Work apps list</string>
<!-- Label for button in all applications label to go back home (to the workspace / desktop)
for accessibilty (spoken when the button gets focus). -->
<string name="all_apps_home_button_label">Home</string>
<!-- Label for remove drop target (from the homescreen only).
May appear next to uninstall_drop_target_label [CHAR_LIMIT=20] -->
<string name="remove_drop_target_label">Remove</string>
@ -348,7 +348,8 @@
<!-- content description for paused work apps list -->
<string name="work_apps_paused_content_description">Work profile is paused. Work apps can\t send you notifications, use your battery, or access your location</string>
<!-- A hint shown in launcher settings develop options filter box -->
<string name="developer_options_filter_hint">Filter</string>
<!-- A tip shown pointing at work toggle -->
<string name="work_switch_tip">Pause work apps and notifications</string>

View File

@ -149,6 +149,15 @@
<style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="preferenceTheme">@style/HomeSettingsPreferenceTheme</item>
</style>
<style name="HomeSettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
<item name="preferenceFragmentCompatStyle">@style/HomeSettingsFragmentCompatStyle</item>
</style>
<style name="HomeSettingsFragmentCompatStyle" parent="@style/PreferenceFragment.Material">
<item name="android:layout">@layout/home_settings</item>
</style>
<!--

View File

@ -62,7 +62,8 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
TYPE_ALL_APPS_EDU,
TYPE_TASK_MENU,
TYPE_OPTIONS_POPUP
TYPE_OPTIONS_POPUP,
TYPE_ICON_SURFACE
})
@Retention(RetentionPolicy.SOURCE)
public @interface FloatingViewType {}
@ -80,16 +81,18 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch
// Popups related to quickstep UI
public static final int TYPE_TASK_MENU = 1 << 10;
public static final int TYPE_OPTIONS_POPUP = 1 << 11;
public static final int TYPE_ICON_SURFACE = 1 << 12;
public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
| TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU;
| TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER | TYPE_ALL_APPS_EDU
| TYPE_ICON_SURFACE;
// Type of popups which should be kept open during launcher rebind
public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
| TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
| TYPE_ALL_APPS_EDU;
| TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE;
// Usually we show the back button when a floating view is open. Instead, hide for these types.
public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE

View File

@ -81,7 +81,7 @@ public abstract class BaseDraggingActivity extends BaseActivity
super.onCreate(savedInstanceState);
mIsSafeModeEnabled = TraceHelper.whitelistIpcs("isSafeMode",
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
() -> getPackageManager().isSafeMode());
DefaultDisplay.INSTANCE.get(this).addChangeListener(this);

View File

@ -16,11 +16,8 @@
package com.android.launcher3;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@ -29,8 +26,6 @@ import android.view.accessibility.AccessibilityNodeInfo;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.RecyclerViewFastScroller;
@ -183,10 +178,6 @@ public abstract class BaseRecyclerView extends RecyclerView {
public void onScrollStateChanged(int state) {
super.onScrollStateChanged(state);
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "onScrollStateChanged: " + state);
}
if (state == SCROLL_STATE_IDLE) {
AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
}
@ -196,23 +187,5 @@ public abstract class BaseRecyclerView extends RecyclerView {
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (isLayoutSuppressed()) info.setScrollable(false);
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
Log.d(TestProtocol.NO_SCROLL_END_WIDGETS,
"onInitializeAccessibilityNodeInfo, scrollable: " + info.isScrollable());
}
}
@Override
public void setLayoutFrozen(boolean frozen) {
final boolean changing = frozen != isLayoutSuppressed();
super.setLayoutFrozen(frozen);
if (changing) {
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "setLayoutFrozen " + frozen
+ " @ " + Log.getStackTraceString(new Throwable()));
ActivityContext.lookupContext(getContext()).getDragLayer()
.sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
}
}
}
}

View File

@ -331,10 +331,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
public boolean onTouchEvent(MotionEvent event) {
// ignore events if they happen in padding area
if (event.getAction() == MotionEvent.ACTION_DOWN
&& (event.getY() < getPaddingTop()
|| event.getX() < getPaddingLeft()
|| event.getY() > getHeight() - getPaddingBottom()
|| event.getX() > getWidth() - getPaddingRight())) {
&& shouldIgnoreTouchDown(event.getX(), event.getY())) {
return false;
}
if (isLongClickable()) {
@ -347,6 +344,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
}
/**
* Returns true if the touch down at the provided position be ignored
*/
protected boolean shouldIgnoreTouchDown(float x, float y) {
return y < getPaddingTop()
|| x < getPaddingLeft()
|| y > getHeight() - getPaddingBottom()
|| x > getWidth() - getPaddingRight();
}
void setStayPressed(boolean stayPressed) {
mStayPressed = stayPressed;
refreshDrawableState();
@ -607,6 +614,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
@Override
public void setIconVisible(boolean visible) {
mIsIconVisible = visible;
if (!mIsIconVisible) {
resetIconScale();
}
Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT);
applyCompoundDrawables(icon);
}
@ -746,11 +756,14 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
@Override
public SafeCloseable prepareDrawDragView() {
if (getIcon() instanceof FastBitmapDrawable) {
FastBitmapDrawable icon = (FastBitmapDrawable) getIcon();
icon.setScale(1f);
}
resetIconScale();
setForceHideDot(true);
return () -> { };
}
private void resetIconScale() {
if (mIcon instanceof FastBitmapDrawable) {
((FastBitmapDrawable) mIcon).setScale(1f);
}
}
}

View File

@ -0,0 +1,101 @@
/*
* 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;
import static android.content.Intent.EXTRA_COMPONENT_NAME;
import static android.content.Intent.EXTRA_USER;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.SurfaceControl;
import androidx.annotation.Nullable;
/**
* Class to encapsulate the handshake protocol between Launcher and gestureNav.
*/
public class GestureNavContract {
private static final String TAG = "GestureNavContract";
public static final String EXTRA_GESTURE_CONTRACT = "gesture_nav_contract_v1";
public static final String EXTRA_ICON_POSITION = "gesture_nav_contract_icon_position";
public static final String EXTRA_ICON_SURFACE = "gesture_nav_contract_surface_control";
public static final String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK";
public final ComponentName componentName;
public final UserHandle user;
private final Message mCallback;
public GestureNavContract(ComponentName componentName, UserHandle user, Message callback) {
this.componentName = componentName;
this.user = user;
this.mCallback = callback;
}
/**
* Sends the position information to the receiver
*/
@TargetApi(Build.VERSION_CODES.R)
public void sendEndPosition(RectF position, @Nullable SurfaceControl surfaceControl) {
Bundle result = new Bundle();
result.putParcelable(EXTRA_ICON_POSITION, position);
result.putParcelable(EXTRA_ICON_SURFACE, surfaceControl);
Message callback = Message.obtain();
callback.copyFrom(mCallback);
callback.setData(result);
try {
callback.replyTo.send(callback);
} catch (RemoteException e) {
Log.e(TAG, "Error sending icon position", e);
}
}
/**
* Clears and returns the GestureNavContract if it was present in the intent.
*/
public static GestureNavContract fromIntent(Intent intent) {
if (!Utilities.ATLEAST_R) {
return null;
}
Bundle extras = intent.getBundleExtra(EXTRA_GESTURE_CONTRACT);
if (extras == null) {
return null;
}
intent.removeExtra(EXTRA_GESTURE_CONTRACT);
ComponentName componentName = extras.getParcelable(EXTRA_COMPONENT_NAME);
UserHandle userHandle = extras.getParcelable(EXTRA_USER);
Message callback = extras.getParcelable(EXTRA_REMOTE_CALLBACK);
if (componentName != null && userHandle != null && callback != null
&& callback.replyTo != null) {
return new GestureNavContract(componentName, userHandle, callback);
}
return null;
}
}

View File

@ -21,6 +21,7 @@ import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP;
@ -118,7 +119,6 @@ import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.data.AppInfo;
@ -168,6 +168,7 @@ import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.FloatingSurfaceView;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.views.ScrimView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@ -451,10 +452,10 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
} else if (finalState == OVERVIEW || finalState == OVERVIEW_PEEK) {
mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
mScrimView.setAlpha(alpha);
} else {
mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f);
mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f);
mScrimView.setAlpha(1f);
}
}
});
@ -509,6 +510,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
}
@Override
@ -550,7 +552,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
} else if (state == OVERVIEW || state == OVERVIEW_PEEK) {
mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
mScrimView.setAlpha(alpha);
}
}
@ -908,14 +910,12 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
logStopAndResume(Action.Command.RESUME);
getUserEventDispatcher().startSession();
AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
// Process any items that were added while Launcher was away.
InstallShortcutReceiver.disableAndFlushInstallQueue(
InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
// Refresh shortcuts if the permission changed.
mModel.refreshShortcutsIfRequired();
mModel.validateModelDataOnResume();
// Set the notification listener and fetch updated notifications when we resume
NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
@ -1450,6 +1450,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
mLauncherCallbacks.onHomeIntent(internalStateHandled);
}
mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
handleGestureContract(intent);
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
getStateManager().goToState(ALL_APPS, alreadyOnHome);
}
@ -1457,6 +1458,17 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
TraceHelper.INSTANCE.endSection(traceToken);
}
/**
* Handles gesture nav contract
*/
protected void handleGestureContract(Intent intent) {
GestureNavContract gnc = GestureNavContract.fromIntent(intent);
if (gnc != null) {
AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
FloatingSurfaceView.show(this, gnc);
}
}
/**
* Hides the keyboard if visible
*/
@ -1535,7 +1547,6 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
mOverlayManager.onActivityDestroyed(this);
mAppTransitionManager.unregisterRemoteAnimations();
mUserChangedCallbackCloseable.close();
mAllAppsController.onActivityDestroyed();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@ -1922,7 +1933,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
// Populate event with a fake title based on the current state.
// TODO: When can workspace be null?
text.add(mWorkspace == null
? getString(R.string.all_apps_home_button_label)
? getString(R.string.home_screen)
: mStateManager.getState().getDescription(this));
return result;
}
@ -2480,7 +2491,7 @@ public class Launcher extends StatefulActivity<LauncherState> implements Launche
* @param updated list of shortcuts which have changed.
*/
@Override
public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) {
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
if (!updated.isEmpty()) {
mWorkspace.updateShortcuts(updated);
}

View File

@ -128,7 +128,7 @@ public class LauncherAppState {
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
mModel = new LauncherModel(context, this, mIconCache, AppFilter.newInstance(mContext));
mPredictionModel = PredictionModel.newInstance(mContext);
}
@ -157,6 +157,7 @@ public class LauncherAppState {
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
public void onTerminate() {
mModel.destroy();
if (mModelChangeReceiver != null) {
mContext.unregisterReceiver(mModelChangeReceiver);
}

View File

@ -20,7 +20,6 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.content.Context;
import android.content.Intent;
@ -46,6 +45,7 @@ import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.CacheDataUpdatedTask;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.PackageInstallStateChangedTask;
import com.android.launcher3.model.PackageUpdatedTask;
@ -112,20 +112,22 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
*/
private final BgDataModel mBgDataModel = new BgDataModel();
private final ModelDelegate mModelDelegate;
// Runnable to check if the shortcuts permission has changed.
private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
private final Runnable mDataValidationCheck = new Runnable() {
@Override
public void run() {
if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
!= mBgAllAppsList.hasShortcutHostPermission()) {
forceReload();
if (mModelLoaded) {
mModelDelegate.validateData();
}
}
};
LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
mApp = app;
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel);
}
/**
@ -217,6 +219,13 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
}
}
/**
* Called when the model is destroyed
*/
public void destroy() {
MODEL_EXECUTOR.execute(mModelDelegate::destroy);
}
public void onBroadcastIntent(Intent intent) {
if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
final String action = intent.getAction();
@ -372,7 +381,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
public void startLoaderForResults(LoaderResults results) {
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
mLoaderTask = new LoaderTask(
mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results);
// Always post the loader task, instead of running directly (even on same thread) so
// that we exit any nested synchronized blocks
@ -491,9 +501,9 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
* Current implementation simply reloads the workspace, but it can be optimized to
* use partial updates similar to {@link UserCache}
*/
public void refreshShortcutsIfRequired() {
MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
public void validateModelDataOnResume() {
MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
MODEL_EXECUTOR.post(mDataValidationCheck);
}
/**

View File

@ -1448,11 +1448,8 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
int minDistanceFromScreenCenterIndex = -1;
final int childCount = getChildCount();
for (int i = 0; i < childCount; ++i) {
View layout = getPageAt(i);
int childSize = mOrientationHandler.getMeasuredSize(layout);
int halfChildSize = (childSize / 2);
int childCenter = getChildOffset(i) + halfChildSize;
int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
int distanceFromScreenCenter = Math.abs(
getDisplacementFromScreenCenter(i, screenCenter));
if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
minDistanceFromScreenCenter = distanceFromScreenCenter;
minDistanceFromScreenCenterIndex = i;
@ -1461,6 +1458,20 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
return minDistanceFromScreenCenterIndex;
}
private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) {
View layout = getPageAt(childIndex);
int childSize = mOrientationHandler.getMeasuredSize(layout);
int halfChildSize = (childSize / 2);
int childCenter = getChildOffset(childIndex) + halfChildSize;
return childCenter - screenCenter;
}
protected int getDisplacementFromScreenCenter(int childIndex) {
int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
int screenCenter = mOrientationHandler.getPrimaryScroll(this) + (pageOrientationSize / 2);
return getDisplacementFromScreenCenter(childIndex, screenCenter);
}
protected void snapToDestination() {
snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
}

View File

@ -115,6 +115,7 @@ import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverla
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
/**
@ -309,7 +310,9 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
// In portrait, we want the pages spaced such that there is no
// overhang of the previous / next page into the current page viewport.
// We assume symmetrical padding in portrait mode.
setPageSpacing(Math.max(grid.edgeMarginPx, padding.left + 1));
int maxInsets = Math.max(insets.left, insets.right);
int maxPadding = Math.max(grid.edgeMarginPx, padding.left + 1);
setPageSpacing(Math.max(maxInsets, maxPadding));
}
@ -3085,7 +3088,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
return false;
}
void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) {
void updateShortcuts(List<WorkspaceItemInfo> shortcuts) {
final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
ItemOperator op = (info, v) -> {
if (v instanceof BubbleTextView && updates.contains(info)) {
@ -3260,7 +3263,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
if (nScreens == 0) {
// When the workspace is not loaded, we do not know how many screen will be bound.
return getContext().getString(R.string.all_apps_home_button_label);
return getContext().getString(R.string.home_screen);
}
return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
}

View File

@ -66,6 +66,8 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
// A divider that separates the apps list and the search market button
public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
@ -274,6 +276,9 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
case VIEW_TYPE_ALL_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
return new ViewHolder(
mLayoutInflater.inflate(R.layout.search_section_title, parent, false));
default:
throw new RuntimeException("Unexpected view type");
}
@ -302,6 +307,10 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.
searchView.setVisibility(View.GONE);
}
break;
case VIEW_TYPE_SEARCH_CORPUS_TITLE:
TextView titleView = (TextView) holder.itemView;
titleView.setText(mApps.getAdapterItems().get(position).searchSectionInfo.getTitle(
titleView.getContext()));
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
break;

View File

@ -0,0 +1,197 @@
/*
* 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.allapps;
import android.graphics.Insets;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimationControlListener;
import android.view.WindowInsetsAnimationController;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.core.os.BuildCompat;
/**
* Handles IME over all apps to be synchronously transitioning along with the passed in
* root inset.
*/
public class AllAppsInsetTransitionController {
private static final boolean DEBUG = false;
private static final String TAG = "AllAppsInsetTransitionController";
private static final Interpolator LINEAR = new LinearInterpolator();
private WindowInsetsAnimationController mAnimationController;
private WindowInsetsAnimationControlListener mCurrentRequest;
private float mAllAppsHeight;
private int mDownInsetBottom;
private boolean mShownAtDown;
private int mHiddenInsetBottom;
private int mShownInsetBottom;
private float mDown, mCurrent;
private View mApps;
public AllAppsInsetTransitionController(float allAppsHeight, View appsView) {
mAllAppsHeight = allAppsHeight;
mApps = appsView;
}
public void hide() {
if (!BuildCompat.isAtLeastR()) return;
WindowInsets insets = mApps.getRootWindowInsets();
if (insets == null) return;
if (insets.isVisible(WindowInsets.Type.ime())) {
mApps.getWindowInsetsController().hide(WindowInsets.Type.ime());
}
}
/**
* Initializes member variables and requests for the {@link WindowInsetsAnimationController}
* object.
*
* @param progress value between 0..1
*/
@RequiresApi(api = Build.VERSION_CODES.R)
public void onDragStart(float progress) {
if (!BuildCompat.isAtLeastR()) return;
onAnimationEnd(progress);
mDown = progress * mAllAppsHeight;
// Below two values are sometimes incorrect. Possibly a platform bug
mDownInsetBottom = mApps.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
mShownAtDown = mApps.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
// override this value based on what it should actually be.
mShownAtDown = Float.compare(progress, 1f) == 0 ? false : true;
mDownInsetBottom = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
if (DEBUG) {
Log.d(TAG, "\nonDragStart mDownInsets=" + mDownInsetBottom
+ " mShownAtDown =" + mShownAtDown);
}
mApps.getWindowInsetsController().controlWindowInsetsAnimation(
WindowInsets.Type.ime(), -1 /* no predetermined duration */, LINEAR, null,
mCurrentRequest = new WindowInsetsAnimationControlListener() {
@Override
public void onReady(WindowInsetsAnimationController controller, int types) {
if (DEBUG) {
Log.d(TAG, "Listener.onReady " + (mCurrentRequest == this));
}
if (mCurrentRequest == this) {
mAnimationController = controller;
} else {
controller.finish(mShownAtDown);
}
}
@Override
public void onFinished(WindowInsetsAnimationController controller) {
// when screen lock happens, then this method get called
mAnimationController.finish(false);
mAnimationController = null;
if (DEBUG) {
Log.d(TAG, "Listener.onFinished ctrl=" + controller);
}
}
@Override
public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
mAnimationController = null;
if (controller != null) {
controller.finish(mShownAtDown);
}
if (DEBUG) {
Log.d(TAG, "Listener.onCancelled ctrl=" + controller);
}
}
});
}
/**
* Handles the translation using the progress.
*
* @param progress value between 0..1
*/
@RequiresApi(api = 30)
public void setProgress(float progress) {
if (!BuildCompat.isAtLeastR()) return;
// progress that equals to 0 or 1 is error prone. Do not use them.
// Instead use onDragStart and onAnimationEnd
if (mAnimationController == null || progress <= 0f || progress >= 1f) return;
mCurrent = progress * mAllAppsHeight;
mHiddenInsetBottom = mAnimationController.getHiddenStateInsets().bottom; // 0
mShownInsetBottom = mAnimationController.getShownStateInsets().bottom; // 1155
int shift = mShownAtDown ? 0 : (int) (mAllAppsHeight - mShownInsetBottom);
int inset = (int) (mDownInsetBottom + (mDown - mCurrent) - shift);
if (DEBUG) {
Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
+ mDown + " hidden=" + mHiddenInsetBottom
+ " shown=" + mShownInsetBottom
+ " mDownInsets.bottom=" + mDownInsetBottom + " inset:" + inset
+ " shift: " + shift);
}
final int start = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom;
inset = Math.max(inset, mHiddenInsetBottom);
inset = Math.min(inset, mShownInsetBottom);
Log.d(TAG, "updateInset inset:" + inset);
mAnimationController.setInsetsAndAlpha(
Insets.of(0, 0, 0, inset),
1f, (inset - start) / (float) (end - start));
}
/**
* Report to the animation controller that we no longer plan to translate anymore.
*
* @param progress value between 0..1
*/
@RequiresApi(api = 30)
public void onAnimationEnd(float progress) {
if (DEBUG) {
Log.d(TAG, "endTranslation progress=" + progress
+ " mAnimationController=" + mAnimationController);
}
if (mAnimationController == null) return;
if (Float.compare(progress, 1f) == 0 /* bottom */) {
mAnimationController.finish(false /* gone */);
}
if (Float.compare(progress, 0f) == 0 /* top */) {
mAnimationController.finish(true /* show */);
}
mAnimationController = null;
mCurrentRequest = null;
}
}

View File

@ -107,6 +107,7 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET, 1);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE, 1);
mViewHeights.clear();
mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);

View File

@ -1,10 +1,24 @@
/*
* Copyright (C) 2015 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.allapps;
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
import static com.android.launcher3.anim.Interpolators.INSTANT;
@ -14,31 +28,29 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FA
import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.util.FloatProperty;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.EditText;
import androidx.core.os.BuildCompat;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.views.ScrimView;
import com.android.systemui.plugins.AllAppsSearchPlugin;
import com.android.systemui.plugins.PluginListener;
/**
* Handles AllApps view transition.
@ -51,7 +63,7 @@ import com.android.systemui.plugins.PluginListener;
* closer to top or closer to the page indicator.
*/
public class AllAppsTransitionController implements StateHandler<LauncherState>,
OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> {
OnDeviceProfileChangeListener {
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@ -85,10 +97,7 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
private float mProgress; // [0, 1], mShiftRange * mProgress = shiftCurrent
private float mScrollRangeDelta = 0;
// plugin related variables
private AllAppsSearchPlugin mPlugin;
private View mPluginContent;
private AllAppsInsetTransitionController mInsetController;
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
@ -103,6 +112,10 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
return mShiftRange;
}
public AllAppsInsetTransitionController getInsetController() {
return mInsetController;
}
@Override
public void onDeviceProfileChanged(DeviceProfile dp) {
mIsVerticalLayout = dp.isVerticalBarLayout();
@ -130,8 +143,8 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
float shiftCurrent = progress * mShiftRange;
mAppsView.setTranslationY(shiftCurrent);
if (mPlugin != null) {
mPlugin.setProgress(progress);
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mInsetController.setProgress(progress);
}
}
@ -201,20 +214,13 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
if (mPlugin == null) {
setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
hasAllAppsContent, setter, headerFade, allAppsFade);
} else {
setter.setViewAlpha(mPluginContent, hasAllAppsContent ? 1 : 0, allAppsFade);
setter.setViewAlpha(mAppsView.getContentView(), 0, allAppsFade);
setter.setViewAlpha(mAppsView.getScrollBar(), 0, allAppsFade);
}
mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
(visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
// Set visibility of the container at the very beginning or end of the transition.
setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
@ -228,8 +234,12 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
mAppsView = appsView;
mScrimView = scrimView;
PluginManagerWrapper.INSTANCE.get(mLauncher)
.addPluginListener(this, AllAppsSearchPlugin.class, false);
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
/**
@ -252,47 +262,19 @@ public class AllAppsTransitionController implements StateHandler<LauncherState>,
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.reset(false /* animate */);
}
updatePluginAnimationEnd();
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
mInsetController.onAnimationEnd(mProgress);
if (Float.compare(mProgress, 0f) == 0) {
EditText editText = mAppsView.getSearchUiManager().getEditText();
if (editText != null) {
editText.requestFocus();
}
@Override
public void onPluginConnected(AllAppsSearchPlugin plugin, Context context) {
mPlugin = plugin;
mPluginContent = mLauncher.getLayoutInflater().inflate(
R.layout.all_apps_content_layout, mAppsView, false);
mAppsView.addView(mPluginContent);
mPluginContent.setAlpha(0f);
mPlugin.setup((ViewGroup) mPluginContent, mLauncher, mShiftRange);
}
@Override
public void onPluginDisconnected(AllAppsSearchPlugin plugin) {
mPlugin = null;
mAppsView.removeView(mPluginContent);
}
public void onActivityDestroyed() {
PluginManagerWrapper.INSTANCE.get(mLauncher).removePluginListener(this);
}
/** Used for the plugin to signal when drag starts happens
* @param toAllApps*/
public void onDragStart(boolean toAllApps) {
if (mPlugin == null) return;
if (toAllApps) {
EditText editText = mAppsView.getSearchUiManager().setTextSearchEnabled(true);
mPlugin.setEditText(editText);
}
mPlugin.onDragStart(toAllApps ? 1f : 0f);
}
private void updatePluginAnimationEnd() {
if (mPlugin == null) return;
mPlugin.onAnimationEnd(mProgress);
if (Float.compare(mProgress, 1f) == 0) {
mAppsView.getSearchUiManager().setTextSearchEnabled(false);
mPlugin.setEditText(null);
// Called when home gesture closes all apps container.
// TODO: should make the controller hide synchronously
mInsetController.hide();
}
}
}
}

View File

@ -19,8 +19,9 @@ package com.android.launcher3.allapps;
import android.content.Context;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.allapps.search.SearchSectionInfo;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
@ -30,6 +31,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
/**
* The alphabetically sorted list of applications.
@ -82,6 +84,8 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
public AppInfo appInfo = null;
// The index of this app not including sections
public int appIndex = -1;
// Search section associated to result
public SearchSectionInfo searchSectionInfo = null;
public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
int appIndex) {
@ -114,6 +118,17 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
item.position = pos;
return item;
}
/**
* Factory method for search section title AdapterItem
*/
public static AdapterItem asSearchTitle(SearchSectionInfo sectionInfo, int pos) {
AdapterItem item = new AdapterItem();
item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE;
item.position = pos;
item.searchSectionInfo = sectionInfo;
return item;
}
}
private final BaseDraggingActivity mLauncher;
@ -132,7 +147,7 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
private final boolean mIsWork;
// The of ordered component names as a result of a search query
private ArrayList<ComponentKey> mSearchResults;
private ArrayList<AdapterItem> mSearchResults;
private AllAppsGridAdapter mAdapter;
private AppInfoComparator mAppNameComparator;
private final int mNumAppsPerRow;
@ -210,10 +225,10 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
}
/**
* Sets the sorted list of filtered components.
* Sets results list for search
*/
public boolean setOrderedFilter(ArrayList<ComponentKey> f) {
if (mSearchResults != f) {
public boolean setSearchResults(ArrayList<AdapterItem> f) {
if (f == null || mSearchResults != f) {
boolean same = mSearchResults != null && mSearchResults.equals(f);
mSearchResults = f;
onAppsUpdated();
@ -298,7 +313,8 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
// Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
// ordered set of sections
for (AppInfo info : getFiltersAppInfos()) {
if (!hasFilter()) {
for (AppInfo info : mApps) {
String sectionName = info.sectionName;
// Create a new section if the section names do not match
@ -316,8 +332,13 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
mAdapterItems.add(appItem);
mFilteredApps.add(info);
}
if (hasFilter()) {
} else {
mAdapterItems.addAll(mSearchResults);
List<AppInfo> appInfos = mSearchResults.stream().filter(
i -> AllAppsGridAdapter.isIconViewType(i.viewType)).map(i -> i.appInfo).collect(
Collectors.toList());
mFilteredApps.addAll(appInfos);
if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
// Append the search market item
if (hasNoFilteredResults()) {
mAdapterItems.add(AdapterItem.asEmptySearch(position++));
@ -325,8 +346,9 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
}
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
}
}
}
if (mNumAppsPerRow != 0) {
// Update the number of rows in the adapter after we do all the merging (otherwise, we
// would have to shift the values again)
@ -381,18 +403,4 @@ public class AlphabeticalAppsList implements AllAppsStore.OnUpdateListener {
}
}
}
private List<AppInfo> getFiltersAppInfos() {
if (mSearchResults == null) {
return mApps;
}
ArrayList<AppInfo> result = new ArrayList<>();
for (ComponentKey key : mSearchResults) {
AppInfo match = mAllAppsStore.getApp(key);
if (match != null) {
result.add(match);
}
}
return result;
}
}

View File

@ -66,12 +66,8 @@ public interface SearchUiManager {
}
/**
* Called to control how the search UI result should be handled.
*
* @param isEnabled when {@code true}, the search is all handled inside AOSP
* and is not overlayable.
* @return the searchbox edit text object
* @return the edit text object
*/
@Nullable
EditText setTextSearchEnabled(boolean isEnabled);
EditText getEditText();
}

View File

@ -28,7 +28,7 @@ import android.widget.TextView.OnEditorActionListener;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
@ -50,6 +50,7 @@ public class AllAppsSearchBarController
public void setVisibility(int visibility) {
mInput.setVisibility(visibility);
}
/**
* Sets the references to the apps model and the search result callback.
*/
@ -164,9 +165,9 @@ public class AllAppsSearchBarController
/**
* Called when the search is complete.
*
* @param apps sorted list of matching components or null if in case of failure.
* @param items sorted list of search result adapter items.
*/
void onSearchResult(String query, ArrayList<ComponentKey> apps);
void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items);
/**
* Called when the search results should be cleared.

View File

@ -38,13 +38,13 @@ import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
@ -135,7 +135,8 @@ public class AppsSearchContainerLayout extends ExtendedEditText
mApps = appsView.getApps();
mAppsView = appsView;
mSearchBarController.initialize(
new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this);
new DefaultAppSearchAlgorithm(LauncherAppState.getInstance(mLauncher)), this,
mLauncher, this);
}
@Override
@ -168,9 +169,9 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
@Override
public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
if (apps != null) {
mApps.setOrderedFilter(apps);
public void onSearchResult(String query, ArrayList<AlphabeticalAppsList.AdapterItem> items) {
if (items != null) {
mApps.setSearchResults(items);
notifyResultChanged();
mAppsView.setLastSearchQuery(query);
}
@ -178,7 +179,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
@Override
public void clearSearchResult() {
if (mApps.setOrderedFilter(null)) {
if (mApps.setSearchResults(null)) {
notifyResultChanged();
}
@ -216,7 +217,7 @@ public class AppsSearchContainerLayout extends ExtendedEditText
}
@Override
public EditText setTextSearchEnabled(boolean isEnabled) {
public EditText getEditText() {
return this;
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.allapps.search;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.AppInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* A device search section for handling app searches
*/
public class AppsSearchPipeline implements SearchPipeline {
private static final int MAX_RESULTS_COUNT = 5;
private final SearchSectionInfo mSearchSectionInfo;
private final LauncherAppState mLauncherAppState;
public AppsSearchPipeline(LauncherAppState launcherAppState) {
mLauncherAppState = launcherAppState;
mSearchSectionInfo = new SearchSectionInfo(R.string.search_corpus_apps);
}
@Override
@WorkerThread
public void performSearch(String query, Consumer<ArrayList<AdapterItem>> callback) {
mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
callback.accept(getAdapterItems(getTitleMatchResult(apps.data, query)));
}
});
}
/**
* Filters {@link AppInfo}s matching specified query
*/
public static ArrayList<AppInfo> getTitleMatchResult(List<AppInfo> apps, String query) {
// Do an intersection of the words in the query and each title, and filter out all the
// apps that don't match all of the words in the query.
final String queryTextLower = query.toLowerCase();
final ArrayList<AppInfo> result = new ArrayList<>();
DefaultAppSearchAlgorithm.StringMatcher matcher =
DefaultAppSearchAlgorithm.StringMatcher.getInstance();
for (AppInfo info : apps) {
if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) {
result.add(info);
}
}
return result;
}
private ArrayList<AdapterItem> getAdapterItems(List<AppInfo> matchingApps) {
ArrayList<AdapterItem> items = new ArrayList<>();
if (matchingApps.isEmpty()) {
return items;
}
items.add(AdapterItem.asSearchTitle(mSearchSectionInfo, 0));
int existingItems = items.size();
int searchResultsCount = Math.min(matchingApps.size(), MAX_RESULTS_COUNT);
for (int i = 0; i < searchResultsCount; i++) {
AdapterItem appItem = AdapterItem.asApp(i + existingItems, "", matchingApps.get(i), i);
appItem.searchSectionInfo = mSearchSectionInfo;
items.add(appItem);
}
return items;
}
}

View File

@ -17,24 +17,22 @@ package com.android.launcher3.allapps.search;
import android.os.Handler;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.util.ComponentKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.List;
/**
* The default search implementation.
*/
public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
private final List<AppInfo> mApps;
protected final Handler mResultHandler;
private final AppsSearchPipeline mAppsSearchPipeline;
public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
mApps = apps;
public DefaultAppSearchAlgorithm(LauncherAppState launcherAppState) {
mResultHandler = new Handler();
mAppsSearchPipeline = new AppsSearchPipeline(launcherAppState);
}
@Override
@ -47,28 +45,8 @@ public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
@Override
public void doSearch(final String query,
final AllAppsSearchBarController.Callbacks callback) {
final ArrayList<ComponentKey> result = getTitleMatchResult(query);
mResultHandler.post(new Runnable() {
@Override
public void run() {
callback.onSearchResult(query, result);
}
});
}
private ArrayList<ComponentKey> getTitleMatchResult(String query) {
// Do an intersection of the words in the query and each title, and filter out all the
// apps that don't match all of the words in the query.
final String queryTextLower = query.toLowerCase();
final ArrayList<ComponentKey> result = new ArrayList<>();
StringMatcher matcher = StringMatcher.getInstance();
for (AppInfo info : mApps) {
if (matches(info, queryTextLower, matcher)) {
result.add(info.toComponentKey());
}
}
return result;
mAppsSearchPipeline.performSearch(query,
results -> mResultHandler.post(() -> callback.onSearchResult(query, results)));
}
public static boolean matches(AppInfo info, String query, StringMatcher matcher) {

View File

@ -0,0 +1,32 @@
/*
* 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.allapps.search;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import java.util.ArrayList;
import java.util.function.Consumer;
/**
* An interface for handling search within pipeline
*/
public interface SearchPipeline {
/**
* Perform query
*/
void performSearch(String query, Consumer<ArrayList<AlphabeticalAppsList.AdapterItem>> cb);
}

View File

@ -0,0 +1,36 @@
/*
* 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.allapps.search;
import android.content.Context;
/**
* Info class for a search section
*/
public class SearchSectionInfo {
private final int mTitleResId;
public SearchSectionInfo(int titleResId) {
mTitleResId = titleResId;
}
/**
* Returns the section's title
*/
public String getTitle(Context context) {
return context.getString(mTitleResId);
}
}

View File

@ -70,36 +70,33 @@ public class AccessibilityManagerCompat {
final Bundle parcel = new Bundle();
parcel.putInt(TestProtocol.STATE_FIELD, stateOrdinal);
sendEventToTest(accessibilityManager, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
sendEventToTest(
accessibilityManager, context, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "sendStateEventToTest: " + stateOrdinal);
}
public static void sendScrollFinishedEventToTest(Context context) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendScrollFinishedEventToTest");
}
final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
if (accessibilityManager == null) return;
sendEventToTest(accessibilityManager, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
sendEventToTest(accessibilityManager, context, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
}
public static void sendPauseDetectedEventToTest(Context context) {
final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
if (accessibilityManager == null) return;
sendEventToTest(accessibilityManager, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
sendEventToTest(accessibilityManager, context, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
}
private static void sendEventToTest(
AccessibilityManager accessibilityManager, String eventTag, Bundle data) {
AccessibilityManager accessibilityManager,
Context context, String eventTag, Bundle data) {
final AccessibilityEvent e = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
e.setClassName(eventTag);
e.setParcelableData(data);
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "sendEventToTest " + e);
}
e.setPackageName(context.getApplicationContext().getPackageName());
accessibilityManager.sendAccessibilityEvent(e);
}

View File

@ -94,6 +94,10 @@ public final class FeatureFlags {
public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
"ENABLE_SUGGESTED_ACTIONS_OVERVIEW", false, "Show chip hints on the overview screen");
public static final BooleanFlag ENABLE_DEVICE_SEARCH = getDebugFlag(
"ENABLE_DEVICE_SEARCH", false, "Allows on device search in all apps");
public static final BooleanFlag FOLDER_NAME_SUGGEST = new DeviceFlag(
"FOLDER_NAME_SUGGEST", true,
"Suggests folder names instead of blank text.");
@ -177,6 +181,10 @@ public final class FeatureFlags {
public static final BooleanFlag USER_EVENT_DISPATCHER = new DeviceFlag(
"USER_EVENT_DISPATCHER", true, "User event dispatcher collects logs.");
public static final BooleanFlag ENABLE_MINIMAL_DEVICE = new DeviceFlag(
"ENABLE_MINIMAL_DEVICE", false,
"Allow user to toggle minimal device mode in launcher.");
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {

View File

@ -741,7 +741,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
@Override
protected View getAccessibilityInitialFocusView() {
return mContent.getFirstItem();
View firstItem = mContent.getFirstItem();
return firstItem != null ? firstItem : super.getAccessibilityInitialFocusView();
}
private void closeComplete(boolean wasAnimated) {
@ -1134,6 +1135,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
* Rearranges the children based on their rank.
*/
public void rearrangeChildren() {
if (!mContent.areViewsBound()) {
return;
}
mContent.arrangeChildren(getIconsInReadingOrder());
mItemsInvalidated = true;
}

View File

@ -129,6 +129,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
private float mDotScale;
private Animator mDotScaleAnim;
private Rect mTouchArea = new Rect();
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
private float mScaleForReorderBounce = 1f;
@ -711,6 +713,11 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN
&& shouldIgnoreTouchDown(event.getX(), event.getY())) {
return false;
}
// Call the superclass onTouchEvent first, because sometimes it changes the state to
// isPressed() on an ACTION_UP
super.onTouchEvent(event);
@ -719,6 +726,15 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
return true;
}
/**
* Returns true if the touch down at the provided position be ignored
*/
protected boolean shouldIgnoreTouchDown(float x, float y) {
mTouchArea.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
getHeight() - getPaddingBottom());
return !mTouchArea.contains((int) x, (int) y);
}
@Override
public void cancelLongPress() {
super.cancelLongPress();

Some files were not shown because too many files have changed in this diff Show More