Merge "Add `deskId` to `DesktopTask`." into main
diff --git a/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
index 29586c4..d88fc94 100644
--- a/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
+++ b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
@@ -16,10 +16,17 @@
package com.android.launcher3.dagger
+import com.android.launcher3.model.ModelDelegate
+import com.android.launcher3.model.QuickstepModelDelegate
+import dagger.Binds
import dagger.Module
/**
* Module containing bindings for the final derivative app, an implementation of this module should
* be included in the final app code.
*/
-@Module abstract class AppModule {}
+@Module
+abstract class AppModule {
+
+ @Binds abstract fun bindModelDelegate(impl: QuickstepModelDelegate): ModelDelegate
+}
diff --git a/quickstep/res/layout/task_thumbnail_view_header.xml b/quickstep/res/layout/task_thumbnail_view_header.xml
index ecc1559..70e4a42 100644
--- a/quickstep/res/layout/task_thumbnail_view_header.xml
+++ b/quickstep/res/layout/task_thumbnail_view_header.xml
@@ -18,6 +18,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:id="@+id/task_thumbnail_view_header"
android:background="@drawable/task_thumbnail_header_bg">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -61,6 +62,7 @@
android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
android:src="@drawable/task_header_close_button"
android:tint="@android:color/darker_gray"
+ android:background="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index e7619c2..3201886 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -90,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Tout est prêt!"</string>
<string name="allset_hint" msgid="459504134589971527">"Balayez l\'écran vers le haut pour accéder à l\'écran d\'accueil"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Toucher le bouton d\'accueil pour passer sur votre écran d\'accueil"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"Vous êtes maintenant prêt à utiliser votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Vous êtes maintenant prêt à utiliser votre <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
<string name="default_device_name" msgid="6660656727127422487">"appareil"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Paramètres de navigation du système"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Partager"</string>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index dab58d7..2313a07 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -92,7 +92,7 @@
<string name="allset_button_hint" msgid="2395219947744706291">"Натисніть кнопку головного екрана, щоб відкрити його"</string>
<string name="allset_description_generic" msgid="5385500062202019855">"Тепер ви можете використовувати <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
<string name="default_device_name" msgid="6660656727127422487">"пристрій"</string>
- <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Системні налаштування навігації"</annotation></string>
+ <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Налаштування навігації в системі"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Поділитися"</string>
<string name="action_screenshot" msgid="8171125848358142917">"Знімок екрана"</string>
<string name="action_split" msgid="2098009717623550676">"Розділити"</string>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 1cd2e87..06ca97a 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -90,7 +90,7 @@
<string name="allset_title" msgid="5021126669778966707">"Hammasi tayyor!"</string>
<string name="allset_hint" msgid="459504134589971527">"Boshiga qaytish uchun tepaga suring"</string>
<string name="allset_button_hint" msgid="2395219947744706291">"Bosh ekranga oʻtish uchun bosh ekran tugmasini bosing"</string>
- <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> xizmatga tayyor"</string>
+ <string name="allset_description_generic" msgid="5385500062202019855">"Sizning <xliff:g id="DEVICE">%1$s</xliff:g> xizmatga tayyor"</string>
<string name="default_device_name" msgid="6660656727127422487">"qurilma"</string>
<string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Tizim navigatsiya sozlamalari"</annotation></string>
<string name="action_share" msgid="2648470652637092375">"Ulashish"</string>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index a530325..d699cdf 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -27,7 +27,6 @@
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
<string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
<string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
- <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
<string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
<string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
<string name="taskbar_view_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarViewCallbacksFactory</string>
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 1cf7dda..f992913 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -322,13 +322,14 @@
stringCache.loadStrings(this);
bindStringCache(stringCache);
- bindWidgets(mModel.getWidgetsByPackageItem(), mModel.getDefaultWidgetsFilter());
+ bindWidgets(mModel.getWidgetsByPackageItemForPicker(),
+ mModel.getDefaultWidgetsFilter());
// Open sheet once widgets are available, so that it doesn't interrupt the open
// animation.
openWidgetsSheet();
if (mUiSurface != null) {
mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
- mUiSurface, mModel.getWidgetsByComponentKey());
+ mUiSurface, mModel.getWidgetsByComponentKeyForPicker());
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
});
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
index 533a86a..688018b 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
@@ -186,10 +186,12 @@
onAnimFinish: (Animator) -> Unit,
): Animator {
return MinimizeAnimator.create(
- context.resources.displayMetrics,
+ context,
change,
transaction,
onAnimFinish,
+ interactionJankMonitor,
+ context.mainThreadHandler,
)
}
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 40e8fc2..74b73d4 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -47,6 +47,7 @@
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Log;
import android.util.StatsEvent;
@@ -61,6 +62,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.icons.cache.CacheLookupFlag;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
@@ -89,6 +91,9 @@
import java.util.Objects;
import java.util.stream.IntStream;
+import javax.inject.Inject;
+import javax.inject.Named;
+
/**
* Model delegate which loads prediction items
*/
@@ -114,18 +119,29 @@
CONTAINER_WIDGETS_PREDICTION, "widgets_prediction", DESKTOP_ICON_FLAG);
private final InvariantDeviceProfile mIDP;
+ private final PackageManagerHelper mPmHelper;
private final AppEventProducer mAppEventProducer;
+
private final StatsManager mStatsManager;
protected boolean mActive = false;
- public QuickstepModelDelegate(Context context) {
+ @Inject
+ public QuickstepModelDelegate(@ApplicationContext Context context,
+ InvariantDeviceProfile idp,
+ PackageManagerHelper pmHelper,
+ @Nullable @Named("ICONS_DB") String dbFileName) {
super(context);
- mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
+ mIDP = idp;
+ mPmHelper = pmHelper;
- mIDP = InvariantDeviceProfile.INSTANCE.get(context);
+ mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
- mStatsManager = context.getSystemService(StatsManager.class);
+
+ // Only register for launcher snapshot logging if this is the primary ModelDelegate
+ // instance, as there will be additional instances that may be destroyed at any time.
+ mStatsManager = TextUtils.isEmpty(dbFileName)
+ ? null : context.getSystemService(StatsManager.class);
}
@CallSuper
@@ -154,10 +170,10 @@
// TODO: Implement caching and preloading
WorkspaceItemFactory factory =
- new WorkspaceItemFactory(mApp, ums, mPmHelper, pinnedShortcuts, numColumns,
+ new WorkspaceItemFactory(mContext, ums, mPmHelper, pinnedShortcuts, numColumns,
state.containerId, state.lookupFlag);
FixedContainerItems fci = new FixedContainerItems(state.containerId,
- state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
+ state.storage.read(mContext, factory, ums.allUsers::get));
mDataModel.extraItems.put(state.containerId, fci);
}
@@ -220,7 +236,7 @@
super.modelLoadComplete();
// Log snapshot of the model
- LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
+ LauncherPrefs prefs = LauncherPrefs.get(mContext);
long lastSnapshotTimeMillis = prefs.get(LAST_SNAPSHOT_TIME_MILLIS);
// Log snapshot only if previous snapshot was older than a day
long now = System.currentTimeMillis();
@@ -245,11 +261,7 @@
prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
}
- // Only register for launcher snapshot logging if this is the primary ModelDelegate
- // instance, as there will be additional instances that may be destroyed at any time.
- if (mIsPrimaryInstance) {
- registerSnapshotLoggingCallback();
- }
+ registerSnapshotLoggingCallback();
}
protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
@@ -257,9 +269,9 @@
/**
* Registers a callback to log launcher workspace layout using Statsd pulled atom.
*/
- protected void registerSnapshotLoggingCallback() {
+ private void registerSnapshotLoggingCallback() {
if (mStatsManager == null) {
- Log.d(TAG, "Failed to get StatsManager");
+ Log.d(TAG, "Skipping snapshot logging");
}
try {
@@ -332,7 +344,7 @@
super.destroy();
mActive = false;
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
- if (mIsPrimaryInstance && mStatsManager != null) {
+ if (mStatsManager != null) {
try {
mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
} catch (RuntimeException e) {
@@ -354,25 +366,24 @@
if (!mActive) {
return;
}
- Context context = mApp.getContext();
- AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+ AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
registerPredictor(mAllAppsState, apm.createAppPredictionSession(
- new AppPredictionContext.Builder(context)
+ new AppPredictionContext.Builder(mContext)
.setUiSurface("home")
.setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns)
.build()));
// TODO: get bundle
- registerHotseatPredictor(apm, context);
+ registerHotseatPredictor(apm, mContext);
registerWidgetsPredictor(apm.createAppPredictionSession(
- new AppPredictionContext.Builder(context)
+ new AppPredictionContext.Builder(mContext)
.setUiSurface("widgets")
- .setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel))
+ .setExtras(getBundleForWidgetsOnWorkspace(mContext, mDataModel))
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
.build()));
}
@@ -383,12 +394,11 @@
if (!mActive) {
return;
}
- Context context = mApp.getContext();
- AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+ AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
- registerHotseatPredictor(apm, context);
+ registerHotseatPredictor(apm, mContext);
}
private void registerHotseatPredictor(AppPredictionManager apm, Context context) {
@@ -413,7 +423,7 @@
// No diff, skip
return;
}
- mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
+ mModel.enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
}
private void registerWidgetsPredictor(AppPredictor predictor) {
@@ -424,7 +434,7 @@
// No diff, skip
return;
}
- mApp.getModel().enqueueModelUpdateTask(
+ mModel.enqueueModelUpdateTask(
new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets));
});
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
@@ -536,7 +546,7 @@
private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory<ItemInfo> {
- private final LauncherAppState mAppState;
+ private final Context mContext;
private final UserManagerState mUMS;
private final PackageManagerHelper mPmHelper;
private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
@@ -546,10 +556,11 @@
private int mReadCount = 0;
- protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
+ protected WorkspaceItemFactory(
+ Context context, UserManagerState ums,
PackageManagerHelper pmHelper, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
int maxCount, int container, CacheLookupFlag lookupFlag) {
- mAppState = appState;
+ mContext = context;
mUMS = ums;
mPmHelper = pmHelper;
mPinnedShortcuts = pinnedShortcuts;
@@ -566,7 +577,7 @@
}
switch (itemType) {
case ITEM_TYPE_APPLICATION: {
- LauncherActivityInfo lai = mAppState.getContext()
+ LauncherActivityInfo lai = mContext
.getSystemService(LauncherApps.class)
.resolveActivity(intent, user);
if (lai == null) {
@@ -574,14 +585,15 @@
}
AppInfo info = new AppInfo(
lai,
- UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user),
- ApiWrapper.INSTANCE.get(mAppState.getContext()),
+ UserCache.INSTANCE.get(mContext).getUserInfo(user),
+ ApiWrapper.INSTANCE.get(mContext),
mPmHelper,
mUMS.isUserQuiet(user));
info.container = mContainer;
- mAppState.getIconCache().getTitleAndIcon(info, lai, mLookupFlag);
+ LauncherAppState.getInstance(mContext).getIconCache()
+ .getTitleAndIcon(info, lai, mLookupFlag);
mReadCount++;
- return info.makeWorkspaceItem(mAppState.getContext());
+ return info.makeWorkspaceItem(mContext);
}
case ITEM_TYPE_DEEP_SHORTCUT: {
ShortcutKey key = ShortcutKey.fromIntent(intent, user);
@@ -592,9 +604,9 @@
if (si == null) {
return null;
}
- WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
+ WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mContext);
wii.container = mContainer;
- mAppState.getIconCache().getShortcutIcon(wii, si);
+ LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(wii, si);
mReadCount++;
return wii;
}
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
index 8c98bab..d3ac975 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -203,7 +203,7 @@
List<ItemInfo> items;
if (enableCategorizedWidgetSuggestions()) {
WidgetRecommendationCategoryProvider categoryProvider =
- WidgetRecommendationCategoryProvider.newInstance(mContext);
+ new WidgetRecommendationCategoryProvider();
items = widgetItems.stream()
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
categoryProvider.getWidgetRecommendationCategory(mContext, it)))
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 40e1c10..8bd2ba8 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -79,7 +79,7 @@
// Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
// being in predictions.
Map<ComponentKey, WidgetItem> allEligibleWidgets =
- dataModel.widgetsModel.getWidgetsByComponentKey()
+ dataModel.widgetsModel.getWidgetsByComponentKeyForPicker()
.entrySet()
.stream()
.filter(entry -> entry.getValue().widgetInfo != null
@@ -137,7 +137,7 @@
List<ItemInfo> items;
if (enableCategorizedWidgetSuggestions()) {
WidgetRecommendationCategoryProvider categoryProvider =
- WidgetRecommendationCategoryProvider.newInstance(context);
+ new WidgetRecommendationCategoryProvider();
items = servicePredictedItems.stream()
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
categoryProvider.getWidgetRecommendationCategory(context, it)))
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index bf5c0c8..f80dc90 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -197,8 +197,7 @@
final boolean isLeftRightSplit = !splitBounds.appsStackedVertically;
- final float leftOrTopTaskPercent = isLeftRightSplit
- ? splitBounds.leftTaskPercent : splitBounds.topTaskPercent;
+ final float leftOrTopTaskPercent = splitBounds.getLeftTopTaskPercent();
ConstraintLayout.LayoutParams leftTopParams = (ConstraintLayout.LayoutParams)
mThumbnailView1.getLayoutParams();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2eb4412..2e42e45 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -47,7 +47,6 @@
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
-import static com.android.window.flags.Flags.enableStartLaunchTransitionFromTaskbarBugfix;
import static com.android.wm.shell.Flags.enableBubbleBar;
import static com.android.wm.shell.Flags.enableBubbleBarOnPhones;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
@@ -1727,7 +1726,7 @@
}
// There is no task associated with this launch - launch a new task through an intent
ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions();
- if (enableStartLaunchTransitionFromTaskbarBugfix()) {
+ if (DesktopModeFlags.ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX.isTrue()) {
mSysUiProxy.startLaunchIntentTransition(intent, opts.options.toBundle(), displayId);
} else {
startActivity(intent, opts.options.toBundle());
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
index c8f46a9..cca8bf8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -20,6 +20,7 @@
import com.android.app.animation.Interpolators.FINAL_FRAME
import com.android.app.animation.Interpolators.INSTANT
import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.LauncherState
import com.android.launcher3.anim.AnimatedFloat
@@ -51,6 +52,7 @@
import com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION
import com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION
import com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA
+import com.android.quickstep.views.RecentsViewUtils.Companion.DESK_EXPLODE_PROGRESS
import com.android.quickstep.views.TaskView.Companion.FLAG_UPDATE_ALL
/**
@@ -74,6 +76,13 @@
recentsView,
if (state.displayOverviewTasksAsGrid(launcher.deviceProfile)) 1f else 0f,
)
+ if (enableDesktopExplodedView()) {
+ DESK_EXPLODE_PROGRESS.set(
+ recentsView,
+ if (state.displayOverviewTasksAsGrid(launcher.deviceProfile)) 1f else 0f,
+ )
+ }
+
TASK_THUMBNAIL_SPLASH_ALPHA.set(
recentsView,
if (state.showTaskThumbnailSplash()) 1f else 0f,
@@ -156,6 +165,15 @@
getOverviewInterpolator(fromState, toState),
)
+ if (enableDesktopExplodedView()) {
+ builder.setFloat(
+ recentsView,
+ DESK_EXPLODE_PROGRESS,
+ if (toState.isRecentsViewVisible) 1f else 0f,
+ getOverviewInterpolator(fromState, toState),
+ )
+ }
+
if (enableLargeDesktopWindowingTile()) {
builder.setFloat(
recentsView,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
index 7c09e9a..0d2cfbf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
@@ -317,6 +317,12 @@
)
addPreference(
Preference(context).apply {
+ title = "Launch Full Gesture Tutorial"
+ intent = Intent(launchSandboxIntent).putExtra("use_tutorial_menu", false)
+ }
+ )
+ addPreference(
+ Preference(context).apply {
title = "Launch Back Tutorial"
intent =
Intent(launchSandboxIntent)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 05d12c3..58b274a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -64,6 +64,7 @@
import android.graphics.PointF;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
+import android.window.DesktopModeFlags;
import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherState;
@@ -86,7 +87,6 @@
import com.android.quickstep.util.WorkspaceRevealAnim;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.window.flags.Flags;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
@@ -188,7 +188,7 @@
}
if (DesktopModeStatus.canEnterDesktopMode(mLauncher)
//TODO(b/345296916): replace with dev option once in teamfood
- && Flags.enableQuickswitchDesktopSplitBugfix()
+ && DesktopModeFlags.ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX.isTrue()
&& mRecentsView.getNonDesktopTaskViewCount() < 1) {
return false;
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3640e1f..67a54e6 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -223,6 +223,7 @@
mRecentsView = null;
mContainer = null;
mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
+ mRecentsAnimationStartCallbacks.clear();
};
private static int FLAG_COUNT = 0;
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 9810308..cb11afa 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -286,8 +286,32 @@
mCallbacks.addListener(listener);
final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
+ options.setTransientLaunch();
+ options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
- // TODO:(b/365777482) if flag is enabled, but on launcher it will crash.
+ // Notify taskbar that we should skip reacting to launcher visibility change to
+ // avoid a jumping taskbar.
+ TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
+ if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
+ taskbarUIController.setSkipLauncherVisibilityChange(true);
+
+ mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
+ @Override
+ public void onRecentsAnimationCanceled(
+ @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
+ taskbarUIController.setSkipLauncherVisibilityChange(false);
+ }
+
+ @Override
+ public void onRecentsAnimationFinished(
+ @NonNull RecentsAnimationController controller) {
+ taskbarUIController.setSkipLauncherVisibilityChange(false);
+ }
+ });
+ }
+
if(containerInterface.getCreatedContainer() instanceof RecentsWindowManager
&& (Flags.enableFallbackOverviewInWindow()
|| Flags.enableLauncherOverviewInWindow())) {
@@ -297,32 +321,6 @@
.getRecentsWindowManager(mDeviceState.getDisplayId())
.startRecentsWindow(mCallbacks);
} else {
- options.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
- options.setTransientLaunch();
- options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
-
- // Notify taskbar that we should skip reacting to launcher visibility change to
- // avoid a jumping taskbar.
- TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
- if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
- taskbarUIController.setSkipLauncherVisibilityChange(true);
-
- mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
- @Override
- public void onRecentsAnimationCanceled(
- @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
- taskbarUIController.setSkipLauncherVisibilityChange(false);
- }
-
- @Override
- public void onRecentsAnimationFinished(
- @NonNull RecentsAnimationController controller) {
- taskbarUIController.setSkipLauncherVisibilityChange(false);
- }
- });
- }
-
mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent,
options, mCallbacks, false /* useSyntheticRecentsTransition */);
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index a69d472..f92581e 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -312,7 +312,7 @@
RecentsView<?, ?> recentsView = taskView.getRecentsView();
if (recentsView != null) {
dismissTaskMenuView();
- recentsView.dismissTask(taskView, true, true);
+ recentsView.dismissTaskView(taskView, true, true);
mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
.log(LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP);
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index b4b80c5..56dd696 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -18,6 +18,7 @@
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableDesktopExplodedView;
import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
@@ -36,6 +37,7 @@
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
+import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
import android.util.FloatProperty;
@@ -123,6 +125,10 @@
state.detachDesktopCarousel() ? 1f : 0f,
getOverviewInterpolator(state));
}
+ if (enableDesktopExplodedView()) {
+ setter.setFloat(mRecentsView, DESK_EXPLODE_PROGRESS, showAsGrid ? 1f : 0f,
+ getOverviewInterpolator(state));
+ }
setter.setViewBackgroundColor(mRecentsViewContainer.getScrimView(),
state.getScrimColor(mRecentsViewContainer.asContext()),
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
index 047658c..cd48136 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
@@ -61,9 +61,13 @@
return dragLayer
}
+ fun initDeviceProfile() {
+ deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
+ }
+
override fun getDeviceProfile(): DeviceProfile {
if (deviceProfile == null) {
- deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this).copy(this)
+ initDeviceProfile()
}
return deviceProfile!!
}
@@ -79,7 +83,10 @@
* @param type The window type to pass to the created WindowManager.LayoutParams.
* @param title The window title to pass to the created WindowManager.LayoutParams.
*/
- fun createDefaultWindowLayoutParams(type: Int, title: String): WindowManager.LayoutParams {
+ private fun createDefaultWindowLayoutParams(
+ type: Int,
+ title: String,
+ ): WindowManager.LayoutParams {
var windowFlags =
(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index 07288d8..1f4961a 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -21,6 +21,7 @@
import android.content.ComponentName
import android.content.Context
import android.content.LocusId
+import android.content.res.Configuration
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
@@ -32,6 +33,7 @@
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
import android.window.RemoteTransition
+import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.BaseActivity
import com.android.launcher3.LauncherAnimationRunner
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory
@@ -135,73 +137,6 @@
listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
private var onInitListener: Predicate<Boolean>? = null
- private val taskStackChangeListener =
- object : TaskStackChangeListener {
- override fun onTaskMovedToFront(taskId: Int) {
- if ((isShowing() && isInState(DEFAULT))) {
- // handling state where we end recents animation by swiping livetile away
- // TODO: animate this switch.
- cleanupRecentsWindow()
- }
- }
- }
-
- private val recentsAnimationListener =
- object : RecentsAnimationListener {
- override fun onRecentsAnimationCanceled(thumbnailDatas: HashMap<Int, ThumbnailData>) {
- recentAnimationStopped()
- }
-
- override fun onRecentsAnimationFinished(controller: RecentsAnimationController) {
- recentAnimationStopped()
- }
- }
-
- init {
- TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
- }
-
- override fun destroy() {
- super.destroy()
- cleanupRecentsWindow()
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
- callbacks?.removeListener(recentsAnimationListener)
- recentsWindowTracker.onContextDestroyed(this)
- recentsView?.destroy()
- }
-
- override fun startHome() {
- startHome(/* finishRecentsAnimation= */ true)
- }
-
- fun startHome(finishRecentsAnimation: Boolean) {
- val recentsView: RecentsView<*, *> = getOverviewPanel()
-
- if (!finishRecentsAnimation) {
- recentsView.switchToScreenshot(/* onFinishRunnable= */ null)
- startHomeInternal()
- return
- }
- recentsView.switchToScreenshot {
- recentsView.finishRecentsAnimation(/* toRecents= */ true) { startHomeInternal() }
- }
- }
-
- private fun startHomeInternal() {
- val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
- val options =
- ActivityOptions.makeRemoteAnimation(
- RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
- RemoteTransition(
- runner.toRemoteTransition(),
- iApplicationThread,
- "StartHomeFromRecents",
- ),
- )
- OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
- stateManager.moveToRestState()
- }
-
private val animationToHomeFactory =
RemoteAnimationFactory {
_: Int,
@@ -229,7 +164,7 @@
anim,
this@RecentsWindowManager,
{
- getStateManager().goToState(BG_LAUNCHER, false)
+ getStateManager().goToState(BG_LAUNCHER, true)
cleanupRecentsWindow()
},
true, /* skipFirstFrame */
@@ -242,17 +177,49 @@
TestLogging.recordEvent(SEQUENCE_MAIN, "onBackInvoked")
}
- private fun cleanupRecentsWindow() {
- RecentsWindowProtoLogProxy.logCleanup(isShowing())
- if (isShowing()) {
- windowManager.removeViewImmediate(windowView)
+ private val taskStackChangeListener =
+ object : TaskStackChangeListener {
+ override fun onTaskMovedToFront(taskId: Int) {
+ if ((isShowing() && isInState(DEFAULT))) {
+ // handling state where we end recents animation by swiping livetile away
+ // TODO: animate this switch.
+ cleanupRecentsWindow()
+ }
+ }
}
- stateManager.moveToRestState()
- callbacks?.removeListener(recentsAnimationListener)
+
+ private val recentsAnimationListener =
+ object : RecentsAnimationListener {
+ override fun onRecentsAnimationCanceled(thumbnailDatas: HashMap<Int, ThumbnailData>) {
+ recentAnimationStopped()
+ }
+
+ override fun onRecentsAnimationFinished(controller: RecentsAnimationController) {
+ recentAnimationStopped()
+ }
+ }
+
+ init {
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
}
- private fun isShowing(): Boolean {
- return windowView?.parent != null
+ override fun handleConfigurationChanged(configuration: Configuration?) {
+ initDeviceProfile()
+ AbstractFloatingView.closeOpenViews(
+ this,
+ true,
+ AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(),
+ )
+ dispatchDeviceProfileChanged()
+ }
+
+ override fun destroy() {
+ super.destroy()
+ cleanupRecentsWindow()
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
+ callbacks?.removeListener(recentsAnimationListener)
+ recentsWindowTracker.onContextDestroyed(this)
+ recentsView?.destroy()
}
fun startRecentsWindow(callbacks: RecentsAnimationCallbacks? = null) {
@@ -301,6 +268,51 @@
callbacks?.addListener(recentsAnimationListener)
}
+ override fun startHome() {
+ startHome(/* finishRecentsAnimation= */ true)
+ }
+
+ fun startHome(finishRecentsAnimation: Boolean) {
+ val recentsView: RecentsView<*, *> = getOverviewPanel()
+
+ if (!finishRecentsAnimation) {
+ recentsView.switchToScreenshot /* onFinishRunnable= */ {}
+ startHomeInternal()
+ return
+ }
+ recentsView.switchToScreenshot {
+ recentsView.finishRecentsAnimation(/* toRecents= */ true) { startHomeInternal() }
+ }
+ }
+
+ private fun startHomeInternal() {
+ val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
+ val options =
+ ActivityOptions.makeRemoteAnimation(
+ RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
+ RemoteTransition(
+ runner.toRemoteTransition(),
+ iApplicationThread,
+ "StartHomeFromRecents",
+ ),
+ )
+ OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
+ stateManager.moveToRestState()
+ }
+
+ private fun cleanupRecentsWindow() {
+ RecentsWindowProtoLogProxy.logCleanup(isShowing())
+ if (isShowing()) {
+ windowManager.removeViewImmediate(windowView)
+ }
+ stateManager.moveToRestState()
+ callbacks?.removeListener(recentsAnimationListener)
+ }
+
+ private fun isShowing(): Boolean {
+ return windowView?.parent != null
+ }
+
private fun recentAnimationStopped() {
if (isInState(BACKGROUND_APP)) {
cleanupRecentsWindow()
@@ -352,7 +364,6 @@
override fun onStateSetEnd(state: RecentsState) {
super.onStateSetEnd(state)
RecentsWindowProtoLogProxy.logOnStateSetEnd(getStateName(state))
-
if (state == HOME || state == BG_LAUNCHER) {
cleanupRecentsWindow()
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index afe988d..a703c23 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -19,6 +19,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DEEP_PRESS_STASHED_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_STASHED_TASKBAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.LogConfig.NAV_HANDLE_LONG_PRESS;
@@ -30,6 +31,7 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.DeviceConfigWrapper;
@@ -49,6 +51,8 @@
private static final String TAG = "NavHandleLongPressIC";
private static final boolean DEBUG_NAV_HANDLE = Utilities.isPropertyEnabled(
NAV_HANDLE_LONG_PRESS);
+ // Minimum time between touch down and abandon to log.
+ @VisibleForTesting static final long MIN_TIME_TO_LOG_ABANDON_MS = 200;
private NavHandleLongPressHandler mNavHandleLongPressHandler;
private final float mNavHandleWidth;
@@ -62,11 +66,12 @@
private final int mOuterLongPressTimeout;
private final boolean mDeepPressEnabled;
private final NavHandle mNavHandle;
- private final StatsLogManager mStatsLogManager;
+ private StatsLogManager mStatsLogManager;
private final TopTaskTracker mTopTaskTracker;
private final GestureState mGestureState;
- private MotionEvent mCurrentDownEvent;
+ private MotionEvent mCurrentDownEvent; // Down event that started the current gesture.
+ private MotionEvent mCurrentMotionEvent; // Most recent motion event.
private boolean mDeepPressLogged; // Whether deep press has been logged for the current touch.
public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
@@ -125,6 +130,10 @@
@Override
public void onMotionEvent(MotionEvent ev) {
+ if (mCurrentMotionEvent != null) {
+ mCurrentMotionEvent.recycle();
+ }
+ mCurrentMotionEvent = MotionEvent.obtain(ev);
if (mDelegate.allowInterceptByParent()) {
handleMotionEvent(ev);
} else if (MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)) {
@@ -244,6 +253,15 @@
if (DEBUG_NAV_HANDLE) {
Log.d(TAG, "cancelLongPress: " + reason);
}
+ // Log LPNH abandon latency if we didn't trigger but were still prepared to.
+ long latencyMs = mCurrentMotionEvent.getEventTime() - mCurrentDownEvent.getEventTime();
+ if (mState != STATE_ACTIVE && MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)
+ && latencyMs >= MIN_TIME_TO_LOG_ABANDON_MS) {
+ mStatsLogManager.latencyLogger()
+ .withInstanceId(new InstanceIdSequence().newInstanceId())
+ .withLatency(latencyMs)
+ .log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
+ }
mGestureState.setIsInExtendedSlopRegion(false);
MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
mNavHandleLongPressHandler.onTouchFinished(mNavHandle, reason);
@@ -274,4 +292,9 @@
void setNavHandleLongPressHandler(NavHandleLongPressHandler navHandleLongPressHandler) {
mNavHandleLongPressHandler = navHandleLongPressHandler;
}
+
+ @VisibleForTesting
+ void setStatsLogManager(StatsLogManager statsLogManager) {
+ mStatsLogManager = statsLogManager;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 594c99a..58e54cf 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -382,18 +382,15 @@
return;
}
- if (mItemInfo.container < 0 || !LauncherAppState.INSTANCE.executeIfCreated(app -> {
- // Item is inside a collection, fetch collection info in a BG thread
- // and then write to StatsLog.
- app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
- write(event, applyOverwrites(mItemInfo.buildProto(
- (CollectionInfo) dataModel.itemsIdMap.get(mItemInfo.container),
- mContext))));
- })) {
- // Write log on the model thread so that logs do not go out of order
- // (for eg: drop comes after drag)
- Executors.MODEL_EXECUTOR.execute(
- () -> write(event, applyOverwrites(mItemInfo.buildProto(mContext))));
+ // Item is inside a collection, fetch collection info in a BG thread
+ // and then write to StatsLog.
+ if (mItemInfo.container < 0) {
+ LauncherAppState.INSTANCE.get(mContext).getModel().enqueueModelUpdateTask(
+ (taskController, dataModel, apps) -> write(event, applyOverwrites(
+ mItemInfo.buildProto(
+ (CollectionInfo) dataModel.itemsIdMap
+ .get(mItemInfo.container),
+ mContext))));
}
}
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index e72ccbf..59ea8fa 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -285,11 +285,7 @@
translationY = snapshotParams.topMargin.toFloat()
} else {
val topLeftTaskPlusDividerPercent =
- if (splitBounds.appsStackedVertically) {
- splitBounds.topTaskPercent + splitBounds.dividerHeightPercent
- } else {
- splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent
- }
+ splitBounds.leftTopTaskPercent + splitBounds.dividerPercent
translationY =
snapshotParams.topMargin +
(taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent
@@ -440,15 +436,8 @@
splitInfo: SplitBounds,
desiredStagePosition: Int
) {
- val topLeftTaskPercent: Float
- val dividerBarPercent: Float
- if (splitInfo.appsStackedVertically) {
- topLeftTaskPercent = splitInfo.topTaskPercent
- dividerBarPercent = splitInfo.dividerHeightPercent
- } else {
- topLeftTaskPercent = splitInfo.leftTaskPercent
- dividerBarPercent = splitInfo.dividerWidthPercent
- }
+ val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+ val dividerBarPercent = splitInfo.dividerPercent
if (desiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
outRect.bottom = outRect.top + (outRect.height() * topLeftTaskPercent).toInt()
@@ -510,12 +499,7 @@
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
- val taskPercent =
- if (splitBoundsConfig.appsStackedVertically) {
- splitBoundsConfig.topTaskPercent
- } else {
- splitBoundsConfig.leftTaskPercent
- }
+ val taskPercent = splitBoundsConfig.leftTopTaskPercent
val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
val secondTaskViewSize =
Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
@@ -715,11 +699,7 @@
* @return The divider size for the group task view.
*/
protected fun getDividerBarSize(totalThumbnailHeight: Int, splitConfig: SplitBounds): Int {
- return Math.round(
- totalThumbnailHeight *
- if (splitConfig.appsStackedVertically) splitConfig.dividerHeightPercent
- else splitConfig.dividerWidthPercent
- )
+ return Math.round(totalThumbnailHeight * splitConfig.dividerPercent)
}
/**
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index c1e1c2b..d9ad7ce 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -271,12 +271,8 @@
if (splitBounds != null) {
if (deviceProfile.isLeftRightSplit) {
if (desiredTaskId == splitBounds.rightBottomTaskId) {
- float leftTopTaskPercent = splitBounds.appsStackedVertically
- ? splitBounds.topTaskPercent
- : splitBounds.leftTaskPercent;
- float dividerThicknessPercent = splitBounds.appsStackedVertically
- ? splitBounds.dividerHeightPercent
- : splitBounds.dividerWidthPercent;
+ float leftTopTaskPercent = splitBounds.getLeftTopTaskPercent();
+ float dividerThicknessPercent = splitBounds.getDividerPercent();
translationX = ((taskViewWidth * leftTopTaskPercent)
+ (taskViewWidth * dividerThicknessPercent));
}
@@ -285,9 +281,9 @@
FrameLayout.LayoutParams snapshotParams =
(FrameLayout.LayoutParams) thumbnailViews[0]
.getLayoutParams();
- float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
- ? (1f - splitBounds.topTaskPercent)
- : (1f - splitBounds.leftTaskPercent);
+ float bottomRightTaskPlusDividerPercent =
+ splitBounds.getRightBottomTaskPercent()
+ + splitBounds.getDividerPercent();
translationY = -((taskViewHeight - snapshotParams.topMargin)
* bottomRightTaskPlusDividerPercent);
}
@@ -506,12 +502,8 @@
@Override
public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
SplitBounds splitInfo, int desiredStagePosition) {
- float topLeftTaskPercent = splitInfo.appsStackedVertically
- ? splitInfo.topTaskPercent
- : splitInfo.leftTaskPercent;
- float dividerBarPercent = splitInfo.appsStackedVertically
- ? splitInfo.dividerHeightPercent
- : splitInfo.dividerWidthPercent;
+ float topLeftTaskPercent = splitInfo.getLeftTopTaskPercent();
+ float dividerBarPercent = splitInfo.getDividerPercent();
int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight);
@@ -559,9 +551,7 @@
primaryParams.topMargin = spaceAboveSnapshot;
int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- float dividerScale = splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent;
+ float dividerScale = splitBoundsConfig.getDividerPercent();
Pair<Point, Point> taskViewSizes =
getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
if (!inSplitSelection) {
@@ -610,12 +600,8 @@
int parentHeight) {
int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
- float dividerScale = splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.dividerHeightPercent
- : splitBoundsConfig.dividerWidthPercent;
- float taskPercent = splitBoundsConfig.appsStackedVertically
- ? splitBoundsConfig.topTaskPercent
- : splitBoundsConfig.leftTaskPercent;
+ float dividerScale = splitBoundsConfig.getDividerPercent();
+ float taskPercent = splitBoundsConfig.getLeftTopTaskPercent();
Point firstTaskViewSize = new Point();
Point secondTaskViewSize = new Point();
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 3fb4f54..9bfa2bf 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -106,15 +106,8 @@
splitInfo: SplitBounds,
desiredStagePosition: Int
) {
- val topLeftTaskPercent: Float
- val dividerBarPercent: Float
- if (splitInfo.appsStackedVertically) {
- topLeftTaskPercent = splitInfo.topTaskPercent
- dividerBarPercent = splitInfo.dividerHeightPercent
- } else {
- topLeftTaskPercent = splitInfo.leftTaskPercent
- dividerBarPercent = splitInfo.dividerWidthPercent
- }
+ val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+ val dividerBarPercent = splitInfo.dividerPercent
// In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
// the screen. This is to preserve consistency when the user rotates: From the user's POV,
@@ -166,11 +159,7 @@
} else {
if (desiredTaskId == splitBounds.leftTopTaskId) {
val bottomRightTaskPlusDividerPercent =
- if (splitBounds.appsStackedVertically) {
- 1f - splitBounds.topTaskPercent
- } else {
- 1f - splitBounds.leftTaskPercent
- }
+ splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent
translationY =
banner.height -
(taskViewHeight - snapshotParams.topMargin) *
@@ -331,12 +320,7 @@
val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
- val taskPercent =
- if (splitBoundsConfig.appsStackedVertically) {
- splitBoundsConfig.topTaskPercent
- } else {
- splitBoundsConfig.leftTaskPercent
- }
+ val taskPercent = splitBoundsConfig.leftTopTaskPercent
val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
val secondTaskViewSize =
Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
index fb62268..619075f 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -16,6 +16,7 @@
package com.android.quickstep.recents.ui.mapper
+import android.view.View.OnClickListener
import com.android.quickstep.recents.ui.viewmodel.TaskData
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
@@ -36,29 +37,38 @@
* @param taskData The [TaskData] to convert. Can be null or a specific subclass.
* @param isLiveTile A flag indicating whether the task data represents live tile.
* @param hasHeader A flag indicating whether the UI should display a header.
+ * @param clickCloseListener A callback when the close button in the UI is clicked.
* @return A [TaskThumbnailUiState] representing the UI state for the given task data.
*/
fun toTaskThumbnailUiState(
taskData: TaskData?,
isLiveTile: Boolean,
hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
): TaskThumbnailUiState =
when {
taskData !is TaskData.Data -> Uninitialized
- isLiveTile -> createLiveTileState(taskData, hasHeader)
+ isLiveTile -> createLiveTileState(taskData, hasHeader, clickCloseListener)
isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
isSnapshotSplash(taskData) ->
- SnapshotSplash(createSnapshotState(taskData, hasHeader), taskData.icon)
+ SnapshotSplash(
+ createSnapshotState(taskData, hasHeader, clickCloseListener),
+ taskData.icon,
+ )
else -> Uninitialized
}
- private fun createSnapshotState(taskData: TaskData.Data, hasHeader: Boolean): Snapshot =
- if (canHeaderBeCreated(taskData, hasHeader)) {
+ private fun createSnapshotState(
+ taskData: TaskData.Data,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ): Snapshot =
+ if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
Snapshot.WithHeader(
taskData.thumbnailData?.thumbnail!!,
taskData.thumbnailData.rotation,
taskData.backgroundColor,
- ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!),
+ ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!),
)
} else {
Snapshot.WithoutHeader(
@@ -74,13 +84,26 @@
private fun isSnapshotSplash(taskData: TaskData.Data) =
taskData.thumbnailData?.thumbnail != null && !taskData.isLocked
- private fun canHeaderBeCreated(taskData: TaskData.Data, hasHeader: Boolean) =
- hasHeader && taskData.icon != null && taskData.titleDescription != null
+ private fun canHeaderBeCreated(
+ taskData: TaskData.Data,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ) =
+ hasHeader &&
+ taskData.icon != null &&
+ taskData.titleDescription != null &&
+ clickCloseListener != null
- private fun createLiveTileState(taskData: TaskData.Data, hasHeader: Boolean) =
- if (canHeaderBeCreated(taskData, hasHeader)) {
+ private fun createLiveTileState(
+ taskData: TaskData.Data,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ) =
+ if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
// TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
// null.
- LiveTile.WithHeader(ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!))
+ LiveTile.WithHeader(
+ ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!)
+ )
} else LiveTile.WithoutHeader
}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index 6118544..db593d3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -19,6 +19,7 @@
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.Surface
+import android.view.View.OnClickListener
import androidx.annotation.ColorInt
sealed class TaskThumbnailUiState {
@@ -54,5 +55,9 @@
) : Snapshot()
}
- data class ThumbnailHeader(val icon: Drawable, val title: String)
+ data class ThumbnailHeader(
+ val icon: Drawable,
+ val title: String,
+ val clickCloseListener: OnClickListener,
+ )
}
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index f20d7a5..8385485 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -26,6 +26,7 @@
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.systemui.shared.recents.utilities.Utilities.isFreeformTask;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex;
@@ -69,6 +70,7 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.Arrays;
@@ -337,10 +339,12 @@
* c) App B is on-screen, but App A isn't.
* d) Neither is on-screen.
*
- * If the user tapped an app pair while inside a single app, there are 3 cases:
- * a) The on-screen app is App A of the app pair.
- * b) The on-screen app is App B of the app pair.
- * c) It is neither.
+ * If the user tapped an app pair while a fullscreen or freeform app is visible on screen,
+ * there are 4 cases:
+ * a) At least one of the apps in the app pair is in freeform windowing mode.
+ * b) The on-screen app is App A of the app pair.
+ * c) The on-screen app is App B of the app pair.
+ * d) It is neither.
*
* For each case, we call the appropriate animation and split launch type.
*/
@@ -422,6 +426,14 @@
foundTasks -> {
Task foundTask1 = foundTasks[0];
Task foundTask2 = foundTasks[1];
+
+ if (DesktopModeStatus.canEnterDesktopMode(context) && (isFreeformTask(
+ foundTask1) || isFreeformTask(foundTask2))) {
+ launchAppPair(launchingIconView,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
+ return;
+ }
+
boolean task1IsOnScreen;
boolean task2IsOnScreen;
if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index 9f3c017..9943770 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -91,6 +91,9 @@
}
}
+ protected fun getScrollAdjustment(showAsGrid: Boolean): Int =
+ if (showAsGrid) gridTranslationX.toInt() else 0
+
private fun getBorderBounds(bounds: Rect) {
bounds.set(0, 0, width, height)
val outlinePadding =
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index fbda3b3..75f3b69 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -15,7 +15,6 @@
*/
package com.android.quickstep.views
-import android.animation.Animator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Matrix
@@ -36,7 +35,6 @@
import com.android.launcher3.Flags.enableOverviewIconMenu
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
-import com.android.launcher3.anim.AnimatedFloat
import com.android.launcher3.testing.TestLogging
import com.android.launcher3.testing.shared.TestProtocol
import com.android.launcher3.util.RunnableList
@@ -165,9 +163,6 @@
?.inflate()
}
- fun startWindowExplodeAnimation(): Animator =
- AnimatedFloat { progress -> explodeProgress = progress }.animateToValue(0.0f, 1.0f)
-
private fun positionTaskWindows() {
if (taskContainers.isEmpty()) {
return
@@ -350,14 +345,7 @@
explodeProgress = 0.0f
viewModel = null
visibility = VISIBLE
- taskContainers.forEach {
- contentView.removeView(it.snapshotView)
- if (enableRefactorTaskThumbnail()) {
- taskThumbnailViewPool!!.recycle(it.thumbnailView)
- } else {
- taskThumbnailViewDeprecatedPool!!.recycle(it.thumbnailViewDeprecated)
- }
- }
+ taskContainers.forEach { removeAndRecycleThumbnailView(it) }
}
@SuppressLint("RtlHardcoded")
@@ -365,19 +353,7 @@
super.updateTaskSize(lastComputedTaskSize, lastComputedGridTaskSize)
this.lastComputedTaskSize.set(lastComputedTaskSize)
- BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
- val desktopSize = Size(tempPointF.x.toInt(), tempPointF.y.toInt())
- DEFAULT_BOUNDS.set(0, 0, desktopSize.width / 4, desktopSize.height / 4)
-
- fullscreenTaskPositions =
- taskContainers.map {
- DesktopTaskBoundsData(it.task.key.id, it.task.appBounds ?: DEFAULT_BOUNDS)
- }
-
- if (enableDesktopExplodedView()) {
- viewModel?.organizeDesktopTasks(desktopSize, fullscreenTaskPositions)
- }
- positionTaskWindows()
+ updateTaskPositions()
}
override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
@@ -463,6 +439,56 @@
ViewUtils.addAccessibleChildToList(backgroundView, outChildren)
}
+ fun removeTaskFromExplodedView(taskId: Int, animate: Boolean) {
+ if (!enableDesktopExplodedView()) {
+ Log.e(
+ TAG,
+ "removeTaskFromExplodedView called when enableDesktopExplodedView flag is false",
+ )
+ return
+ }
+
+ // Remove the task's [taskContainer] and its associated Views.
+ val taskContainer = getTaskContainerById(taskId) ?: return
+ removeAndRecycleThumbnailView(taskContainer)
+ taskContainer.destroy()
+ taskContainers = taskContainers.filterNot { it == taskContainer }
+
+ // Dismiss the current DesktopTaskView if all its windows are closed.
+ if (taskContainers.isEmpty()) {
+ recentsView?.dismissTaskView(this, animate, /* removeTask= */ true)
+ } else {
+ // Otherwise, re-position the remaining task windows.
+ // TODO(b/353949276): Implement the re-layout animations.
+ updateTaskPositions()
+ }
+ }
+
+ private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) {
+ contentView.removeView(taskContainer.snapshotView)
+ if (enableRefactorTaskThumbnail()) {
+ taskThumbnailViewPool!!.recycle(taskContainer.thumbnailView)
+ } else {
+ taskThumbnailViewDeprecatedPool!!.recycle(taskContainer.thumbnailViewDeprecated)
+ }
+ }
+
+ private fun updateTaskPositions() {
+ BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
+ val desktopSize = Size(tempPointF.x.toInt(), tempPointF.y.toInt())
+ DEFAULT_BOUNDS.set(0, 0, desktopSize.width / 4, desktopSize.height / 4)
+
+ fullscreenTaskPositions =
+ taskContainers.map {
+ DesktopTaskBoundsData(it.task.key.id, it.task.appBounds ?: DEFAULT_BOUNDS)
+ }
+
+ if (enableDesktopExplodedView()) {
+ viewModel?.organizeDesktopTasks(desktopSize, fullscreenTaskPositions)
+ }
+ positionTaskWindows()
+ }
+
companion object {
private const val TAG = "DesktopTaskView"
private const val DEBUG = false
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
index c07b7fb..5c4a35d 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
@@ -189,11 +189,11 @@
SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
// For landscape grid, for 30% width we only show icon, otherwise show icon and time
task.key.id == splitBounds.leftTopTaskId ->
- if (splitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY)
+ if (splitBounds.leftTopTaskPercent < THRESHOLD_LEFT_ICON_ONLY)
SplitBannerConfig.SPLIT_GRID_BANNER_SMALL
else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
else ->
- if (splitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY)
+ if (splitBounds.leftTopTaskPercent > THRESHOLD_RIGHT_ICON_ONLY)
SplitBannerConfig.SPLIT_GRID_BANNER_SMALL
else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 2abfb13..25011d7 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -189,16 +189,12 @@
val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
if (enableFlexibleTwoAppSplit()) {
- val topLeftTaskPercent =
- if (deviceProfile.isLeftRightSplit) splitBoundsConfig.leftTaskPercent
- else splitBoundsConfig.topTaskPercent
- val bottomRightTaskPercent = 1 - topLeftTaskPercent
- leftTopTaskContainer.iconView.setFlexSplitAlpha(
- if (topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
- rightBottomTaskContainer.iconView.setFlexSplitAlpha(
- if (bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON) 0f else 1f
- )
+ val topLeftTaskPercent = splitBoundsConfig.leftTopTaskPercent
+ val bottomRightTaskPercent = splitBoundsConfig.rightBottomTaskPercent
+ val hideTopLeftIcon = topLeftTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+ val hideBottomRightIcon = bottomRightTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+ leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideTopLeftIcon) 0f else 1f)
+ rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideBottomRightIcon) 0f else 1f)
}
if (enableOverviewIconMenu()) {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
index 96eed87..3430b39 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
@@ -76,7 +76,7 @@
}
.addEndListener { _, _, _, _ ->
if (isDismissing) {
- recentsView.dismissTask(
+ recentsView.dismissTaskView(
draggedTaskView,
/* animateTaskView = */ false,
/* removeTask = */ true,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 36af01a..f53583a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -38,6 +38,7 @@
import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Flags.enableDesktopExplodedView;
import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation;
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
@@ -72,6 +73,7 @@
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
+import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -674,11 +676,11 @@
MAIN_EXECUTOR,
apkRemoved -> {
if (apkRemoved) {
- dismissTask(taskId);
+ dismissTask(taskId, /*animate=*/true, /*removeTask=*/false);
} else {
mModel.isTaskRemoved(taskKey.id, taskRemoved -> {
if (taskRemoved) {
- dismissTask(taskId);
+ dismissTask(taskId, /*animate=*/true, /*removeTask=*/false);
}
}, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter()));
}
@@ -2910,13 +2912,15 @@
}
if (enableDesktopExplodedView()) {
+ if (animatorSet == null) {
+ mUtils.setDeskExplodeProgress(1);
+ } else {
+ animatorSet.play(
+ ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, 1));
+ }
+
for (TaskView taskView : getTaskViews()) {
if (taskView instanceof DesktopTaskView desktopTaskView) {
- if (animatorSet == null) {
- desktopTaskView.setExplodeProgress(1.0f);
- } else {
- animatorSet.play(desktopTaskView.startWindowExplodeAnimation());
- }
desktopTaskView.setRemoteTargetHandles(remoteTargetHandles);
}
}
@@ -3858,7 +3862,8 @@
newClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart;
}
}
- if (lastGridTaskView != null && lastGridTaskView.isVisibleToUser()) {
+ if (lastGridTaskView != null && (lastGridTaskView.isVisibleToUser() || (
+ enableExpressiveDismissTaskMotion() && lastGridTaskView == dismissedTaskView))) {
// After dismissal, animate translation of the remaining tasks to fill any gap left
// between the end of the grid and the clear all button. Only animate if the clear
// all button is visible or would become visible after dismissal.
@@ -4626,17 +4631,27 @@
}
@UiThread
- private void dismissTask(int taskId) {
+ public void dismissTask(int taskId, boolean animate, boolean removeTask) {
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView == null) {
Log.d(TAG, "dismissTask: " + taskId + ", no associated TaskView");
return;
}
Log.d(TAG, "dismissTask: " + taskId);
- dismissTask(taskView, true /* animate */, false /* removeTask */);
+
+ if (enableDesktopExplodedView() && taskView instanceof DesktopTaskView desktopTaskView) {
+ desktopTaskView.removeTaskFromExplodedView(taskId, animate);
+
+ if (removeTask) {
+ ActivityManagerWrapper.getInstance().removeTask(taskId);
+ }
+ } else {
+ dismissTaskView(taskView, animate, removeTask);
+ }
}
- public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
+ /** Dismisses the entire [taskView]. */
+ public void dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask) {
PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION,
false /* dismissingForSplitSelection*/);
@@ -4652,7 +4667,7 @@
private void dismissCurrentTask() {
TaskView taskView = getNextPageTaskView();
if (taskView != null) {
- dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
+ dismissTaskView(taskView, true /*animateTaskView*/, true /*removeTask*/);
}
}
@@ -6247,8 +6262,8 @@
int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
if (addDesktopButtonIndex != -1 && addDesktopButtonIndex < outPageScrolls.length) {
outPageScrolls[addDesktopButtonIndex] =
- newPageScrolls[addDesktopButtonIndex] + Math.round(
- mAddDesktopButton.getGridTranslationX());
+ newPageScrolls[addDesktopButtonIndex] + mAddDesktopButton.getScrollAdjustment(
+ showAsGrid);
}
int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index f742ec3..8df7430 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.views
import android.graphics.Rect
+import android.util.FloatProperty
import android.view.View
import androidx.core.view.children
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
@@ -294,7 +295,24 @@
}
}
+ var deskExplodeProgress: Float = 0f
+ set(value) {
+ field = value
+ taskViews.filterIsInstance<DesktopTaskView>().forEach { it.explodeProgress = field }
+ }
+
companion object {
+ @JvmField
+ val DESK_EXPLODE_PROGRESS =
+ object : FloatProperty<RecentsView<*, *>>("deskExplodeProgress") {
+ override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
+ recentsView.mUtils.deskExplodeProgress = value
+ }
+
+ override fun get(recentsView: RecentsView<*, *>) =
+ recentsView.mUtils.deskExplodeProgress
+ }
+
val TEMP_RECT = Rect()
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 7301cfc..6339c5e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -19,6 +19,7 @@
import android.graphics.Bitmap
import android.graphics.Matrix
import android.view.View
+import android.view.View.OnClickListener
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.model.data.TaskViewItemInfo
import com.android.launcher3.util.SplitConfigurationOptions
@@ -127,9 +128,19 @@
overlay.addChildForAccessibility(outChildren)
}
- fun setState(state: TaskData?, liveTile: Boolean, hasHeader: Boolean) {
+ fun setState(
+ state: TaskData?,
+ liveTile: Boolean,
+ hasHeader: Boolean,
+ clickCloseListener: OnClickListener?,
+ ) {
thumbnailView.setState(
- TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader),
+ TaskUiStateMapper.toTaskThumbnailUiState(
+ state,
+ liveTile,
+ hasHeader,
+ clickCloseListener,
+ ),
state?.taskId,
)
thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
index 9eb294a..9a8805b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.util.AttributeSet
import android.widget.FrameLayout
+import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
import com.android.launcher3.R
@@ -30,9 +31,11 @@
private val headerTitleView: TextView by lazy { findViewById(R.id.header_app_title) }
private val headerIconView: ImageView by lazy { findViewById(R.id.header_app_icon) }
+ private val headerCloseButton: ImageButton by lazy { findViewById(R.id.header_close_button) }
fun setHeader(header: ThumbnailHeader) {
headerTitleView.setText(header.title)
headerIconView.setImageDrawable(header.icon)
+ headerCloseButton.setOnClickListener(header.clickCloseListener)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index ba54232..0e5382a 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -44,6 +44,7 @@
import androidx.core.view.updateLayoutParams
import com.android.app.animation.Interpolators
import com.android.launcher3.Flags.enableCursorHoverStates
+import com.android.launcher3.Flags.enableDesktopExplodedView
import com.android.launcher3.Flags.enableGridOnlyOverview
import com.android.launcher3.Flags.enableHoverOfChildElementsInTaskview
import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
@@ -768,11 +769,27 @@
// Updating containers
val mapOfTasks = state.tasks.associateBy { it.taskId }
taskContainers.forEach { container ->
- val containerState = mapOfTasks[container.task.key.id]
+ val taskId = container.task.key.id
+ val containerState = mapOfTasks[taskId]
+ val shouldHaveHeader = (type == TaskViewType.DESKTOP) && enableDesktopExplodedView()
container.setState(
state = containerState,
liveTile = state.isLiveTile,
- hasHeader = state.hasHeader,
+ hasHeader = shouldHaveHeader,
+ clickCloseListener =
+ if (shouldHaveHeader) {
+ {
+ // Update the layout UI to remove this task from the layout grid, and
+ // remove the task from ActivityManager afterwards.
+ recentsView?.dismissTask(
+ taskId,
+ /* animate= */ true,
+ /* removeTask= */ true,
+ )
+ }
+ } else {
+ null
+ },
)
updateThumbnailValidity(container)
updateThumbnailMatrix(
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
index 0005df6..09c62aa 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
@@ -19,7 +19,6 @@
import android.app.prediction.AppTarget
import android.app.prediction.AppTargetEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION
import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS
@@ -54,11 +53,17 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
modelHelper = LauncherModelHelper()
- underTest = QuickstepModelDelegate(modelHelper.sandboxContext)
+ underTest =
+ QuickstepModelDelegate(
+ modelHelper.sandboxContext,
+ modelHelper.sandboxContext.appComponent.idp,
+ modelHelper.sandboxContext.appComponent.packageManagerHelper,
+ "", /* dbFileName */
+ )
underTest.mAllAppsState.predictor = allAppsPredictor
underTest.mHotseatState.predictor = hotseatPredictor
underTest.mWidgetsRecommendationState.predictor = widgetRecommendationPredictor
- underTest.mApp = LauncherAppState.getInstance(modelHelper.sandboxContext)
+ underTest.mModel = modelHelper.model
underTest.mDataModel = BgDataModel()
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 7776351..de6920b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -24,6 +24,8 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.quickstep.DeviceConfigWrapper.DEFAULT_LPNH_TIMEOUT_MS;
@@ -33,9 +35,11 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.os.SystemClock;
@@ -47,6 +51,7 @@
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.AllModulesForTest;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
@@ -83,6 +88,7 @@
private NavHandleLongPressInputConsumer mUnderTest;
private SandboxContext mContext;
private float mScreenWidth;
+ private long mDownTimeMs;
@Mock InputConsumer mDelegate;
@Mock InputMonitorCompat mInputMonitor;
@Mock RecentsAnimationDeviceState mDeviceState;
@@ -91,6 +97,9 @@
@Mock NavHandleLongPressHandler mNavHandleLongPressHandler;
@Mock TopTaskTracker mTopTaskTracker;
@Mock TopTaskTracker.CachedTaskInfo mTaskInfo;
+ @Mock StatsLogManager mStatsLogManager;
+ @Mock StatsLogManager.StatsLogger mStatsLogger;
+ @Mock StatsLogManager.StatsLatencyLogger mStatsLatencyLogger;
@Before
public void setup() {
@@ -100,6 +109,11 @@
when(mDelegate.allowInterceptByParent()).thenReturn(true);
mLongPressTriggered.set(false);
when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(mLongPressRunnable);
+ when(mStatsLogger.withPackageName(any())).thenReturn(mStatsLogger);
+ when(mStatsLatencyLogger.withInstanceId(any())).thenReturn(mStatsLatencyLogger);
+ when(mStatsLatencyLogger.withLatency(anyLong())).thenReturn(mStatsLatencyLogger);
+ when(mStatsLogManager.logger()).thenReturn(mStatsLogger);
+ when(mStatsLogManager.latencyLogger()).thenReturn(mStatsLatencyLogger);
initializeObjectUnderTest();
}
@@ -124,17 +138,24 @@
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
public void testDelegateDisallowsTouchInterceptAfterTouchDown() {
+ // Touch down and wait the minimum abandonment time.
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ SystemClock.sleep(NavHandleLongPressInputConsumer.MIN_TIME_TO_LOG_ABANDON_MS);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
// Delegate should still get touches unless long press is triggered.
verify(mDelegate).onMotionEvent(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ // Child delegate blocks us from intercepting further motion events.
when(mDelegate.allowInterceptByParent()).thenReturn(false);
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_MOVE));
@@ -144,6 +165,9 @@
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ // Because we handled touch down before the child blocked additional events, log abandon.
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -156,6 +180,12 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
+
+ // Ensure abandon latency is still not logged after long press.
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -170,6 +200,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -184,6 +216,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -215,6 +249,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -235,6 +271,8 @@
assertTrue(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -258,6 +296,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -278,6 +318,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -299,6 +341,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -320,6 +364,8 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
}
@Test
@@ -354,6 +400,8 @@
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
// Touch cancelled.
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -391,6 +439,8 @@
verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
// Touch cancelled.
verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogger);
+ verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -408,6 +458,9 @@
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -422,6 +475,21 @@
mUnderTest.onHoverEvent(generateCenteredMotionEvent(ACTION_HOVER_ENTER));
verify(mDelegate, times(2)).onHoverEvent(any());
+
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
+ }
+
+ @Test
+ public void testNoLogsForShortTouch() {
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+ SystemClock.sleep(10);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
private void initializeObjectUnderTest() {
@@ -437,6 +505,8 @@
mUnderTest = new NavHandleLongPressInputConsumer(mContext, mDelegate, mInputMonitor,
mDeviceState, mNavHandle, mGestureState);
mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler);
+ mUnderTest.setStatsLogManager(mStatsLogManager);
+ mDownTimeMs = 0;
}
/** Generate a motion event centered horizontally in the screen. */
@@ -449,8 +519,12 @@
return generateMotionEvent(motionAction, mScreenWidth / 2f, y);
}
- private static MotionEvent generateMotionEvent(int motionAction, float x, float y) {
- return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
+ private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
+ if (motionAction == ACTION_DOWN) {
+ mDownTimeMs = SystemClock.uptimeMillis();
+ }
+ long eventTime = SystemClock.uptimeMillis();
+ return MotionEvent.obtain(mDownTimeMs, eventTime, motionAction, x, y, 0);
}
private static AutoCloseable overrideTwoStageFlag(boolean value) {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
index 124045f..7ca194a 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
@@ -21,6 +21,7 @@
import android.graphics.drawable.ShapeDrawable
import android.platform.test.annotations.EnableFlags
import android.view.Surface
+import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.Flags
import com.android.quickstep.recents.ui.viewmodel.TaskData
@@ -43,6 +44,7 @@
taskData = null,
isLiveTile = false,
hasHeader = false,
+ clickCloseListener = null,
)
assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
}
@@ -57,6 +59,7 @@
taskData = input,
isLiveTile = true,
hasHeader = false,
+ clickCloseListener = null,
)
assertThat(result).isEqualTo(LiveTile.WithoutHeader)
}
@@ -72,14 +75,18 @@
TASK_DATA.copy(isLocked = true),
TASK_DATA.copy(title = null),
)
+ val closeCallback = View.OnClickListener {}
val expected =
- LiveTile.WithHeader(header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION))
+ LiveTile.WithHeader(
+ header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback)
+ )
inputs.forEach { taskData ->
val result =
TaskUiStateMapper.toTaskThumbnailUiState(
taskData = taskData,
isLiveTile = true,
hasHeader = true,
+ clickCloseListener = closeCallback,
)
assertThat(result).isEqualTo(expected)
}
@@ -101,6 +108,7 @@
taskData = taskData,
isLiveTile = true,
hasHeader = true,
+ clickCloseListener = {},
)
assertThat(result).isEqualTo(LiveTile.WithoutHeader)
}
@@ -113,6 +121,7 @@
taskData = TASK_DATA,
isLiveTile = false,
hasHeader = false,
+ clickCloseListener = null,
)
val expected =
@@ -133,6 +142,7 @@
@Test
fun taskData_isStaticTile_withHeader_returns_SnapshotSplashWithHeader() {
val inputs = listOf(TASK_DATA, TASK_DATA.copy(title = null))
+ val closeCallback = View.OnClickListener {}
val expected =
TaskThumbnailUiState.SnapshotSplash(
snapshot =
@@ -140,7 +150,7 @@
backgroundColor = TASK_BACKGROUND_COLOR,
bitmap = TASK_THUMBNAIL,
thumbnailRotation = Surface.ROTATION_0,
- header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION),
+ header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback),
),
splash = TASK_ICON,
)
@@ -150,6 +160,7 @@
taskData = taskData,
isLiveTile = false,
hasHeader = true,
+ clickCloseListener = closeCallback,
)
assertThat(result).isEqualTo(expected)
}
@@ -176,6 +187,7 @@
taskData = taskData,
isLiveTile = false,
hasHeader = true,
+ clickCloseListener = {},
)
assertThat(result).isInstanceOf(TaskThumbnailUiState.SnapshotSplash::class.java)
@@ -191,6 +203,7 @@
taskData = TASK_DATA.copy(thumbnailData = null),
isLiveTile = false,
hasHeader = false,
+ clickCloseListener = null,
)
val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
@@ -204,6 +217,7 @@
taskData = TASK_DATA.copy(isLocked = true),
isLiveTile = false,
hasHeader = false,
+ clickCloseListener = null,
)
val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
@@ -212,6 +226,7 @@
private companion object {
const val TASK_TITLE_DESCRIPTION = "Title Description 1"
+ var TASK_ID = 1
val TASK_ICON = ShapeDrawable()
val TASK_THUMBNAIL = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
val TASK_THUMBNAIL_DATA =
@@ -219,7 +234,7 @@
val TASK_BACKGROUND_COLOR = Color.rgb(1, 2, 3)
val TASK_DATA =
TaskData.Data(
- 1,
+ TASK_ID,
title = "Task 1",
titleDescription = TASK_TITLE_DESCRIPTION,
icon = TASK_ICON,
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
index ee70e0a..76d36d3 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -16,7 +16,9 @@
package com.android.quickstep.util
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
+import android.content.res.Resources
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.logging.StatsLogManager
@@ -28,6 +30,7 @@
import com.android.quickstep.TopTaskTracker.CachedTaskInfo
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33
@@ -54,6 +57,7 @@
@RunWith(AndroidJUnit4::class)
class AppPairsControllerTest {
@Mock lateinit var context: Context
+ @Mock lateinit var resources: Resources
@Mock lateinit var splitSelectStateController: SplitSelectStateController
@Mock lateinit var statsLogManager: StatsLogManager
@@ -109,6 +113,8 @@
doNothing()
.whenever(spyAppPairsController)
.launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
+ whenever(mockAppPairIcon.context.resources).thenReturn(resources)
+ whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(false)
}
@Test
@@ -392,6 +398,68 @@
}
@Test
+ fun handleAppPairLaunchInApp_freeformTask1IsOnScreen_shouldLaunchAppPair() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true)
+ /// Test launching apps 1 and 2 from app pair
+ whenever(mockTaskKey1.getId()).thenReturn(1)
+ whenever(mockTaskKey2.getId()).thenReturn(2)
+ // Task 1 is in freeform windowing mode
+ mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM
+ // ... and app 1 is already on screen
+ if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(1))).thenReturn(true)
+ } else {
+ whenever(mockCachedTaskInfo.taskId).thenReturn(1)
+ }
+
+ // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback
+ spyAppPairsController.handleAppPairLaunchInApp(
+ mockAppPairIcon,
+ listOf(mockItemInfo1, mockItemInfo2),
+ )
+ verify(splitSelectStateController)
+ .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture())
+ val callback: Consumer<Array<Task>> = callbackCaptor.value
+ callback.accept(arrayOf(mockTask1, mockTask2))
+
+ // Verify that launchAppPair was called
+ verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
+ verify(spyAppPairsController, never())
+ .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
+ }
+
+ @Test
+ fun handleAppPairLaunchInApp_freeformTask2IsOnScreen_shouldLaunchAppPair() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true)
+ /// Test launching apps 1 and 2 from app pair
+ whenever(mockTaskKey1.getId()).thenReturn(1)
+ whenever(mockTaskKey2.getId()).thenReturn(2)
+ // Task 2 is in freeform windowing mode
+ mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM
+ // ... and app 2 is already on screen
+ if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+ whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(2))).thenReturn(true)
+ } else {
+ whenever(mockCachedTaskInfo.taskId).thenReturn(2)
+ }
+
+ // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback
+ spyAppPairsController.handleAppPairLaunchInApp(
+ mockAppPairIcon,
+ listOf(mockItemInfo1, mockItemInfo2),
+ )
+ verify(splitSelectStateController)
+ .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture())
+ val callback: Consumer<Array<Task>> = callbackCaptor.value
+ callback.accept(arrayOf(mockTask1, mockTask2))
+
+ // Verify that launchAppPair was called
+ verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
+ verify(spyAppPairsController, never())
+ .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
+ }
+
+ @Test
fun handleAppPairLaunchInApp_shouldLaunchAppPairNormallyWhenUnrelatedSingleAppIsFullscreen() {
// Test launching apps 1 and 2 from app pair
whenever(mockTaskKey1.getId()).thenReturn(1)
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index fa2eb1e..d52d054 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -91,6 +91,7 @@
private AppWidgetProviderInfo mApp4Provider1;
private AppWidgetProviderInfo mApp4Provider2;
private AppWidgetProviderInfo mApp5Provider1;
+ private AppWidgetProviderInfo mApp6PinOnlyProvider1;
private List<AppWidgetProviderInfo> allWidgets;
private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
@@ -117,8 +118,14 @@
ComponentName.createRelative("app4", ".provider2"));
mApp5Provider1 = createAppWidgetProviderInfo(
ComponentName.createRelative("app5", "provider1"));
+ mApp6PinOnlyProvider1 = createAppWidgetProviderInfo(
+ ComponentName.createRelative("app6", "provider1"),
+ /*hideFromPicker=*/ true
+ );
+
+
allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
- mApp4Provider1, mApp4Provider2, mApp5Provider1);
+ mApp4Provider1, mApp4Provider2, mApp5Provider1, mApp6PinOnlyProvider1);
mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
doAnswer(i -> {
@@ -270,6 +277,32 @@
});
}
+ @Test
+ public void widgetsRecommendations_excludesWidgetsHiddenForPicker() {
+ runOnExecutorSync(MODEL_EXECUTOR, () -> {
+
+ // Not installed widget - hence eligible
+ AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
+ mUserHandle);
+ // Provider marked as hidden from picker - hence not eligible
+ AppTarget widget6 = new AppTarget(new AppTargetId("app6"), "app6", "provider1",
+ mUserHandle);
+
+ mCallback.mRecommendedWidgets = null;
+ mModelHelper.getModel().enqueueModelUpdateTask(
+ newWidgetsPredicationTask(List.of(widget1, widget6)));
+ runOnExecutorSync(MAIN_EXECUTOR, () -> { });
+
+ // Only widget 1 (and no widget 6 as its meant to be hidden from picker).
+ List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
+ .stream()
+ .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
+ .collect(Collectors.toList());
+ assertThat(recommendedWidgets).hasSize(1);
+ assertThat(recommendedWidgets.get(0).componentName.getPackageName()).isEqualTo("app1");
+ });
+ }
+
private void assertWidgetInfo(
LauncherAppWidgetProviderInfo actual, AppWidgetProviderInfo expected) {
assertThat(actual.provider).isEqualTo(expected.provider);
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 1c7f51c..1c87bce 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -23,6 +23,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
@@ -581,6 +582,39 @@
numTasks - 1, recentsView.getTaskViewCount()));
}
+ @Test
+ @PortraitLandscape
+ public void testDismissLastGridRow() throws Exception {
+ assumeTrue(mLauncher.isTablet());
+ mLauncher.goHome().switchToOverview().dismissAllTasks();
+ startTestAppsWithCheck();
+ startTestActivity(3);
+ startTestActivity(4);
+ runOnRecentsView(
+ recentsView -> assertNotEquals("Grid overview should have unequal row counts",
+ recentsView.getTopRowTaskCountForTablet(),
+ recentsView.getBottomRowTaskCountForTablet()));
+ Overview overview = mLauncher.goHome().switchToOverview();
+ assertIsInState("Launcher internal state didn't switch to Overview",
+ ExpectedState.OVERVIEW);
+ overview.flingForwardUntilClearAllVisible();
+ assertTrue("Clear All not visible.", overview.isClearAllVisible());
+ final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
+ OverviewTask lastGridTask = overview.getCurrentTasksForTablet().stream().min(
+ Comparator.comparingInt(OverviewTask::getTaskCenterX)).get();
+ assertNotNull("lastGridTask null.", lastGridTask);
+
+ lastGridTask.dismiss();
+
+ runOnRecentsView(recentsView -> assertEquals(
+ "Dismissing a lastGridTask didn't remove 1 lastGridTask from Overview",
+ numTasks - 1, recentsView.getTaskViewCount()));
+ runOnRecentsView(recentsView -> assertEquals("Grid overview should have equal row counts.",
+ recentsView.getTopRowTaskCountForTablet(),
+ recentsView.getBottomRowTaskCountForTablet()));
+ assertTrue("Clear All not visible.", overview.isClearAllVisible());
+ }
+
private void startTestAppsWithCheck() throws Exception {
startTestApps();
expectLaunchedAppState();
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 6d44a97..98571add 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -62,7 +62,7 @@
<string name="widget_button_text" msgid="2880537293434387943">"Приспособления"</string>
<string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Търсене"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Изчистване на текста от полето за търсене"</string>
- <string name="no_widgets_available" msgid="4337693382501046170">"Няма налице преки пътища и приспособления"</string>
+ <string name="no_widgets_available" msgid="4337693382501046170">"Няма налични преки пътища и приспособления"</string>
<string name="no_search_results" msgid="3787956167293097509">"Няма открити преки пътища или приспособления"</string>
<string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Лични"</string>
<string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Служебни"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index ff8a541..c0568bf 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -41,7 +41,7 @@
<string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite da pomjerite vidžet."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite da pomjerite vidžet ili da koristite prilagođene radnje."</string>
<string name="widget_picker_widget_options_button_description" msgid="4770099264476852363">"Više opcija"</string>
- <string name="widget_picker_show_all_widgets_menu_item_title" msgid="9023638224586908119">"Prikazuj sve vidžete"</string>
+ <string name="widget_picker_show_all_widgets_menu_item_title" msgid="9023638224586908119">"Prikaži sve vidžete"</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, visina %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 2ac7233..f96b907 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -46,12 +46,12 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"šířka %1$d, výška %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, šířka %2$d, výška %3$d"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pokud chcete widgetem pohybovat po ploše, podržte ho"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pokud chcete widgetem pohybovat po ploše, podržte ho."</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Přidat na plochu"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> byl přidán na plochu"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Návrhy"</string>
<string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Nejdůležitější aplikace"</string>
- <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Zprávy a časopisy"</string>
+ <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noviny a časopisy"</string>
<string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zábava"</string>
<string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sociální sítě"</string>
<string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Návrhy pro vás"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 4925294..5a33983 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -60,7 +60,7 @@
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# શૉર્ટકટ}one{# શૉર્ટકટ}other{# શૉર્ટકટ}}"</string>
<string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"વિજેટ"</string>
- <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"શોધ"</string>
+ <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"શોધો"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"શોધ બૉક્સમાંથી ટેક્સ્ટ સાફ કરો"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"વિજેટ અને શૉર્ટકટ ઉપલબ્ધ નથી"</string>
<string name="no_search_results" msgid="3787956167293097509">"કોઈ વિજેટ અથવા શૉર્ટકટ મળ્યા નથી"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 75e7cd2..3145133 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -46,7 +46,7 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d di larghezza per %2$d di altezza"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d di larghezza per %3$d di altezza"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Tocca e tieni premuto il widget per spostarlo nella schermata Home"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Tieni premuto il widget per spostarlo nella schermata Home"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Aggiungi alla schermata Home"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> aggiunto alla schermata Home"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggerimenti"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index b6b5b66..b98b61d 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -46,7 +46,7 @@
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"Туурасы: %1$d, бийиктиги: %2$d"</string>
<string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджети"</string>
<string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджети, кеңдиги %2$d жана бийиктиги %3$d"</string>
- <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Башкы экранга жылдыруу үчүн виджетти коё бербей басып туруңуз"</string>
+ <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Вижетти коё бербей басып туруп башкы экранга жылдырыңыз"</string>
<string name="add_to_home_screen" msgid="9168649446635919791">"Башкы экранга кошуу"</string>
<string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджети башкы экранга кошулду"</string>
<string name="suggested_widgets_header_title" msgid="1844314680798145222">"Сунуштар"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 46aadc0..3eb680f 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -60,7 +60,7 @@
<string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{#ଟି ସର୍ଟକଟ୍}other{#ଟି ସର୍ଟକଟ୍}}"</string>
<string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
<string name="widget_button_text" msgid="2880537293434387943">"ୱିଜେଟ୍"</string>
- <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+ <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"ସନ୍ଧାନ ବାକ୍ସରୁ ଟେକ୍ସଟ୍ ଖାଲି କରନ୍ତୁ"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"ୱିଜେଟ୍ ଏବଂ ସର୍ଟକଟଗୁଡ଼ିକ ଉପଲବ୍ଧ ନାହିଁ"</string>
<string name="no_search_results" msgid="3787956167293097509">"କୌଣସି ୱିଜେଟ କିମ୍ବା ସର୍ଟକଟ ମିଳିଲା ନାହିଁ"</string>
@@ -164,7 +164,7 @@
<string name="action_add_to_workspace" msgid="215894119683164916">"ହୋମ ସ୍କ୍ରିନରେ ଯୋଗ କରନ୍ତୁ"</string>
<string name="action_move_here" msgid="2170188780612570250">"ଆଇଟମ୍କୁ ଏଠାକୁ ଘୁଞ୍ଚାନ୍ତୁ"</string>
<string name="item_removed" msgid="851119963877842327">"ଆଇଟମକୁ କାଢ଼ି ଦିଆଯାଇଛି"</string>
- <string name="undo" msgid="4151576204245173321">"ପୂର୍ବବତ କରନ୍ତୁ"</string>
+ <string name="undo" msgid="4151576204245173321">"ଅନଡୁ କରନ୍ତୁ"</string>
<string name="action_move" msgid="4339390619886385032">"ଆଇଟମ୍ ଘୁଞ୍ଚାନ୍ତୁ"</string>
<string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>ରେ ଧାଡି <xliff:g id="NUMBER_0">%1$s</xliff:g> ସ୍ତମ୍ଭ <xliff:g id="NUMBER_1">%2$s</xliff:g>କୁ ମୁଭ କରନ୍ତୁ"</string>
<string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ସ୍ଥିତିକୁ ନିଅନ୍ତୁ"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 4145fb6..4b25912 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -64,8 +64,8 @@
<string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"సెర్చ్ బాక్స్ నుండి టెక్స్ట్ను క్లియర్ చేయండి"</string>
<string name="no_widgets_available" msgid="4337693382501046170">"విడ్జెట్లు, షార్ట్కట్లు అందుబాటులో లేవు"</string>
<string name="no_search_results" msgid="3787956167293097509">"విడ్జెట్లు లేదా షార్ట్కట్లు కనుగొనబడలేదు"</string>
- <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"వ్యక్తిగత గ్యాడ్జెట్స్"</string>
- <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ఆఫీస్"</string>
+ <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"వ్యక్తిగత విడ్జెట్స్"</string>
+ <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"వర్క్ విడ్జెట్స్"</string>
<string name="widget_category_conversations" msgid="8894438636213590446">"సంభాషణలు"</string>
<string name="widget_category_note_taking" msgid="3469689394504266039">"నోట్-టేకింగ్"</string>
<string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"యాడ్ చేసే (జోడించే) బటన్ను చూపండి"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index cb47400..3fdae59 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -31,7 +31,7 @@
<string name="recent_task_option_split_screen" msgid="6690461455618725183">"Bölünmüş ekran"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s uygulama bilgileri"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ile ilgili kullanım ayarları"</string>
- <string name="new_window_option_taskbar" msgid="6448780542727767211">"Yeni Pencere"</string>
+ <string name="new_window_option_taskbar" msgid="6448780542727767211">"Yeni pencere"</string>
<string name="manage_windows_option_taskbar" msgid="2294109489960654212">"Pencereleri yönet"</string>
<string name="save_app_pair" msgid="5647523853662686243">"Uygulama çiftini kaydedin"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index ed002ba..855a8c3 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -32,7 +32,7 @@
<string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s کے لیے ایپ کی معلومات"</string>
<string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s کیلئے استعمال کی ترتیبات"</string>
<string name="new_window_option_taskbar" msgid="6448780542727767211">"نئی ونڈو"</string>
- <string name="manage_windows_option_taskbar" msgid="2294109489960654212">"Windows کا نظم کریں"</string>
+ <string name="manage_windows_option_taskbar" msgid="2294109489960654212">"ونڈوز کا نظم کریں"</string>
<string name="save_app_pair" msgid="5647523853662686243">"ایپس کے جوڑے کو محفوظ کریں"</string>
<string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
<string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ایپس کا یہ جوڑا اس آلے پر تعاون یافتہ نہیں ہے"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index cecfedb..febc8c2 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -127,7 +127,7 @@
<string name="folder_name_format_overflow" msgid="4270108890534995199">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 个或更多项目"</string>
<string name="unnamed_folder" msgid="2420192029474044442">"未命名文件夹"</string>
<string name="app_pair_name_format" msgid="8134106404716224054">"应用对:“<xliff:g id="APP1">%1$s</xliff:g>”和“<xliff:g id="APP2">%2$s</xliff:g>”"</string>
- <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁纸与个性化"</string>
+ <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁纸与风格"</string>
<string name="edit_home_screen" msgid="8947858375782098427">"修改主屏幕"</string>
<string name="settings_button_text" msgid="8873672322605444408">"主屏幕设置"</string>
<string name="msg_disabled_by_admin" msgid="6898038085516271325">"已被您的管理员停用"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 07f97bc..1a2ac9e 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -67,7 +67,6 @@
<string name="main_process_initializer_class" translatable="false"></string>
<string name="app_launch_tracker_class" translatable="false"></string>
<string name="test_information_handler_class" translatable="false"></string>
- <string name="model_delegate_class" translatable="false"></string>
<string name="secondary_display_predictions_class" translatable="false"></string>
<string name="widget_holder_factory_class" translatable="false"></string>
<string name="taskbar_search_session_controller_class" translatable="false"></string>
@@ -75,8 +74,6 @@
<string name="taskbar_view_callbacks_factory_class" translatable="false"></string>
<string name="launcher_restore_event_logger_class" translatable="false"></string>
<string name="taskbar_edu_tooltip_controller_class" translatable="false"></string>
- <!-- Used for determining category of a widget presented in widget recommendations. -->
- <string name="widget_recommendation_category_provider_class" translatable="false"></string>
<!-- Default packages -->
<string name="wallpaper_picker_package" translatable="false"></string>
diff --git a/res/values/id.xml b/res/values/id.xml
index 67692d8..78b8308 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -79,6 +79,7 @@
<item type="id" name="saved_clip_children_tag_id" />
<item type="id" name="saved_clip_to_padding_tag_id" />
+ <item type="id" name="perform_a11y_action_on_launcher_state_normal_tag" />
<item type="id" name="saved_floating_widget_foreground" />
<item type="id" name="saved_floating_widget_background" />
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
index 3db456c..8ee7053 100644
--- a/src/com/android/launcher3/AppFilter.java
+++ b/src/com/android/launcher3/AppFilter.java
@@ -3,10 +3,14 @@
import android.content.ComponentName;
import android.content.Context;
+import com.android.launcher3.dagger.ApplicationContext;
+
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
+import javax.inject.Inject;
+
/**
* Utility class to filter out components from various lists
*/
@@ -14,7 +18,8 @@
private final Set<ComponentName> mFilteredComponents;
- public AppFilter(Context context) {
+ @Inject
+ public AppFilter(@ApplicationContext Context context) {
mFilteredComponents = Arrays.stream(
context.getResources().getStringArray(R.array.filtered_components))
.map(ComponentName::unflattenFromString)
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 257f911..0ce966b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,7 +16,6 @@
package com.android.launcher3;
-import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
@@ -71,7 +70,6 @@
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.util.CellAndSpan;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.MSDLPlayerWrapper;
@@ -791,18 +789,8 @@
// Whenever an app is added, if Accessibility service is enabled, focus on that app.
if (mActivity instanceof Launcher) {
- Launcher.cast(mActivity).getStateManager().addStateListener(
- new StateManager.StateListener<LauncherState>() {
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState == NORMAL) {
- child.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
- Launcher.cast(mActivity).getStateManager()
- .removeStateListener(this);
- }
- }
- });
+ child.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag,
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
}
if (markCells) markCellsAsOccupiedForView(child);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
deleted file mode 100644
index bf2ad92..0000000
--- a/src/com/android/launcher3/LauncherAppState.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.dagger.LauncherComponentProvider;
-import com.android.launcher3.graphics.ThemeManager;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.icons.LauncherIconProvider;
-import com.android.launcher3.model.ModelInitializer;
-import com.android.launcher3.model.WidgetsFilterDataProvider;
-import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
-
-public class LauncherAppState implements SafeCloseable {
-
- public static final String TAG = "LauncherAppState";
-
- // We do not need any synchronization for this variable as its only written on UI thread.
- public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
- new MainThreadInitializedObject<>(LauncherAppState::new);
-
- private final Context mContext;
- private final LauncherModel mModel;
- private final LauncherIconProvider mIconProvider;
- private final IconCache mIconCache;
- private final InvariantDeviceProfile mInvariantDeviceProfile;
- private boolean mIsSafeModeEnabled;
-
- private final RunnableList mOnTerminateCallback = new RunnableList();
-
- public static LauncherAppState getInstance(Context context) {
- return INSTANCE.get(context);
- }
-
- public Context getContext() {
- return mContext;
- }
-
- @SuppressWarnings("NewApi")
- public LauncherAppState(Context context) {
- this(context, LauncherFiles.APP_ICONS_DB);
- Log.v(Launcher.TAG, "LauncherAppState initiated");
- Preconditions.assertUIThread();
-
- mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
- () -> context.getPackageManager().isSafeMode());
-
- ModelInitializer initializer = new ModelInitializer(
- context,
- LauncherComponentProvider.get(context).getIconPool(),
- mIconCache,
- mInvariantDeviceProfile,
- ThemeManager.INSTANCE.get(context),
- UserCache.INSTANCE.get(context),
- SettingsCache.INSTANCE.get(context),
- mIconProvider,
- CustomWidgetManager.INSTANCE.get(context),
- InstallSessionHelper.INSTANCE.get(context),
- closeable -> mOnTerminateCallback.add(closeable::close)
- );
- initializer.initialize(mModel);
- }
-
- public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
- mContext = context;
-
- mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
- mIconProvider = new LauncherIconProvider(context);
- mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
- iconCacheFileName, mIconProvider);
- mModel = new LauncherModel(context, this, mIconCache,
- WidgetsFilterDataProvider.Companion.newInstance(context), new AppFilter(mContext),
- PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
- mOnTerminateCallback.add(mIconCache::close);
- mOnTerminateCallback.add(mModel::destroy);
- }
-
- /**
- * Call from Application.onTerminate(), which is not guaranteed to ever be called.
- */
- @Override
- public void close() {
- mOnTerminateCallback.executeAllAndDestroy();
- }
-
- public IconProvider getIconProvider() {
- return mIconProvider;
- }
-
- public IconCache getIconCache() {
- return mIconCache;
- }
-
- public LauncherModel getModel() {
- return mModel;
- }
-
- public InvariantDeviceProfile getInvariantDeviceProfile() {
- return mInvariantDeviceProfile;
- }
-
- public boolean isSafeModeEnabled() {
- return mIsSafeModeEnabled;
- }
-
- /**
- * Shorthand for {@link #getInvariantDeviceProfile()}
- */
- public static InvariantDeviceProfile getIDP(Context context) {
- return InvariantDeviceProfile.INSTANCE.get(context);
- }
-}
diff --git a/src/com/android/launcher3/LauncherAppState.kt b/src/com/android/launcher3/LauncherAppState.kt
new file mode 100644
index 0000000..ff84c3c
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppState.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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 android.content.Context
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.LauncherIconProvider
+import com.android.launcher3.util.DaggerSingletonObject
+import javax.inject.Inject
+import javax.inject.Named
+
+/** A collection of common dependencies used across Launcher */
+@Deprecated("Inject the specific targets directly instead of using LauncherAppState")
+data class LauncherAppState
+@Inject
+constructor(
+ @ApplicationContext val context: Context,
+ val iconProvider: LauncherIconProvider,
+ val iconCache: IconCache,
+ val model: LauncherModel,
+ val invariantDeviceProfile: InvariantDeviceProfile,
+ @Named("SAFE_MODE") val isSafeModeEnabled: Boolean,
+) {
+
+ companion object {
+
+ @JvmField var INSTANCE = DaggerSingletonObject { it.launcherAppState }
+
+ @JvmStatic fun getInstance(context: Context) = INSTANCE[context]
+
+ /** Shorthand for [.getInvariantDeviceProfile] */
+ @JvmStatic fun getIDP(context: Context) = InvariantDeviceProfile.INSTANCE[context]
+ }
+}
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
index 678901b..03eaeea 100644
--- a/src/com/android/launcher3/LauncherApplication.java
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -20,6 +20,7 @@
import com.android.launcher3.dagger.DaggerLauncherAppComponent;
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.launcher3.util.TraceHelper;
/**
* Main application class for Launcher
@@ -41,7 +42,8 @@
if (mAppComponent == null) {
// Initialize the dagger component on demand as content providers can get
// accessed before the Launcher application (b/36917845#comment4)
- initDaggerComponent(DaggerLauncherAppComponent.builder());
+ initDaggerComponent(DaggerLauncherAppComponent.builder()
+ .iconsDbName(LauncherFiles.APP_ICONS_DB));
}
}
}
@@ -55,7 +57,11 @@
/**
* Init with the desired dagger component.
*/
- public void initDaggerComponent(LauncherAppComponent.Builder componentBuilder) {
- mAppComponent = componentBuilder.appContext(this).build();
+ public void initDaggerComponent(LauncherBaseAppComponent.Builder componentBuilder) {
+ mAppComponent = componentBuilder
+ .appContext(this)
+ .setSafeModeEnabled(TraceHelper.allowIpcs(
+ "isSafeMode", () -> getPackageManager().isSafeMode()))
+ .build();
}
}
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
index 6e4276d..892a218 100644
--- a/src/com/android/launcher3/LauncherModel.kt
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -23,6 +23,8 @@
import android.util.Pair
import androidx.annotation.WorkerThread
import com.android.launcher3.celllayout.CellPosMapper
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.AddWorkspaceItemsTask
import com.android.launcher3.model.AllAppsList
@@ -33,6 +35,7 @@
import com.android.launcher3.model.LoaderTask
import com.android.launcher3.model.ModelDbController
import com.android.launcher3.model.ModelDelegate
+import com.android.launcher3.model.ModelInitializer
import com.android.launcher3.model.ModelLauncherCallbacks
import com.android.launcher3.model.ModelTaskController
import com.android.launcher3.model.ModelWriter
@@ -45,31 +48,42 @@
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.pm.UserCache
import com.android.launcher3.shortcuts.ShortcutRequest
+import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
-import com.android.launcher3.util.PackageManagerHelper
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.Preconditions
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.CancellationException
import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Provider
/**
* Maintains in-memory state of the Launcher. It is expected that there should be only one
* LauncherModel object held in a static. Also provide APIs for updating the database state for the
* Launcher.
*/
-class LauncherModel(
- private val context: Context,
- private val mApp: LauncherAppState,
+@LauncherAppSingleton
+class LauncherModel
+@Inject
+constructor(
+ @ApplicationContext private val context: Context,
+ private val appProvider: Provider<LauncherAppState>,
private val iconCache: IconCache,
- private val widgetsFilterDataProvider: WidgetsFilterDataProvider,
+ private val prefs: LauncherPrefs,
+ private val installQueue: ItemInstallQueue,
appFilter: AppFilter,
- mPmHelper: PackageManagerHelper,
- isPrimaryInstance: Boolean,
+ @Named("ICONS_DB") dbFileName: String?,
+ initializer: ModelInitializer,
+ lifecycle: DaggerSingletonTracker,
+ val modelDelegate: ModelDelegate,
) {
+ private val widgetsFilterDataProvider = WidgetsFilterDataProvider.newInstance(context)
+
private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1)
// < only access in worker thread >
@@ -81,16 +95,6 @@
*/
private val mBgDataModel = BgDataModel()
- val modelDelegate: ModelDelegate =
- ModelDelegate.newInstance(
- context,
- mApp,
- mPmHelper,
- mBgAllAppsList,
- mBgDataModel,
- isPrimaryInstance,
- )
-
val modelDbController = ModelDbController(context)
private val mLock = Any()
@@ -125,6 +129,14 @@
}
}
+ init {
+ if (!dbFileName.isNullOrEmpty()) {
+ initializer.initialize(this)
+ }
+ lifecycle.addCloseable { destroy() }
+ modelDelegate.init(this, mBgAllAppsList, mBgDataModel)
+ }
+
fun newModelCallbacks() = ModelLauncherCallbacks(this::enqueueModelUpdateTask)
/** Adds the provided items to the workspace. */
@@ -137,7 +149,7 @@
verifyChanges: Boolean,
cellPosMapper: CellPosMapper?,
owner: BgDataModel.Callbacks?,
- ) = ModelWriter(mApp.context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
+ ) = ModelWriter(context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
/** Returns the [WidgetsFilterDataProvider] that manages widget filters. */
fun getWidgetsFilterDataProvider(): WidgetsFilterDataProvider {
@@ -202,7 +214,7 @@
UserCache.ACTION_PROFILE_UNLOCKED ->
enqueueModelUpdateTask(UserLockStateChangedTask(user, true))
Intent.ACTION_MANAGED_PROFILE_REMOVED -> {
- LauncherPrefs.get(mApp.context).put(LauncherPrefs.WORK_EDU_STEP, 0)
+ prefs.put(LauncherPrefs.WORK_EDU_STEP, 0)
forceReload()
}
UserCache.ACTION_PROFILE_ADDED,
@@ -233,6 +245,13 @@
rebindCallbacks()
}
+ /** Reloads the model if it is already in use */
+ fun reloadIfActive() {
+ val wasActive: Boolean
+ synchronized(mLock) { wasActive = mModelLoaded || stopLoader() }
+ if (wasActive) forceReload()
+ }
+
/** Rebinds all existing callbacks with already loaded model */
fun rebindCallbacks() {
if (hasCallbacks()) {
@@ -280,7 +299,7 @@
private fun startLoader(newCallbacks: Array<BgDataModel.Callbacks>): Boolean {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
- ItemInstallQueue.INSTANCE.get(context).pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING)
+ installQueue.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING)
synchronized(mLock) {
// If there is already one running, tell it to stop.
val wasRunning = stopLoader()
@@ -292,7 +311,12 @@
callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) }
val launcherBinder =
- BaseLauncherBinder(mApp, mBgDataModel, mBgAllAppsList, callbacksList)
+ BaseLauncherBinder(
+ appProvider.get(),
+ mBgDataModel,
+ mBgAllAppsList,
+ callbacksList,
+ )
if (bindDirectly) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
@@ -304,19 +328,20 @@
launcherBinder.bindWidgets()
return true
} else {
- mLoaderTask =
+ val task =
LoaderTask(
- mApp,
+ appProvider.get(),
mBgAllAppsList,
mBgDataModel,
this.modelDelegate,
launcherBinder,
widgetsFilterDataProvider,
)
+ mLoaderTask = task
// Always post the loader task, instead of running directly
// (even on same thread) so that we exit any nested synchronized blocks
- MODEL_EXECUTOR.post(mLoaderTask)
+ MODEL_EXECUTOR.post(task)
}
}
}
@@ -412,7 +437,7 @@
/** Called when the labels for the widgets has updated in the icon cache. */
fun onWidgetLabelsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) {
enqueueModelUpdateTask { taskController, dataModel, _ ->
- dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp)
+ dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, appProvider.get())
taskController.bindUpdatedWidgets(dataModel)
}
}
@@ -435,7 +460,13 @@
return@execute
}
task.execute(
- ModelTaskController(mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR),
+ ModelTaskController(
+ appProvider.get(),
+ mBgDataModel,
+ mBgAllAppsList,
+ this,
+ MAIN_EXECUTOR,
+ ),
mBgDataModel,
mBgAllAppsList,
)
@@ -496,8 +527,6 @@
}
companion object {
- private const val DEBUG_RECEIVER = false
-
const val TAG = "Launcher.Model"
}
}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a526b89..03ecf14 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -61,11 +61,10 @@
*/
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
- LauncherAppState.INSTANCE.executeIfCreated(appState -> {
- if (appState.getModel().isModelLoaded()) {
- appState.getModel().dumpState("", fd, writer, args);
- }
- });
+ LauncherModel model = LauncherAppState.INSTANCE.get(getContext()).getModel();
+ if (model.isModelLoaded()) {
+ model.dumpState("", fd, writer, args);
+ }
}
@Override
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index cfb1161..94ff441 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -53,6 +53,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
@@ -299,6 +300,17 @@
private final StatsLogManager mStatsLogManager;
private final MSDLPlayerWrapper mMSDLPlayerWrapper;
+
+ private final StateManager.StateListener<LauncherState> mAccessibilityDropListener =
+ new StateListener<>() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == NORMAL) {
+ performAccessibilityActionOnViewTree(Workspace.this);
+ }
+ }
+ };
+
@Nullable
private DragController.DragListener mAccessibilityDragListener;
@@ -1454,11 +1466,13 @@
super.onAttachedToWindow();
mWallpaperOffset.setWindowToken(getWindowToken());
computeScroll();
+ mLauncher.getStateManager().addStateListener(mAccessibilityDropListener);
}
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mWallpaperOffset.setWindowToken(null);
+ mLauncher.getStateManager().removeStateListener(mAccessibilityDropListener);
}
@Override
@@ -2239,16 +2253,8 @@
// the order of operations in this method related to the StateListener below, please
// test that accessibility moves retain focus after accessibility dropping an item.
// Accessibility focus must be requested after launcher is back to a normal state
- mLauncher.getStateManager().addStateListener(new StateListener<LauncherState>() {
- @Override
- public void onStateTransitionComplete(LauncherState finalState) {
- if (finalState == NORMAL) {
- cell.performAccessibilityAction(
- AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
- mLauncher.getStateManager().removeStateListener(this);
- }
- }
- });
+ cell.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag,
+ AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
}
}
@@ -3580,4 +3586,22 @@
onEndStateTransition();
}
}
+
+ /**
+ * Recursively check view tag {@link R.id.perform_a11y_action_on_launcher_state_normal_tag} and
+ * call {@link View#performAccessibilityAction(int, Bundle)} on view tree. The tag is cleared
+ * after this call.
+ */
+ private static void performAccessibilityActionOnViewTree(View view) {
+ Object tag = view.getTag(R.id.perform_a11y_action_on_launcher_state_normal_tag);
+ if (tag instanceof Integer) {
+ view.performAccessibilityAction((int) tag, null);
+ view.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, null);
+ }
+ if (view instanceof ViewGroup viewgroup) {
+ for (int i = 0; i < viewgroup.getChildCount(); i++) {
+ performAccessibilityActionOnViewTree(viewgroup.getChildAt(i));
+ }
+ }
+ }
}
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index f6acda4..249a214 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,7 +18,10 @@
import android.content.Context;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.graphics.GridCustomizationsProxy;
import com.android.launcher3.graphics.ThemeManager;
@@ -44,6 +47,8 @@
import dagger.BindsInstance;
+import javax.inject.Named;
+
/**
* Launcher base component for Dagger injection.
*
@@ -75,12 +80,15 @@
LockedUserState getLockedUserState();
InvariantDeviceProfile getIDP();
IconPool getIconPool();
+ LauncherAppState getLauncherAppState();
GridCustomizationsProxy getGridCustomizationsProxy();
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@BindsInstance Builder appContext(@ApplicationContext Context context);
+ @BindsInstance Builder iconsDbName(@Nullable @Named("ICONS_DB") String dbFileName);
+ @BindsInstance Builder setSafeModeEnabled(@Named("SAFE_MODE") boolean safeModeEnabled);
LauncherBaseAppComponent build();
}
}
diff --git a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
index 71e3354..6199149 100644
--- a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
+++ b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
@@ -39,8 +39,10 @@
// Create a new component
return Holder(
- DaggerLauncherAppComponent.builder().appContext(app).build()
- as LauncherAppComponent,
+ DaggerLauncherAppComponent.builder()
+ .appContext(app)
+ .setSafeModeEnabled(true)
+ .build() as LauncherAppComponent,
existingFilter,
)
.apply { inflater.filter = this }
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 01c9d7e..062c753 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -377,7 +377,9 @@
if (Flags.newCustomizationPickerUi()
&& com.android.launcher3.Flags.enableLauncherIconShapes()) {
String shapeKey = message.getData().getString(KEY_SHAPE_KEY);
- renderer.updateShape(shapeKey);
+ if (!TextUtils.isEmpty(shapeKey)) {
+ renderer.updateShape(shapeKey);
+ }
}
break;
case MESSAGE_ID_UPDATE_GRID:
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 03f0582..740b87b 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -28,6 +28,7 @@
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
+import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import android.app.Fragment;
@@ -67,7 +68,6 @@
import com.android.launcher3.Hotseat;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.ProxyPrefs;
@@ -138,16 +138,15 @@
private final String mPrefName;
- public PreviewContext(Context base, String gridName) {
+ public PreviewContext(Context base, String gridName, String shapeKey) {
super(base);
mPrefName = "preview-" + UUID.randomUUID().toString();
LauncherPrefs prefs =
new ProxyPrefs(this, getSharedPreferences(mPrefName, MODE_PRIVATE));
prefs.put(GRID_NAME, gridName);
+ prefs.put(PREF_ICON_SHAPE, shapeKey);
initDaggerComponent(
DaggerLauncherPreviewRenderer_PreviewAppComponent.builder().bindPrefs(prefs));
- putObject(LauncherAppState.INSTANCE,
- new LauncherAppState(this, null /* iconCacheFileName */));
}
@Override
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 6fe5804..4dd9c5b 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -73,7 +73,6 @@
import java.util.ArrayList;
import java.util.Map;
-import java.util.Objects;
import java.util.concurrent.TimeUnit;
/** Render preview using surface view. */
@@ -120,6 +119,7 @@
if (mGridName == null) {
mGridName = LauncherPrefs.get(context).get(GRID_NAME);
}
+ mShapeKey = LauncherPrefs.get(context).get(PREF_ICON_SHAPE);
mWallpaperColors = bundle.getParcelable(KEY_COLORS);
if (Flags.newCustomizationPickerUi()) {
updateColorOverrides(bundle);
@@ -225,8 +225,8 @@
*
* @param shapeKey key for the IconShape model
*/
- public void updateShape(@Nullable String shapeKey) {
- if (Objects.equals(mShapeKey, shapeKey)) {
+ public void updateShape(String shapeKey) {
+ if (shapeKey.equals(mShapeKey)) {
Log.w(TAG, "Preview shape already set, skipping. shape=" + mShapeKey);
return;
}
@@ -332,12 +332,10 @@
private void loadModelData() {
final Context inflationContext = getPreviewContext();
if (!mGridName.equals(LauncherPrefs.INSTANCE.get(mContext).get(GRID_NAME))
- || mShapeKey != null) {
+ || !mShapeKey.equals(LauncherPrefs.INSTANCE.get(mContext).get(PREF_ICON_SHAPE))) {
// Start the migration
- PreviewContext previewContext = new PreviewContext(inflationContext, mGridName);
- if (mShapeKey != null) {
- LauncherPrefs.INSTANCE.get(previewContext).put(PREF_ICON_SHAPE, mShapeKey);
- }
+ PreviewContext previewContext =
+ new PreviewContext(inflationContext, mGridName, mShapeKey);
// Copy existing data to preview DB
LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
.getModel().getModelDbController().getDb(),
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 9f99e8f..b8f6424 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -51,6 +51,8 @@
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Utilities;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CacheLookupFlag;
import com.android.launcher3.icons.cache.CachedObject;
@@ -66,6 +68,7 @@
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.WidgetSections;
@@ -79,9 +82,13 @@
import java.util.function.Supplier;
import java.util.stream.Stream;
+import javax.inject.Inject;
+import javax.inject.Named;
+
/**
* Cache of application icons. Icons can be made from any thread.
*/
+@LauncherAppSingleton
public class IconCache extends BaseIconCache {
// Shortcut extra which can point to a packageName and can be used to indicate an alternate
@@ -96,24 +103,39 @@
private final LauncherApps mLauncherApps;
private final UserCache mUserManager;
+ private final InstallSessionHelper mInstallSessionHelper;
private final InstantAppResolver mInstantAppResolver;
private final CancellableTask mCancelledTask;
+ private final LauncherIcons.IconPool mIconPool;
private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
private int mPendingIconRequestCount = 0;
- public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName,
- IconProvider iconProvider) {
+ @Inject
+ public IconCache(
+ @ApplicationContext Context context,
+ InvariantDeviceProfile idp,
+ @Nullable @Named("ICONS_DB") String dbFileName,
+ UserCache userCache,
+ LauncherIconProvider iconProvider,
+ InstallSessionHelper installSessionHelper,
+ LauncherIcons.IconPool iconPool,
+ DaggerSingletonTracker lifecycle) {
super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
mLauncherApps = context.getSystemService(LauncherApps.class);
- mUserManager = UserCache.INSTANCE.get(context);
+ mUserManager = userCache;
+ mInstallSessionHelper = installSessionHelper;
+ mIconPool = iconPool;
+
mInstantAppResolver = InstantAppResolver.newInstance(context);
mWidgetCategoryBitmapInfos = new SparseArray<>();
mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { });
mCancelledTask.cancel();
+
+ lifecycle.addCloseable(this::close);
}
@Override
@@ -129,7 +151,7 @@
@NonNull
@Override
public BaseIconFactory getIconFactory() {
- return LauncherIcons.obtain(context);
+ return mIconPool.obtain();
}
/**
@@ -199,7 +221,7 @@
private void onIconRequestEnd() {
mPendingIconRequestCount--;
if (mPendingIconRequestCount <= 0) {
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
}
@@ -279,8 +301,7 @@
String override = shortcutInfo.getExtras() == null ? null
: shortcutInfo.getExtras().getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE);
if (!TextUtils.isEmpty(override)
- && InstallSessionHelper.INSTANCE.get(context)
- .isTrustedPackage(pkg, shortcutInfo.getUserHandle())) {
+ && mInstallSessionHelper.isTrustedPackage(pkg, shortcutInfo.getUserHandle())) {
pkg = override;
} else {
// Try component based badge before trying the normal package badge
@@ -536,7 +557,7 @@
return;
}
- try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ try (LauncherIcons li = mIconPool.obtain()) {
final BitmapInfo tempBitmap = li.createBadgedIconBitmap(
context.getDrawable(widgetSection.mSectionDrawable),
new BaseIconFactory.IconOptions());
diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java
index 836b7d1..7241198 100644
--- a/src/com/android/launcher3/icons/LauncherIconProvider.java
+++ b/src/com/android/launcher3/icons/LauncherIconProvider.java
@@ -30,6 +30,8 @@
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.graphics.ShapeDelegate;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.util.ApiWrapper;
@@ -39,9 +41,12 @@
import java.util.Collections;
import java.util.Map;
+import javax.inject.Inject;
+
/**
* Extension of {@link IconProvider} with support for overriding theme icons
*/
+@LauncherAppSingleton
public class LauncherIconProvider extends IconProvider {
private static final String TAG_ICON = "icon";
@@ -56,10 +61,14 @@
private final ApiWrapper mApiWrapper;
private final ThemeManager mThemeManager;
- public LauncherIconProvider(Context context) {
+ @Inject
+ public LauncherIconProvider(
+ @ApplicationContext Context context,
+ ThemeManager themeManager,
+ ApiWrapper apiWrapper) {
super(context);
- mThemeManager = ThemeManager.INSTANCE.get(context);
- mApiWrapper = ApiWrapper.INSTANCE.get(context);
+ mThemeManager = themeManager;
+ mApiWrapper = apiWrapper;
setIconThemeSupported(mThemeManager.isMonoThemeEnabled());
}
@@ -79,7 +88,7 @@
@Override
public void updateSystemState() {
super.updateSystemState();
- mSystemState += "," + ThemeManager.INSTANCE.get(mContext).getIconState().toUniqueId();
+ mSystemState += "," + mThemeManager.getIconState().toUniqueId();
}
@Override
diff --git a/src/com/android/launcher3/icons/LauncherIcons.kt b/src/com/android/launcher3/icons/LauncherIcons.kt
index 6c018e8..29c0de1 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.kt
+++ b/src/com/android/launcher3/icons/LauncherIcons.kt
@@ -66,11 +66,16 @@
return userCache.getUserInfo(user)
}
- public override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
- if (!Flags.enableLauncherIconShapes()) return drawable.iconMask
+ override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
+ if (!Flags.enableLauncherIconShapes()) return super.getShapePath(drawable, iconBounds)
return themeManager.iconShape.getPath(iconBounds)
}
+ override fun getIconScale(): Float {
+ if (!Flags.enableLauncherIconShapes()) return super.getIconScale()
+ return themeManager.iconState.iconScale
+ }
+
override fun drawAdaptiveIcon(
canvas: Canvas,
drawable: AdaptiveIconDrawable,
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 924a440..5b25418 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -1,6 +1,6 @@
package com.android.launcher3.logging;
-import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+import static com.android.launcher3.util.LooperExecutor.createAndStartNewLooper;
import android.os.Handler;
import android.os.HandlerThread;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9a1c874..2f1af68 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -905,6 +905,10 @@
@UiEvent(doc = "Time passed between Contextual Search runnable creation and execution. This"
+ " ensures that Recent animations have finished before Contextual Search starts.")
LAUNCHER_LATENCY_OMNI_RUNNABLE(1546),
+
+ @UiEvent(doc = "Time passed between nav handle touch down and cancellation without "
+ + "triggering Contextual Search")
+ LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON(2171),
;
private final int mId;
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 003bef3..3ee8b87 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -167,7 +167,7 @@
return;
}
Map<PackageItemInfo, List<WidgetItem>>
- widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItem();
+ widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker();
List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
.build(widgetsByPackageItem);
Predicate<WidgetItem> filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter();
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 5a2aef0..52a2188 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -23,62 +23,45 @@
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.ResourceBasedOverride;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Map;
+import javax.inject.Inject;
+
/**
* Class to extend LauncherModel functionality to provide extra data
*/
-public class ModelDelegate implements ResourceBasedOverride {
-
- /**
- * Creates and initializes a new instance of the delegate
- */
- public static ModelDelegate newInstance(
- Context context, LauncherAppState app, PackageManagerHelper pmHelper,
- AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) {
- ModelDelegate delegate = Overrides.getObject(
- ModelDelegate.class, context, R.string.model_delegate_class);
- delegate.init(app, pmHelper, appsList, dataModel, isPrimaryInstance);
- return delegate;
- }
+public class ModelDelegate {
protected final Context mContext;
- protected PackageManagerHelper mPmHelper;
- protected LauncherAppState mApp;
+ protected LauncherModel mModel;
protected AllAppsList mAppsList;
protected BgDataModel mDataModel;
- protected boolean mIsPrimaryInstance;
- public ModelDelegate(Context context) {
+ @Inject
+ public ModelDelegate(@ApplicationContext Context context) {
mContext = context;
}
/**
* Initializes the object with the given params.
*/
- private void init(LauncherAppState app, PackageManagerHelper pmHelper, AllAppsList appsList,
- BgDataModel dataModel, boolean isPrimaryInstance) {
- this.mApp = app;
- this.mPmHelper = pmHelper;
+ public void init(LauncherModel model, AllAppsList appsList, BgDataModel dataModel) {
+ this.mModel = model;
this.mAppsList = appsList;
this.mDataModel = dataModel;
- this.mIsPrimaryInstance = isPrimaryInstance;
}
/** Called periodically to validate and update any data */
@WorkerThread
public void validateData() {
- if (hasShortcutsPermission(mApp.getContext())
- != mAppsList.hasShortcutHostPermission()) {
- mApp.getModel().forceReload();
+ if (hasShortcutsPermission(mContext) != mAppsList.hasShortcutHostPermission()) {
+ mModel.forceReload();
}
}
diff --git a/src/com/android/launcher3/model/ModelInitializer.kt b/src/com/android/launcher3/model/ModelInitializer.kt
index 69a320a..735a52a 100644
--- a/src/com/android/launcher3/model/ModelInitializer.kt
+++ b/src/com/android/launcher3/model/ModelInitializer.kt
@@ -38,18 +38,20 @@
import com.android.launcher3.notification.NotificationListener
import com.android.launcher3.pm.InstallSessionHelper
import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.MODEL_EXECUTOR
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
-import com.android.launcher3.util.SafeCloseable
import com.android.launcher3.util.SettingsCache
import com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI
import com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI
import com.android.launcher3.util.SimpleBroadcastReceiver
import com.android.launcher3.widget.custom.CustomWidgetManager
-import java.util.function.Consumer
+import javax.inject.Inject
/** Utility class for initializing all model callbacks */
-class ModelInitializer(
+class ModelInitializer
+@Inject
+constructor(
@ApplicationContext private val context: Context,
private val iconPool: IconPool,
private val iconCache: IconCache,
@@ -60,7 +62,7 @@
private val iconProvider: LauncherIconProvider,
private val customWidgetManager: CustomWidgetManager,
private val installSessionHelper: InstallSessionHelper,
- private val closeActions: Consumer<SafeCloseable>,
+ private val lifeCycle: DaggerSingletonTracker,
) {
fun initialize(model: LauncherModel) {
@@ -75,18 +77,18 @@
if (modelChanged) refreshAndReloadLauncher()
}
idp.addOnChangeListener(idpChangeListener)
- closeActions.accept { idp.removeOnChangeListener(idpChangeListener) }
+ lifeCycle.addCloseable { idp.removeOnChangeListener(idpChangeListener) }
// Theme changes
val themeChangeListener = ThemeChangeListener { refreshAndReloadLauncher() }
themeManager.addChangeListener(themeChangeListener)
- closeActions.accept { themeManager.removeChangeListener(themeChangeListener) }
+ lifeCycle.addCloseable { themeManager.removeChangeListener(themeChangeListener) }
// System changes
val modelCallbacks = model.newModelCallbacks()
val launcherApps = context.getSystemService(LauncherApps::class.java)!!
- launcherApps.registerCallback(modelCallbacks)
- closeActions.accept { launcherApps.unregisterCallback(modelCallbacks) }
+ launcherApps.registerCallback(modelCallbacks, MODEL_EXECUTOR.handler)
+ lifeCycle.addCloseable { launcherApps.unregisterCallback(modelCallbacks) }
if (Utilities.ATLEAST_V && Flags.enableSupportForArchiving()) {
launcherApps.setArchiveCompatibility(
@@ -101,23 +103,23 @@
val dpUpdateReceiver =
SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) { model.reloadStringCache() }
dpUpdateReceiver.register(ACTION_DEVICE_POLICY_RESOURCE_UPDATED)
- closeActions.accept { dpUpdateReceiver.unregisterReceiverSafely() }
+ lifeCycle.addCloseable { dpUpdateReceiver.unregisterReceiverSafely() }
// Development helper
if (BuildConfig.IS_STUDIO_BUILD) {
val reloadReceiver =
SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) { model.forceReload() }
reloadReceiver.register(Context.RECEIVER_EXPORTED, ACTION_FORCE_RELOAD)
- closeActions.accept { reloadReceiver.unregisterReceiverSafely() }
+ lifeCycle.addCloseable { reloadReceiver.unregisterReceiverSafely() }
}
// User changes
- closeActions.accept(userCache.addUserEventListener(model::onUserEvent))
+ lifeCycle.addCloseable(userCache.addUserEventListener(model::onUserEvent))
// Private space settings changes
val psSettingsListener = SettingsCache.OnChangeListener { model.forceReload() }
settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psSettingsListener)
- closeActions.accept {
+ lifeCycle.addCloseable {
settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psSettingsListener)
}
@@ -131,7 +133,7 @@
}
settingsCache.register(NOTIFICATION_BADGING_URI, notificationChanges)
notificationChanges.onSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI))
- closeActions.accept {
+ lifeCycle.addCloseable {
settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationChanges)
}
@@ -142,21 +144,21 @@
if (LoaderTask.SMARTSPACE_ON_HOME_SCREEN == key) model.forceReload()
}
getPrefs(context).registerOnSharedPreferenceChangeListener(smartSpacePrefChanges)
- closeActions.accept {
+ lifeCycle.addCloseable {
getPrefs(context).unregisterOnSharedPreferenceChangeListener(smartSpacePrefChanges)
}
}
// Custom widgets
- closeActions.accept(customWidgetManager.addWidgetRefreshCallback(model::rebindCallbacks))
+ lifeCycle.addCloseable(customWidgetManager.addWidgetRefreshCallback(model::rebindCallbacks))
// Icon changes
- closeActions.accept(
+ lifeCycle.addCloseable(
iconProvider.registerIconChangeListener(model::onAppIconChanged, MODEL_EXECUTOR.handler)
)
// Install session changes
- closeActions.accept(installSessionHelper.registerInstallTracker(modelCallbacks))
+ lifeCycle.addCloseable(installSessionHelper.registerInstallTracker(modelCallbacks))
}
companion object {
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index 40ea17d..6e3e35e 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -77,7 +77,7 @@
}
fun bindUpdatedWidgets(dataModel: BgDataModel) {
- val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItem
+ val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItemForPicker
val allWidgets = WidgetsListBaseEntriesBuilder(app.context).build(widgetsByPackageItem)
val defaultWidgetsFilter = dataModel.widgetsModel.defaultWidgetsFilter
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index a176465..ab960d8 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -70,6 +70,7 @@
private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
@Nullable private Predicate<WidgetItem> mDefaultWidgetsFilter = null;
@Nullable private Predicate<WidgetItem> mPredictedWidgetsFilter = null;
+ @Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null;
/**
* Returns all widgets keyed by their component key.
@@ -87,13 +88,44 @@
}
/**
- * Returns widgets grouped by the package item that they should belong to.
+ * Returns widgets (eligible for display in picker) keyed by their component key.
*/
- public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
- if (!WIDGETS_ENABLED) {
+ public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKeyForPicker() {
+ if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
return Collections.emptyMap();
}
- return new HashMap<>(mWidgetsByPackageItem);
+
+ return mWidgetsByPackageItem.values().stream()
+ .flatMap(Collection::stream).distinct()
+ .filter(widgetItem -> mWidgetValidityCheckForPicker.test(widgetItem))
+ .collect(Collectors.toMap(
+ widget -> new ComponentKey(widget.componentName, widget.user),
+ Function.identity()
+ ));
+ }
+
+ /**
+ * Returns widgets (displayable in the widget picker) grouped by the package item that
+ * they should belong to.
+ */
+ public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItemForPicker() {
+ if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
+ return Collections.emptyMap();
+ }
+
+ return mWidgetsByPackageItem.entrySet().stream()
+ .collect(
+ Collectors.toMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().stream()
+ .filter(widgetItem ->
+ mWidgetValidityCheckForPicker.test(widgetItem))
+ .collect(Collectors.toList())
+ )
+ )
+ .entrySet().stream()
+ .filter(entry -> !entry.getValue().isEmpty())
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
@@ -181,6 +213,9 @@
Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
}
+ // Refresh the validity checker with latest app state.
+ mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(app);
+
// Temporary cache for {@link PackageItemInfos} to avoid having to go through
// {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
@@ -195,7 +230,6 @@
// add and update.
mWidgetsByPackageItem.putAll(rawWidgetsShortcuts.stream()
- .filter(new WidgetValidityCheck(app))
.filter(new WidgetFlagCheck())
.flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
.map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
@@ -270,12 +304,15 @@
return packageUserKeys;
}
- private static class WidgetValidityCheck implements Predicate<WidgetItem> {
+ /**
+ * Checks if widgets are eligible for displaying in widget picker / tray.
+ */
+ private static class WidgetValidityCheckForPicker implements Predicate<WidgetItem> {
private final InvariantDeviceProfile mIdp;
private final AppFilter mAppFilter;
- WidgetValidityCheck(LauncherAppState app) {
+ WidgetValidityCheckForPicker(LauncherAppState app) {
mIdp = app.getInvariantDeviceProfile();
mAppFilter = new AppFilter(app.getContext());
}
@@ -310,6 +347,10 @@
}
}
+ /**
+ * Checks if certain widgets that are available behind flag can be used across all surfaces in
+ * launcher.
+ */
private static class WidgetFlagCheck implements Predicate<WidgetItem> {
private static final String BUBBLES_SHORTCUT_WIDGET =
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 3919eb7..99f2837 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -194,29 +194,35 @@
if (intent.`package` == null) {
intent.`package` = targetPkg
}
- val isPreArchived = appInfoWrapper.isArchived() && c.restoreFlag != 0
+
+ val isPreArchivedShortcut =
+ Flags.restoreArchivedShortcuts() &&
+ appInfoWrapper.isArchived() &&
+ c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
+ c.restoreFlag != 0
// else if cn == null => can't infer much, leave it
// else if !validPkg => could be restored icon or missing sd-card
when {
- !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchived) -> {
+ !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchivedShortcut) -> {
// Points to a valid app (superset of cn != null) but the apk
// is not available.
when {
- c.restoreFlag != 0 || isPreArchived -> {
+ c.restoreFlag != 0 || isPreArchivedShortcut -> {
// Package is not yet available but might be
// installed later.
FileLog.d(
TAG,
"package not yet restored: $targetPkg, itemType=${c.itemType}" +
- "isPreArchived=$isPreArchived, restoreFlag=${c.restoreFlag}",
+ ", isPreArchivedShortcut=$isPreArchivedShortcut" +
+ ", restoreFlag=${c.restoreFlag}",
)
tempPackageKey.update(targetPkg, c.user)
when {
c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED) -> {
// Restore has started once.
}
- installingPkgs.containsKey(tempPackageKey) || isPreArchived -> {
+ installingPkgs.containsKey(tempPackageKey) || isPreArchivedShortcut -> {
// App restore has started. Update the flag
c.restoreFlag =
c.restoreFlag or WorkspaceItemInfo.FLAG_RESTORE_STARTED
@@ -268,7 +274,7 @@
)
validTarget = false
}
- if (validTarget && !isPreArchived) {
+ if (validTarget && !isPreArchivedShortcut) {
FileLog.d(
TAG,
"valid target true, marking restored: $targetPkg," +
@@ -283,7 +289,7 @@
when {
c.restoreFlag != 0 -> {
// Already verified above that user is same as default user
- info = c.getRestoredItemInfo(intent, isPreArchived)
+ info = c.getRestoredItemInfo(intent, isPreArchivedShortcut)
}
c.itemType == Favorites.ITEM_TYPE_APPLICATION ->
info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, false)
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index ff40f30..b60b8cc 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.os.Process;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.Flags;
@@ -44,6 +45,7 @@
/**
* The bitmap for the application icon
*/
+ @NonNull
public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
/**
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 0da431f..23941bb 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -538,7 +538,7 @@
}
logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null);
- LauncherAppState.INSTANCE.executeIfCreated(app -> app.getModel().forceReload());
+ LauncherAppState.INSTANCE.get(context).getModel().reloadIfActive();
}
private static void logDatabaseWidgetInfo(ModelDbController controller) {
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index c622b71..296fc8a 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -16,8 +16,8 @@
package com.android.launcher3.util;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
@@ -51,21 +51,20 @@
/**
* An {@link LooperExecutor} to be used with async task where order is important.
*/
- public static final LooperExecutor ORDERED_BG_EXECUTOR = new LooperExecutor(
- createAndStartNewLooper("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND));
+ public static final LooperExecutor ORDERED_BG_EXECUTOR =
+ new LooperExecutor("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND);
/**
* Returns the executor for running tasks on the main thread.
*/
public static final LooperExecutor MAIN_EXECUTOR =
- new LooperExecutor(Looper.getMainLooper());
+ new LooperExecutor(Looper.getMainLooper(), THREAD_PRIORITY_FOREGROUND);
/**
* A background executor for using time sensitive actions where user is waiting for response.
*/
public static final LooperExecutor UI_HELPER_EXECUTOR =
- new LooperExecutor(
- createAndStartNewLooper("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND));
+ new LooperExecutor("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND);
/** A background executor to preinflate views. */
@@ -75,26 +74,9 @@
"preinflate-allapps-icons", THREAD_PRIORITY_BACKGROUND));
/**
- * Utility method to get a started handler thread statically
- */
- public static Looper createAndStartNewLooper(String name) {
- return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT);
- }
-
- /**
- * Utility method to get a started handler thread statically with the provided priority
- */
- public static Looper createAndStartNewLooper(String name, int priority) {
- HandlerThread thread = new HandlerThread(name, priority);
- thread.start();
- return thread.getLooper();
- }
-
- /**
* Executor used for running Launcher model related tasks (eg loading icons or updated db)
*/
- public static final LooperExecutor MODEL_EXECUTOR =
- new LooperExecutor(createAndStartNewLooper("launcher-loader"));
+ public static final LooperExecutor MODEL_EXECUTOR = new LooperExecutor("launcher-loader");
/**
* Returns and caches a single thread executor for a given package.
@@ -102,9 +84,7 @@
* @param packageName Package associated with the executor.
*/
public static LooperExecutor getPackageExecutor(String packageName) {
- return PACKAGE_EXECUTORS.computeIfAbsent(
- packageName, p -> new LooperExecutor(
- createAndStartNewLooper(p, Process.THREAD_PRIORITY_DEFAULT)));
+ return PACKAGE_EXECUTORS.computeIfAbsent(packageName, LooperExecutor::new);
}
/**
diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java
deleted file mode 100644
index 3a8a13c..0000000
--- a/src/com/android/launcher3/util/LooperExecutor.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.launcher3.util;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
-
-import java.util.List;
-import java.util.concurrent.AbstractExecutorService;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Extension of {@link AbstractExecutorService} which executed on a provided looper.
- */
-public class LooperExecutor extends AbstractExecutorService {
-
- private final Handler mHandler;
-
- public LooperExecutor(Looper looper) {
- mHandler = new Handler(looper);
- }
-
- public Handler getHandler() {
- return mHandler;
- }
-
- @Override
- public void execute(Runnable runnable) {
- if (getHandler().getLooper() == Looper.myLooper()) {
- runnable.run();
- } else {
- getHandler().post(runnable);
- }
- }
-
- /**
- * Same as execute, but never runs the action inline.
- */
- public void post(Runnable runnable) {
- getHandler().post(runnable);
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public void shutdown() {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public List<Runnable> shutdownNow() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isShutdown() {
- return false;
- }
-
- @Override
- public boolean isTerminated() {
- return false;
- }
-
- /**
- * Not supported and throws an exception when used.
- */
- @Override
- @Deprecated
- public boolean awaitTermination(long l, TimeUnit timeUnit) {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Returns the thread for this executor
- */
- public Thread getThread() {
- return getHandler().getLooper().getThread();
- }
-
- /**
- * Returns the looper for this executor
- */
- public Looper getLooper() {
- return getHandler().getLooper();
- }
-
- /**
- * Set the priority of a thread, based on Linux priorities.
- * @param priority Linux priority level, from -20 for highest scheduling priority
- * to 19 for lowest scheduling priority.
- * @see Process#setThreadPriority(int, int)
- */
- public void setThreadPriority(int priority) {
- Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority);
- }
-}
diff --git a/src/com/android/launcher3/util/LooperExecutor.kt b/src/com/android/launcher3/util/LooperExecutor.kt
new file mode 100644
index 0000000..f5ba906
--- /dev/null
+++ b/src/com/android/launcher3/util/LooperExecutor.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.launcher3.util
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import java.util.concurrent.AbstractExecutorService
+import java.util.concurrent.TimeUnit
+
+/** Extension of [AbstractExecutorService] which executed on a provided looper. */
+class LooperExecutor(looper: Looper, private val defaultPriority: Int) : AbstractExecutorService() {
+ val handler: Handler = Handler(looper)
+
+ @JvmOverloads
+ constructor(
+ name: String,
+ defaultPriority: Int = Process.THREAD_PRIORITY_DEFAULT,
+ ) : this(createAndStartNewLooper(name, defaultPriority), defaultPriority)
+
+ /** Returns the thread for this executor */
+ val thread: Thread
+ get() = handler.looper.thread
+
+ /** Returns the looper for this executor */
+ val looper: Looper
+ get() = handler.looper
+
+ override fun execute(runnable: Runnable) {
+ if (handler.looper == Looper.myLooper()) {
+ runnable.run()
+ } else {
+ handler.post(runnable)
+ }
+ }
+
+ /** Same as execute, but never runs the action inline. */
+ fun post(runnable: Runnable) {
+ handler.post(runnable)
+ }
+
+ @Deprecated("Not supported and throws an exception when used")
+ override fun shutdown() {
+ throw UnsupportedOperationException()
+ }
+
+ @Deprecated("Not supported and throws an exception when used.")
+ override fun shutdownNow(): List<Runnable> {
+ throw UnsupportedOperationException()
+ }
+
+ override fun isShutdown() = false
+
+ override fun isTerminated() = false
+
+ @Deprecated("Not supported and throws an exception when used.")
+ override fun awaitTermination(l: Long, timeUnit: TimeUnit): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ /**
+ * Set the priority of a thread, based on Linux priorities.
+ *
+ * @param priority Linux priority level, from -20 for highest scheduling priority to 19 for
+ * lowest scheduling priority.
+ * @see Process.setThreadPriority
+ */
+ fun setThreadPriority(priority: Int) {
+ Process.setThreadPriority((thread as HandlerThread).threadId, priority)
+ }
+
+ companion object {
+ /** Utility method to get a started handler thread statically with the provided priority */
+ @JvmOverloads
+ @JvmStatic
+ fun createAndStartNewLooper(
+ name: String,
+ priority: Int = Process.THREAD_PRIORITY_DEFAULT,
+ ): Looper = HandlerThread(name, priority).apply { start() }.looper
+ }
+}
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 44a7c6f..e1ef77a 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -127,10 +127,10 @@
/** This rect represents the actual gap between the two apps */
public final Rect visualDividerBounds;
// This class is orientation-agnostic, so we compute both for later use
- public final float topTaskPercent;
- public final float leftTaskPercent;
- public final float dividerWidthPercent;
- public final float dividerHeightPercent;
+ private final float topTaskPercent;
+ private final float leftTaskPercent;
+ private final float dividerWidthPercent;
+ private final float dividerHeightPercent;
public final int snapPosition;
/**
@@ -190,6 +190,39 @@
dividerHeightPercent = visualDividerBounds.height() / totalHeight;
}
+ /**
+ * Returns the percentage size of the left/top task (compared to the full width/height of
+ * the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and the
+ * right task is 4 units, this method will return 0.4f.
+ */
+ public float getLeftTopTaskPercent() {
+ // topTaskPercent and leftTaskPercent are defined at creation time, and are not updated
+ // on device rotate, so we have to check appsStackedVertically to return the right
+ // creation-time measurements.
+ return appsStackedVertically ? topTaskPercent : leftTaskPercent;
+ }
+
+ /**
+ * Returns the percentage size of the divider's thickness (compared to the full width/height
+ * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
+ * the right task is 4 units, this method will return 0.2f.
+ */
+ public float getDividerPercent() {
+ // dividerHeightPercent and dividerWidthPercent are defined at creation time, and are
+ // not updated on device rotate, so we have to check appsStackedVertically to return
+ // the right creation-time measurements.
+ return appsStackedVertically ? dividerHeightPercent : dividerWidthPercent;
+ }
+
+ /**
+ * Returns the percentage size of the right/bottom task (compared to the full width/height
+ * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
+ * the right task is 4 units, this method will return 0.4f.
+ */
+ public float getRightBottomTaskPercent() {
+ return 1 - (getLeftTopTaskPercent() + getDividerPercent());
+ }
+
@Override
public String toString() {
return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
index f8dc6b0..8f34fe3 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
@@ -35,19 +35,7 @@
* own implementation. Method {@code getWidgetRecommendationCategory} is called per widget to get
* the category.</p>
*/
-public class WidgetRecommendationCategoryProvider implements ResourceBasedOverride {
- private static final String TAG = "WidgetRecommendationCategoryProvider";
-
- /**
- * Retrieve instance of this object that can be overridden in runtime based on the build
- * variant of the application.
- */
- public static WidgetRecommendationCategoryProvider newInstance(Context context) {
- Preconditions.assertWorkerThread();
- return Overrides.getObject(
- WidgetRecommendationCategoryProvider.class, context.getApplicationContext(),
- R.string.widget_recommendation_category_provider_class);
- }
+public class WidgetRecommendationCategoryProvider {
/**
* Returns a {@link WidgetRecommendationCategory} for the provided widget item that can be used
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
index 9026748..0aaf4d7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
@@ -56,7 +56,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.icons.cache.CachingLogic;
import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
@@ -69,11 +69,13 @@
import com.android.launcher3.util.ApplicationInfoWrapper;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SandboxApplication;
import com.google.common.truth.Truth;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -85,20 +87,18 @@
@RunWith(AndroidJUnit4.class)
public class IconCacheTest {
- private Context mContext;
+ @Rule public SandboxApplication mContext = new SandboxApplication();
+
private IconCache mIconCache;
private ComponentName mMyComponent;
@Before
public void setup() {
- mContext = getInstrumentation().getTargetContext();
mMyComponent = new ComponentName(mContext, SettingsActivity.class);
// In memory icon cache
- mIconCache = new IconCache(mContext,
- InvariantDeviceProfile.INSTANCE.get(mContext), null,
- new LauncherIconProvider(mContext));
+ mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
}
@After
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index ce04682..08b8f81 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -155,7 +155,7 @@
private fun verifyItemSpaceFinderCall(nonEmptyScreenIds: List<Int>, numberOfExpectedCall: Int) {
verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall))
.findSpaceForItem(
- same(mAppState),
+ eq(mAppState),
same(mModelHelper.bgDataModel),
eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())),
eq(IntArray()),
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
index 11047fb..ad40818 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -70,6 +70,7 @@
import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -258,7 +259,6 @@
initCursor(ITEM_TYPE_APPLICATION, "title");
assertTrue(mLoaderCursor.moveToNext());
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
- itemInfo.bitmap = null;
itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
Bitmap expectedBitmap = LauncherIcons.obtain(mContext)
.createIconBitmap(decodeByteArray(sTestBlob, 0, sTestBlob.length))
@@ -289,7 +289,7 @@
initCursor(ITEM_TYPE_APPLICATION, "title");
assertTrue(mLoaderCursor.moveToNext());
WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
- itemInfo.bitmap = null;
+ BitmapInfo original = itemInfo.bitmap;
itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
Intent intent = new Intent();
intent.setComponent(new ComponentName("package", "class"));
@@ -297,7 +297,7 @@
// When
mLoaderCursor.loadWorkspaceTitleAndIcon(false, false, itemInfo);
// Then
- assertThat(itemInfo.bitmap).isNull();
+ assertThat(itemInfo.bitmap).isEqualTo(original);
}
@Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
index ae4ff04..d704195 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
@@ -119,6 +119,11 @@
// A widget in different package (none of that app's widgets are in widget
// sections xml)
createAppWidgetProviderInfo(AppBTestWidgetComponent),
+ // A widget in different app that is meant to be hidden from picker
+ createAppWidgetProviderInfo(
+ AppCPinOnlyTestWidgetComponent,
+ /*hideFromPicker=*/ true,
+ ),
)
)
@@ -129,12 +134,13 @@
}
@Test
- fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
+ fun widgetsByPackageForPicker_treatsWidgetSectionsAsSeparatePackageItems() {
loadWidgets()
- val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
+ val packages: Map<PackageItemInfo, List<WidgetItem>> =
+ underTest.widgetsByPackageItemForPicker
- // expect 3 package items
+ // expect 3 package items (no app C as its widget is hidden from picker)
// one for the custom section with widget from appA
// one for package section for second widget from appA (that wasn't listed in xml)
// and one for package section for appB
@@ -167,6 +173,13 @@
assertThat(appBPackageSection).hasSize(1)
val widgetsInAppBSection = appBPackageSection.entries.first().value
assertThat(widgetsInAppBSection).hasSize(1)
+
+ // No App C's package section - as the only widget hosted by it is hidden in picker
+ val appCPackageSection =
+ packageSections.filter {
+ it.key.packageName == AppCPinOnlyTestWidgetComponent.packageName
+ }
+ assertThat(appCPackageSection).isEmpty()
}
@Test
@@ -175,7 +188,29 @@
val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
+ // Has all widgets including ones not visible in picker
+ assertThat(widgetsByComponentKey).hasSize(4)
+ widgetsByComponentKey.forEach { entry ->
+ assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
+ }
+ }
+
+ @Test
+ fun widgetComponentMapForPicker_excludesWidgetsHiddenInPicker() {
+ loadWidgets()
+
+ val widgetsByComponentKey: Map<ComponentKey, WidgetItem> =
+ underTest.widgetsByComponentKeyForPicker
+
+ // Has all widgets excluding the appC's widget.
assertThat(widgetsByComponentKey).hasSize(3)
+ assertThat(
+ widgetsByComponentKey.filter {
+ it.key.componentName == AppCPinOnlyTestWidgetComponent
+ }
+ )
+ .isEmpty()
+ // widgets mapped correctly
widgetsByComponentKey.forEach { entry ->
assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
}
@@ -189,7 +224,7 @@
}
@Test
- fun getWidgetsByPackageItem_returnsACopyOfMap() {
+ fun getWidgetsByPackageItemForPicker_returnsACopyOfMap() {
loadWidgets()
val latch = CountDownLatch(1)
@@ -198,8 +233,8 @@
// each "widgetsByPackageItem" read returns a different copy of the map held internally.
// Modifying one shouldn't impact another.
- for ((_, _) in underTest.widgetsByPackageItem.entries) {
- underTest.widgetsByPackageItem.clear()
+ for ((_, _) in underTest.widgetsByPackageItemForPicker.entries) {
+ underTest.widgetsByPackageItemForPicker.clear()
if (update) { // trigger update
update = false
// Similarly, model could update its code independently while a client is
@@ -256,6 +291,9 @@
private val AppBTestWidgetComponent: ComponentName =
ComponentName.createRelative("com.test.package", "TestProvider")
+ private val AppCPinOnlyTestWidgetComponent: ComponentName =
+ ComponentName.createRelative("com.testC.package", "PinOnlyTestProvider")
+
private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index 09b9a3b..cee5559 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -21,8 +21,8 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static com.android.launcher3.util.TestUtil.grantWriteSecurePermission;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -51,6 +51,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ModelDbController;
@@ -267,14 +268,6 @@
}
@Override
- public <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
- if (object == LauncherAppState.INSTANCE) {
- return (T) new LauncherAppState(this, null /* iconCacheFileName */);
- }
- return super.createObject(object);
- }
-
- @Override
public File getDatabasePath(String name) {
if (!mDbDir.exists()) {
mDbDir.mkdirs();
@@ -342,5 +335,10 @@
}
return success;
}
+
+ @Override
+ public void initDaggerComponent(LauncherBaseAppComponent.Builder componentBuilder) {
+ super.initDaggerComponent(componentBuilder.iconsDbName(null));
+ }
}
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index a87a208..9fbd7ff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.util;
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
+
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -83,14 +85,30 @@
/**
* Creates a {@link AppWidgetProviderInfo} for the provided component name
+ *
+ * @param cn component name of the appwidget provider
+ * @param hideFromPicker indicates if the widget should appear in widget picker
*/
- public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+ public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn,
+ boolean hideFromPicker) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.applicationInfo = new ApplicationInfo();
activityInfo.applicationInfo.uid = Process.myUid();
AppWidgetProviderInfo info = new AppWidgetProviderInfo();
+ if (hideFromPicker) {
+ info.widgetFeatures = WIDGET_FEATURE_HIDE_FROM_PICKER;
+ }
info.providerInfo = activityInfo;
info.provider = cn;
return info;
}
+
+ /**
+ * Creates a {@link AppWidgetProviderInfo} for the provided component name
+ *
+ * @param cn component name of the appwidget provider
+ */
+ public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+ return createAppWidgetProviderInfo(cn, /*hideFromPicker=*/ false);
+ }
}
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
similarity index 97%
rename from tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
rename to tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 15accbd..c956395 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2025 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.
@@ -20,6 +20,7 @@
import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
@@ -61,11 +62,11 @@
import org.junit.runner.Description
import org.junit.runner.RunWith
import org.junit.runners.model.Statement
+import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
-import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -85,6 +86,7 @@
@Before
fun setup() {
+ MockitoAnnotations.initMocks(this)
modelHelper = LauncherModelHelper()
context = modelHelper.sandboxContext
context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder())
@@ -93,10 +95,8 @@
}
folderIcon = FolderIcon(ActivityContextWrapper(context))
- val app = spy(LauncherAppState.getInstance(context))
- iconCache = spy(app.iconCache)
- doReturn(iconCache).whenever(app).iconCache
- context.putObject(LauncherAppState.INSTANCE, app)
+ iconCache = LauncherAppState.INSTANCE[context].iconCache
+ spyOn(iconCache)
doReturn(null).whenever(iconCache).updateIconInBackground(any(), any())
previewItemManager = PreviewItemManager(folderIcon)
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 8f64e84..582cf3c 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -14,6 +14,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.launcher3.Flags
import com.android.launcher3.LauncherAppState
import com.android.launcher3.LauncherModel
@@ -67,7 +68,6 @@
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
-import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -92,7 +92,6 @@
)
private lateinit var mockitoSession: MockitoSession
- @Mock private lateinit var app: LauncherAppState
@Mock private lateinit var bgAllAppsList: AllAppsList
@Mock private lateinit var modelDelegate: ModelDelegate
@Mock private lateinit var launcherBinder: BaseLauncherBinder
@@ -108,6 +107,9 @@
@get:Rule val setFlagsRule = SetFlagsRule()
+ private val app: LauncherAppState
+ get() = context.appComponent.launcherAppState
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -118,32 +120,28 @@
.strictness(Strictness.LENIENT)
.mockStatic(FirstScreenBroadcastHelper::class.java)
.startMocking()
- val idp =
- context.appComponent.idp.apply {
- numRows = 5
- numColumns = 6
- numDatabaseHotseatIcons = 5
- }
- context.putObject(LauncherAppState.INSTANCE, app)
-
doReturn(TestViewHelpers.findWidgetProvider(false))
.`when`(context.spyService(AppWidgetManager::class.java))
.getAppWidgetInfo(any())
- `when`(app.context).thenReturn(context)
- `when`(app.model).thenReturn(launcherModel)
`when`(launcherModel.beginLoader(any())).thenReturn(transaction)
- `when`(app.iconCache).thenReturn(iconCache)
`when`(launcherModel.modelDbController)
.thenReturn(FactitiousDbController(context, INSERTION_STATEMENT_FILE))
- `when`(app.invariantDeviceProfile).thenReturn(idp)
`when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock)
`when`(idleLock.awaitLocked(1000)).thenReturn(false)
`when`(iconCache.getUpdateHandler()).thenReturn(iconCacheUpdateHandler)
`when`(widgetsFilterDataProvider.getDefaultWidgetsFilter()).thenReturn(Predicate { true })
context.initDaggerComponent(
- DaggerLoaderTaskTest_TestComponent.builder().bindUserCache(userCache)
+ DaggerLoaderTaskTest_TestComponent.builder()
+ .bindUserCache(userCache)
+ .bindIconCache(iconCache)
+ .bindLauncherModel(launcherModel)
)
+ context.appComponent.idp.apply {
+ numRows = 5
+ numColumns = 6
+ numDatabaseHotseatIcons = 5
+ }
TestUtil.grantWriteSecurePermission()
}
@@ -281,8 +279,8 @@
@EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
fun `When broadcast flag on and is restore and secure setting off then send new broadcast`() {
// Given
- val spyContext = spy(context)
- `when`(app.context).thenReturn(spyContext)
+ spyOn(context)
+ val spyContext = context
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
any(),
@@ -357,8 +355,8 @@
@EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
fun `When not a restore then installed item broadcast not sent`() {
// Given
- val spyContext = spy(context)
- `when`(app.context).thenReturn(spyContext)
+ spyOn(context)
+ val spyContext = context
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
any(),
@@ -398,8 +396,8 @@
@DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
fun `When broadcast flag off then installed item broadcast not sent`() {
// Given
- val spyContext = spy(context)
- `when`(app.context).thenReturn(spyContext)
+ spyOn(context)
+ val spyContext = context
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
any(),
@@ -444,8 +442,8 @@
@EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
fun `When failsafe secure setting on then installed item broadcast not sent`() {
// Given
- val spyContext = spy(context)
- `when`(app.context).thenReturn(spyContext)
+ spyOn(context)
+ val spyContext = context
whenever(
FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
any(),
@@ -644,6 +642,10 @@
interface Builder : LauncherAppComponent.Builder {
@BindsInstance fun bindUserCache(userCache: UserCache): Builder
+ @BindsInstance fun bindLauncherModel(model: LauncherModel): Builder
+
+ @BindsInstance fun bindIconCache(iconCache: IconCache): Builder
+
override fun build(): TestComponent
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index ef72a0f..f633d48 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -157,12 +157,7 @@
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"dismissing all tasks")) {
final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
- for (int i = 0;
- i < FLINGS_FOR_DISMISS_LIMIT
- && !verifyActiveContainer().hasObject(clearAllSelector);
- ++i) {
- flingForwardImpl();
- }
+ flingForwardUntilClearAllVisible();
final Runnable clickClearAll = () -> mLauncher.clickLauncherObject(
mLauncher.waitForObjectInContainer(verifyActiveContainer(),
@@ -183,6 +178,17 @@
}
/**
+ * Scrolls until Clear-all button is visible.
+ */
+ public void flingForwardUntilClearAllVisible() {
+ final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
+ for (int i = 0; i < FLINGS_FOR_DISMISS_LIMIT
+ && !verifyActiveContainer().hasObject(clearAllSelector); ++i) {
+ flingForwardImpl();
+ }
+ }
+
+ /**
* Touch to the right of current task. This should dismiss overview and go back to Workspace.
*/
public Workspace touchOutsideFirstTask() {
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 4e94e1e..8fbb5e3 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -125,7 +125,7 @@
return right - left;
}
- int getTaskCenterX() {
+ public int getTaskCenterX() {
return mTask.getVisibleCenter().x;
}