Merge "Only draw app below launcher for TaskViewSimulator at swipe up or in Overview" into ub-launcher3-master
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
index 7dc7bfc..b891120 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -310,4 +310,9 @@
public Class<AppsDividerView> getTypeClass() {
return AppsDividerView.class;
}
+
+ @Override
+ public View getFocusedChild() {
+ return null;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index 8a810e3..d3c4c3d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -169,6 +169,7 @@
mDecorationHandler.extendBounds(getChildAt(i));
}
mDecorationHandler.onDraw(canvas);
+ mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
}
mFocusHelper.draw(canvas);
super.dispatchDraw(canvas);
@@ -183,7 +184,7 @@
@Override
public boolean shouldDraw() {
- return getVisibility() != GONE;
+ return getVisibility() == VISIBLE;
}
@Override
@@ -364,4 +365,9 @@
public Class<PredictionRowView> getTypeClass() {
return PredictionRowView.class;
}
+
+ @Override
+ public View getFocusedChild() {
+ return getChildAt(0);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java
index 721e2be..b0fba3d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -21,6 +21,7 @@
import android.app.prediction.AppTarget;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
@@ -29,6 +30,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -43,27 +45,22 @@
public class PredictionUpdateTask extends BaseModelUpdateTask {
private final List<AppTarget> mTargets;
- private final int mContainerId;
+ private final PredictorState mPredictorState;
- PredictionUpdateTask(int containerId, List<AppTarget> targets) {
- mContainerId = containerId;
+ PredictionUpdateTask(PredictorState predictorState, List<AppTarget> targets) {
+ mPredictorState = predictorState;
mTargets = targets;
}
@Override
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
- // TODO: persist the whole list
- Utilities.getDevicePrefs(app.getContext()).edit()
+ Context context = app.getContext();
+
+ // TODO: remove this
+ Utilities.getDevicePrefs(context).edit()
.putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
- FixedContainerItems fci;
- synchronized (dataModel) {
- fci = dataModel.extraItems.get(mContainerId);
- if (fci == null) {
- return;
- }
- }
-
+ FixedContainerItems fci = mPredictorState.items;
Set<UserHandle> usersForChangedShortcuts = new HashSet<>(fci.items.stream()
.filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT)
.map(info -> info.user)
@@ -75,7 +72,7 @@
ShortcutInfo si = target.getShortcutInfo();
if (si != null) {
usersForChangedShortcuts.add(si.getUserHandle());
- itemInfo = new WorkspaceItemInfo(si, app.getContext());
+ itemInfo = new WorkspaceItemInfo(si, context);
app.getIconCache().getShortcutIcon(itemInfo, si);
} else {
String className = target.getClassName();
@@ -87,16 +84,18 @@
UserHandle user = target.getUser();
itemInfo = apps.data.stream()
.filter(info -> user.equals(info.user) && cn.equals(info.componentName))
- .map(AppInfo::makeWorkspaceItem)
+ .map(ai -> {
+ app.getIconCache().getTitleAndIcon(ai, false);
+ return ai.makeWorkspaceItem();
+ })
.findAny()
.orElseGet(() -> {
- LauncherActivityInfo lai = app.getContext()
- .getSystemService(LauncherApps.class)
+ LauncherActivityInfo lai = context.getSystemService(LauncherApps.class)
.resolveActivity(AppInfo.makeLaunchIntent(cn), user);
if (lai == null) {
return null;
}
- AppInfo ai = new AppInfo(app.getContext(), lai, user);
+ AppInfo ai = new AppInfo(context, lai, user);
app.getIconCache().getTitleAndIcon(ai, lai, false);
return ai.makeWorkspaceItem();
});
@@ -106,12 +105,15 @@
}
}
- itemInfo.container = mContainerId;
+ itemInfo.container = fci.containerId;
fci.items.add(itemInfo);
}
bindExtraContainerItems(fci);
usersForChangedShortcuts.forEach(
u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
+
+ // Save to disk
+ mPredictorState.storage.write(context, fci.items);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java
index b516469..166cb6c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -17,6 +17,8 @@
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
@@ -24,16 +26,32 @@
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
+import com.android.launcher3.LauncherAppState;
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;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.PersistedItemArray;
import com.android.quickstep.logging.StatsLogCompatManager;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.IntStream;
/**
* Model delegate which loads prediction items
@@ -42,10 +60,12 @@
public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
+ private final PredictorState mAllAppsState =
+ new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
+
private final InvariantDeviceProfile mIDP;
private final AppEventProducer mAppEventProducer;
- private AppPredictor mAllAppsPredictor;
private boolean mActive = false;
public QuickstepModelDelegate(Context context) {
@@ -57,11 +77,15 @@
}
@Override
- public void loadItems() {
+ public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
// TODO: Implement caching and preloading
- super.loadItems();
- mDataModel.extraItems.put(
- CONTAINER_PREDICTION, new FixedContainerItems(CONTAINER_PREDICTION));
+ super.loadItems(ums, pinnedShortcuts);
+
+ WorkspaceItemFactory factory =
+ new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numAllAppsColumns);
+ mAllAppsState.items.setItems(
+ mAllAppsState.storage.read(mApp.getContext(), factory, ums.allUsers::get));
+ mDataModel.extraItems.put(CONTAINER_PREDICTION, mAllAppsState.items);
mActive = true;
recreatePredictors();
@@ -70,8 +94,8 @@
@Override
public void validateData() {
super.validateData();
- if (mAllAppsPredictor != null) {
- mAllAppsPredictor.requestPredictionUpdate();
+ if (mAllAppsState.predictor != null) {
+ mAllAppsState.predictor.requestPredictionUpdate();
}
}
@@ -86,10 +110,7 @@
}
private void destroyPredictors() {
- if (mAllAppsPredictor != null) {
- mAllAppsPredictor.destroy();
- mAllAppsPredictor = null;
- }
+ mAllAppsState.destroyPredictor();
}
@WorkerThread
@@ -98,7 +119,6 @@
if (!mActive) {
return;
}
-
Context context = mApp.getContext();
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
if (apm == null) {
@@ -107,19 +127,23 @@
int count = mIDP.numAllAppsColumns;
- mAllAppsPredictor = apm.createAppPredictionSession(
+ mAllAppsState.predictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(context)
.setUiSurface("home")
.setPredictedTargetCount(count)
.build());
- mAllAppsPredictor.registerPredictionUpdates(
- Executors.MODEL_EXECUTOR, this::onAllAppsPredictionChanged);
- mAllAppsPredictor.requestPredictionUpdate();
+ mAllAppsState.predictor.registerPredictionUpdates(
+ Executors.MODEL_EXECUTOR, t -> handleUpdate(mAllAppsState, t));
+ mAllAppsState.predictor.requestPredictionUpdate();
}
- private void onAllAppsPredictionChanged(List<AppTarget> targets) {
- mApp.getModel().enqueueModelUpdateTask(
- new PredictionUpdateTask(CONTAINER_PREDICTION, targets));
+
+ private void handleUpdate(PredictorState state, List<AppTarget> targets) {
+ if (state.setTargets(targets)) {
+ // No diff, skip
+ return;
+ }
+ mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
}
@Override
@@ -131,8 +155,119 @@
}
private void onAppTargetEvent(AppTargetEvent event) {
- if (mAllAppsPredictor != null) {
- mAllAppsPredictor.notifyAppTargetEvent(event);
+ if (mAllAppsState.predictor != null) {
+ mAllAppsState.predictor.notifyAppTargetEvent(event);
+ }
+ }
+
+ static class PredictorState {
+
+ public final FixedContainerItems items;
+ public final PersistedItemArray storage;
+ public AppPredictor predictor;
+
+ private List<AppTarget> mLastTargets;
+
+ PredictorState(int container, String storageName) {
+ items = new FixedContainerItems(container);
+ storage = new PersistedItemArray(storageName);
+ mLastTargets = Collections.emptyList();
+ }
+
+ public void destroyPredictor() {
+ if (predictor != null) {
+ predictor.destroy();
+ predictor = null;
+ }
+ }
+
+ /**
+ * Sets the new targets and returns true if it was different than before.
+ */
+ boolean setTargets(List<AppTarget> newTargets) {
+ List<AppTarget> oldTargets = mLastTargets;
+ mLastTargets = newTargets;
+
+ int size = oldTargets.size();
+ return size == newTargets.size() && IntStream.range(0, size)
+ .allMatch(i -> areAppTargetsSame(oldTargets.get(i), newTargets.get(i)));
+ }
+ }
+
+ /**
+ * Compares two targets for the properties which we care about
+ */
+ private static boolean areAppTargetsSame(AppTarget t1, AppTarget t2) {
+ if (!Objects.equals(t1.getPackageName(), t2.getPackageName())
+ || !Objects.equals(t1.getUser(), t2.getUser())
+ || !Objects.equals(t1.getClassName(), t2.getClassName())) {
+ return false;
+ }
+
+ ShortcutInfo s1 = t1.getShortcutInfo();
+ ShortcutInfo s2 = t2.getShortcutInfo();
+ if (s1 != null) {
+ if (s2 == null || !Objects.equals(s1.getId(), s2.getId())) {
+ return false;
+ }
+ } else if (s2 != null) {
+ return false;
+ }
+ return true;
+ }
+
+ private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory {
+
+ private final LauncherAppState mAppState;
+ private final UserManagerState mUMS;
+ private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
+ private final int mMaxCount;
+
+ private int mReadCount = 0;
+
+ protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
+ Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount) {
+ mAppState = appState;
+ mUMS = ums;
+ mPinnedShortcuts = pinnedShortcuts;
+ mMaxCount = maxCount;
+ }
+
+ @Nullable
+ @Override
+ public ItemInfo createInfo(int itemType, UserHandle user, Intent intent) {
+ if (mReadCount >= mMaxCount) {
+ return null;
+ }
+ switch (itemType) {
+ case ITEM_TYPE_APPLICATION: {
+ LauncherActivityInfo lai = mAppState.getContext()
+ .getSystemService(LauncherApps.class)
+ .resolveActivity(intent, user);
+ if (lai == null) {
+ return null;
+ }
+ AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
+ mAppState.getIconCache().getTitleAndIcon(info, lai, false);
+ mReadCount++;
+ return info.makeWorkspaceItem();
+ }
+ case ITEM_TYPE_DEEP_SHORTCUT: {
+ ShortcutKey key = ShortcutKey.fromIntent(intent, user);
+ if (key == null) {
+ return null;
+ }
+ ShortcutInfo si = mPinnedShortcuts.get(key);
+ if (si == null) {
+ return null;
+ }
+ WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
+ mAppState.getIconCache().getShortcutIcon(wii, si);
+ mReadCount++;
+ return wii;
+ }
+ }
+ return null;
}
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 085b9b3..5ccc1e8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
@@ -57,7 +58,7 @@
mRecentsView.updateEmptyMessage();
mRecentsView.resetTaskVisuals();
}
- setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state);
+ setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
}
@@ -75,17 +76,19 @@
AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
}
- setAlphas(builder, toState);
+ setAlphas(builder, config, toState);
builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
toState.getOverviewFullscreenProgress(), LINEAR);
}
- private void setAlphas(PropertySetter propertySetter, LauncherState state) {
+ private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
+ LauncherState state) {
float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
buttonAlpha, LINEAR);
propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
- MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+ MultiValueAlpha.VALUE, buttonAlpha, config.getInterpolator(
+ ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 131fcbf..daa1aad 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -25,6 +25,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
@@ -163,10 +164,15 @@
config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
- config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
- config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
- Workspace workspace = mActivity.getWorkspace();
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
+ if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
+ config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
+ } else {
+ config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+ }
+
+ Workspace workspace = mActivity.getWorkspace();
// Start from a higher workspace scale, but only if we're invisible so we don't jump.
boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
if (isWorkspaceVisible) {
@@ -206,8 +212,10 @@
config.setInterpolator(ANIM_WORKSPACE_SCALE,
fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+ config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
} else {
config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+ config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
// Scale up the recents, if it is not coming from the side
RecentsView overview = mActivity.getOverviewPanel();
@@ -225,7 +233,6 @@
: OVERSHOOT_1_7;
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
- config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
} else if (fromState == HINT_STATE && toState == NORMAL) {
config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
if (mHintToNormalDuration == -1) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index e45fa9d..57fd11a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -19,13 +19,13 @@
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import android.animation.ValueAnimator;
@@ -45,6 +45,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.testing.TestProtocol;
@@ -52,7 +53,9 @@
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -63,6 +66,8 @@
SingleAxisSwipeDetector.Listener {
private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
+ // How much of the overview scrim we can remove during the transition.
+ private static final float OVERVIEW_TO_HOME_SCRIM_PROGRESS = 0.5f;
private final Launcher mLauncher;
private final SingleAxisSwipeDetector mSwipeDetector;
@@ -156,8 +161,13 @@
final PendingAnimation builder = new PendingAnimation(accuracy);
if (mStartState.overviewUi) {
RecentsView recentsView = mLauncher.getOverviewPanel();
- builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
- -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
+ AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
+ builder);
+ float endScrimAlpha = Utilities.mapRange(OVERVIEW_TO_HOME_SCRIM_PROGRESS,
+ mStartState.getOverviewScrimAlpha(mLauncher),
+ mEndState.getOverviewScrimAlpha(mLauncher));
+ builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(),
+ OverviewScrim.SCRIM_PROGRESS, endScrimAlpha, PULLBACK_INTERPOLATOR);
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
builder.addOnFrameCallback(recentsView::redrawLiveTile);
}
@@ -211,8 +221,13 @@
recentsView.switchToScreenshot(null,
() -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
}
- mLauncher.getStateManager().goToState(mEndState, true,
- () -> onSwipeInteractionCompleted(mEndState));
+ if (mStartState == OVERVIEW) {
+ new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
+ .animateWithVelocity(velocity);
+ } else {
+ mLauncher.getStateManager().goToState(mEndState, true,
+ () -> onSwipeInteractionCompleted(mEndState));
+ }
if (mStartState != mEndState) {
logStateChange(mStartState.containerType, logAction);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 9316938..dbff20a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -21,11 +21,8 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import android.animation.Animator;
-import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
@@ -35,14 +32,15 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.OverviewToHomeAnim;
import com.android.quickstep.views.RecentsView;
/**
@@ -62,10 +60,10 @@
private boolean mDidTouchStartInNavBar;
private boolean mReachedOverview;
- private boolean mIsOverviewRehidden;
- private boolean mIsHomeStaggeredAnimFinished;
// The last recorded displacement before we reached overview.
private PointF mStartDisplacement = new PointF();
+ private float mStartY;
+ private AnimatorPlaybackController mOverviewResistYAnim;
// Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
private ObjectAnimator mNormalToHintOverviewScrimAnimator;
@@ -123,6 +121,7 @@
mToState.getOverviewScrimAlpha(mLauncher));
}
mReachedOverview = false;
+ mOverviewResistYAnim = null;
}
@Override
@@ -160,6 +159,9 @@
mNormalToHintOverviewScrimAnimator = null;
mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+ mOverviewResistYAnim = AnimatorControllerWithResistance
+ .createRecentsResistanceFromOverviewAnim(mLauncher, null)
+ .createPlaybackController();
mReachedOverview = true;
maybeSwipeInteractionToOverviewComplete();
});
@@ -173,13 +175,6 @@
}
}
- // Used if flinging back to home after reaching overview
- private void maybeSwipeInteractionToHomeComplete() {
- if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) {
- onSwipeInteractionCompleted(NORMAL, Touch.FLING);
- }
- }
-
@Override
protected boolean handlingOverviewAnim() {
return mDidTouchStartInNavBar && super.handlingOverviewAnim();
@@ -193,11 +188,17 @@
if (mMotionPauseDetector.isPaused()) {
if (!mReachedOverview) {
mStartDisplacement.set(xDisplacement, yDisplacement);
+ mStartY = event.getY();
} else {
mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
* OVERVIEW_MOVEMENT_FACTOR);
- mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
- * OVERVIEW_MOVEMENT_FACTOR);
+ float yProgress = (mStartDisplacement.y - yDisplacement) / mStartY;
+ if (yProgress > 0 && mOverviewResistYAnim != null) {
+ mOverviewResistYAnim.setPlayFraction(yProgress);
+ } else {
+ mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
+ * OVERVIEW_MOVEMENT_FACTOR);
+ }
}
// Stay in Overview.
return true;
@@ -212,35 +213,8 @@
StateManager<LauncherState> stateManager = mLauncher.getStateManager();
boolean goToHomeInsteadOfOverview = isFling;
if (goToHomeInsteadOfOverview) {
- if (velocity > 0) {
- stateManager.goToState(NORMAL, true,
- () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
- } else {
- mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false;
-
- StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
- mLauncher, velocity, false /* animateOverviewScrim */);
- staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- mIsHomeStaggeredAnimFinished = true;
- maybeSwipeInteractionToHomeComplete();
- }
- }).start();
-
- // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
- stateManager.cancelAnimation();
- StateAnimationConfig config = new StateAnimationConfig();
- config.duration = OVERVIEW.getTransitionDuration(mLauncher);
- config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
- AnimatorSet anim = stateManager.createAtomicAnimation(
- stateManager.getState(), NORMAL, config);
- anim.addListener(AnimationSuccessListener.forRunnable(() -> {
- mIsOverviewRehidden = true;
- maybeSwipeInteractionToHomeComplete();
- }));
- anim.start();
- }
+ new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL, Touch.FLING))
+ .animateWithVelocity(velocity);
}
if (mReachedOverview) {
float distanceDp = dpiFromPx(Math.max(
@@ -256,6 +230,13 @@
.withEndAction(goToHomeInsteadOfOverview
? null
: this::maybeSwipeInteractionToOverviewComplete);
+ if (!goToHomeInsteadOfOverview) {
+ // Return to normal properties for the overview state.
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = duration;
+ LauncherState state = mLauncher.getStateManager().getState();
+ mLauncher.getStateManager().createAtomicAnimation(state, state, config).start();
+ }
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 821ada4..5c9fd47 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -22,7 +22,6 @@
import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
@@ -47,6 +46,7 @@
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_SECONDARY_TRANSLATION;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.Animator;
@@ -74,7 +74,9 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.AnimatedFloat;
import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ShelfPeekAnim;
@@ -90,16 +92,17 @@
BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener {
/** The minimum progress of the scale/translationY animation until drag end. */
- private static final float Y_ANIM_MIN_PROGRESS = 0.15f;
+ private static final float Y_ANIM_MIN_PROGRESS = 0.25f;
private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5;
private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
- private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
+ private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
private final BaseQuickstepLauncher mLauncher;
private final BothAxesSwipeDetector mSwipeDetector;
private final ShelfPeekAnim mShelfPeekAnim;
private final float mXRange;
private final float mYRange;
+ private final float mMaxYProgress;
private final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
private final LauncherRecentsView mRecentsView;
@@ -113,7 +116,7 @@
// and the other two to set overview properties based on x and y progress.
private AnimatorPlaybackController mNonOverviewAnim;
private AnimatorPlaybackController mXOverviewAnim;
- private AnimatorPlaybackController mYOverviewAnim;
+ private AnimatedFloat mYOverviewAnim;
public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
mLauncher = launcher;
@@ -123,6 +126,7 @@
mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
mYRange = LayoutUtils.getShelfTrackingDistance(
mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
+ mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange;
mMotionPauseDetector = new MotionPauseDetector(mLauncher);
mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
R.dimen.motion_pause_detector_min_displacement_from_app);
@@ -270,8 +274,18 @@
SCALE_DOWN_INTERPOLATOR);
yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR);
- mYOverviewAnim = yAnim.createPlaybackController();
- mYOverviewAnim.dispatchOnStart();
+ AnimatorPlaybackController yNormalController = yAnim.createPlaybackController();
+ AnimatorControllerWithResistance yAnimWithResistance = AnimatorControllerWithResistance
+ .createForRecents(yNormalController, mLauncher,
+ mRecentsView.getPagedViewOrientedState(), mLauncher.getDeviceProfile(),
+ mRecentsView, RECENTS_SCALE_PROPERTY, mRecentsView,
+ TASK_SECONDARY_TRANSLATION);
+ mYOverviewAnim = new AnimatedFloat(() -> {
+ if (mYOverviewAnim != null) {
+ yAnimWithResistance.setProgress(mYOverviewAnim.value, mMaxYProgress);
+ }
+ });
+ yNormalController.dispatchOnStart();
}
@Override
@@ -307,7 +321,7 @@
mXOverviewAnim.setPlayFraction(xProgress);
}
if (mYOverviewAnim != null) {
- mYOverviewAnim.setPlayFraction(yProgress);
+ mYOverviewAnim.updateValue(yProgress);
}
return true;
}
@@ -354,9 +368,11 @@
} else if (verticalFling) {
targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
} else {
- // If user isn't flinging, just snap to the closest state based on x progress.
+ // If user isn't flinging, just snap to the closest state.
boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
- targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL;
+ boolean passedVerticalThreshold = mYOverviewAnim.value > 1f;
+ targetState = passedHorizontalThreshold && !passedVerticalThreshold
+ ? QUICK_SWITCH : NORMAL;
}
// Animate the various components to the target state.
@@ -375,9 +391,9 @@
boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
- float yProgress = mYOverviewAnim.getProgressFraction();
+ float yProgress = mYOverviewAnim.value;
float startYProgress = Utilities.boundToRange(yProgress
- - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f);
+ - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, mMaxYProgress);
final float endYProgress;
if (flingUpToNormal) {
endYProgress = 1;
@@ -387,12 +403,11 @@
} else {
endYProgress = 0;
}
- long yDuration = BaseSwipeDetector.calculateDuration(velocity.y,
- Math.abs(endYProgress - startYProgress));
- ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
- yOverviewAnim.setFloatValues(startYProgress, endYProgress);
+ float yDistanceToCover = Math.abs(endYProgress - startYProgress) * mYRange;
+ long yDuration = (long) (yDistanceToCover / Math.max(1f, Math.abs(velocity.y)));
+ ValueAnimator yOverviewAnim = mYOverviewAnim.animateToValue(startYProgress, endYProgress);
yOverviewAnim.setDuration(yDuration);
- mYOverviewAnim.dispatchOnStart();
+ mYOverviewAnim.updateValue(startYProgress);
ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
if (flingUpToNormal && !mIsHomeScreenVisible) {
@@ -457,7 +472,7 @@
mXOverviewAnim.getAnimationPlayer().cancel();
}
if (mYOverviewAnim != null) {
- mYOverviewAnim.getAnimationPlayer().cancel();
+ mYOverviewAnim.cancelAnimation();
}
mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
mMotionPauseDetector.clear();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index fce019b..3586b4f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -24,6 +24,7 @@
import android.animation.AnimatorListenerAdapter;
import android.view.MotionEvent;
import android.view.View;
+import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
@@ -205,14 +206,19 @@
long maxDuration = 2 * secondaryLayerDimension;
int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
+ // The interpolator controlling the most prominent visual movement. We use this to determine
+ // whether we passed SUCCESS_TRANSITION_PROGRESS.
+ final Interpolator currentInterpolator;
if (goingUp) {
+ currentInterpolator = Interpolators.LINEAR;
mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
true /* animateTaskView */, true /* removeTask */, maxDuration);
mEndDisplacement = -secondaryTaskDimension;
} else {
+ currentInterpolator = Interpolators.ZOOM_IN;
mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
- mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
+ mTaskBeingDragged, maxDuration, currentInterpolator);
// Since the thumbnail is what is filling the screen, based the end displacement on it.
View thumbnailView = mTaskBeingDragged.getThumbnail();
@@ -227,6 +233,9 @@
}
mCurrentAnimation = mPendingAnimation.createPlaybackController()
.setOnCancelRunnable(this::clearState);
+ // Setting this interpolator doesn't affect the visual motion, but is used to determine
+ // whether we successfully reached the target state in onDragEnd().
+ mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
onUserControlledAnimationCreated(mCurrentAnimation);
mCurrentAnimation.getTarget().addListener(this);
mCurrentAnimation.dispatchOnStart();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
index b6d44eb..d2fee30 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -525,6 +525,20 @@
recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
}
mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+
+ // Reapply window transform throughout the attach animation, as the animation affects how
+ // much the window is bound by overscroll (vs moving freely).
+ if (animate) {
+ ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
+ reapplyWindowTransformAnim.addUpdateListener(anim -> {
+ if (mRunningWindowAnim == null) {
+ applyWindowTransform();
+ }
+ });
+ reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
+ } else {
+ applyWindowTransform();
+ }
}
public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 1012ab2..2baba78 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -101,24 +101,6 @@
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Wrapper around a list for processing arguments.
- */
-class ArgList extends LinkedList<String> {
- public ArgList(List<String> l) {
- super(l);
- }
-
- public String peekArg() {
- return peekFirst();
- }
-
- public String nextArg() {
- return pollFirst().toLowerCase();
- }
-}
/**
* Service connected by system-UI for handling touch interaction.
@@ -238,23 +220,6 @@
WindowBounds wb = new WindowBounds(bounds, insets);
MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
}
-
- /** Deprecated methods **/
- public void onQuickStep(MotionEvent motionEvent) { }
-
- public void onQuickScrubEnd() { }
-
- public void onQuickScrubProgress(float progress) { }
-
- public void onQuickScrubStart() { }
-
- public void onPreMotionEvent(int downHitTarget) { }
-
- public void onMotionEvent(MotionEvent ev) {
- ev.recycle();
- }
-
- public void onBind(ISystemUiProxy iSystemUiProxy) { }
};
private static boolean sConnected = false;
@@ -837,10 +802,10 @@
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
- ArgList args = new ArgList(Arrays.asList(rawArgs));
- switch (args.nextArg()) {
+ LinkedList<String> args = new LinkedList(Arrays.asList(rawArgs));
+ switch (args.pollFirst()) {
case "cmd":
- if (args.peekArg() == null) {
+ if (args.peekFirst() == null) {
printAvailableCommands(pw);
} else {
onCommand(pw, args);
@@ -881,8 +846,8 @@
pw.println(" clear-touch-log: Clears the touch interaction log");
}
- private void onCommand(PrintWriter pw, ArgList args) {
- switch (args.nextArg()) {
+ private void onCommand(PrintWriter pw, LinkedList<String> args) {
+ switch (args.pollFirst()) {
case "clear-touch-log":
ActiveGestureLog.INSTANCE.clear();
break;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java
new file mode 100644
index 0000000..d2e1ded
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.util.Log;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Runs an animation from overview to home. Currently, this animation is just a wrapper around the
+ * normal state transition, in order to keep RecentsView at the same scale and translationY that
+ * it started out at as it translates offscreen. It also scrolls RecentsView to page 0 and may play
+ * a {@link StaggeredWorkspaceAnim} if we're starting from an upward fling.
+ */
+public class OverviewToHomeAnim {
+
+ private static final String TAG = "OverviewToHomeAnim";
+
+ // Constants to specify how to scroll RecentsView to the default page if it's not already there.
+ private static final int DEFAULT_PAGE = 0;
+ private static final int PER_PAGE_SCROLL_DURATION = 150;
+ private static final int MAX_PAGE_SCROLL_DURATION = 750;
+
+ private final Launcher mLauncher;
+ private final Runnable mOnReachedHome;
+
+ // Only run mOnReachedHome when both of these are true.
+ private boolean mIsHomeStaggeredAnimFinished;
+ private boolean mIsOverviewHidden;
+
+ public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome) {
+ mLauncher = launcher;
+ mOnReachedHome = onReachedHome;
+ }
+
+ /**
+ * Starts the animation. If velocity < 0 (i.e. upwards), also plays a
+ * {@link StaggeredWorkspaceAnim}.
+ */
+ public void animateWithVelocity(float velocity) {
+ StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+ LauncherState startState = stateManager.getState();
+ if (startState != OVERVIEW) {
+ Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState);
+ }
+
+ boolean playStaggeredWorkspaceAnim = velocity < 0;
+ if (playStaggeredWorkspaceAnim) {
+ StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
+ mLauncher, velocity, false /* animateOverviewScrim */);
+ staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mIsHomeStaggeredAnimFinished = true;
+ maybeOverviewToHomeAnimComplete();
+ }
+ }).start();
+ } else {
+ mIsHomeStaggeredAnimFinished = true;
+ }
+
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ int numPagesToScroll = recentsView.getNextPage() - DEFAULT_PAGE;
+ int scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
+ numPagesToScroll * PER_PAGE_SCROLL_DURATION);
+ int duration = Math.max(scrollDuration, startState.getTransitionDuration(mLauncher));
+
+ StateAnimationConfig config = new UseFirstInterpolatorStateAnimConfig();
+ config.duration = duration;
+ config.animFlags = playStaggeredWorkspaceAnim
+ // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
+ ? PLAY_ATOMIC_OVERVIEW_PEEK
+ : ANIM_ALL_COMPONENTS;
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, DEACCEL);
+ config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
+ config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
+ config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, INSTANT);
+ AnimatorSet anim = stateManager.createAtomicAnimation(
+ startState, NORMAL, config);
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mIsOverviewHidden = true;
+ maybeOverviewToHomeAnimComplete();
+ }
+ });
+ stateManager.cancelAnimation();
+ anim.start();
+ recentsView.snapToPage(DEFAULT_PAGE, duration);
+ }
+
+ private void maybeOverviewToHomeAnimComplete() {
+ if (mIsHomeStaggeredAnimFinished && mIsOverviewHidden) {
+ mOnReachedHome.run();
+ }
+ }
+
+ /**
+ * Wrapper around StateAnimationConfig that doesn't allow interpolators to be set if they are
+ * already set. This ensures they aren't overridden before being used.
+ */
+ private static class UseFirstInterpolatorStateAnimConfig extends StateAnimationConfig {
+ @Override
+ public void setInterpolator(int animId, Interpolator interpolator) {
+ if (mInterpolators[animId] == null || interpolator == null) {
+ super.setInterpolator(animId, interpolator);
+ }
+ }
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
index 79d57c5..1bf2fbf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
@@ -105,6 +105,7 @@
public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr, 0);
mMultiValueAlpha = new MultiValueAlpha(this, 4);
+ mMultiValueAlpha.setUpdateVisibility(true);
}
@Override
@@ -168,7 +169,6 @@
}
boolean isHidden = mHiddenFlags != 0;
mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
- setVisibility(isHidden ? INVISIBLE : VISIBLE);
}
/**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 00a8540..4d58a0d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -2355,7 +2355,14 @@
if (pageIndex == -1) {
return 0;
}
- return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this);
+ // Unbound the scroll (due to overscroll) if the adjacent tasks are offset away from it.
+ // This allows the page to move freely, given there's no visual indication why it shouldn't.
+ int boundedScroll = mOrientationHandler.getPrimaryScroll(this);
+ int unboundedScroll = getUnboundedScroll();
+ float unboundedProgress = mAdjacentPageOffset;
+ int scroll = Math.round(unboundedScroll * unboundedProgress
+ + boundedScroll * (1 - unboundedProgress));
+ return getScrollForPage(pageIndex) - scroll;
}
public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 7651dd8..c5f673f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -326,22 +326,14 @@
// Draw the background in all cases, except when the thumbnail data is opaque
final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
|| mThumbnailData == null;
- if (drawBackgroundOnly || mPreviewPositionHelper.mClipBottom > 0
- || mThumbnailData.isTranslucent) {
+ if (drawBackgroundOnly || mThumbnailData.isTranslucent) {
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
if (drawBackgroundOnly) {
return;
}
}
- if (mPreviewPositionHelper.mClipBottom > 0) {
- canvas.save();
- canvas.clipRect(x, y, width, mPreviewPositionHelper.mClipBottom);
- canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
- canvas.restore();
- } else {
- canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
- }
+ canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
}
public TaskView getTaskView() {
@@ -379,7 +371,6 @@
}
private void updateThumbnailMatrix() {
- mPreviewPositionHelper.mClipBottom = -1;
mPreviewPositionHelper.mIsOrientationChanged = false;
if (mBitmapShader != null && mThumbnailData != null) {
mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
@@ -473,40 +464,97 @@
/**
* Updates the matrix based on the provided parameters
*/
- public void updateThumbnailMatrix(Rect thumbnailPosition, ThumbnailData thumbnailData,
+ public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
boolean isRotated = false;
boolean isOrientationDifferent;
- mClipBottom = -1;
- float scale = thumbnailData.scale;
- Rect activityInsets = dp.getInsets();
- Rect thumbnailInsets = getBoundedInsets(activityInsets, thumbnailData.insets);
- final float thumbnailWidth = thumbnailPosition.width()
- - (thumbnailInsets.left + thumbnailInsets.right) * scale;
- final float thumbnailHeight = thumbnailPosition.height()
- - (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
-
- final float thumbnailScale;
int thumbnailRotation = thumbnailData.rotation;
int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+ RectF thumbnailClipHint = new RectF(thumbnailData.insets);
+
+ float scale = thumbnailData.scale;
+ final float thumbnailScale;
// Landscape vs portrait change
boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
&& thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
isOrientationDifferent = isOrientationChange(deltaRotate)
&& windowingModeSupportsRotation;
- if (canvasWidth == 0) {
+ if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
// If we haven't measured , skip the thumbnail drawing and only draw the background
// color
thumbnailScale = 0f;
} else {
// Rotate the screenshot if not in multi-window mode
isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
- // Scale the screenshot to always fit the width of the card.
- thumbnailScale = isOrientationDifferent
- ? canvasWidth / thumbnailHeight
- : canvasWidth / thumbnailWidth;
+
+ float surfaceWidth = thumbnailBounds.width() / scale;
+ float surfaceHeight = thumbnailBounds.height() / scale;
+ float availableWidth = surfaceWidth
+ - (thumbnailClipHint.left + thumbnailClipHint.right);
+ float availableHeight = surfaceHeight
+ - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+
+ final float targetW, targetH;
+ if (isOrientationDifferent) {
+ targetW = canvasHeight;
+ targetH = canvasWidth;
+ } else {
+ targetW = canvasWidth;
+ targetH = canvasHeight;
+ }
+ float canvasAspect = targetW / targetH;
+
+ // Update the clipHint such that
+ // > the final clipped position has same aspect ratio as requested by canvas
+ // > the clipped region is within the task insets if possible
+ // > the clipped region is not scaled up when drawing. If that is not possible
+ // while staying within the taskInsets, move outside the insets.
+ float croppedWidth = availableWidth;
+ if (croppedWidth < targetW) {
+ croppedWidth = Math.min(targetW, surfaceWidth);
+ }
+
+ float croppedHeight = croppedWidth / canvasAspect;
+ if (croppedHeight > availableHeight) {
+ croppedHeight = availableHeight;
+ if (croppedHeight < targetH) {
+ croppedHeight = Math.min(targetH, surfaceHeight);
+ }
+ croppedWidth = croppedHeight * canvasAspect;
+
+ // One last check in case the task aspect radio messed up something
+ if (croppedWidth > surfaceWidth) {
+ croppedWidth = surfaceWidth;
+ croppedHeight = croppedWidth / canvasAspect;
+ }
+ }
+
+ // Update the clip hints
+ float halfExtraW = (availableWidth - croppedWidth) / 2;
+ thumbnailClipHint.left += halfExtraW;
+ thumbnailClipHint.right += halfExtraW;
+ if (thumbnailClipHint.left < 0) {
+ thumbnailClipHint.right += thumbnailClipHint.left;
+ thumbnailClipHint.left = 0;
+ } else if (thumbnailClipHint.right < 0) {
+ thumbnailClipHint.left += thumbnailClipHint.right;
+ thumbnailClipHint.right = 0;
+ }
+
+ float halfExtraH = (availableHeight - croppedHeight) / 2;
+ thumbnailClipHint.top += halfExtraH;
+ thumbnailClipHint.bottom += halfExtraH;
+ if (thumbnailClipHint.top < 0) {
+ thumbnailClipHint.bottom += thumbnailClipHint.top;
+ thumbnailClipHint.top = 0;
+ } else if (thumbnailClipHint.bottom < 0) {
+ thumbnailClipHint.top += thumbnailClipHint.bottom;
+ thumbnailClipHint.bottom = 0;
+ }
+
+ thumbnailScale = targetW / (croppedWidth * scale);
}
Rect splitScreenInsets = dp.getInsets();
@@ -516,24 +564,24 @@
mClippedInsets.offsetTo(splitScreenInsets.left * scale,
splitScreenInsets.top * scale);
} else {
- mClippedInsets.offsetTo(thumbnailInsets.left * scale,
- thumbnailInsets.top * scale);
+ mClippedInsets.offsetTo(thumbnailClipHint.left * scale,
+ thumbnailClipHint.top * scale);
}
mMatrix.setTranslate(
- -thumbnailInsets.left * scale,
- -thumbnailInsets.top * scale);
+ -thumbnailClipHint.left * scale,
+ -thumbnailClipHint.top * scale);
} else {
- setThumbnailRotation(deltaRotate, thumbnailInsets, scale, thumbnailPosition);
+ setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds);
}
final float widthWithInsets;
final float heightWithInsets;
if (isOrientationDifferent) {
- widthWithInsets = thumbnailPosition.height() * thumbnailScale;
- heightWithInsets = thumbnailPosition.width() * thumbnailScale;
+ widthWithInsets = thumbnailBounds.height() * thumbnailScale;
+ heightWithInsets = thumbnailBounds.width() * thumbnailScale;
} else {
- widthWithInsets = thumbnailPosition.width() * thumbnailScale;
- heightWithInsets = thumbnailPosition.height() * thumbnailScale;
+ widthWithInsets = thumbnailBounds.width() * thumbnailScale;
+ heightWithInsets = thumbnailBounds.height() * thumbnailScale;
}
mClippedInsets.left *= thumbnailScale;
mClippedInsets.top *= thumbnailScale;
@@ -549,22 +597,9 @@
}
mMatrix.postScale(thumbnailScale, thumbnailScale);
-
- float bitmapHeight = Math.max(0,
- (isOrientationDifferent ? thumbnailWidth : thumbnailHeight) * thumbnailScale);
- if (Math.round(bitmapHeight) < canvasHeight) {
- mClipBottom = bitmapHeight;
- }
mIsOrientationChanged = isOrientationDifferent;
}
- private Rect getBoundedInsets(Rect activityInsets, Rect insets) {
- return new Rect(Math.min(insets.left, activityInsets.left),
- Math.min(insets.top, activityInsets.top),
- Math.min(insets.right, activityInsets.right),
- Math.min(insets.bottom, activityInsets.bottom));
- }
-
private int getRotationDelta(int oldRotation, int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
@@ -580,12 +615,12 @@
return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
}
- private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale,
+ private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale,
Rect thumbnailPosition) {
- int newLeftInset = 0;
- int newTopInset = 0;
- int translateX = 0;
- int translateY = 0;
+ float newLeftInset = 0;
+ float newTopInset = 0;
+ float translateX = 0;
+ float translateY = 0;
mMatrix.setRotate(90 * deltaRotate);
switch (deltaRotate) { /* Counter-clockwise */
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index 23b02d5..a19a67c 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -18,6 +18,8 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.animation.TimeInterpolator;
import android.content.Context;
@@ -27,12 +29,16 @@
import android.graphics.RectF;
import android.util.FloatProperty;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.views.RecentsView;
/**
* Controls an animation that can go beyond progress = 1, at which point resistance should be
@@ -48,15 +54,32 @@
*/
public static final float TWO_BUTTON_EXTRA_DRAG_FACTOR = 0.25f;
- /**
- * Start slowing down the rate of scaling down when recents view is smaller than this scale.
- */
- private static final float RECENTS_SCALE_START_RESIST = 0.75f;
+ private enum RecentsParams {
+ FROM_APP(0.75f, 0.5f, 1f),
+ FROM_OVERVIEW(1f, 0.75f, 0.5f);
- /**
- * Recents view will reach this scale at the very end of the drag.
- */
- private static final float RECENTS_SCALE_MAX_RESIST = 0.5f;
+ RecentsParams(float scaleStartResist, float scaleMaxResist, float translationFactor) {
+ this.scaleStartResist = scaleStartResist;
+ this.scaleMaxResist = scaleMaxResist;
+ this.translationFactor = translationFactor;
+ }
+
+ /**
+ * Start slowing down the rate of scaling down when recents view is smaller than this scale.
+ */
+ public final float scaleStartResist;
+
+ /**
+ * Recents view will reach this scale at the very end of the drag.
+ */
+ public final float scaleMaxResist;
+
+ /**
+ * How much translation to apply to RecentsView when the drag reaches the top of the screen,
+ * where 0 will keep it centered and 1 will have it barely touch the top of the screen.
+ */
+ public final float translationFactor;
+ }
private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
@@ -115,6 +138,24 @@
RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
FloatProperty<TRANSLATION> translationProperty) {
+
+ PendingAnimation resistAnim = createRecentsResistanceAnim(null, context,
+ recentsOrientedState, dp, scaleTarget, scaleProperty, translationTarget,
+ translationProperty, RecentsParams.FROM_APP);
+
+ AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
+ return new AnimatorControllerWithResistance(normalController, resistanceController);
+ }
+
+ /**
+ * Creates the resistance animation for {@link #createForRecents}, or can be used separately
+ * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
+ */
+ public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
+ @Nullable PendingAnimation resistAnim, Context context,
+ RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
+ FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
+ FloatProperty<TRANSLATION> translationProperty, RecentsParams params) {
Rect startRect = new Rect();
LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, startRect,
recentsOrientedState.getOrientationHandler());
@@ -125,7 +166,9 @@
distanceToCover = (long)
((dp.heightPx - startRect.bottom) * TWO_BUTTON_EXTRA_DRAG_FACTOR);
}
- PendingAnimation resistAnim = new PendingAnimation(distanceToCover * 2);
+ if (resistAnim == null) {
+ resistAnim = new PendingAnimation(distanceToCover * 2);
+ }
PointF pivot = new PointF();
float fullscreenScale = recentsOrientedState.getFullScreenScaleAndPivot(
@@ -141,9 +184,9 @@
} else {
// Create an interpolator that resists the scale so the scale doesn't get smaller than
// RECENTS_SCALE_MAX_RESIST.
- float startResist = Utilities.getProgress(RECENTS_SCALE_START_RESIST, startScale,
+ float startResist = Utilities.getProgress(params.scaleStartResist , startScale,
endScale);
- float maxResist = Utilities.getProgress(RECENTS_SCALE_MAX_RESIST, startScale, endScale);
+ float maxResist = Utilities.getProgress(params.scaleMaxResist, startScale, endScale);
scaleInterpolator = t -> {
if (t < startResist) {
return t;
@@ -160,17 +203,28 @@
// Compute where the task view would be based on the end scale, if we didn't translate.
RectF endRectF = new RectF(startRect);
Matrix temp = new Matrix();
- temp.setScale(RECENTS_SCALE_MAX_RESIST, RECENTS_SCALE_MAX_RESIST, pivot.x, pivot.y);
+ temp.setScale(params.scaleMaxResist, params.scaleMaxResist, pivot.x, pivot.y);
temp.mapRect(endRectF);
// Translate such that the task view touches the top of the screen when drag does.
float endTranslation = endRectF.top * recentsOrientedState.getOrientationHandler()
- .getSecondaryTranslationDirectionFactor();
+ .getSecondaryTranslationDirectionFactor() * params.translationFactor;
resistAnim.addFloat(translationTarget, translationProperty, 0, endTranslation,
RECENTS_TRANSLATE_RESIST_INTERPOLATOR);
}
- AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
- return new AnimatorControllerWithResistance(normalController, resistanceController);
+ return resistAnim;
}
+ /**
+ * Helper method to update or create a PendingAnimation suitable for animating
+ * a RecentsView interaction that started from the overview state.
+ */
+ public static PendingAnimation createRecentsResistanceFromOverviewAnim(
+ BaseDraggingActivity activity, @Nullable PendingAnimation resistanceAnim) {
+ RecentsView recentsView = activity.getOverviewPanel();
+ return createRecentsResistanceAnim(resistanceAnim, activity,
+ recentsView.getPagedViewOrientedState(), activity.getDeviceProfile(),
+ recentsView, RECENTS_SCALE_PROPERTY, recentsView, TASK_SECONDARY_TRANSLATION,
+ RecentsParams.FROM_OVERVIEW);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 81d24d7..df9b0cf 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -71,7 +71,7 @@
public final class RecentsOrientedState implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "RecentsOrientedState";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
@Override
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 9a89dee..fe9717c 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -35,7 +35,8 @@
<color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
- <color name="all_apps_section_fill">#327d7d7d</color>
+ <color name="all_apps_section_fill">#32c0c0c0</color>
+ <color name="all_apps_section_focused_item">#40c0c0c0</color>
<color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
<color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 0e760f9..fb08c56 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -51,7 +51,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Executors;
@@ -92,9 +92,9 @@
SCREEN, CELLX, CELLY, RESTORED, INTENT
});
- mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp,
- new UserManagerState());
- mLoaderCursor.allUsers.put(0, Process.myUserHandle());
+ UserManagerState ums = new UserManagerState();
+ mLoaderCursor = new LoaderCursor(mCursor, Favorites.CONTENT_URI, mApp, ums);
+ ums.allUsers.put(0, Process.myUserHandle());
}
private void initCursor(int itemType, String title) {
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 432073e..f61bc05 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -658,7 +658,7 @@
}
}
- protected static void beginDocument(XmlPullParser parser, String firstElementName)
+ public static void beginDocument(XmlPullParser parser, String firstElementName)
throws XmlPullParserException, IOException {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index be6adc7..d2b05c5 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -16,6 +16,13 @@
package com.android.launcher3;
+import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
+
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
+import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.appwidget.AppWidgetManager;
@@ -24,37 +31,25 @@
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Parcelable;
import android.os.Process;
import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Thunk;
import org.json.JSONException;
import org.json.JSONObject;
@@ -81,17 +76,6 @@
private static final String TAG = "InstallShortcutReceiver";
private static final boolean DBG = false;
- private static final String LAUNCH_INTENT_KEY = "intent.launch";
- private static final String NAME_KEY = "name";
- private static final String ICON_KEY = "icon";
- private static final String ICON_RESOURCE_NAME_KEY = "iconResource";
- private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
-
- private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
- private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut";
- private static final String APP_WIDGET_TYPE_KEY = "isAppWidget";
- private static final String USER_HANDLE_KEY = "userHandle";
-
// The set of shortcuts that are pending install
private static final String APPS_PENDING_INSTALL = "apps_to_install";
@@ -100,7 +84,7 @@
@WorkerThread
private static void addToQueue(Context context, PendingInstallShortcutInfo info) {
- String encoded = info.encodeToString();
+ String encoded = info.encodeToString(context);
SharedPreferences prefs = Utilities.getPrefs(context);
Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
@@ -123,25 +107,14 @@
return;
}
- LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
for (String encoded : strings) {
PendingInstallShortcutInfo info = decode(encoded, context);
if (info == null) {
continue;
}
- String pkg = getIntentPackage(info.launchIntent);
- if (!TextUtils.isEmpty(pkg)
- && !launcherApps.isPackageEnabled(pkg, info.user)
- && !info.isActivity) {
- if (DBG) {
- Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
- }
- continue;
- }
-
// Generate a shortcut info to add into the model
- installQueue.add(info.getItemInfo());
+ installQueue.add(info.getItemInfo(context));
}
prefs.edit().remove(APPS_PENDING_INSTALL).apply();
if (!installQueue.isEmpty()) {
@@ -172,8 +145,8 @@
String encoded = newStringsIter.next();
try {
Decoder decoder = new Decoder(encoded, context);
- if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
- user.equals(decoder.user)) {
+ if (packageNames.contains(getIntentPackage(decoder.intent))
+ && user.equals(decoder.user)) {
newStringsIter.remove();
}
} catch (JSONException | URISyntaxException e) {
@@ -184,57 +157,17 @@
sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
- /**
- * @return true is the extra is either null or is of type {@param type}
- */
- private static boolean isValidExtraType(Intent intent, String key, Class type) {
- Object extra = intent.getParcelableExtra(key);
- return extra == null || type.isInstance(extra);
- }
-
- /**
- * Verifies the intent and creates a {@link PendingInstallShortcutInfo}
- */
- private static PendingInstallShortcutInfo createPendingInfo(Context context, Intent data) {
- if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) ||
- !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.class)) ||
- !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
-
- if (DBG) Log.e(TAG, "Invalid install shortcut intent");
- return null;
- }
-
- PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(
- data, Process.myUserHandle(), context);
- if (info.launchIntent == null || info.label == null) {
- if (DBG) Log.e(TAG, "Invalid install shortcut intent");
- return null;
- }
-
- return convertToLauncherActivityIfPossible(info);
- }
-
- public static WorkspaceItemInfo fromShortcutIntent(Context context, Intent data) {
- PendingInstallShortcutInfo info = createPendingInfo(context, data);
- return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first;
- }
-
public static void queueShortcut(ShortcutInfo info, Context context) {
- queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(info), context);
}
public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
- queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId), context);
}
- public static void queueApplication(LauncherActivityInfo info, Context context) {
- queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
- }
-
- public static void queueApplication(Intent data, UserHandle user, Context context) {
- queuePendingShortcutInfo(new PendingInstallShortcutInfo(data, context, user),
- context);
+ public static void queueApplication(
+ String packageName, UserHandle userHandle, Context context) {
+ queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle), context);
}
public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
@@ -248,8 +181,8 @@
for (String encoded : strings) {
try {
Decoder decoder = new Decoder(encoded, context);
- if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
- result.add(ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user));
+ if (decoder.optInt(Favorites.ITEM_TYPE, -1) == ITEM_TYPE_DEEP_SHORTCUT) {
+ result.add(ShortcutKey.fromIntent(decoder.intent, decoder.user));
}
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
@@ -279,224 +212,111 @@
MODEL_EXECUTOR.post(() -> flushQueueInBackground(context));
}
- /**
- * Ensures that we have a valid, non-null name. If the provided name is null, we will return
- * the application name instead.
- */
- @Thunk static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
- if (name == null) {
- try {
- PackageManager pm = context.getPackageManager();
- ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
- name = info.loadLabel(pm);
- } catch (PackageManager.NameNotFoundException nnfe) {
- return "";
- }
- }
- return name;
- }
- private static class PendingInstallShortcutInfo {
+ private static class PendingInstallShortcutInfo extends ItemInfo {
- final boolean isActivity;
- @Nullable final ShortcutInfo shortcutInfo;
- @Nullable final AppWidgetProviderInfo providerInfo;
+ final Intent intent;
- @Nullable final Intent data;
- final Context mContext;
- final Intent launchIntent;
- final String label;
- final UserHandle user;
+ @Nullable ShortcutInfo shortcutInfo;
+ @Nullable AppWidgetProviderInfo providerInfo;
/**
- * Initializes a PendingInstallShortcutInfo received from a different app.
+ * Initializes a PendingInstallShortcutInfo to represent a pending launcher target.
*/
- public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
- isActivity = false;
- shortcutInfo = null;
- providerInfo = null;
-
- this.data = data;
- this.user = user;
- mContext = context;
-
- launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
- label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+ public PendingInstallShortcutInfo(String packageName, UserHandle userHandle) {
+ itemType = Favorites.ITEM_TYPE_APPLICATION;
+ intent = new Intent().setPackage(packageName);
+ user = userHandle;
}
/**
- * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+ * Initializes a PendingInstallShortcutInfo to represent a deep shortcut.
*/
- public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
- isActivity = true;
- shortcutInfo = null;
- providerInfo = null;
-
- String packageName = info.getComponentName().getPackageName();
- data = new Intent();
- data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
- new ComponentName(packageName, "")).setPackage(packageName));
- data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel());
-
- user = info.getUser();
- mContext = context;
-
- launchIntent = AppInfo.makeLaunchIntent(info);
- label = info.getLabel().toString();
- }
-
- /**
- * Initializes a PendingInstallShortcutInfo to represent a launcher target.
- */
- public PendingInstallShortcutInfo(Intent data, Context context, UserHandle user) {
- isActivity = true;
- shortcutInfo = null;
- providerInfo = null;
-
- this.data = data;
- this.user = user;
- mContext = context;
-
- launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
- label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
- }
-
- /**
- * Initializes a PendingInstallShortcutInfo to represent a launcher target.
- */
- public PendingInstallShortcutInfo(ShortcutInfo info, Context context) {
- isActivity = false;
- shortcutInfo = info;
- providerInfo = null;
-
- data = null;
- mContext = context;
+ public PendingInstallShortcutInfo(ShortcutInfo info) {
+ itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+ intent = ShortcutKey.makeIntent(info);
user = info.getUserHandle();
- launchIntent = ShortcutKey.makeIntent(info);
- label = info.getShortLabel().toString();
+ shortcutInfo = info;
}
/**
- * Initializes a PendingInstallShortcutInfo to represent a launcher target.
+ * Initializes a PendingInstallShortcutInfo to represent an app widget.
*/
- public PendingInstallShortcutInfo(
- AppWidgetProviderInfo info, int widgetId, Context context) {
- isActivity = false;
- shortcutInfo = null;
- providerInfo = info;
-
- data = null;
- mContext = context;
+ public PendingInstallShortcutInfo(AppWidgetProviderInfo info, int widgetId) {
+ itemType = Favorites.ITEM_TYPE_APPWIDGET;
+ intent = new Intent()
+ .setComponent(info.provider)
+ .putExtra(EXTRA_APPWIDGET_ID, widgetId);
user = info.getProfile();
- launchIntent = new Intent().setComponent(info.provider)
- .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
- label = info.label;
+ providerInfo = info;
}
- public String encodeToString() {
+ public String encodeToString(Context context) {
try {
- if (shortcutInfo != null) {
- // If it a launcher target, we only need component name, and user to
- // recreate this.
- return new JSONStringer()
- .object()
- .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
- .key(DEEPSHORTCUT_TYPE_KEY).value(true)
- .key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
- .getSerialNumberForUser(user))
- .endObject().toString();
- } else if (providerInfo != null) {
- // If it a launcher target, we only need component name, and user to
- // recreate this.
- return new JSONStringer()
- .object()
- .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
- .key(APP_WIDGET_TYPE_KEY).value(true)
- .key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
- .getSerialNumberForUser(user))
- .endObject().toString();
- }
-
- if (launchIntent.getAction() == null) {
- launchIntent.setAction(Intent.ACTION_VIEW);
- } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
- launchIntent.getCategories() != null &&
- launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
- launchIntent.addFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
- }
-
- // This name is only used for comparisons and notifications, so fall back to activity
- // name if not supplied
- String name = ensureValidName(mContext, launchIntent, label).toString();
- Bitmap icon = data == null ? null
- : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
- Intent.ShortcutIconResource iconResource = data == null ? null
- : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-
- // Only encode the parameters which are supported by the API.
- JSONStringer json = new JSONStringer()
- .object()
- .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
- .key(NAME_KEY).value(name)
- .key(USER_HANDLE_KEY).value(
- UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user))
- .key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
- if (icon != null) {
- byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
- if (iconByteArray != null) {
- json = json.key(ICON_KEY).value(
- Base64.encodeToString(
- iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
- }
- }
- if (iconResource != null) {
- json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
- json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
- .value(iconResource.packageName);
- }
- return json.endObject().toString();
+ return new JSONStringer()
+ .object()
+ .key(Favorites.ITEM_TYPE).value(itemType)
+ .key(Favorites.INTENT).value(intent.toUri(0))
+ .key(PROFILE_ID).value(
+ UserCache.INSTANCE.get(context).getSerialNumberForUser(user))
+ .endObject().toString();
} catch (JSONException e) {
Log.d(TAG, "Exception when adding shortcut: " + e);
return null;
}
}
- public Pair<ItemInfo, Object> getItemInfo() {
- if (isActivity) {
- WorkspaceItemInfo si = createWorkspaceItemInfo(data, user,
- LauncherAppState.getInstance(mContext));
- si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
- return Pair.create(si, null);
- } else if (shortcutInfo != null) {
- WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
- LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(
- itemInfo, shortcutInfo);
- return Pair.create(itemInfo, shortcutInfo);
- } else if (providerInfo != null) {
- LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
- .fromProviderInfo(mContext, providerInfo);
- LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
- launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
- info.provider);
- InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
- widgetInfo.minSpanX = info.minSpanX;
- widgetInfo.minSpanY = info.minSpanY;
- widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
- widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
- return Pair.create(widgetInfo, providerInfo);
- } else {
- WorkspaceItemInfo itemInfo =
- createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext));
- return Pair.create(itemInfo, null);
- }
- }
+ public Pair<ItemInfo, Object> getItemInfo(Context context) {
+ switch (itemType) {
+ case ITEM_TYPE_APPLICATION: {
+ String packageName = intent.getPackage();
+ List<LauncherActivityInfo> laiList =
+ context.getSystemService(LauncherApps.class)
+ .getActivityList(packageName, user);
- public boolean isLauncherActivity() {
- return isActivity;
+ final WorkspaceItemInfo si = new WorkspaceItemInfo();
+ si.user = user;
+ si.itemType = ITEM_TYPE_APPLICATION;
+
+ LauncherActivityInfo lai;
+ boolean usePackageIcon = laiList.isEmpty();
+ if (usePackageIcon) {
+ lai = null;
+ si.intent = makeLaunchIntent(new ComponentName(packageName, ""))
+ .setPackage(packageName);
+ si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+ } else {
+ lai = laiList.get(0);
+ si.intent = makeLaunchIntent(lai);
+ }
+ LauncherAppState.getInstance(context).getIconCache()
+ .getTitleAndIcon(si, () -> lai, usePackageIcon, false);
+ return Pair.create(si, null);
+ }
+ case ITEM_TYPE_DEEP_SHORTCUT: {
+ WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, context);
+ LauncherAppState.getInstance(context).getIconCache()
+ .getShortcutIcon(itemInfo, shortcutInfo);
+ return Pair.create(itemInfo, shortcutInfo);
+ }
+ case ITEM_TYPE_APPWIDGET: {
+ LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
+ .fromProviderInfo(context, providerInfo);
+ LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
+ intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
+ info.provider);
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+ widgetInfo.minSpanX = info.minSpanX;
+ widgetInfo.minSpanY = info.minSpanY;
+ widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
+ widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
+ widgetInfo.user = user;
+ return Pair.create(widgetInfo, providerInfo);
+ }
+ }
+ return null;
}
}
@@ -508,56 +328,32 @@
private static PendingInstallShortcutInfo decode(String encoded, Context context) {
try {
Decoder decoder = new Decoder(encoded, context);
- if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
- LauncherActivityInfo info = context.getSystemService(LauncherApps.class)
- .resolveActivity(decoder.launcherIntent, decoder.user);
- if (info != null) {
- return new PendingInstallShortcutInfo(info, context);
+ switch (decoder.optInt(Favorites.ITEM_TYPE, -1)) {
+ case Favorites.ITEM_TYPE_APPLICATION:
+ return new PendingInstallShortcutInfo(
+ decoder.intent.getPackage(), decoder.user);
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+ List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.intent, decoder.user)
+ .buildRequest(context)
+ .query(ShortcutRequest.ALL);
+ if (si.isEmpty()) {
+ return null;
+ } else {
+ return new PendingInstallShortcutInfo(si.get(0));
+ }
}
- } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
- List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user)
- .buildRequest(context)
- .query(ShortcutRequest.ALL);
- if (si.isEmpty()) {
- return null;
- } else {
- return new PendingInstallShortcutInfo(si.get(0), context);
+ case Favorites.ITEM_TYPE_APPWIDGET: {
+ int widgetId = decoder.intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
+ AppWidgetProviderInfo info =
+ AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId);
+ if (info == null || !info.provider.equals(decoder.intent.getComponent())
+ || !info.getProfile().equals(decoder.user)) {
+ return null;
+ }
+ return new PendingInstallShortcutInfo(info, widgetId);
}
- } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) {
- int widgetId = decoder.launcherIntent
- .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
- AppWidgetProviderInfo info = AppWidgetManager.getInstance(context)
- .getAppWidgetInfo(widgetId);
- if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) ||
- !info.getProfile().equals(decoder.user)) {
- return null;
- }
- return new PendingInstallShortcutInfo(info, widgetId, context);
- }
-
- Intent data = new Intent();
- data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent);
- data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(NAME_KEY));
-
- String iconBase64 = decoder.optString(ICON_KEY);
- String iconResourceName = decoder.optString(ICON_RESOURCE_NAME_KEY);
- String iconResourcePackageName = decoder.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
- if (iconBase64 != null && !iconBase64.isEmpty()) {
- byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
- Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
- data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
- } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
- Intent.ShortcutIconResource iconResource =
- new Intent.ShortcutIconResource();
- iconResource.resourceName = iconResourceName;
- iconResource.packageName = iconResourcePackageName;
- data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
- }
-
- if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
- return new PendingInstallShortcutInfo(data, context, decoder.user);
- } else {
- return new PendingInstallShortcutInfo(data, decoder.user, context);
+ default:
+ Log.e(TAG, "Unknown item type");
}
} catch (JSONException | URISyntaxException e) {
Log.d(TAG, "Exception reading shortcut to add: " + e);
@@ -566,88 +362,18 @@
}
private static class Decoder extends JSONObject {
- public final Intent launcherIntent;
+ public final Intent intent;
public final UserHandle user;
private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
super(encoded);
- launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0);
- user = has(USER_HANDLE_KEY) ? UserCache.INSTANCE.get(context)
- .getUserForSerialNumber(getLong(USER_HANDLE_KEY))
+ intent = Intent.parseUri(getString(Favorites.INTENT), 0);
+ user = has(PROFILE_ID)
+ ? UserCache.INSTANCE.get(context).getUserForSerialNumber(getLong(PROFILE_ID))
: Process.myUserHandle();
- if (user == null) {
- throw new JSONException("Invalid user");
+ if (user == null || intent == null) {
+ throw new JSONException("Invalid data");
}
}
}
-
- /**
- * Tries to create a new PendingInstallShortcutInfo which represents the same target,
- * but is an app target and not a shortcut.
- * @return the newly created info or the original one.
- */
- private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible(
- PendingInstallShortcutInfo original) {
- if (original.isLauncherActivity()) {
- // Already an activity target
- return original;
- }
- if (!PackageManagerHelper.isLauncherAppTarget(original.launchIntent)) {
- return original;
- }
-
- LauncherActivityInfo info = original.mContext.getSystemService(LauncherApps.class)
- .resolveActivity(original.launchIntent, original.user);
- if (info == null) {
- return original;
- }
- // Ignore any conflicts in the label name, as that can change based on locale.
- return new PendingInstallShortcutInfo(info, original.mContext);
- }
-
- private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user,
- LauncherAppState app) {
- if (data == null) {
- Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
- return null;
- }
-
- Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
- String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
- Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-
- if (intent == null) {
- // If the intent is null, return null as we can't construct a valid WorkspaceItemInfo
- Log.e(TAG, "Can't construct WorkspaceItemInfo with null intent");
- return null;
- }
-
- final WorkspaceItemInfo info = new WorkspaceItemInfo();
- info.user = user;
-
- BitmapInfo iconInfo = null;
- LauncherIcons li = LauncherIcons.obtain(app.getContext());
- if (bitmap instanceof Bitmap) {
- iconInfo = li.createIconBitmap((Bitmap) bitmap);
- } else {
- Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
- if (extra instanceof Intent.ShortcutIconResource) {
- info.iconResource = (Intent.ShortcutIconResource) extra;
- iconInfo = li.createIconBitmap(info.iconResource);
- }
- }
- li.recycle();
-
- if (iconInfo == null) {
- iconInfo = app.getIconCache().getDefaultIcon(info.user);
- }
- info.bitmap = iconInfo;
-
- info.title = Utilities.trim(name);
- info.contentDescription = app.getContext().getPackageManager()
- .getUserBadgedLabel(info.title, info.user);
- info.intent = intent;
- return info;
- }
-
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e723408..3f64df3 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -120,6 +120,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.ModelUtils;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
@@ -1202,7 +1203,7 @@
if (info == null) {
// Legacy shortcuts are only supported for primary profile.
info = Process.myUserHandle().equals(args.user)
- ? InstallShortcutReceiver.fromShortcutIntent(this, data) : null;
+ ? ModelUtils.fromLegacyShortcutIntent(this, data) : null;
if (info == null) {
Log.e(TAG, "Unable to parse a valid custom shortcut result");
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index a8d6490..e48ffb9 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -19,8 +19,6 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
@@ -29,8 +27,6 @@
import com.android.launcher3.pm.InstallSessionHelper;
-import java.util.List;
-
/**
* BroadcastReceiver to handle session commit intent.
*/
@@ -63,18 +59,7 @@
return;
}
- queueAppIconAddition(context, info.getAppPackageName(), user);
- }
-
- public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
- List<LauncherActivityInfo> activities = context.getSystemService(LauncherApps.class)
- .getActivityList(packageName, user);
- if (activities.isEmpty()) {
- // no activity found
- return;
- }
-
- InstallShortcutReceiver.queueApplication(activities.get(0), context);
+ InstallShortcutReceiver.queueApplication(info.getAppPackageName(), user, context);
}
public static boolean isEnabled(Context context) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 068d0bc..dea2a8d 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -25,6 +25,7 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
@@ -639,6 +640,14 @@
}
}
+ /**
+ * @return true is the extra is either null or is of type {@param type}
+ */
+ public static boolean isValidExtraType(Intent intent, String key, Class type) {
+ Object extra = intent.getParcelableExtra(key);
+ return extra == null || type.isInstance(extra);
+ }
+
public static float squaredHypot(float x, float y) {
return x * x + y * y;
}
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 2d711e6..af3722a 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -527,6 +527,22 @@
return mViewPager == null ? getActiveRecyclerView() : mViewPager;
}
+ /**
+ * Returns the ItemInfo of a view that is in focus, ready to be launched by an IME.
+ */
+ public ItemInfo getHighlightedItemInfo() {
+ View view = getFloatingHeaderView().getFocusedChild();
+ if (view != null && view.getTag() instanceof ItemInfo) {
+ return ((ItemInfo) view.getTag());
+ }
+ if (getActiveRecyclerView().getApps().getFocusedChild() != null) {
+ // TODO: when new pipelines are included, getSearchResults
+ // should be supported at recycler view level and not apps list level.
+ return getActiveRecyclerView().getApps().getFocusedChild().appInfo;
+ }
+ return null;
+ }
+
public RecyclerViewFastScroller getScrollBar() {
AllAppsRecyclerView rv = getActiveRecyclerView();
return rv == null ? null : rv.getScrollbar();
@@ -658,7 +674,8 @@
applyPadding();
setupOverlay();
if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
- recyclerView.addItemDecoration(new AllAppsSectionDecorator(getApps()));
+ recyclerView.addItemDecoration(new AllAppsSectionDecorator(
+ AllAppsContainerView.this));
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index ac55072..a168c06 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -21,6 +21,7 @@
import android.graphics.RectF;
import android.view.View;
+import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.R;
@@ -34,19 +35,19 @@
*/
public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
- private final AlphabeticalAppsList mApps;
+ private final AllAppsContainerView mAppsView;
- AllAppsSectionDecorator(AlphabeticalAppsList appsList) {
- mApps = appsList;
+ AllAppsSectionDecorator(AllAppsContainerView appsContainerView) {
+ mAppsView = appsContainerView;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// Iterate through views in recylerview and draw bounds around views in the same section.
// Since views in the same section will follow each other, we can skip to a last view in
- // a section to get the bounds of the section without having to iterate on evert item.
+ // a section to get the bounds of the section without having to iterate on every item.
int itemCount = parent.getChildCount();
- List<AlphabeticalAppsList.AdapterItem> adapterItems = mApps.getAdapterItems();
+ List<AlphabeticalAppsList.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
SectionDecorationHandler lastDecorationHandler = null;
int i = 0;
while (i < itemCount) {
@@ -69,7 +70,6 @@
i = endIndex;
continue;
}
-
}
i++;
}
@@ -78,13 +78,21 @@
}
}
- private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler, View parent) {
+ private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler,
+ RecyclerView parent) {
if (decorationHandler == null) return;
if (decorationHandler.mIsFullWidth) {
decorationHandler.mBounds.left = parent.getPaddingLeft();
decorationHandler.mBounds.right = parent.getWidth() - parent.getPaddingRight();
}
decorationHandler.onDraw(c);
+ if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
+ && mAppsView.getApps().getFocusedChild() != null) {
+ int index = mAppsView.getApps().getFocusedChildIndex();
+ if (index >= 0 && index < parent.getChildCount()) {
+ decorationHandler.onFocusDraw(c, parent.getChildAt(index));
+ }
+ }
decorationHandler.reset();
}
@@ -95,18 +103,21 @@
protected RectF mBounds = new RectF();
private final boolean mIsFullWidth;
private final float mRadius;
+
+ private final int mFocusColor;
private final int mFillcolor;
- Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public SectionDecorationHandler(Context context, boolean isFullWidth) {
mIsFullWidth = isFullWidth;
mFillcolor = context.getColor(R.color.all_apps_section_fill);
+ mFocusColor = context.getColor(R.color.all_apps_section_focused_item);
mRadius = Themes.getDialogCornerRadius(context);
}
/**
- * Extends current bounds to include view
+ * Extends current bounds to include the view.
*/
public void extendBounds(View view) {
if (mBounds.isEmpty()) {
@@ -122,7 +133,7 @@
}
/**
- * Draw bounds onto canvas
+ * Draw bounds onto canvas.
*/
public void onDraw(Canvas canvas) {
mPaint.setColor(mFillcolor);
@@ -130,7 +141,19 @@
}
/**
- * Reset view bounds to empty
+ * Draw the bound of the view to the canvas.
+ */
+ public void onFocusDraw(Canvas canvas, @Nullable View view) {
+ if (view == null) {
+ return;
+ }
+ mPaint.setColor(mFocusColor);
+ canvas.drawRoundRect(view.getLeft(), view.getTop(),
+ view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
+ }
+
+ /**
+ * Reset view bounds to empty.
*/
public void reset() {
mBounds.setEmpty();
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 9076f2a..7379dbed 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -224,6 +224,25 @@
}
/**
+ * Returns the child adapter item with IME launch focus.
+ */
+ public AdapterItem getFocusedChild() {
+ return mAdapterItems.get(getFocusedChildIndex());
+ }
+
+ /**
+ * Returns the index of the child with IME launch focus.
+ */
+ public int getFocusedChildIndex() {
+ for (AdapterItem item : mAdapterItems) {
+ if (item.isCountedForAccessibility()) {
+ return mAdapterItems.indexOf(item);
+ }
+ }
+ return -1;
+ }
+
+ /**
* Returns the number of rows of applications
*/
public int getNumAppRows() {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index f899587..e357f61 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -16,6 +16,7 @@
package com.android.launcher3.allapps;
import android.graphics.Rect;
+import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile;
@@ -55,4 +56,9 @@
void setVerticalScroll(int scroll, boolean isScrolledOut);
Class<? extends FloatingHeaderRow> getTypeClass();
+
+ /**
+ * Returns a child that has focus to be launched by the IME.
+ */
+ View getFocusedChild();
}
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 81e1b94..11d3fb9 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -38,6 +38,7 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.systemui.plugins.AllAppsRow;
import com.android.systemui.plugins.AllAppsRow.OnHeightUpdatedListener;
@@ -194,6 +195,19 @@
onHeightUpdated();
}
+ @Override
+ public View getFocusedChild() {
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ for (FloatingHeaderRow row : mAllRows) {
+ if (row.hasVisibleContent() && row.shouldDraw()) {
+ return row.getFocusedChild();
+ }
+ }
+ return null;
+ }
+ return super.getFocusedChild();
+ }
+
public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
for (FloatingHeaderRow row : mAllRows) {
row.setup(this, mAllRows, tabsHidden);
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index 3089b18..cf7142c 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -83,4 +83,9 @@
public Class<PluginHeaderRow> getTypeClass() {
return PluginHeaderRow.class;
}
+
+ @Override
+ public View getFocusedChild() {
+ return null;
+ }
}
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index db94e8b..06faaac 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -27,8 +27,11 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AlphabeticalAppsList;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.ArrayList;
@@ -101,6 +104,16 @@
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+ ItemInfo info = Launcher.getLauncher(mLauncher).getAppsView()
+ .getHighlightedItemInfo();
+ if (info != null) {
+ return mLauncher.startActivitySafely(v, info.getIntent(), info);
+ }
+ }
+ }
+
// Skip if it's not the right action
if (actionId != EditorInfo.IME_ACTION_SEARCH) {
return false;
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index ff0f773..444c2da 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -260,14 +260,6 @@
}
/**
- * Fill in info with the icon and label for deep shortcut.
- */
- public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
- return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
- () -> info, mShortcutCachingLogic, false, false);
- }
-
- /**
* Fill in {@param info} with the icon and label. If the
* corresponding activity is not found, it reverts to the package icon.
*/
@@ -295,7 +287,7 @@
/**
* Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
*/
- private synchronized void getTitleAndIcon(
+ public synchronized void getTitleAndIcon(
@NonNull ItemInfoWithIcon infoInOut,
@NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
boolean usePkgIcon, boolean useLowResIcon) {
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index c50189c..ae7ad10 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -32,6 +32,8 @@
import android.view.View.OnFocusChangeListener;
import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Themes;
/**
* A helper class to draw background of a focused view.
@@ -93,6 +95,7 @@
private ObjectAnimator mCurrentAnimation;
private float mAlpha;
+ private float mRadius;
public FocusIndicatorHelper(View container) {
mContainer = container;
@@ -104,6 +107,9 @@
setAlpha(0);
mShift = 0;
+ if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+ mRadius = Themes.getDialogCornerRadius(container.getContext());
+ }
}
protected void setAlpha(float alpha) {
@@ -129,13 +135,15 @@
}
public void draw(Canvas c) {
- if (mAlpha > 0) {
- Rect newRect = getDrawRect();
- if (newRect != null) {
- mDirtyRect.set(newRect);
- c.drawRect(mDirtyRect, mPaint);
- mIsDirty = true;
- }
+ if (mAlpha <= 0) return;
+
+ Rect newRect = getDrawRect();
+ if (newRect != null) {
+ mDirtyRect.set(newRect);
+ c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
+ (float) mDirtyRect.right, (float) mDirtyRect.bottom,
+ mRadius, mRadius, mPaint);
+ mIsDirty = true;
}
}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 0374009..2282339 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -313,7 +313,10 @@
LAUNCHER_NAVIGATION_MODE_2_BUTTON(624),
@UiEvent(doc = "System navigation mode is 0 button mode/gesture navigation mode .")
- LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625);
+ LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625),
+
+ @UiEvent(doc = "User tapped on image content in Overview Select mode.")
+ LAUNCHER_SELECT_MODE_IMAGE(627);
// ADD MORE
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 8b0ef7b..586333f 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -27,6 +27,7 @@
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.PagedView;
import com.android.launcher3.model.BgDataModel.Callbacks;
+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.LauncherAppWidgetInfo;
@@ -76,18 +77,20 @@
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
final IntArray orderedScreenIds = new IntArray();
+ ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
synchronized (mBgDataModel) {
workspaceItems.addAll(mBgDataModel.workspaceItems);
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
+ mBgDataModel.extraItems.forEach(extraItems::add);
mBgDataModel.lastBindId++;
mMyBindingId = mBgDataModel.lastBindId;
}
for (Callbacks cb : mCallbacksList) {
new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
- workspaceItems, appWidgets, orderedScreenIds).bind();
+ workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
}
}
@@ -135,7 +138,7 @@
private final ArrayList<ItemInfo> mWorkspaceItems;
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
private final IntArray mOrderedScreenIds;
-
+ private final ArrayList<FixedContainerItems> mExtraItems;
WorkspaceBinder(Callbacks callbacks,
Executor uiExecutor,
@@ -144,6 +147,7 @@
int myBindingId,
ArrayList<ItemInfo> workspaceItems,
ArrayList<LauncherAppWidgetInfo> appWidgets,
+ ArrayList<FixedContainerItems> extraItems,
IntArray orderedScreenIds) {
mCallbacks = callbacks;
mUiExecutor = uiExecutor;
@@ -152,6 +156,7 @@
mMyBindingId = myBindingId;
mWorkspaceItems = workspaceItems;
mAppWidgets = appWidgets;
+ mExtraItems = extraItems;
mOrderedScreenIds = orderedScreenIds;
}
@@ -198,6 +203,8 @@
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
bindAppWidgets(currentAppWidgets, mainExecutor);
+ mExtraItems.forEach(item ->
+ executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
// Locate available spots for prediction using currentWorkspaceItems
IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
@@ -207,6 +214,7 @@
// happens later).
// This ensures that the first screen is immediately visible (eg. during rotation)
// In case of !validFirstPage, bind all pages one after other.
+
final Executor deferredExecutor =
validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index fd8520d..140342f 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -424,6 +424,14 @@
public FixedContainerItems clone() {
return new FixedContainerItems(containerId, new ArrayList<>(items));
}
+
+ public void setItems(List<ItemInfo> newItems) {
+ items.clear();
+ newItems.forEach(item -> {
+ item.container = containerId;
+ items.add(item);
+ });
+ }
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 165d1ea..a27ac23 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -65,7 +65,7 @@
private static final String TAG = "LoaderCursor";
- public final LongSparseArray<UserHandle> allUsers;
+ private final LongSparseArray<UserHandle> allUsers;
private final Uri mContentUri;
private final Context mContext;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index e89031e..1dd8c11 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -360,15 +360,12 @@
final int optionsIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.OPTIONS);
- final LongSparseArray<UserHandle> allUsers = c.allUsers;
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
mUserManagerState.init(mUserCache, mUserManager);
for (UserHandle user : mUserCache.getUserProfiles()) {
long serialNo = mUserCache.getSerialNumberForUser(user);
- allUsers.put(serialNo, user);
-
boolean userUnlocked = mUserManager.isUserUnlocked(user);
// We can only query for shortcuts when the user is unlocked.
@@ -419,16 +416,6 @@
ComponentName cn = intent.getComponent();
targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
- if (allUsers.indexOfValue(c.user) < 0) {
- if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
- c.markDeleted("Legacy shortcuts are only allowed for current users");
- continue;
- } else if (c.restoreFlag != 0) {
- // Don't restore items for other profiles.
- c.markDeleted("Restore from other profiles not supported");
- continue;
- }
- }
if (TextUtils.isEmpty(targetPkg) &&
c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
c.markDeleted("Only legacy shortcuts can have null package");
@@ -773,7 +760,7 @@
}
// Load delegate items
- mModelDelegate.loadItems();
+ mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
// Break early if we've stopped loading
if (mStopped) {
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index ce4eed5..53e8a86 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -18,13 +18,17 @@
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.content.Context;
+import android.content.pm.ShortcutInfo;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ResourceBasedOverride;
+import java.util.Map;
+
/**
* Class to extend LauncherModel functionality to provide extra data
*/
@@ -65,11 +69,12 @@
* Load delegate items if any in the data model
*/
@WorkerThread
- public void loadItems() { }
+ public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
/**
* Called when the delegate is no loner needed
*/
@WorkerThread
public void destroy() { }
+
}
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 4efeba5..a8cc9ad 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,10 +15,22 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.Utilities.isValidExtraType;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.util.Log;
+
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -33,6 +45,8 @@
*/
public class ModelUtils {
+ private static final String TAG = "ModelUtils";
+
/**
* Filters the set of items who are directly or indirectly (via another container) on the
* specified screen.
@@ -125,4 +139,52 @@
IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
return result;
}
+
+
+ /**
+ * Creates a workspace item info for the legacy shortcut intent
+ */
+ @SuppressWarnings("deprecation")
+ public static WorkspaceItemInfo fromLegacyShortcutIntent(Context context, Intent data) {
+ if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class)
+ || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+ Intent.ShortcutIconResource.class))
+ || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
+
+ Log.e(TAG, "Invalid install shortcut intent");
+ return null;
+ }
+
+ Intent launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+ String label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+ if (launchIntent == null || label == null) {
+ Log.e(TAG, "Invalid install shortcut intent");
+ return null;
+ }
+
+ final WorkspaceItemInfo info = new WorkspaceItemInfo();
+ info.user = Process.myUserHandle();
+
+ BitmapInfo iconInfo = null;
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+ if (bitmap != null) {
+ iconInfo = li.createIconBitmap(bitmap);
+ } else {
+ info.iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+ if (info.iconResource != null) {
+ iconInfo = li.createIconBitmap(info.iconResource);
+ }
+ }
+ }
+
+ if (iconInfo == null) {
+ Log.e(TAG, "Invalid icon by the app");
+ return null;
+ }
+ info.bitmap = iconInfo;
+ info.contentDescription = info.title = Utilities.trim(label);
+ info.intent = launchIntent;
+ return info;
+ }
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 59233cd..e03fd72 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -258,12 +258,6 @@
}
/**
- * Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
- */
- public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
- }
-
- /**
* Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
*/
public LauncherAtom.ItemInfo buildProto() {
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index b0d19a6..c04b7f0 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -26,7 +26,6 @@
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.util.ContentWriter;
/**
@@ -195,13 +194,4 @@
public final boolean hasOptionFlag(int option) {
return (options & option) != 0;
}
-
- @Override
- public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
- builder.setWidget(LauncherAtom.Widget.newBuilder()
- .setSpanX(spanX)
- .setSpanY(spanY)
- .setComponentName(providerName.toString())
- .setPackageName(providerName.getPackageName()));
- }
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index ce4644f..d546013 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -18,9 +18,7 @@
import static com.android.launcher3.Utilities.getPrefs;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
@@ -215,20 +213,8 @@
&& !mPromiseIconIds.contains(sessionInfo.getSessionId())
&& new PackageManagerHelper(mAppContext).getApplicationInfo(
sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
-
- String packageName = sessionInfo.getAppPackageName();
- if (mAppContext.getSystemService(LauncherApps.class)
- .getActivityList(packageName, getUserHandle(sessionInfo)).isEmpty()) {
- // Ensure application isn't already installed.
- Intent data = new Intent();
- data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
- new ComponentName(packageName, "")).setPackage(packageName));
- data.putExtra(Intent.EXTRA_SHORTCUT_NAME, sessionInfo.getAppLabel());
- data.putExtra(Intent.EXTRA_SHORTCUT_ICON, sessionInfo.getAppIcon());
-
- InstallShortcutReceiver.queueApplication(data, getUserHandle(sessionInfo),
- mAppContext);
- }
+ InstallShortcutReceiver.queueApplication(
+ sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), mAppContext);
mPromiseIconIds.add(sessionInfo.getSessionId());
updatePromiseIconPrefs();
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index f90ad3c..8b72177 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -71,6 +71,7 @@
ANIM_ALL_APPS_HEADER_FADE,
ANIM_OVERVIEW_MODAL,
ANIM_DEPTH,
+ ANIM_OVERVIEW_ACTIONS_FADE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimType {}
@@ -89,10 +90,11 @@
public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
public static final int ANIM_OVERVIEW_MODAL = 13;
public static final int ANIM_DEPTH = 14;
+ public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15;
- private static final int ANIM_TYPES_COUNT = 15;
+ private static final int ANIM_TYPES_COUNT = 16;
- private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
+ protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
public StateAnimationConfig() { }
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index fcb96d7..1cec0ec 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -16,20 +16,19 @@
package com.android.launcher3.util;
-import android.content.Context;
+import android.os.FileUtils;
import android.util.Log;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.UUID;
/**
* Supports various IO utility functions
@@ -52,6 +51,9 @@
}
public static long copy(InputStream from, OutputStream to) throws IOException {
+ if (Utilities.ATLEAST_Q) {
+ return FileUtils.copy(from, to);
+ }
byte[] buf = new byte[BUF_SIZE];
long total = 0;
int r;
@@ -62,25 +64,6 @@
return total;
}
- /**
- * Utility method to debug binary data
- */
- public static String createTempFile(Context context, byte[] data) {
- if (!FeatureFlags.IS_STUDIO_BUILD) {
- throw new IllegalStateException("Method only allowed in development mode");
- }
-
- String name = UUID.randomUUID().toString();
- File file = new File(context.getCacheDir(), name);
- try (FileOutputStream fo = new FileOutputStream(file)) {
- fo.write(data);
- fo.flush();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- return file.getAbsolutePath();
- }
-
public static void closeSilently(Closeable c) {
if (c != null) {
try {
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index a8642b0..5be9529 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -19,6 +19,8 @@
import android.util.FloatProperty;
import android.view.View;
+import com.android.launcher3.anim.AlphaUpdateListener;
+
import java.util.Arrays;
/**
@@ -44,6 +46,8 @@
private final AlphaProperty[] mMyProperties;
private int mValidMask;
+ // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
+ private boolean mUpdateVisibility;
public MultiValueAlpha(View view, int size) {
mView = view;
@@ -66,6 +70,11 @@
return mMyProperties[index];
}
+ /** Sets whether we should update between INVISIBLE and VISIBLE based on alpha. */
+ public void setUpdateVisibility(boolean updateVisibility) {
+ mUpdateVisibility = updateVisibility;
+ }
+
public class AlphaProperty {
private final int mMyMask;
@@ -99,6 +108,9 @@
mValue = value;
mView.setAlpha(mOthers * mValue);
+ if (mUpdateVisibility) {
+ AlphaUpdateListener.updateVisibility(mView);
+ }
}
public float getValue() {
diff --git a/src/com/android/launcher3/util/PersistedItemArray.java b/src/com/android/launcher3/util/PersistedItemArray.java
new file mode 100644
index 0000000..ae20638
--- /dev/null
+++ b/src/com/android/launcher3/util/PersistedItemArray.java
@@ -0,0 +1,180 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.LongFunction;
+
+/**
+ * Utility class to read/write a list of {@link com.android.launcher3.model.data.ItemInfo} on disk.
+ * This class is not thread safe, the caller should ensure proper threading
+ */
+public class PersistedItemArray<T extends ItemInfo> {
+
+ private static final String TAG = "PersistedItemArray";
+
+ private static final String TAG_ROOT = "items";
+ private static final String TAG_ENTRY = "entry";
+
+ private final String mFileName;
+
+ public PersistedItemArray(String fileName) {
+ mFileName = fileName + ".xml";
+ }
+
+ /**
+ * Writes the provided list of items on the disk
+ */
+ @WorkerThread
+ public void write(Context context, List<T> items) {
+ AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
+
+ FileOutputStream fos;
+ try {
+ fos = file.startWrite();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to persist items in " + mFileName, e);
+ return;
+ }
+
+ UserCache userCache = UserCache.INSTANCE.get(context);
+
+ try {
+ XmlSerializer out = Xml.newSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_ROOT);
+ for (T item : items) {
+ Intent intent = item.getIntent();
+ if (intent == null) {
+ continue;
+ }
+
+ out.startTag(null, TAG_ENTRY);
+ out.attribute(null, Favorites.ITEM_TYPE, Integer.toString(item.itemType));
+ out.attribute(null, Favorites.PROFILE_ID,
+ Long.toString(userCache.getSerialNumberForUser(item.user)));
+ out.attribute(null, Favorites.INTENT, intent.toUri(0));
+ out.endTag(null, TAG_ENTRY);
+ }
+ out.endTag(null, TAG_ROOT);
+ out.endDocument();
+ } catch (IOException e) {
+ file.failWrite(fos);
+ Log.e(TAG, "Unable to persist items in " + mFileName, e);
+ return;
+ }
+
+ file.finishWrite(fos);
+ }
+
+ /**
+ * Reads the items from the disk
+ */
+ @WorkerThread
+ public List<T> read(Context context, ItemFactory<T> factory) {
+ return read(context, factory, UserCache.INSTANCE.get(context)::getUserForSerialNumber);
+ }
+
+ /**
+ * Reads the items from the disk
+ * @param userFn method to provide user handle for a given user serial
+ */
+ @WorkerThread
+ public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
+ List<T> result = new ArrayList<>();
+ AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
+
+ try (FileInputStream fis = file.openRead()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
+
+ AutoInstallsLayout.beginDocument(parser, TAG_ROOT);
+ final int depth = parser.getDepth();
+
+ int type;
+ while (((type = parser.next()) != XmlPullParser.END_TAG
+ || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG || !TAG_ENTRY.equals(parser.getName())) {
+ continue;
+ }
+ try {
+ int itemType = Integer.parseInt(
+ parser.getAttributeValue(null, Favorites.ITEM_TYPE));
+ UserHandle user = userFn.apply(Long.parseLong(
+ parser.getAttributeValue(null, Favorites.PROFILE_ID)));
+ Intent intent = Intent.parseUri(
+ parser.getAttributeValue(null, Favorites.INTENT), 0);
+
+ if (user != null && intent != null) {
+ T item = factory.createInfo(itemType, user, intent);
+ if (item != null) {
+ result.add(item);
+ }
+ }
+ } catch (Exception e) {
+ // Ignore this entry
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Unable to read items in " + mFileName, e);
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
+ /**
+ * Interface to create an ItemInfo during parsing
+ */
+ public interface ItemFactory<T extends ItemInfo> {
+
+ /**
+ * Returns an item info or null in which case the entry is ignored
+ */
+ @Nullable
+ T createInfo(int itemType, UserHandle user, Intent intent);
+ }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index a77c1c6..92ab9b8 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -64,6 +64,7 @@
import com.android.launcher3.ResourceUtils;
import com.android.launcher3.testing.TestProtocol;
+import com.android.systemui.shared.system.ContextUtils;
import com.android.systemui.shared.system.QuickStepContract;
import org.junit.Assert;
@@ -238,11 +239,12 @@
if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
if (TestHelpers.isInLauncherProcess()) {
- getContext().getPackageManager().setComponentEnabledSetting(
- cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+ pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
} else {
try {
- mDevice.executeShellCommand("pm enable " + cn.flattenToString());
+ final int userId = ContextUtils.getUserId(getContext());
+ mDevice.executeShellCommand(
+ "pm enable --user " + userId + " " + cn.flattenToString());
} catch (IOException e) {
fail(e.toString());
}