Merge "[DO NOT MERGE] Folder name suggest Bug: 142498588" into ub-launcher3-master
diff --git a/OWNERS b/OWNERS
index 538ca33..7340e84 100644
--- a/OWNERS
+++ b/OWNERS
@@ -10,6 +10,7 @@
sunnygoyal@google.com
twickham@google.com
winsonc@google.com
+zakcohen@google.com
per-file FeatureFlags.java = sunnygoyal@google.com, adamcohen@google.com
per-file BaseFlags.java = sunnygoyal@google.com, adamcohen@google.com
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index fae1eff..f84a82e 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -22,7 +22,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3" >
- <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"/>
+ <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
<application
android:backupAgent="com.android.launcher3.LauncherBackupAgent"
diff --git a/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..e7099ec
--- /dev/null
+++ b/go/quickstep/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import com.android.launcher3.Launcher;
+
+/** Empty class, only exists so that lowRamWithQuickstepIconRecentsDebug compiles. */
+public class ShelfPeekAnim {
+ public ShelfPeekAnim(Launcher launcher) {
+ }
+
+ public enum ShelfAnimState {
+ }
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index ea1ca53..a89ede7 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -17,6 +17,7 @@
import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageInfo;
import android.os.LocaleList;
import android.os.UserHandle;
@@ -45,6 +46,13 @@
}
/**
+ * Returns the timestamp the entry was last updated in cache.
+ */
+ default long getLastUpdatedTime(T object, PackageInfo info) {
+ return info.lastUpdateTime;
+ }
+
+ /**
* Returns true the object should be added to mem cache; otherwise returns false.
*/
default boolean addToMemCache() {
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index 8224966..bcdbce5 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -171,7 +171,8 @@
long updateTime = c.getLong(indexLastUpdate);
int version = c.getInt(indexVersion);
T app = componentMap.remove(component);
- if (version == info.versionCode && updateTime == info.lastUpdateTime
+ if (version == info.versionCode
+ && updateTime == cachingLogic.getLastUpdatedTime(app, info)
&& TextUtils.equals(c.getString(systemStateIndex),
mIconCache.getIconSystemState(info.packageName))) {
@@ -231,7 +232,6 @@
}
}
-
/**
* A runnable that updates invalid icons and adds missing icons in the DB for the provided
* LauncherActivityInfo list. Items are updated/added one at a time, so that the
diff --git a/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml
new file mode 100644
index 0000000..cfc6d48
--- /dev/null
+++ b/quickstep/recents_ui_overrides/res/drawable/predicted_icon_background.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:inset="@dimen/predicted_icon_background_inset">
+ <shape>
+ <solid android:color="?attr/folderFillColor" />
+ <corners android:radius="@dimen/predicted_icon_background_corner_radius" />
+ </shape>
+</inset>
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 863a8ba..ee672d4 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -28,4 +28,9 @@
<dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
<dimen name="swipe_up_y_overshoot">10dp</dimen>
<dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
+
+ <!-- Predicted icon related -->
+ <dimen name="predicted_icon_background_corner_radius">15dp</dimen>
+ <dimen name="predicted_icon_background_inset">8dp</dimen>
+
</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
index ba44213..424333c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java
@@ -15,7 +15,15 @@
*/
package com.android.launcher3;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+
import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -25,14 +33,16 @@
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.appprediction.ComponentKeyMapper;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.appprediction.DynamicItemCache;
import com.android.launcher3.dragndrop.DragController;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
import java.util.Collections;
@@ -43,105 +53,164 @@
* pinning of predicted apps and manages replacement of predicted apps with user drag.
*/
public class HotseatPredictionController implements DragController.DragListener,
- View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher> {
+ View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
+ InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
+ IconCache.ItemInfoUpdateReceiver {
private static final String TAG = "PredictiveHotseat";
private static final boolean DEBUG = false;
+ private static final String PREDICTION_CLIENT = "hotseat";
private boolean mDragStarted = false;
- private PredictionUiStateManager mPredictionUiStateManager;
- private ArrayList<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
+ private int mHotSeatItemsCount;
+
private Launcher mLauncher;
+ private Hotseat mHotseat;
+
+ private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
+
+ private DynamicItemCache mDynamicItemCache;
+
+ private AppPredictor mAppPredictor;
+ private AllAppsStore mAllAppsStore;
public HotseatPredictionController(Launcher launcher) {
mLauncher = launcher;
- mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(mLauncher);
- if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
- mLauncher.getHotseat().addOnAttachStateChangeListener(this);
- }
+ mHotseat = launcher.getHotseat();
+ mAllAppsStore = mLauncher.getAppsView().getAppsStore();
+ mAllAppsStore.addUpdateListener(this);
+ mDynamicItemCache = new DynamicItemCache(mLauncher, () -> fillGapsWithPrediction(false));
+ mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+ launcher.getDeviceProfile().inv.addOnChangeListener(this);
+ mHotseat.addOnAttachStateChangeListener(this);
+ createPredictor();
}
@Override
public void onViewAttachedToWindow(View view) {
- mPredictionUiStateManager.setHotseatPredictionController(this);
mLauncher.getDragController().addDragListener(this);
}
@Override
public void onViewDetachedFromWindow(View view) {
- mPredictionUiStateManager.setHotseatPredictionController(null);
mLauncher.getDragController().removeDragListener(this);
}
/**
- * sets the list of predicted items. gets called from PredictionUiStateManager
- */
- public void setPredictedApps(List<ComponentKeyMapper> apps) {
- mPredictedApps.clear();
- mPredictedApps.addAll(mapToWorkspaceItemInfo(apps));
- fillGapsWithPrediction(false);
- }
-
- /**
* Fills gaps in the hotseat with predictions
*/
public void fillGapsWithPrediction(boolean animate) {
- if (!FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() || mDragStarted) {
+ if (mDragStarted) {
return;
}
- removePredictedApps(false);
+ List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
int predictionIndex = 0;
- ArrayList<ItemInfo> itemInfos = new ArrayList<>();
- int cellCount = mLauncher.getWallpaperDeviceProfile().inv.numHotseatIcons;
- for (int rank = 0; rank < cellCount; rank++) {
- if (mPredictedApps.size() == predictionIndex) {
- break;
- }
- View child = mLauncher.getHotseat().getChildAt(
- mLauncher.getHotseat().getCellXFromOrder(rank),
- mLauncher.getHotseat().getCellYFromOrder(rank));
- if (child != null) {
- // we already have an item there. skip cell
+ ArrayList<ItemInfo> newItemsToAdd = new ArrayList<>();
+ for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
+ View child = mHotseat.getChildAt(
+ mHotseat.getCellXFromOrder(rank),
+ mHotseat.getCellYFromOrder(rank));
+
+ if (child != null && !isPredictedIcon(child)) {
continue;
}
- WorkspaceItemInfo predictedItem = mPredictedApps.get(predictionIndex++);
+ if (predictedApps.size() <= predictionIndex) {
+ // Remove predicted apps from the past
+ if (isPredictedIcon(child)) {
+ mHotseat.removeView(child);
+ }
+ continue;
+ }
+
+ WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
+ if (isPredictedIcon(child)) {
+ BubbleTextView icon = (BubbleTextView) child;
+ icon.applyFromWorkspaceItem(predictedItem);
+ } else {
+ newItemsToAdd.add(predictedItem);
+ }
preparePredictionInfo(predictedItem, rank);
- itemInfos.add(predictedItem);
}
- mLauncher.bindItems(itemInfos, animate);
+ mLauncher.bindItems(newItemsToAdd, animate);
for (BubbleTextView icon : getPredictedIcons()) {
icon.verifyHighRes();
icon.setOnLongClickListener((v) -> {
PopupContainerWithArrow.showForIcon((BubbleTextView) v);
return true;
});
+ icon.setBackgroundResource(R.drawable.predicted_icon_background);
}
}
+ /**
+ * Unregisters callbacks and frees resources
+ */
+ public void destroy() {
+ mAllAppsStore.removeUpdateListener(this);
+ mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
+ mHotseat.removeOnAttachStateChangeListener(this);
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ }
+ }
+
+ private void createPredictor() {
+ AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
+ if (apm == null) {
+ return;
+ }
+ if (mAppPredictor != null) {
+ mAppPredictor.destroy();
+ }
+ mAppPredictor = apm.createAppPredictionSession(
+ new AppPredictionContext.Builder(mLauncher)
+ .setUiSurface(PREDICTION_CLIENT)
+ .setPredictedTargetCount(mHotSeatItemsCount)
+ .build());
+ mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
+ this::setPredictedApps);
+ mAppPredictor.requestPredictionUpdate();
+ }
+
+ private void setPredictedApps(List<AppTarget> appTargets) {
+ mComponentKeyMappers.clear();
+ for (AppTarget appTarget : appTargets) {
+ ComponentKey key;
+ if (appTarget.getShortcutInfo() != null) {
+ key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
+ } else {
+ key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
+ appTarget.getClassName()), appTarget.getUser());
+ }
+ mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
+ }
+ updateDependencies();
+ fillGapsWithPrediction(false);
+ }
+
+ private void updateDependencies() {
+ mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
+ mHotSeatItemsCount);
+ }
+
private void pinPrediction(ItemInfo info) {
- BubbleTextView icon = (BubbleTextView) mLauncher.getHotseat().getChildAt(
- mLauncher.getHotseat().getCellXFromOrder(info.rank),
- mLauncher.getHotseat().getCellYFromOrder(info.rank));
+ BubbleTextView icon = (BubbleTextView) mHotseat.getChildAt(
+ mHotseat.getCellXFromOrder(info.rank),
+ mHotseat.getCellYFromOrder(info.rank));
if (icon == null) {
return;
}
WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
- workspaceItemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
- workspaceItemInfo.container, workspaceItemInfo.screenId,
+ LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
workspaceItemInfo.cellX, workspaceItemInfo.cellY);
- icon.animate().scaleY(0.8f).scaleX(0.8f).setListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- icon.animate().scaleY(1).scaleX(1);
- }
- });
+ ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
+ icon.reset();
icon.applyFromWorkspaceItem(workspaceItemInfo);
icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
}
-
private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
List<ComponentKeyMapper> components) {
AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
@@ -163,7 +232,7 @@
}
}
// Stop at the number of hotseat items
- if (predictedApps.size() == mLauncher.getDeviceProfile().inv.numHotseatIcons) {
+ if (predictedApps.size() == mHotSeatItemsCount) {
break;
}
}
@@ -172,12 +241,10 @@
private List<BubbleTextView> getPredictedIcons() {
List<BubbleTextView> icons = new ArrayList<>();
- ViewGroup vg = mLauncher.getHotseat().getShortcutsAndWidgets();
+ ViewGroup vg = mHotseat.getShortcutsAndWidgets();
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
- if (child instanceof BubbleTextView && child.getTag() instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) child.getTag()).container
- == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ if (isPredictedIcon(child)) {
icons.add((BubbleTextView) child);
}
}
@@ -191,13 +258,13 @@
@Override
public void onAnimationSuccess(Animator animator) {
if (icon.getParent() != null) {
- ((ViewGroup) icon.getParent()).removeView(icon);
+ mHotseat.removeView(icon);
}
}
});
} else {
if (icon.getParent() != null) {
- ((ViewGroup) icon.getParent()).removeView(icon);
+ mHotseat.removeView(icon);
}
}
}
@@ -222,7 +289,6 @@
@Override
public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
ItemInfo itemInfo) {
- if (!FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) return null;
if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
return null;
}
@@ -233,10 +299,27 @@
itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
itemInfo.rank = rank;
itemInfo.cellX = rank;
- itemInfo.cellY = LauncherAppState.getIDP(mLauncher).numHotseatIcons - rank - 1;
+ itemInfo.cellY = mHotSeatItemsCount - rank - 1;
itemInfo.screenId = rank;
}
+ @Override
+ public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+ this.mHotSeatItemsCount = profile.numHotseatIcons;
+ createPredictor();
+ }
+
+ @Override
+ public void onAppsUpdated() {
+ updateDependencies();
+ fillGapsWithPrediction(false);
+ }
+
+ @Override
+ public void reapplyItemInfo(ItemInfoWithIcon info) {
+
+ }
+
private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
@@ -250,4 +333,10 @@
pinPrediction(mItemInfo);
}
}
+
+ private static boolean isPredictedIcon(View view) {
+ return view instanceof BubbleTextView && view.getTag() instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) view.getTag()).container
+ == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index d842484..6946508 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -17,7 +17,10 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
@@ -27,12 +30,15 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.LauncherState.ScaleAndTranslation;
+import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.SpringAnimationBuilder;
@@ -145,8 +151,37 @@
@Override
public Animator createStateElementAnimation(int index, float... values) {
switch (index) {
- case INDEX_SHELF_ANIM:
- return mLauncher.getAllAppsController().createSpringAnimation(values);
+ case INDEX_SHELF_ANIM: {
+ AllAppsTransitionController aatc = mLauncher.getAllAppsController();
+ Animator springAnim = aatc.createSpringAnimation(values);
+
+ if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
+ // Translate hotseat with the shelf until reaching overview.
+ float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+ ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
+ float shiftRange = aatc.getShiftRange();
+ if (values.length == 1) {
+ values = new float[] {aatc.getProgress(), values[0]};
+ }
+ ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
+ hotseatAnim.addUpdateListener(anim -> {
+ float progress = (Float) anim.getAnimatedValue();
+ if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
+ float hotseatShift = (progress - overviewProgress) * shiftRange;
+ mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
+ }
+ });
+ hotseatAnim.setInterpolator(LINEAR);
+ hotseatAnim.setDuration(springAnim.getDuration());
+
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(hotseatAnim);
+ anim.play(springAnim);
+ return anim;
+ }
+
+ return springAnim;
+ }
case INDEX_RECENTS_FADE_ANIM:
return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
deleted file mode 100644
index 76050d5..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherInitListenerEx.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3;
-
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-
-import java.util.function.BiPredicate;
-
-public class LauncherInitListenerEx extends LauncherInitListener {
-
- public LauncherInitListenerEx(BiPredicate<Launcher, Boolean> onInitListener) {
- super(onInitListener);
- }
-
- @Override
- public boolean init(Launcher launcher, boolean alreadyOnHome) {
- PredictionUiStateManager.INSTANCE.get(launcher).switchClient(Client.OVERVIEW);
- return super.init(launcher, alreadyOnHome);
- }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 06580b9..38bb180 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -18,6 +18,7 @@
import static android.content.pm.PackageManager.MATCH_INSTANT;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.content.Context;
import android.content.Intent;
@@ -37,8 +38,10 @@
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.AppInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -72,6 +75,7 @@
private final Handler mUiHandler;
private final InstantAppResolver mInstantAppResolver;
private final Runnable mOnUpdateCallback;
+ private final IconCache mIconCache;
private final Map<ShortcutKey, WorkspaceItemInfo> mShortcuts;
private final Map<String, InstantAppItemInfo> mInstantApps;
@@ -82,6 +86,7 @@
mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
mInstantAppResolver = InstantAppResolver.newInstance(context);
mOnUpdateCallback = onUpdateCallback;
+ mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
mShortcuts = new HashMap<>();
mInstantApps = new HashMap<>();
@@ -240,4 +245,35 @@
public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) {
return mShortcuts.get(key);
}
+
+ /**
+ * requests and caches icons for app targets
+ */
+ public void updateDependencies(List<ComponentKeyMapper> componentKeyMappers,
+ AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
+ List<String> instantAppsToLoad = new ArrayList<>();
+ List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
+ int total = componentKeyMappers.size();
+ for (int i = 0, count = 0; i < total && count < itemCount; i++) {
+ ComponentKeyMapper mapper = componentKeyMappers.get(i);
+ // Update instant apps
+ if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
+ instantAppsToLoad.add(mapper.getPackage());
+ count++;
+ } else if (mapper.getComponentKey() instanceof ShortcutKey) {
+ shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
+ count++;
+ } else {
+ // Reload high res icon
+ AppInfo info = (AppInfo) mapper.getApp(appsStore);
+ if (info != null) {
+ if (info.usingLowResIcon()) {
+ mIconCache.updateIconInBackground(callback, info);
+ }
+ count++;
+ }
+ }
+ }
+ cacheItems(shortcutsToLoad, instantAppsToLoad);
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 8fade5c..8338c2e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,15 +18,12 @@
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.content.ComponentName;
import android.content.Context;
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.HotseatPredictionController;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.ItemInfoWithIcon;
@@ -37,7 +34,6 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
-import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
@@ -90,8 +86,6 @@
private AllAppsContainerView mAppsView;
- private HotseatPredictionController mHotseatPredictionController;
-
private PredictionState mPendingState;
private PredictionState mCurrentState;
@@ -153,10 +147,6 @@
updateDependencies(mCurrentState);
}
- public void setHotseatPredictionController(HotseatPredictionController controller) {
- mHotseatPredictionController = controller;
- }
-
@Override
public void reapplyItemInfo(ItemInfoWithIcon info) { }
@@ -193,9 +183,6 @@
mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mCurrentState.apps);
}
- if (mHotseatPredictionController != null) {
- mHotseatPredictionController.setPredictedApps(mCurrentState.apps);
- }
}
private void updatePredictionStateAfterCallback() {
@@ -260,33 +247,8 @@
if (!state.isEnabled || mAppsView == null) {
return;
}
-
- IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
- List<String> instantAppsToLoad = new ArrayList<>();
- List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
- int total = state.apps.size();
- for (int i = 0, count = 0; i < total && count < mMaxIconsPerRow; i++) {
- ComponentKeyMapper mapper = state.apps.get(i);
- // Update instant apps
- if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
- instantAppsToLoad.add(mapper.getPackage());
- count++;
- } else if (mapper.getComponentKey() instanceof ShortcutKey) {
- shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
- count++;
- } else {
- // Reload high res icon
- AppInfo info = (AppInfo) mapper.getApp(mAppsView.getAppsStore());
- if (info != null) {
- if (info.usingLowResIcon()) {
- // TODO: Update icon cache to support null callbacks.
- iconCache.updateIconInBackground(this, info);
- }
- count++;
- }
- }
- }
- mDynamicItemCache.cacheItems(shortcutsToLoad, instantAppsToLoad);
+ mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
+ mMaxIconsPerRow);
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a6fcf44..eefb7dc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1,4 +1,4 @@
-/**
+/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -31,6 +31,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
@@ -137,7 +138,9 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mHotseatPredictionController = new HotseatPredictionController(this);
+ if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
+ mHotseatPredictionController = new HotseatPredictionController(this);
+ }
}
@Override
@@ -165,8 +168,12 @@
@Override
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
- return Stream.concat(super.getSupportedShortcuts(),
- Stream.of(mHotseatPredictionController));
+ if (mHotseatPredictionController != null) {
+ return Stream.concat(super.getSupportedShortcuts(),
+ Stream.of(mHotseatPredictionController));
+ } else {
+ return super.getSupportedShortcuts();
+ }
}
/**
@@ -187,7 +194,17 @@
@Override
public void finishBindingItems(int pageBoundFirst) {
super.finishBindingItems(pageBoundFirst);
- mHotseatPredictionController.fillGapsWithPrediction(false);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.fillGapsWithPrediction(false);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.destroy();
+ }
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index e4e60a0..f3cbc08 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -98,7 +98,7 @@
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
// Translate hotseat offscreen if we show it in overview.
ScaleAndTranslation scaleAndTranslation = super.getHotseatScaleAndTranslation(launcher);
- scaleAndTranslation.translationY = LayoutUtils.getShelfTrackingDistance(launcher,
+ scaleAndTranslation.translationY += LayoutUtils.getShelfTrackingDistance(launcher,
launcher.getDeviceProfile());
return scaleAndTranslation;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 93d4de1..25eaab1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -32,7 +32,6 @@
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
-import android.content.Context;
import android.graphics.Rect;
import android.view.View;
@@ -47,6 +46,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -91,8 +91,19 @@
@Override
public ScaleAndTranslation getHotseatScaleAndTranslation(Launcher launcher) {
if ((getVisibleElements(launcher) & HOTSEAT_ICONS) != 0) {
- // If the hotseat icons are visible in overview, keep them in their normal position.
- return super.getWorkspaceScaleAndTranslation(launcher);
+ DeviceProfile dp = launcher.getDeviceProfile();
+ if (dp.allAppsIconSizePx >= dp.iconSizePx) {
+ return new ScaleAndTranslation(1, 0, 0);
+ } else {
+ float scale = ((float) dp.allAppsIconSizePx) / dp.iconSizePx;
+ // Distance between the screen center (which is the pivotY for hotseat) and the
+ // bottom of the hotseat (which we want to preserve)
+ float distanceFromBottom = dp.heightPx / 2 - dp.hotseatBarBottomPaddingPx;
+ // On scaling, the bottom edge is moved closer to the pivotY. We move the
+ // hotseat back down so that the bottom edge's position is preserved.
+ float translationY = distanceFromBottom * (1 - scale);
+ return new ScaleAndTranslation(scale, 0, translationY);
+ }
}
return getWorkspaceScaleAndTranslation(launcher);
}
@@ -160,15 +171,7 @@
}
public static float getDefaultSwipeHeight(Launcher launcher) {
- return getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
- }
-
- public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
- float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
- if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
- swipeHeight -= dp.getInsets().bottom;
- }
- return swipeHeight;
+ return LayoutUtils.getDefaultSwipeHeight(launcher, launcher.getDeviceProfile());
}
@Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 1279270..7b4bb02 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -26,7 +26,7 @@
/**
* State to indicate we are about to launch a recent task. Note that this state is only used when
- * quick switching from launcher; quick switching from an app uses WindowTransformSwipeHelper.
+ * quick switching from launcher; quick switching from an app uses LauncherSwipeHandler.
* @see GestureState.GestureEndTarget#NEW_TASK
*/
public class QuickSwitchState extends BackgroundAppState {
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 1897fb2..ad4a343 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
@@ -22,6 +22,7 @@
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_QUICKSTEP_LIVE_TILE;
import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -138,8 +139,13 @@
if (!recentsView.isRtl()) {
pullbackDist = -pullbackDist;
}
- Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist);
+ ObjectAnimator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X,
+ pullbackDist);
pullback.setInterpolator(PULLBACK_INTERPOLATOR);
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ pullback.addUpdateListener(
+ valueAnimator -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
+ }
anim.play(pullback);
} else if (mStartState == ALL_APPS) {
AnimatorSetBuilder builder = new AnimatorSetBuilder();
@@ -192,6 +198,11 @@
boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
|| (velocity < 0 && fling);
if (success) {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.switchToScreenshot(null,
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
+ }
mLauncher.getStateManager().goToState(mEndState, true,
() -> onSwipeInteractionCompleted(mEndState));
if (mStartState != mEndState) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index b6cd456..24f247b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/FallbackNoButtonInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep.inputconsumers;
+package com.android.quickstep;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
@@ -22,7 +22,7 @@
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
@@ -40,26 +40,19 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.ObjectWrapper;
import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
-import com.android.quickstep.AnimatedFloat;
-import com.android.quickstep.BaseSwipeUpHandler;
-import com.android.quickstep.GestureState;
import com.android.quickstep.GestureState.GestureEndTarget;
-import com.android.quickstep.InputConsumer;
-import com.android.quickstep.MultiStateCallback;
-import com.android.quickstep.RecentsActivity;
-import com.android.quickstep.RecentsAnimationController;
-import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.InputConsumerController;
-public class FallbackNoButtonInputConsumer extends
- BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+/**
+ * Handles the navigation gestures when a 3rd party launcher is the default home activity.
+ */
+public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
@@ -108,7 +101,7 @@
private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
private RunningWindowAnim mFinishAnimation;
- public FallbackNoButtonInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+ public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState, InputConsumerController inputConsumer,
boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
super(context, deviceState, gestureState, inputConsumer);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 48b8fc6..844152b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -29,7 +29,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.WindowTransformSwipeHandler.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -47,20 +47,23 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherInitListenerEx;
+import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.uioverrides.states.OverviewState;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
@@ -210,7 +213,7 @@
@Override
public AnimationFactory prepareRecentsUI(boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
- Launcher launcher = getCreatedActivity();
+ BaseQuickstepLauncher launcher = getCreatedActivity();
final LauncherState startState = launcher.getStateManager().getState();
LauncherState resetState = startState;
@@ -226,7 +229,7 @@
launcher.getAppsView().reset(false /* animate */);
return new AnimationFactory() {
- private ShelfAnimState mShelfState;
+ private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim();
private boolean mIsAttachedToWindow;
@Override
@@ -255,30 +258,7 @@
@Override
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
long duration) {
- if (mShelfState == shelfState) {
- return;
- }
- mShelfState = shelfState;
- launcher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
- if (mShelfState == ShelfAnimState.CANCEL) {
- return;
- }
- float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(launcher);
- float shelfOverviewProgress = OVERVIEW.getVerticalProgress(launcher);
- // Peek based on default overview progress so we can see hotseat if we're showing
- // that instead of predictions in overview.
- float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(launcher);
- float shelfPeekingProgress = shelfHiddenProgress
- - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
- float toProgress = mShelfState == ShelfAnimState.HIDE
- ? shelfHiddenProgress
- : mShelfState == ShelfAnimState.PEEK
- ? shelfPeekingProgress
- : shelfOverviewProgress;
- Animator shelfAnim = launcher.getStateManager()
- .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
- shelfAnim.setInterpolator(interpolator);
- shelfAnim.setDuration(duration).start();
+ mShelfAnim.setShelfState(shelfState, interpolator, duration);
}
@Override
@@ -413,14 +393,14 @@
@Override
public ActivityInitListener createActivityInitListener(Predicate<Boolean> onInitListener) {
- return new LauncherInitListenerEx((activity, alreadyOnHome) ->
+ return new LauncherInitListener((activity, alreadyOnHome) ->
onInitListener.test(alreadyOnHome));
}
@Nullable
@Override
- public Launcher getCreatedActivity() {
- return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+ public BaseQuickstepLauncher getCreatedActivity() {
+ return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
}
@Nullable
@@ -541,4 +521,14 @@
}
launcher.setOnDeferredActivityLaunchCallback(r);
}
+
+ @Override
+ public void updateOverviewPredictionState() {
+ Launcher launcher = getCreatedActivity();
+ if (launcher == null) {
+ return;
+ }
+ PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
+ PredictionUiStateManager.Client.OVERVIEW);
+ }
}
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index b55ec20..c5cded0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -24,14 +24,15 @@
import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
-import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.HIDE;
-import static com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState.PEEK;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
+import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
@@ -68,13 +69,14 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
-import com.android.quickstep.BaseActivityInterface.AnimationFactory.ShelfAnimState;
import com.android.quickstep.BaseActivityInterface.HomeAnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
import com.android.quickstep.util.RectFSpringAnim;
+import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -83,10 +85,13 @@
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+/**
+ * Handles the navigation gestures when Launcher is the default home activity.
+ */
@TargetApi(Build.VERSION_CODES.O)
-public class WindowTransformSwipeHandler<T extends BaseDraggingActivity>
+public class LauncherSwipeHandler<T extends BaseDraggingActivity>
extends BaseSwipeUpHandler<T, RecentsView> implements OnApplyWindowInsetsListener {
- private static final String TAG = WindowTransformSwipeHandler.class.getSimpleName();
+ private static final String TAG = LauncherSwipeHandler.class.getSimpleName();
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@@ -147,7 +152,6 @@
Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
private static final String SCREENSHOT_CAPTURED_EVT = "ScreenshotCaptured";
- private static final long SHELF_ANIM_DURATION = 240;
public static final long RECENTS_ATTACH_DURATION = 300;
/**
@@ -189,7 +193,7 @@
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
- public WindowTransformSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+ public LauncherSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
@@ -293,6 +297,12 @@
}
setupRecentsViewUi();
+
+ if (mDeviceState.getNavMode() == TWO_BUTTONS) {
+ // If the device is in two button mode, swiping up will show overview with predictions
+ // so we need to kick off switching to the overview predictions as soon as possible
+ mActivityInterface.updateOverviewPredictionState();
+ }
return true;
}
@@ -422,7 +432,13 @@
@Override
public void onMotionPauseChanged(boolean isPaused) {
- setShelfState(isPaused ? PEEK : HIDE, OVERSHOOT_1_2, SHELF_ANIM_DURATION);
+ setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
+
+ if (mDeviceState.isFullyGesturalNavMode() && isPaused) {
+ // In fully gestural nav mode, switch to overview predictions once the user has paused
+ // (this is a no-op if the predictions are already in that state)
+ mActivityInterface.updateOverviewPredictionState();
+ }
}
public void maybeUpdateRecentsAttachedState() {
@@ -599,7 +615,9 @@
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
super.onRecentsAnimationCanceled(thumbnailData);
- mRecentsView.setRecentsAnimationTargets(null, null);
+ if (mRecentsView != null) {
+ mRecentsView.setRecentsAnimationTargets(null, null);
+ }
mActivityInitListener.unregister();
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
@@ -666,7 +684,7 @@
endLauncherTransitionController();
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// Hide the task view, if not already hidden
- setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha);
+ setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha);
}
BaseDraggingActivity activity = mActivityInterface.getCreatedActivity();
@@ -1028,6 +1046,15 @@
@UiThread
private void startNewTask() {
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
+ } else {
+ startNewTaskInternal();
+ }
+ }
+
+ @UiThread
+ private void startNewTaskInternal() {
startNewTask(STATE_HANDLER_INVALIDATED, success -> {
if (!success) {
// We couldn't launch the task, so take user to overview so they can
@@ -1163,20 +1190,21 @@
private void finishCurrentTransitionToRecents() {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
- } else if (!hasTargets()) {
- // If there are no targets, then there is nothing to finish
+ } else if (!hasTargets() || mRecentsAnimationController == null) {
+ // If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
- synchronized (mRecentsAnimationController) {
- mRecentsAnimationController.finish(true /* toRecents */,
- () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
- }
+ mRecentsAnimationController.finish(true /* toRecents */,
+ () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
}
private void finishCurrentTransitionToHome() {
- synchronized (mRecentsAnimationController) {
+ if (!hasTargets() || mRecentsAnimationController == null) {
+ // If there are no targets or the animation not started, then there is nothing to finish
+ mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
+ } else {
mRecentsAnimationController.finish(true /* toRecents */,
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
true /* sendUserLeaveHint */);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index 7f53d83..c4733bd 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -29,6 +29,7 @@
import androidx.annotation.BinderThread;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.appprediction.PredictionUiStateManager;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.util.ActivityInitListener;
@@ -212,6 +213,10 @@
LauncherLogProto.ContainerType.TASKSWITCHER);
mUserEventLogged = true;
}
+
+ // Switch prediction client to overview
+ PredictionUiStateManager.INSTANCE.get(activity).switchClient(
+ PredictionUiStateManager.Client.OVERVIEW);
return mAnimationProvider.onActivityReady(activity, wasVisible);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 19c289d..92c55da 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -10,7 +10,6 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
@@ -34,7 +33,7 @@
switch (method) {
case TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT: {
final float swipeHeight =
- OverviewState.getDefaultSwipeHeight(mContext, mDeviceProfile);
+ LayoutUtils.getDefaultSwipeHeight(mContext, mDeviceProfile);
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) swipeHeight);
return response;
}
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 7ad3f37..71f8ba4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -66,7 +66,6 @@
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
-import com.android.quickstep.inputconsumers.FallbackNoButtonInputConsumer;
import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
import com.android.quickstep.inputconsumers.OverscrollInputConsumer;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -242,10 +241,10 @@
return sIsInitialized;
}
- private final BaseSwipeUpHandler.Factory mWindowTransformFactory =
- this::createWindowTransformSwipeHandler;
- private final BaseSwipeUpHandler.Factory mFallbackNoButtonFactory =
- this::createFallbackNoButtonSwipeHandler;
+ private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
+ this::createLauncherSwipeHandler;
+ private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
+ this::createFallbackSwipeHandler;
private ActivityManagerWrapper mAM;
private OverviewCommandHelper mOverviewCommandHelper;
@@ -564,11 +563,11 @@
if (mDeviceState.isFullyGesturalNavMode()
&& !mOverviewComponentObserver.isHomeAndOverviewSame()) {
shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
- factory = mFallbackNoButtonFactory;
+ factory = mFallbackSwipeHandlerFactory;
} else {
shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
event);
- factory = mWindowTransformFactory;
+ factory = mLauncherSwipeHandlerFactory;
}
final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
@@ -716,15 +715,15 @@
}
}
- private BaseSwipeUpHandler createWindowTransformSwipeHandler(GestureState gestureState,
+ private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
- return new WindowTransformSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+ return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
- private BaseSwipeUpHandler createFallbackNoButtonSwipeHandler(GestureState gestureState,
+ private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
- return new FallbackNoButtonInputConsumer(this, mDeviceState, gestureState,
+ return new FallbackSwipeHandler(this, mDeviceState, gestureState,
mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index e448c5b..5a34520 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -22,7 +22,7 @@
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
import android.content.ComponentName;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 24e7f0e..4a39e73 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -169,7 +169,7 @@
return null;
}
- float progress = params.progress;
+ float progress = Utilities.boundToRange(params.progress, 0, 1);
updateCurrentRect(params);
SurfaceParams[] surfaceParams = new SurfaceParams[params.targetSet.unfilteredApps.length];
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
new file mode 100644
index 0000000..41be683
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.INDEX_SHELF_ANIM;
+import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.uioverrides.states.OverviewState;
+
+/**
+ * Animates the shelf between states HIDE, PEEK, and OVERVIEW.
+ */
+public class ShelfPeekAnim {
+
+ public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
+ public static final long DURATION = 240;
+
+ private final Launcher mLauncher;
+
+ private ShelfAnimState mShelfState;
+ private boolean mIsPeeking;
+
+ public ShelfPeekAnim(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ /**
+ * Animates to the given state, canceling the previous animation if it was still running.
+ */
+ public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
+ if (mShelfState == shelfState) {
+ return;
+ }
+ mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
+ mShelfState = shelfState;
+ mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE;
+ if (mShelfState == ShelfAnimState.CANCEL) {
+ return;
+ }
+ float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher);
+ float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
+ // Peek based on default overview progress so we can see hotseat if we're showing
+ // that instead of predictions in overview.
+ float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
+ float shelfPeekingProgress = shelfHiddenProgress
+ - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
+ float toProgress = mShelfState == ShelfAnimState.HIDE
+ ? shelfHiddenProgress
+ : mShelfState == ShelfAnimState.PEEK
+ ? shelfPeekingProgress
+ : shelfOverviewProgress;
+ Animator shelfAnim = mLauncher.getStateManager()
+ .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
+ shelfAnim.setInterpolator(interpolator);
+ shelfAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mShelfState = ShelfAnimState.CANCEL;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mIsPeeking = mShelfState == ShelfAnimState.PEEK;
+ }
+ });
+ shelfAnim.setDuration(duration).start();
+ }
+
+ /** @return Whether the shelf is currently peeking or animating to or from peeking. */
+ public boolean isPeeking() {
+ return mIsPeeking;
+ }
+
+ /** The various shelf states we can animate to. */
+ public enum ShelfAnimState {
+ HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
+
+ ShelfAnimState(boolean shouldPreformHaptic) {
+ this.shouldPreformHaptic = shouldPreformHaptic;
+ }
+
+ public final boolean shouldPreformHaptic;
+ }
+}
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 c875ba0..cf616fe 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
@@ -1742,7 +1742,17 @@
return;
}
- mRecentsAnimationController.finish(toRecents, onFinishComplete);
+ mRecentsAnimationController.finish(toRecents, () -> {
+ if (onFinishComplete != null) {
+ onFinishComplete.run();
+ // After we finish the recents animation, the current task id should be correctly
+ // reset so that when the task is launched from Overview later, it goes through the
+ // flow of starting a new task instead of finishing recents animation to app. A
+ // typical example of this is (1) user swipes up from app to Overview (2) user
+ // taps on QSB (3) user goes back to Overview and launch the most recent task.
+ setCurrentTask(-1);
+ }
+ });
}
public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 57327f8..a1775f4 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -287,11 +287,19 @@
public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
Handler resultCallbackHandler) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ RecentsView recentsView = getRecentsView();
if (isRunningTask()) {
- getRecentsView().finishRecentsAnimation(false /* toRecents */,
+ recentsView.finishRecentsAnimation(false /* toRecents */,
() -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
} else {
- launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
+ // This is a workaround against the WM issue that app open is not correctly animated
+ // when recents animation is being cleaned up (b/143774568). When that's possible,
+ // we should rely on the framework side to cancel the recents animation, and we will
+ // clean up the screenshot on the launcher side while we launch the next task.
+ recentsView.switchToScreenshot(null,
+ () -> recentsView.finishRecentsAnimation(true /* toRecents */,
+ () -> launchTaskInternal(animate, freezeTaskList, resultCallback,
+ resultCallbackHandler)));
}
} else {
launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index fc9cfcd..9ea13c6 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -48,6 +48,7 @@
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.RemoteFadeOutAnimationListener;
+import com.android.quickstep.util.ShelfPeekAnim;
import java.util.stream.Stream;
@@ -64,6 +65,8 @@
(context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
Float.intBitsToFloat(arg1), arg2 != 0);
+ private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -270,4 +273,8 @@
return Stream.concat(super.getSupportedShortcuts(),
Stream.of(WellbeingModel.SHORTCUT_FACTORY));
}
+
+ public ShelfPeekAnim getShelfPeekAnim() {
+ return mShelfPeekAnim;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 663b125..96340b2 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -22,6 +22,7 @@
import android.os.CancellationSignal;
import android.os.Handler;
+import com.android.launcher3.util.ActivityTracker;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
@@ -32,6 +33,11 @@
private RemoteAnimationProvider mRemoteAnimationProvider;
+ /**
+ * @param onInitListener a callback made when the activity is initialized. The callback should
+ * return true to continue receiving callbacks (ie. for if the activity is
+ * recreated).
+ */
public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
super(onInitListener, Launcher.ACTIVITY_TRACKER);
}
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 852a08e..5aa4388 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -335,8 +335,8 @@
* Shortcut factory for generating wellbeing action
*/
public static final SystemShortcut.Factory SHORTCUT_FACTORY = (activity, info) ->
- WellbeingModel.get(activity).getShortcutForApp(
- info.getTargetComponent().getPackageName(),
- info.user.getIdentifier(),
- activity, info);
+ (info.getTargetComponent() == null) ? null : WellbeingModel.get(activity)
+ .getShortcutForApp(
+ info.getTargetComponent().getPackageName(), info.user.getIdentifier(),
+ activity, info);
}
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index cb18001..fd55e07 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -32,6 +32,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.ShelfPeekAnim;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -89,6 +90,13 @@
}
/**
+ * Updates the prediction state to the overview state.
+ */
+ default void updateOverviewPredictionState() {
+ // By default overview predictions are not supported
+ }
+
+ /**
* Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
*/
int getContainerType();
@@ -106,16 +114,6 @@
interface AnimationFactory {
- enum ShelfAnimState {
- HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
-
- ShelfAnimState(boolean shouldPreformHaptic) {
- this.shouldPreformHaptic = shouldPreformHaptic;
- }
-
- public final boolean shouldPreformHaptic;
- }
-
default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
void createActivityInterface(long transitionLength);
@@ -124,8 +122,8 @@
default void onTransitionCancelled() { }
- default void setShelfState(ShelfAnimState animState, Interpolator interpolator,
- long duration) { }
+ default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
+ Interpolator interpolator, long duration) { }
/**
* @param attached Whether to show RecentsView alongside the app window. If false, recents
diff --git a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
index fe37d60..b1c72ce 100644
--- a/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
+++ b/quickstep/src/com/android/quickstep/util/ActivityInitListener.java
@@ -31,6 +31,11 @@
private final BiPredicate<T, Boolean> mOnInitListener;
private final ActivityTracker<T> mActivityTracker;
+ /**
+ * @param onInitListener a callback made when the activity is initialized. The callback should
+ * return true to continue receiving callbacks (ie. for if the activity is
+ * recreated).
+ */
public ActivityInitListener(BiPredicate<T, Boolean> onInitListener,
ActivityTracker<T> tracker) {
mOnInitListener = onInitListener;
@@ -42,6 +47,10 @@
return mOnInitListener.test(activity, alreadyOnHome);
}
+ /**
+ * Registers the activity-created listener. If the activity is already created, then the
+ * callback provided in the constructor will be called synchronously.
+ */
public void register() {
mActivityTracker.schedule(this);
}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index 050bdff..2e118b4 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -26,7 +26,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.SysUINavigationMode;
import java.lang.annotation.Retention;
@@ -39,12 +39,27 @@
@IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
private @interface MultiWindowStrategy {}
+ /**
+ * The height for the swipe up motion
+ */
+ public static float getDefaultSwipeHeight(Context context, DeviceProfile dp) {
+ float swipeHeight = dp.allAppsCellHeightPx - dp.allAppsIconTextSizePx;
+ if (SysUINavigationMode.getMode(context) == SysUINavigationMode.Mode.NO_BUTTON) {
+ swipeHeight -= dp.getInsets().bottom;
+ }
+ return swipeHeight;
+ }
+
public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
float extraSpace;
if (dp.isVerticalBarLayout()) {
extraSpace = 0;
} else {
- extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
+ Resources res = context.getResources();
+
+ extraSpace = getDefaultSwipeHeight(context, dp) + dp.verticalDragHandleSizePx
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
+ + res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_bottom_padding);
}
calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
}
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 26e9eaf..0e591ca 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -38,12 +38,12 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.util.LayoutUtils;
/**
* Scrim used for all-apps and shelf in Overview
@@ -163,7 +163,7 @@
int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom
+ hotseatPadding.bottom + hotseatPadding.top;
float dragHandleTop =
- Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp));
+ Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
mDragHandleProgress = 1 - (dragHandleTop / mShiftRange);
}
mTopOffset = dp.getInsets().top - mShelfOffset;
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 13731b6..ca81343 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -175,7 +175,7 @@
}
result[0] = f.apply(activity);
return true;
- }).get(), DEFAULT_UI_TIMEOUT);
+ }).get(), DEFAULT_UI_TIMEOUT, mLauncher);
return (T) result[0];
}
@@ -196,7 +196,7 @@
startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
Wait.atMost("Expected three apps in the task list",
- () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT);
+ () -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
BaseOverview overview = mLauncher.getBackground().switchToOverview();
executeOnRecents(recents ->
@@ -255,7 +255,8 @@
private void startAppFastAndWaitForRecentTask(String packageName) {
startAppFast(packageName);
Wait.atMost("Expected app in task list",
- () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT);
+ () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT,
+ mLauncher);
}
private boolean containsRecentTaskWithPackage(String packageName) {
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index c2197ab..fa4c7b9 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageManager;
import android.util.Log;
+import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -80,7 +81,13 @@
Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
return new Statement() {
private void assertTrue(String message, boolean condition) {
- if(!condition) {
+ if (mLauncher.getDevice().hasObject(By.textStartsWith(""))) {
+ // The condition above is "screen is not empty". We are not treating
+ // "Screen is empty" as an anomaly here. It's an acceptable state when
+ // Launcher just starts under instrumentation.
+ mLauncher.checkForAnomaly();
+ }
+ if (!condition) {
final AssertionError assertionError = new AssertionError(message);
FailureWatcher.onError(mLauncher.getDevice(), description, assertionError);
throw assertionError;
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 8cd3bb6..428e647 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -248,6 +248,7 @@
@Test
@NavigationModeSwitch
@PortraitLandscape
+ @Ignore // b/143285809
public void testQuickSwitchFromApp() throws Exception {
startTestActivity(2);
startTestActivity(3);
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ff5f33d..e6f8a85 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -216,6 +216,7 @@
cancelDotScaleAnim();
mDotParams.scale = 0f;
mForceHideDot = false;
+ setBackground(null);
}
private void cancelDotScaleAnim() {
@@ -496,7 +497,8 @@
// Text should be visible everywhere but the hotseat.
Object tag = getParent() instanceof FolderIcon ? ((View) getParent()).getTag() : getTag();
ItemInfo info = tag instanceof ItemInfo ? (ItemInfo) tag : null;
- return info == null || info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+ return info == null || (info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT
+ && info.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
}
public void setTextVisibility(boolean visible) {
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 125332d..21359f1 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
@@ -44,6 +45,7 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.icons.LauncherIcons;
@@ -489,9 +491,13 @@
return Pair.create(si, null);
} else if (shortcutInfo != null) {
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
- LauncherIcons li = LauncherIcons.obtain(mContext);
- itemInfo.bitmap = li.createShortcutIcon(shortcutInfo);
- li.recycle();
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true);
+ } else {
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ itemInfo.bitmap = li.createShortcutIcon(shortcutInfo);
+ li.recycle();
+ }
return Pair.create(itemInfo, shortcutInfo);
} else if (providerInfo != null) {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8d673e9..aafabff 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -36,7 +36,6 @@
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
-import static com.android.launcher3.testing.TestProtocol.CRASH_ADD_CUSTOM_SHORTCUT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -122,7 +121,6 @@
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -1215,13 +1213,8 @@
*/
private void completeAddShortcut(Intent data, int container, int screenId, int cellX,
int cellY, PendingRequestArgs args) {
- if (data == null
- || args.getRequestCode() != REQUEST_CREATE_SHORTCUT
+ if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT
|| args.getPendingIntent().getComponent() == null) {
- if (data == null && TestProtocol.sDebugTracing) {
- Log.d(CRASH_ADD_CUSTOM_SHORTCUT,
- "Failed to add custom shortcut: Intent is null, args = " + args);
- }
return;
}
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 8dedc6c..e0c50e2 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -71,8 +71,13 @@
SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
- PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
+ if (!PackageInstaller.ACTION_SESSION_COMMITTED.equals(intent.getAction())
+ || info == null || user == null) {
+ // Invalid intent.
+ return;
+ }
+ PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context);
if (TextUtils.isEmpty(info.getAppPackageName())
|| info.getInstallReason() != PackageManager.INSTALL_REASON_USER
|| packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 2032845..2bec0ba 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -586,7 +586,7 @@
LauncherIcons li = LauncherIcons.obtain(appState.getContext());
Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).bitmap.icon;
li.recycle();
- float badgeSize = iconSize * LauncherIcons.getBadgeSizeForIconSize(iconSize);
+ float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize);
float insetFraction = (iconSize - badgeSize) / iconSize;
return new InsetDrawable(new FastBitmapDrawable(badge),
insetFraction, insetFraction, 0, 0);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 40c6b5f..7a7e1fe 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -38,7 +38,6 @@
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
/**
@@ -96,14 +95,13 @@
propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
if (!hotseat.getRotationMode().isTransposed) {
- // Set the hotseat's pivot point to match the workspace's, so that it scales together.
- DragLayer dragLayer = mLauncher.getDragLayer();
- float[] workspacePivot =
- new float[]{ mWorkspace.getPivotX(), mWorkspace.getPivotY() };
- dragLayer.getDescendantCoordRelativeToSelf(mWorkspace, workspacePivot);
- dragLayer.mapCoordInSelfToDescendant(hotseat, workspacePivot);
- hotseat.setPivotX(workspacePivot[0]);
- hotseat.setPivotY(workspacePivot[1]);
+ // Set the hotseat's pivot point to match the workspace's, so that it scales
+ // together. Since both hotseat and workspace can move, transform the point
+ // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
+ // related methods.
+ hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
+ hotseat.setPivotX(mWorkspace.getPivotX()
+ + mWorkspace.getLeft() - hotseat.getLeft());
}
float hotseatScale = hotseatScaleAndTranslation.scale;
Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 3836c9f..08ce9c2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -2,8 +2,6 @@
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
@@ -134,15 +132,6 @@
} else {
mLauncher.getSystemUiController().updateUiState(UI_STATE_ALL_APPS, 0);
}
-
- if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
- // Translate hotseat with the shelf until reaching overview.
- float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
- if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
- float hotseatShift = (progress - overviewProgress) * mShiftRange;
- mLauncher.getHotseat().setTranslationY(hotseatShift);
- }
- }
}
public float getProgress() {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index e798038..f4b705e 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -131,6 +131,9 @@
public static final TogglableFlag ENABLE_HYBRID_HOTSEAT = new TogglableFlag(
"ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
+ public static final TogglableFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = new TogglableFlag(
+ "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
+
public static void initialize(Context context) {
// Avoid the disk read for user builds
if (Utilities.IS_DEBUG_DEVICE) {
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 54b363e..3b5fd59 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -277,6 +277,7 @@
page.removeAllViews();
pages.add(page);
}
+ mOrganizer.setFolderInfo(mFolder.getInfo());
setupContentDimensions(itemCount);
Iterator<CellLayout> pageItr = pages.iterator();
@@ -285,7 +286,6 @@
int position = 0;
int rank = 0;
- mOrganizer.setFolderInfo(mFolder.getInfo());
for (int i = 0; i < itemCount; i++) {
View v = list.size() > i ? list.get(i) : null;
if (currentPage == null || position >= mOrganizer.getMaxItemsPerPage()) {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index f7faca6..4ac6ff4 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -28,6 +28,7 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Process;
@@ -49,6 +50,7 @@
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
@@ -65,6 +67,7 @@
private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
+ private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
private final LauncherApps mLauncherApps;
private final UserManagerCompat mUserManager;
@@ -78,6 +81,7 @@
inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
+ mShortcutCachingLogic = new ShortcutCachingLogic();
mLauncherApps = mContext.getSystemService(LauncherApps.class);
mUserManager = UserManagerCompat.getInstance(mContext);
mInstantAppResolver = InstantAppResolver.newInstance(mContext);
@@ -176,6 +180,14 @@
}
/**
+ * 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.
*/
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 3c4041a..4d3599e 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -25,12 +25,14 @@
import android.os.Process;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
@@ -112,7 +114,6 @@
}
// below methods should also migrate to BaseIconFactory
-
public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo) {
return createShortcutIcon(shortcutInfo, true /* badged */);
}
@@ -121,12 +122,20 @@
return createShortcutIcon(shortcutInfo, badged, null);
}
- public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo,
- boolean badged, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
+ public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged,
+ @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ return createShortcutIconCached(shortcutInfo, badged, true, fallbackIconProvider);
+ } else {
+ return createShortcutIconLegacy(shortcutInfo, badged, fallbackIconProvider);
+ }
+ }
+
+ public BitmapInfo createShortcutIconLegacy(ShortcutInfo shortcutInfo, boolean badged,
+ @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
-
final Bitmap unbadgedBitmap;
if (unbadgedDrawable != null) {
unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
@@ -155,6 +164,44 @@
return BitmapInfo.of(icon, badge.bitmap.color);
}
+ @WorkerThread
+ public BitmapInfo createShortcutIconCached(ShortcutInfo shortcutInfo, boolean badged,
+ boolean useCache, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
+ IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
+ final BitmapInfo bitmapInfo;
+ if (useCache) {
+ bitmapInfo = cache.getDeepShortcutTitleAndIcon(shortcutInfo).bitmap;
+ } else {
+ bitmapInfo = new ShortcutCachingLogic().loadIcon(mContext, shortcutInfo);
+ }
+ final Bitmap unbadgedBitmap;
+ if (bitmapInfo.icon != null) {
+ unbadgedBitmap = bitmapInfo.icon;
+ } else {
+ if (fallbackIconProvider != null) {
+ // Fallback icons are already badged and with appropriate shadow
+ ItemInfoWithIcon fullIcon = fallbackIconProvider.get();
+ if (fullIcon != null && fullIcon.bitmap != null) {
+ return fullIcon.bitmap;
+ }
+ }
+ unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
+ }
+
+ if (!badged) {
+ return BitmapInfo.of(unbadgedBitmap, Themes.getColorAccent(mContext));
+ }
+
+ final Bitmap unbadgedfinal = unbadgedBitmap;
+ final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
+
+ Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
+ getShadowGenerator().recreateIcon(unbadgedfinal, c);
+ badgeWithDrawable(c, new FastBitmapDrawable(badge.bitmap));
+ });
+ return BitmapInfo.of(icon, badge.bitmap.color);
+ }
+
public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) {
ComponentName cn = shortcutInfo.getActivity();
String badgePkg = shortcutInfo.getPackage();
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
new file mode 100644
index 0000000..5c21470
--- /dev/null
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.cache.CachingLogic;
+import com.android.launcher3.shortcuts.DeepShortcutManager;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.Themes;
+
+/**
+ * Caching logic for shortcuts.
+ */
+public class ShortcutCachingLogic implements CachingLogic<ShortcutInfo> {
+
+ @Override
+ public ComponentName getComponent(ShortcutInfo info) {
+ return ShortcutKey.fromInfo(info).componentName;
+ }
+
+ @Override
+ public UserHandle getUser(ShortcutInfo info) {
+ return info.getUserHandle();
+ }
+
+ @Override
+ public CharSequence getLabel(ShortcutInfo info) {
+ return info.getShortLabel();
+ }
+
+ @NonNull
+ @Override
+ public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
+ .getShortcutIconDrawable(info, LauncherAppState.getIDP(context).fillResIconDpi);
+ if (unbadgedDrawable == null) return BitmapInfo.LOW_RES_INFO;
+ return new BitmapInfo(li.createScaledBitmapWithoutShadow(
+ unbadgedDrawable, 0), Themes.getColorAccent(context));
+ }
+ }
+
+ @Override
+ public long getLastUpdatedTime(ShortcutInfo shortcutInfo, PackageInfo info) {
+ if (shortcutInfo == null || !FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ return info.lastUpdateTime;
+ }
+ return Math.max(shortcutInfo.getLastChangedTimestamp(), info.lastUpdateTime);
+ }
+
+ @Override
+ public boolean addToMemCache() {
+ return false;
+ }
+}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index b46f465..fa0fe1b 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -140,6 +140,15 @@
// or app was already installed for another user.
itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
.makeWorkspaceItem();
+
+ if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
+ // We need this additional check here since we treat all auto added
+ // workspace items as promise icons. At this point we now have the
+ // correct intent to compare against existing workspace icons.
+ // Icon already exists on the workspace and should not be auto-added.
+ continue;
+ }
+
WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
wii.title = "";
wii.bitmap = app.getIconCache().getDefaultIcon(item.user);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 6383a5f..3a4085c 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -63,6 +63,7 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherActivityCachingLogic;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.PackageInstallInfo;
@@ -71,7 +72,6 @@
import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
@@ -174,7 +174,8 @@
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
TimingLogger logger = new TimingLogger(TAG, "run");
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
- loadWorkspace();
+ List<ShortcutInfo> allShortcuts = new ArrayList<>();
+ loadWorkspace(allShortcuts);
logger.addSplit("loadWorkspace");
verifyNotStopped();
@@ -206,19 +207,33 @@
mApp.getModel()::onPackageIconsUpdated);
logger.addSplit("update icon cache");
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ verifyNotStopped();
+ logger.addSplit("save shortcuts in icon cache");
+ updateHandler.updateIcons(allShortcuts, new ShortcutCachingLogic(),
+ mApp.getModel()::onPackageIconsUpdated);
+ }
+
// Take a break
waitForIdle();
logger.addSplit("step 2 complete");
verifyNotStopped();
// third step
- loadDeepShortcuts();
+ List<ShortcutInfo> allDeepShortcuts = loadDeepShortcuts();
logger.addSplit("loadDeepShortcuts");
verifyNotStopped();
mResults.bindDeepShortcuts();
logger.addSplit("bindDeepShortcuts");
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ verifyNotStopped();
+ logger.addSplit("save deep shortcuts in icon cache");
+ updateHandler.updateIcons(allDeepShortcuts,
+ new ShortcutCachingLogic(), (pkgs, user) -> { });
+ }
+
// Take a break
waitForIdle();
logger.addSplit("step 3 complete");
@@ -256,7 +271,7 @@
this.notify();
}
- private void loadWorkspace() {
+ private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -512,6 +527,7 @@
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
}
intent = info.intent;
+ allDeepShortcuts.add(pinnedShortcut);
} else {
// Create a shortcut info in disabled mode for now.
info = c.loadSimpleWorkspaceItem();
@@ -860,7 +876,8 @@
return allActivityList;
}
- private void loadDeepShortcuts() {
+ private List<ShortcutInfo> loadDeepShortcuts() {
+ List<ShortcutInfo> allShortcuts = new ArrayList<>();
mBgDataModel.deepShortcutMap.clear();
mBgDataModel.hasShortcutHostPermission = mShortcutManager.hasHostPermission();
if (mBgDataModel.hasShortcutHostPermission) {
@@ -868,10 +885,12 @@
if (mUserManager.isUserUnlocked(user)) {
List<ShortcutInfo> shortcuts =
mShortcutManager.queryForAllShortcuts(user);
+ allShortcuts.addAll(shortcuts);
mBgDataModel.updateDeepShortcutCounts(null, user, shortcuts);
}
}
}
+ return allShortcuts;
}
public static boolean isValidProvider(AppWidgetProviderInfo provider) {
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index e13645c..a15399d 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -17,6 +17,7 @@
package com.android.launcher3.pm;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
import android.annotation.TargetApi;
import android.content.Context;
@@ -31,6 +32,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.LauncherIcons;
public class PinRequestHelper {
@@ -81,11 +83,15 @@
ShortcutInfo si = request.getShortcutInfo();
WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
// Apply the unbadged icon and fetch the actual icon asynchronously.
- LauncherIcons li = LauncherIcons.obtain(context);
- info.bitmap = li.createShortcutIcon(si, false /* badged */);
- li.recycle();
- LauncherAppState.getInstance(context).getModel()
- .updateAndBindWorkspaceItem(info, si);
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ fetchAndUpdateShortcutIconAsync(context, info, si, false);
+ } else {
+ LauncherIcons li = LauncherIcons.obtain(context);
+ info.bitmap = li.createShortcutIcon(si, false /* badged */);
+ li.recycle();
+ LauncherAppState.getInstance(context).getModel()
+ .updateAndBindWorkspaceItem(info, si);
+ }
return info;
} else {
return null;
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 28000b9..98f7fd8 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -360,10 +360,14 @@
final TimeInterpolator revealInterpolator = ACCEL_DEACCEL;
// Rectangular reveal.
+ mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
.createRevealAnimator(this, false);
revealAnim.setDuration(revealDuration);
revealAnim.setInterpolator(revealInterpolator);
+ // Clip the popup to the initial outline while the notification dot and arrow animate.
+ revealAnim.start();
+ revealAnim.pause();
ValueAnimator fadeIn = ValueAnimator.ofFloat(0, 1);
fadeIn.setDuration(revealDuration + arrowDuration);
@@ -399,7 +403,6 @@
if (!mIsOpen) {
return;
}
- mEndRect.setEmpty();
if (getOutlineProvider() instanceof RevealOutlineAnimation) {
((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect);
}
@@ -471,9 +474,6 @@
mStartRect.set(arrowCenterX - halfArrowWidth, arrowCenterY, arrowCenterX + halfArrowWidth,
arrowCenterY);
- if (mEndRect.isEmpty()) {
- mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
- }
return new RoundedRectRevealOutlineProvider
(arrowCornerRadius, mOutlineRadius, mStartRect, mEndRect);
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 79b41c1..e70673a 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -570,8 +570,11 @@
@Override
protected void closeComplete() {
- mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
- mOriginalIcon.setForceHideDot(false);
+ PopupContainerWithArrow openPopup = getOpen(mLauncher);
+ if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) {
+ mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
+ mOriginalIcon.setForceHideDot(false);
+ }
super.closeComplete();
}
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index ee97641..3e59b61 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -25,7 +25,9 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.icons.BitmapRenderer;
/**
* Extension of {@link DragPreviewProvider} which generates bitmaps scaled to the default icon size.
@@ -39,16 +41,27 @@
mPositionShift = shift;
}
+ @Override
public Bitmap createDragBitmap() {
+ if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+ int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
+ return BitmapRenderer.createHardwareBitmap(
+ size + blurSizeOutline,
+ size + blurSizeOutline,
+ (c) -> drawDragViewOnBackground(c, size));
+ } else {
+ return createDragBitmapLegacy();
+ }
+ }
+
+ private Bitmap createDragBitmapLegacy() {
Drawable d = mView.getBackground();
Rect bounds = getDrawableBounds(d);
-
int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
final Bitmap b = Bitmap.createBitmap(
size + blurSizeOutline,
size + blurSizeOutline,
Bitmap.Config.ARGB_8888);
-
Canvas canvas = new Canvas(b);
canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
canvas.scale(((float) size) / bounds.width(), ((float) size) / bounds.height(), 0, 0);
@@ -57,6 +70,16 @@
return b;
}
+ private void drawDragViewOnBackground(Canvas canvas, float size) {
+ Drawable d = mView.getBackground();
+ Rect bounds = getDrawableBounds(d);
+
+ canvas.translate(blurSizeOutline / 2, blurSizeOutline / 2);
+ canvas.scale(size / bounds.width(), size / bounds.height(), 0, 0);
+ canvas.translate(bounds.left, bounds.top);
+ d.draw(canvas);
+ }
+
@Override
public float getScaleAndPosition(Bitmap preview, int[] outPos) {
Launcher launcher = Launcher.getLauncher(mView.getContext());
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index d686e95..1cfa4af 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -84,5 +84,4 @@
public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
public static final String NO_DRAG_TO_WORKSPACE = "b/138729456";
public static final String APP_NOT_DISABLED = "b/139891609";
- public static final String CRASH_ADD_CUSTOM_SHORTCUT = "b/141568904";
}
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 08d73d0..12ca5ee 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -33,6 +33,7 @@
* swipe action happens.
*
* @see SingleAxisSwipeDetector
+ * @see BothAxesSwipeDetector
*/
public abstract class BaseSwipeDetector {
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
new file mode 100644
index 0000000..944391e
--- /dev/null
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.touch;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Two dimensional scroll/drag/swipe gesture detector that reports x and y displacement/velocity.
+ */
+public class BothAxesSwipeDetector extends BaseSwipeDetector {
+
+ public static final int DIRECTION_UP = 1 << 0;
+ // Note that this will track left instead of right in RTL.
+ public static final int DIRECTION_RIGHT = 1 << 1;
+ public static final int DIRECTION_DOWN = 1 << 2;
+ // Note that this will track right instead of left in RTL.
+ public static final int DIRECTION_LEFT = 1 << 3;
+
+ /* Client of this gesture detector can register a callback. */
+ private final Listener mListener;
+
+ private int mScrollDirections;
+
+ public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
+ this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
+ }
+
+ @VisibleForTesting
+ protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
+ boolean isRtl) {
+ super(config, isRtl);
+ mListener = l;
+ }
+
+ public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
+ mScrollDirections = scrollDirectionFlags;
+ mIgnoreSlopWhenSettling = ignoreSlop;
+ }
+
+ @Override
+ protected boolean shouldScrollStart(PointF displacement) {
+ // Check if the client is interested in scroll in current direction.
+ boolean canScrollUp = (mScrollDirections & DIRECTION_UP) > 0
+ && displacement.y <= -mTouchSlop;
+ boolean canScrollRight = (mScrollDirections & DIRECTION_RIGHT) > 0
+ && displacement.x >= mTouchSlop;
+ boolean canScrollDown = (mScrollDirections & DIRECTION_DOWN) > 0
+ && displacement.y >= mTouchSlop;
+ boolean canScrollLeft = (mScrollDirections & DIRECTION_LEFT) > 0
+ && displacement.x <= -mTouchSlop;
+ return canScrollUp || canScrollRight || canScrollDown || canScrollLeft;
+ }
+
+ @Override
+ protected void reportDragStartInternal(boolean recatch) {
+ mListener.onDragStart(!recatch);
+ }
+
+ @Override
+ protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
+ mListener.onDrag(displacement, event);
+ }
+
+ @Override
+ protected void reportDragEndInternal(PointF velocity) {
+ mListener.onDragEnd(velocity);
+ }
+
+ /** Listener to receive updates on the swipe. */
+ public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
+ void onDragStart(boolean start);
+
+ boolean onDrag(PointF displacement, MotionEvent motionEvent);
+
+ void onDragEnd(PointF velocity);
+ }
+}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index 0bf2ff6..f2ebc45 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -161,6 +161,7 @@
/** Listener to receive updates on the swipe. */
public interface Listener {
+ /** @param start whether this was the original drag start, as opposed to a recatch. */
void onDragStart(boolean start);
// TODO remove
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 4b931f4..499f655 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -50,22 +50,41 @@
}
}
- public void schedule(SchedulerCallback<? extends T> callback) {
+ /**
+ * Schedules the callback to be notified when the activity is created.
+ * @return true if the activity is already created, false otherwise
+ */
+ public boolean schedule(SchedulerCallback<? extends T> callback) {
synchronized (this) {
mPendingCallback = new WeakReference<>((SchedulerCallback<T>) callback);
}
- MAIN_EXECUTOR.execute(this);
+ if (!notifyInitIfPending()) {
+ // If the activity doesn't already exist, then post and wait for the activity to start
+ MAIN_EXECUTOR.execute(this);
+ return false;
+ }
+ return true;
}
@Override
public void run() {
- T activity = mCurrentActivity.get();
- if (activity != null) {
- initIfPending(activity, activity.isStarted());
- }
+ notifyInitIfPending();
}
- public boolean initIfPending(T activity, boolean alreadyOnHome) {
+ /**
+ * Notifies the pending callback if the activity is now created.
+ * @return true if the activity is now created.
+ */
+ private boolean notifyInitIfPending() {
+ T activity = mCurrentActivity.get();
+ if (activity != null) {
+ notifyInitIfPending(activity, activity.isStarted());
+ return true;
+ }
+ return false;
+ }
+
+ public boolean notifyInitIfPending(T activity, boolean alreadyOnHome) {
SchedulerCallback<T> pendingCallback = mPendingCallback.get();
if (pendingCallback != null) {
if (!pendingCallback.init(activity, alreadyOnHome)) {
@@ -114,7 +133,7 @@
}
}
if (!result && !explicitIntent) {
- result = initIfPending(activity, alreadyOnHome);
+ result = notifyInitIfPending(activity, alreadyOnHome);
}
return result;
}
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index 49c97da..a03b743 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -15,10 +15,19 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+
+import androidx.annotation.NonNull;
+
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -61,6 +70,21 @@
&& info instanceof WorkspaceItemInfo;
}
+ /**
+ * Fetch the shortcut icon in background, then update the UI.
+ */
+ public static void fetchAndUpdateShortcutIconAsync(
+ @NonNull Context context, @NonNull WorkspaceItemInfo info, @NonNull ShortcutInfo si,
+ boolean badged) {
+ MODEL_EXECUTOR.execute(() -> {
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ info.bitmap = li.createShortcutIcon(si, badged, null);
+ LauncherAppState.getInstance(context).getModel()
+ .updateAndBindWorkspaceItem(info, si);
+ }
+ });
+ }
+
private static boolean isActive(ItemInfo info) {
boolean isLoading = info instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) info).hasPromiseIconUi();
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 24b5b02..5cf96c8 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -18,7 +18,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.android.launcher3.tests">
- <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="25"
+ <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"
tools:overrideLibrary="android.support.test.uiautomator.v18"/>
<application android:debuggable="true">
diff --git a/tests/dummy_app/AndroidManifest.xml b/tests/dummy_app/AndroidManifest.xml
index 9d0a74a..f00138c 100644
--- a/tests/dummy_app/AndroidManifest.xml
+++ b/tests/dummy_app/AndroidManifest.xml
@@ -21,7 +21,7 @@
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.aardwolf">
- <uses-sdk android:targetSdkVersion="28" android:minSdkVersion="21"/>
+ <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="21"/>
<application android:label="Aardwolf">
<activity
android:name="Activity1"
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 76b5d28..bb19515 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -26,8 +26,6 @@
import static java.lang.System.exit;
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -306,7 +304,7 @@
protected void waitForLauncherCondition(
String message, Function<Launcher, Boolean> condition, long timeout) {
if (!TestHelpers.isInLauncherProcess()) return;
- Wait.atMost(message, () -> getFromLauncher(condition), timeout);
+ Wait.atMost(message, () -> getFromLauncher(condition), timeout, mLauncher);
}
// Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
@@ -319,7 +317,7 @@
final Object fromLauncher = getFromLauncher(f);
output[0] = fromLauncher;
return fromLauncher != null;
- }, timeout);
+ }, timeout, mLauncher);
return (T) output[0];
}
@@ -333,7 +331,7 @@
Wait.atMost(message, () -> {
testThreadAction.run();
return getFromLauncher(condition);
- }, timeout);
+ }, timeout, mLauncher);
}
protected LauncherActivityInfo getSettingsApp() {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 29da0fa..191b405 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -356,9 +356,9 @@
* Custom shortcuts are replaced by deep shortcuts after api 25.
*/
@Test
- @Ignore("Temporarily disabled to unblock merging to master")
@PortraitLandscape
public void testDragCustomShortcut() {
+ if (!TestHelpers.isInLauncherProcess()) return; // b/143725213
mLauncher.getWorkspace().openAllWidgets()
.getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
.dragToWorkspace();
diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index e1b3ede..0472ce1 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -103,12 +103,12 @@
setResult(acceptConfig);
if (acceptConfig) {
- Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT);
+ Wait.atMost(null, new WidgetSearchCondition(), DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId));
} else {
// Verify that the widget id is deleted.
Wait.atMost(null, () -> mAppWidgetManager.getAppWidgetInfo(mWidgetId) == null,
- DEFAULT_ACTIVITY_TIMEOUT);
+ DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
}
}
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 07129dd..d909ad7 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -59,7 +59,8 @@
@RunWith(AndroidJUnit4.class)
public class RequestPinItemTest extends AbstractLauncherUiTest {
- @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+ @Rule
+ public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
private String mCallbackAction;
private String mShortcutId;
@@ -84,10 +85,10 @@
.equals(AppWidgetNoConfig.class.getName()));
}
- @Test
+ @Test
public void testPinWidgetNoConfig_customPreview() throws Throwable {
// Command to set custom preview
- Intent command = RequestPinItemActivity.getCommandIntent(
+ Intent command = RequestPinItemActivity.getCommandIntent(
RequestPinItemActivity.class, "setRemoteViewColor").putExtra(
RequestPinItemActivity.EXTRA_PARAM + "0", Color.RED);
@@ -169,7 +170,8 @@
// Go back to home
mLauncher.pressHome();
- Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT);
+ Wait.atMost(null, new ItemSearchCondition(itemMatcher), DEFAULT_ACTIVITY_TIMEOUT,
+ mLauncher);
}
/**
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 899686b..2663d02 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -3,6 +3,8 @@
import android.os.SystemClock;
import android.util.Log;
+import com.android.launcher3.tapl.LauncherInstrumentation;
+
import org.junit.Assert;
/**
@@ -12,11 +14,13 @@
private static final long DEFAULT_SLEEP_MS = 200;
- public static void atMost(String message, Condition condition, long timeout) {
- atMost(message, condition, timeout, DEFAULT_SLEEP_MS);
+ public static void atMost(String message, Condition condition, long timeout,
+ LauncherInstrumentation launcher) {
+ atMost(message, condition, timeout, DEFAULT_SLEEP_MS, launcher);
}
- public static void atMost(String message, Condition condition, long timeout, long sleepMillis) {
+ public static void atMost(String message, Condition condition, long timeout, long sleepMillis,
+ LauncherInstrumentation launcher) {
final long startTime = SystemClock.uptimeMillis();
long endTime = startTime + timeout;
Log.d("Wait", "atMost: " + startTime + " - " + endTime);
@@ -40,6 +44,7 @@
throw new RuntimeException(t);
}
Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis());
+ launcher.checkForAnomaly();
Assert.fail(message);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 3713c14..1c851f4 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -64,10 +64,12 @@
import com.android.launcher3.testing.TestProtocol;
import com.android.systemui.shared.system.QuickStepContract;
-import java.util.ArrayList;
+import org.junit.Assert;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
@@ -77,8 +79,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
-import org.junit.Assert;
-
/**
* The main tapl object. The only object that can be explicitly constructed by the using code. It
* produces all other objects.
@@ -298,6 +298,14 @@
return null;
}
+ public void checkForAnomaly() {
+ final String anomalyMessage = getAnomalyMessage();
+ if (anomalyMessage != null) {
+ failWithSystemHealth(
+ "Tests are broken by a non-Launcher system error: " + anomalyMessage);
+ }
+ }
+
private String getVisibleStateMessage() {
if (hasLauncherObject(WIDGETS_RES_ID)) return "Widgets";
if (hasLauncherObject(OVERVIEW_RES_ID)) return "Overview";
@@ -331,20 +339,17 @@
}
private void fail(String message) {
- message = "http://go/tapl : " + getContextDescription() + message;
+ checkForAnomaly();
- final String anomaly = getAnomalyMessage();
- if (anomaly != null) {
- message = anomaly + ", which causes:\n" + message;
- } else {
- message = message + " (visible state: " + getVisibleStateMessage() + ")";
- }
+ failWithSystemHealth("http://go/tapl : " + getContextDescription() + message +
+ " (visible state: " + getVisibleStateMessage() + ")");
+ }
+ private void failWithSystemHealth(String message) {
final String systemHealth = getSystemHealthMessage();
if (systemHealth != null) {
message = message
- + ", which might be a consequence of system health "
- + "problems:\n<<<<<<<<<<<<<<<<<<\n"
+ + ", perhaps because of system health problems:\n<<<<<<<<<<<<<<<<<<\n"
+ systemHealth + "\n>>>>>>>>>>>>>>>>>>";
}
@@ -531,8 +536,7 @@
// accessibility events prior to pressing Home.
final String action;
if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
- final String anomaly = getAnomalyMessage();
- if (anomaly != null) fail("Can't swipe up to Home: " + anomaly);
+ checkForAnomaly();
final Point displaySize = getRealDisplaySize();