Merge remote-tracking branch 'goog/ub-launcher3-master' into temp
Test: make builds Change-Id: I4e845aa51c9247328159e5aa0d95d425bffa435c
This commit is contained in:
commit
bb71582658
|
@ -19,6 +19,7 @@ android_library {
|
|||
"androidx.test.runner",
|
||||
"androidx.test.rules",
|
||||
"androidx.test.uiautomator_uiautomator",
|
||||
"androidx.preference_preference",
|
||||
"SystemUISharedLib",
|
||||
],
|
||||
srcs: [
|
||||
|
|
|
@ -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
1
OWNERS
|
@ -28,6 +28,7 @@ tracyzhou@google.com
|
|||
peanutbutter@google.com
|
||||
xuqiu@google.com
|
||||
sreyasr@google.com
|
||||
thiruram@google.com
|
||||
|
||||
per-file FeatureFlags.java, globs = set noparent
|
||||
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, zakcohen@google.com, mrcasey@google.com, adamcohen@google.com, hyunyoungs@google.com
|
||||
|
|
|
@ -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=""
|
||||
|
|
|
@ -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=""/>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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);
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -1256,7 +1351,7 @@ public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q exte
|
|||
// new thumbnail
|
||||
finishTransitionPosted = ViewUtils.postDraw(taskView,
|
||||
() -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
|
||||
this::isCanceled);
|
||||
this::isCanceled);
|
||||
}
|
||||
}
|
||||
if (!finishTransitionPosted) {
|
||||
|
@ -1325,4 +1420,172 @@ public abstract class BaseSwipeUpHandlerV2<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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
||||
@Override
|
||||
public RectF getWindowTargetRect() {
|
||||
if (canUseWorkspaceView) {
|
||||
if (canUseWorkspaceView) {
|
||||
// We want the window alpha to be 0 once this threshold is met, so that the
|
||||
// FolderIconView can be seen morphing into the icon shape.
|
||||
float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
|
||||
homeAnimFactory = new LauncherHomeAnimationFactory() {
|
||||
@Override
|
||||
public RectF getWindowTargetRect() {
|
||||
return iconLocation;
|
||||
} else {
|
||||
return super.getWindowTargetRect();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public AnimatorPlaybackController createActivityAnimationToHome() {
|
||||
// Return an empty APC here since we have an non-user controlled animation
|
||||
// to home.
|
||||
long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
|
||||
return mActivity.getStateManager().createAnimationToNewWorkspace(
|
||||
NORMAL, accuracy, 0 /* animComponents */);
|
||||
}
|
||||
@Override
|
||||
public void setAnimation(RectFSpringAnim anim) {
|
||||
anim.addAnimatorListener(floatingIconView);
|
||||
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
||||
floatingIconView.setFastFinishRunnable(anim::end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playAtomicAnimation(float velocity) {
|
||||
new StaggeredWorkspaceAnim(mActivity, velocity,
|
||||
true /* animateOverviewScrim */).start();
|
||||
}
|
||||
};
|
||||
@Override
|
||||
public void update(RectF currentRect, float progress, float radius) {
|
||||
floatingIconView.update(currentRect, 1f, progress, windowAlphaThreshold,
|
||||
radius, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
floatingIconView.fastFinish();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
homeAnimFactory = new LauncherHomeAnimationFactory();
|
||||
}
|
||||
} else {
|
||||
homeAnimFactory = new HomeAnimationFactory(null) {
|
||||
homeAnimFactory = new HomeAnimationFactory() {
|
||||
@Override
|
||||
public AnimatorPlaybackController createActivityAnimationToHome() {
|
||||
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
|
||||
|
@ -118,4 +123,22 @@ public class LauncherSwipeHandlerV2 extends
|
|||
mRecentsAnimationController.finish(
|
||||
true /* toRecents */, callback, true /* sendUserLeaveHint */);
|
||||
}
|
||||
|
||||
private class LauncherHomeAnimationFactory extends HomeAnimationFactory {
|
||||
@NonNull
|
||||
@Override
|
||||
public AnimatorPlaybackController createActivityAnimationToHome() {
|
||||
// Return an empty APC here since we have an non-user controlled animation
|
||||
// to home.
|
||||
long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
|
||||
return mActivity.getStateManager().createAnimationToNewWorkspace(
|
||||
NORMAL, accuracy, 0 /* animComponents */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playAtomicAnimation(float velocity) {
|
||||
new StaggeredWorkspaceAnim(mActivity, velocity,
|
||||
true /* animateOverviewScrim */).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,6 +19,7 @@ package com.android.quickstep;
|
|||
import static android.view.Surface.ROTATION_0;
|
||||
|
||||
import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
|
||||
import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
|
||||
import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
@ -146,26 +147,29 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
|
|||
*/
|
||||
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
|
||||
boolean rotated) {
|
||||
final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
|
||||
getActionsView().updateDisabledFlags(DISABLED_NO_THUMBNAIL, thumbnail == null);
|
||||
|
||||
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
|
||||
if (thumbnail != null) {
|
||||
getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
|
||||
final boolean isAllowedByPolicy = thumbnail.isRealSnapshot;
|
||||
|
||||
getActionsView().setCallbacks(new OverlayUICallbacks() {
|
||||
@Override
|
||||
public void onShare() {
|
||||
if (isAllowedByPolicy) {
|
||||
mImageApi.startShareActivity();
|
||||
} else {
|
||||
showBlockedByPolicyMessage();
|
||||
getActionsView().setCallbacks(new OverlayUICallbacks() {
|
||||
@Override
|
||||
public void onShare() {
|
||||
if (isAllowedByPolicy) {
|
||||
mImageApi.startShareActivity();
|
||||
} else {
|
||||
showBlockedByPolicyMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onScreenshot() {
|
||||
saveScreenshot(task);
|
||||
}
|
||||
});
|
||||
@SuppressLint("NewApi")
|
||||
@Override
|
||||
public void onScreenshot() {
|
||||
saveScreenshot(task);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
updateWellbeingData();
|
||||
|
||||
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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,15 +522,19 @@ 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)
|
||||
&& displayInfo.rotation != Surface.ROTATION_90
|
||||
&& displayInfo.rotation != Surface.ROTATION_270
|
||||
&& displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
|
||||
return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
|
||||
&& displayInfo.rotation != Surface.ROTATION_90
|
||||
&& displayInfo.rotation != Surface.ROTATION_270
|
||||
&& displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
|
||||
}
|
||||
|
||||
public boolean isOneHandedModeEnabled() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -358,18 +358,23 @@ public class MotionPauseDetector {
|
|||
|
||||
if (count < 3) {
|
||||
// Too few samples
|
||||
if (count == 2) {
|
||||
int endPos = pointPos - 1;
|
||||
if (endPos < 0) {
|
||||
endPos += HISTORY_SIZE;
|
||||
}
|
||||
float denominator = eventTime - mHistoricTimes[endPos];
|
||||
if (denominator != 0) {
|
||||
return (eventTime - mHistoricPos[endPos]) / denominator;
|
||||
|
||||
switch (count) {
|
||||
case 2: {
|
||||
int endPos = pointPos - 1;
|
||||
if (endPos < 0) {
|
||||
endPos += HISTORY_SIZE;
|
||||
}
|
||||
float denominator = eventTime - mHistoricTimes[endPos];
|
||||
if (denominator != 0) {
|
||||
return (mHistoricPos[pointPos] - mHistoricPos[endPos]) / denominator;
|
||||
}
|
||||
}
|
||||
// fall through
|
||||
case 1:
|
||||
return 0f;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
float Sxx = sxi2 - sxi * sxi / count;
|
||||
|
|
|
@ -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>() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
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
|
||||
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,
|
||||
|
@ -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" />
|
|
@ -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>
|
|
@ -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
|
||||
|
|
|
@ -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"/>
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
<!--
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
|
||||
setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
|
||||
mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
|
||||
hasAllAppsContent, setter, headerFade, 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();
|
||||
}
|
||||
|
||||
@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);
|
||||
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();
|
||||
}
|
||||
}
|
||||
if (Float.compare(mProgress, 1f) == 0) {
|
||||
// Called when home gesture closes all apps container.
|
||||
// TODO: should make the controller hide synchronously
|
||||
mInsetController.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,35 +313,42 @@ 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()) {
|
||||
String sectionName = info.sectionName;
|
||||
if (!hasFilter()) {
|
||||
for (AppInfo info : mApps) {
|
||||
String sectionName = info.sectionName;
|
||||
|
||||
// Create a new section if the section names do not match
|
||||
if (!sectionName.equals(lastSectionName)) {
|
||||
lastSectionName = sectionName;
|
||||
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
|
||||
mFastScrollerSections.add(lastFastScrollerSectionInfo);
|
||||
}
|
||||
// Create a new section if the section names do not match
|
||||
if (!sectionName.equals(lastSectionName)) {
|
||||
lastSectionName = sectionName;
|
||||
lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
|
||||
mFastScrollerSections.add(lastFastScrollerSectionInfo);
|
||||
}
|
||||
|
||||
// Create an app item
|
||||
AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
|
||||
if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
|
||||
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
|
||||
}
|
||||
mAdapterItems.add(appItem);
|
||||
mFilteredApps.add(info);
|
||||
}
|
||||
} 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++));
|
||||
} else {
|
||||
mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
|
||||
}
|
||||
mAdapterItems.add(AdapterItem.asMarketSearch(position++));
|
||||
|
||||
// Create an app item
|
||||
AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
|
||||
if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
|
||||
lastFastScrollerSectionInfo.fastScrollToItem = appItem;
|
||||
}
|
||||
mAdapterItems.add(appItem);
|
||||
mFilteredApps.add(info);
|
||||
}
|
||||
|
||||
if (hasFilter()) {
|
||||
// Append the search market item
|
||||
if (hasNoFilteredResults()) {
|
||||
mAdapterItems.add(AdapterItem.asEmptySearch(position++));
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue