Merge "Removing temporary debug tracing" into main
diff --git a/Android.bp b/Android.bp
index 1e1e0ad..a4a058f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -455,6 +455,9 @@
extra_check_modules: ["Launcher3LintChecker"],
baseline_filename: "lint-baseline.xml",
},
+ kotlincflags: [
+ "-Xjvm-default=all",
+ ],
}
// Library with all the dependencies for building quickstep
@@ -519,6 +522,9 @@
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
+ kotlincflags: [
+ "-Xjvm-default=all",
+ ],
}
// Library with all the source code and dependencies for building Quickstep
@@ -552,6 +558,9 @@
min_sdk_version: "current",
// TODO(b/319712088): re-enable use_resource_processor
use_resource_processor: false,
+ kotlincflags: [
+ "-Xjvm-default=all",
+ ],
}
// Build rule for Quickstep app.
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 80d2eac..46f0e41 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -131,13 +131,11 @@
android:writePermission="${applicationId}.permission.WRITE_SETTINGS"
android:readPermission="${applicationId}.permission.READ_SETTINGS" />
- <!--
- The content provider for exposing various launcher grid options.
- TODO: Add proper permissions
- -->
+ <!-- The content provider for exposing various launcher grid options. -->
<provider
- android:name="com.android.launcher3.graphics.GridCustomizationsProvider"
+ android:name="com.android.launcher3.graphics.LauncherCustomizationProvider"
android:authorities="${applicationId}.grid_control"
+ android:permission="android.permission.BIND_WALLPAPER"
android:exported="true" />
<!--
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/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 625d9b3..3d68dfb 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -29,7 +29,6 @@
android:layout_height="match_parent"
android:gravity="center"
android:scaleType="centerCrop"
- app:lottie_autoPlay="true"
app:lottie_loop="true"
app:layout_constraintTop_toTopOf="parent"
@@ -49,11 +48,10 @@
app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
- android:id="@+id/text_content_view"
+ android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/allset_page_margin_horizontal"
- android:layout_marginEnd="@dimen/allset_page_margin_horizontal"
+ android:paddingHorizontal="@dimen/allset_page_padding_horizontal"
android:layoutDirection="locale"
android:textDirection="locale"
android:forceHasOverlappingRendering="false"
diff --git a/quickstep/res/layout/icon_app_chip_view.xml b/quickstep/res/layout/icon_app_chip_view.xml
index 00b5392..0972be1 100644
--- a/quickstep/res/layout/icon_app_chip_view.xml
+++ b/quickstep/res/layout/icon_app_chip_view.xml
@@ -51,7 +51,7 @@
<TextView
android:id="@+id/icon_text_collapsed"
android:layout_width="@dimen/task_thumbnail_icon_menu_text_collapsed_max_width"
- android:layout_height="@dimen/task_thumbnail_icon_menu_app_icon_collapsed_size"
+ android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:maxLines="1"
android:ellipsize="end"
@@ -62,7 +62,7 @@
<TextView
android:id="@+id/icon_text_expanded"
android:layout_width="@dimen/task_thumbnail_icon_menu_text_expanded_max_width"
- android:layout_height="@dimen/task_thumbnail_icon_menu_app_icon_collapsed_size"
+ android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:maxLines="1"
android:ellipsize="end"
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-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 0052a73..cf7ba00 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -16,7 +16,7 @@
-->
<resources>
<!-- All Set page -->
- <dimen name="allset_page_margin_horizontal">48dp</dimen>
+ <dimen name="allset_page_padding_horizontal">48dp</dimen>
<!-- Gesture Tutorial menu page -->
<dimen name="gesture_tutorial_menu_padding_horizontal">48dp</dimen>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 4996582..3e72651 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -37,7 +37,7 @@
<dimen name="overview_actions_top_margin">24dp</dimen>
<!-- All Set page -->
- <dimen name="allset_page_margin_horizontal">120dp</dimen>
+ <dimen name="allset_page_padding_horizontal">120dp</dimen>
<dimen name="allset_page_allset_text_size">38sp</dimen>
<dimen name="allset_page_swipe_up_text_size">15sp</dimen>
</resources>
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/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 05f0695..52ebdae 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -277,7 +277,7 @@
<dimen name="gesture_tutorial_taskbar_margin_bottom">24dp</dimen>
<!-- All Set page -->
- <dimen name="allset_page_margin_horizontal">40dp</dimen>
+ <dimen name="allset_page_padding_horizontal">40dp</dimen>
<dimen name="allset_page_allset_text_size">36sp</dimen>
<dimen name="allset_page_swipe_up_text_size">14sp</dimen>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 5f2a63d..f8ca8d9 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -321,6 +321,7 @@
<item name="android:fontFamily">google-sans-text-medium</item>
<item name="android:textSize">@dimen/task_thumbnail_icon_menu_text_size</item>
<item name="android:textColor">@color/materialColorOnSurface</item>
+ <item name="android:includeFontPadding">false</item>
<item name="android:letterSpacing">0.025</item>
<item name="android:lineHeight">20sp</item>
</style>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index c748385..9f81124 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -56,7 +56,6 @@
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -79,7 +78,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
@@ -94,7 +92,6 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
-import android.provider.Settings.Global;
import android.util.Pair;
import android.util.Size;
import android.view.CrossWindowBlurListeners;
@@ -226,7 +223,6 @@
private static final int TASKBAR_TO_HOME_DURATION_FAST = 300;
private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000;
protected static final int CONTENT_SCALE_DURATION = 350;
- protected static final int CONTENT_SCRIM_DURATION = 350;
private static final int MAX_NUM_TASKS = 5;
@@ -244,16 +240,12 @@
private final StartingWindowListener mStartingWindowListener =
new StartingWindowListener(this);
- private ContentObserver mAnimationRemovalObserver = new ContentObserver(
- ORDERED_BG_EXECUTOR.getHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
- Global.ANIMATOR_DURATION_SCALE, 1f) > 0
- || Global.getFloat(mLauncher.getContentResolver(),
- Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
- }
- };
+
+ // TODO(b/397690719): Investigate the memory leak from TaskStackChangeListeners#mImpl
+ // This is a temporary fix of memory leak b/397690719. We track registered
+ // {@link TaskRestartedDuringLaunchListener}, and remove them on activity destroy.
+ private final List<TaskRestartedDuringLaunchListener> mRegisteredTaskStackChangeListener =
+ new ArrayList<>();
private DeviceProfile mDeviceProfile;
@@ -282,7 +274,6 @@
// Pairs of window starting type and starting window background color for starting tasks
// Will never be larger than MAX_NUM_TASKS
private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
- private boolean mAreAnimationsEnabled = true;
private final Interpolator mOpeningXInterpolator;
private final Interpolator mOpeningInterpolator;
@@ -293,7 +284,6 @@
mHandler = new Handler(Looper.getMainLooper());
mDeviceProfile = mLauncher.getDeviceProfile();
mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
- checkAndMonitorIfAnimationsAreEnabled();
Resources res = mLauncher.getResources();
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -338,7 +328,14 @@
TaskRestartedDuringLaunchListener restartedListener =
new TaskRestartedDuringLaunchListener();
restartedListener.register(onEndCallback::executeAllAndDestroy);
- onEndCallback.add(restartedListener::unregister);
+ mRegisteredTaskStackChangeListener.add(restartedListener);
+ onEndCallback.add(new Runnable() {
+ @Override
+ public void run() {
+ restartedListener.unregister();
+ mRegisteredTaskStackChangeListener.remove(restartedListener);
+ }
+ });
RemoteAnimationRunnerCompat runner = createAppLaunchRunner(v, onEndCallback);
@@ -1208,8 +1205,12 @@
unregisterRemoteTransitions();
mLauncher.removeOnDeviceProfileChangeListener(this);
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
- ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
- .unregisterContentObserver(mAnimationRemovalObserver));
+ if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) {
+ throw new IllegalStateException("Failed to run onEndCallback created from"
+ + " getActivityLaunchOptions()");
+ }
+ mRegisteredTaskStackChangeListener.forEach(TaskRestartedDuringLaunchListener::unregister);
+ mRegisteredTaskStackChangeListener.clear();
}
/**
@@ -1257,17 +1258,6 @@
}
}
- private void checkAndMonitorIfAnimationsAreEnabled() {
- ORDERED_BG_EXECUTOR.execute(() -> {
- mAnimationRemovalObserver.onChange(true);
- mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
- Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
- mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
- Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
-
- });
- }
-
private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
for (RemoteAnimationTarget target : targets) {
if (target.mode == mode && target.taskInfo != null
@@ -1403,7 +1393,8 @@
(LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
isTransluscent, fallbackBackgroundColor);
- } else if (launcherView != null && mAreAnimationsEnabled) {
+ } else if (launcherView != null && !RemoveAnimationSettingsTracker.INSTANCE.get(
+ mLauncher).isRemoveAnimationEnabled()) {
floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
mLauncher.getTaskbarUIController() == null
? null
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 adbcc75..688018b 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
@@ -21,6 +21,7 @@
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Rect
+import android.util.Log
import android.view.Choreographer
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CLOSE
@@ -63,7 +64,19 @@
fun createAnimators(info: TransitionInfo, finishCallback: (Animator) -> Unit): List<Animator> {
val launchChange = getLaunchChange(info)
- requireNotNull(launchChange) { "expected an app launch Change" }
+ requireNotNull(launchChange) {
+ val changesString =
+ info.changes.joinToString(", ") { change ->
+ "Change: mode=${change.mode}, " +
+ "taskId=${change.taskInfo?.id}, " +
+ "isFreeform=${change.taskInfo?.isFreeform}"
+ }
+ Log.e(
+ TAG,
+ "No launch change found: Transition type=${info.type}, changes=$changesString",
+ )
+ "expected an app launch Change"
+ }
val transaction = transactionSupplier.get()
@@ -173,10 +186,12 @@
onAnimFinish: (Animator) -> Unit,
): Animator {
return MinimizeAnimator.create(
- context.resources.displayMetrics,
+ context,
change,
transaction,
onAnimFinish,
+ interactionJankMonitor,
+ context.mainThreadHandler,
)
}
@@ -189,4 +204,8 @@
}
}
}
+
+ private companion object {
+ const val TAG = "DesktopAppLaunchAnimatorHelper"
+ }
}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
index 578bba5..79072a6 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
@@ -19,6 +19,7 @@
import android.animation.Animator
import android.content.Context
import android.os.IBinder
+import android.util.Log
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -43,7 +44,7 @@
@JvmOverloads
constructor(
context: Context,
- launchType: AppLaunchType,
+ private val launchType: AppLaunchType,
@Cuj.CujType private val cujType: Int,
private val mainExecutor: Executor,
transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
@@ -66,6 +67,7 @@
transaction: Transaction,
transitionFinishedCallback: IRemoteTransitionFinishedCallback,
) {
+ Log.v(TAG, "startAnimation: launchType=$launchType, cujType=$cujType")
val safeTransitionFinishedCallback = RemoteRunnable {
transitionFinishedCallback.onTransitionFinished(/* wct= */ null, /* sct= */ null)
}
@@ -86,6 +88,7 @@
}
companion object {
+ const val TAG = "DesktopAppLaunchTransition"
/** Change modes that represent a task becoming visible / launching in Desktop mode. */
val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
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/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
index 37e8d4d..138f40a 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
@@ -62,8 +62,6 @@
* (Used only when multiple desks are enabled).
*
* @property displayId The ID of the display this object represents.
- * @property canCreateDesks true if it's possible to create new desks on the display represented
- * by this object.
* @property activeDeskId The ID of the active desk on the associated display (if any). It has a
* value of `INACTIVE_DESK_ID` (-1) if there are no active desks. Note that there can only be
* at most one active desk on each display.
@@ -71,11 +69,18 @@
*/
private data class DisplayDeskConfig(
val displayId: Int,
- var canCreateDesks: Boolean,
var activeDeskId: Int = INACTIVE_DESK_ID,
val deskIds: MutableSet<Int>,
)
+ /** True if it is possible to create new desks on current setup. */
+ var canCreateDesks: Boolean = false
+ private set(value) {
+ if (field == value) return
+ field = value
+ desktopVisibilityListeners.forEach { it.onCanCreateDesksChanged(field) }
+ }
+
/** Maps each display by its ID to its desks configuration. */
private val displaysDesksConfigsMap = SparseArray<DisplayDeskConfig>()
@@ -147,6 +152,20 @@
}
}
+ /**
+ * Returns the ID of the active desk (if any) on the display whose ID is [displayId], or
+ * [INACTIVE_DESK_ID] if no desk is currently active or the multiple desks feature is disabled.
+ */
+ fun getActiveDeskId(displayId: Int): Int {
+ if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+ // When the multiple desks feature is disabled, callers should not rely on the concept
+ // of a desk ID.
+ return INACTIVE_DESK_ID
+ }
+
+ return getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
+ }
+
/** Returns whether a desk is currently active on the display with the given [displayId]. */
fun isInDesktopMode(displayId: Int): Boolean {
if (!DesktopModeStatus.enableMultipleDesktops(context)) {
@@ -393,7 +412,10 @@
}
}
- private fun onListenerConnected(displayDeskStates: Array<DisplayDeskState>) {
+ private fun onListenerConnected(
+ displayDeskStates: Array<DisplayDeskState>,
+ canCreateDesks: Boolean,
+ ) {
if (!DesktopModeStatus.enableMultipleDesktops(context)) {
return
}
@@ -404,23 +426,24 @@
displaysDesksConfigsMap[displayDeskState.displayId] =
DisplayDeskConfig(
displayId = displayDeskState.displayId,
- canCreateDesks = displayDeskState.canCreateDesk,
activeDeskId = displayDeskState.activeDeskId,
deskIds = displayDeskState.deskIds.toMutableSet(),
)
}
+
+ this.canCreateDesks = canCreateDesks
}
private fun getDisplayDeskConfig(displayId: Int) =
displaysDesksConfigsMap[displayId]
?: null.also { Slog.e(TAG, "Expected non-null desk config for display: $displayId") }
- private fun onCanCreateDesksChanged(displayId: Int, canCreateDesks: Boolean) {
+ private fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
if (!DesktopModeStatus.enableMultipleDesktops(context)) {
return
}
- getDisplayDeskConfig(displayId)?.canCreateDesks = canCreateDesks
+ this.canCreateDesks = canCreateDesks
}
private fun onDeskAdded(displayId: Int, deskId: Int) {
@@ -525,9 +548,12 @@
) : Stub() {
private val controller = WeakReference(controller)
- override fun onListenerConnected(displayDeskStates: Array<DisplayDeskState>) {
+ override fun onListenerConnected(
+ displayDeskStates: Array<DisplayDeskState>,
+ canCreateDesks: Boolean,
+ ) {
Executors.MAIN_EXECUTOR.execute {
- controller.get()?.onListenerConnected(displayDeskStates)
+ controller.get()?.onListenerConnected(displayDeskStates, canCreateDesks)
}
}
@@ -565,9 +591,9 @@
override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {}
- override fun onCanCreateDesksChanged(displayId: Int, canCreateDesks: Boolean) {
+ override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
Executors.MAIN_EXECUTOR.execute {
- controller.get()?.onCanCreateDesksChanged(displayId, canCreateDesks)
+ controller.get()?.onCanCreateDesksChanged(canCreateDesks)
}
}
@@ -603,6 +629,6 @@
private const val TAG = "DesktopVisController"
private const val DEBUG = false
- private const val INACTIVE_DESK_ID = -1
+ public const val INACTIVE_DESK_ID = -1
}
}
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 d1e63f3..2e42e45 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -18,6 +18,7 @@
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
@@ -46,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;
@@ -876,7 +876,7 @@
@Override
public void onPopupVisibilityChanged(boolean isVisible) {
- setTaskbarWindowFocusable(isVisible);
+ setTaskbarWindowFocusable(isVisible /* focusable */, false /* imeFocusable */);
}
@Override
@@ -1235,17 +1235,29 @@
}
/**
- * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
- * window.
+ * Sets whether the taskbar window should be focusable and IME focusable. This won't be IME
+ * focusable unless it is also focusable.
+ *
+ * @param focusable whether it should be focusable.
+ * @param imeFocusable whether it should be IME focusable.
+ *
+ * @see WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE
+ * @see WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
*/
- public void setTaskbarWindowFocusable(boolean focusable) {
+ public void setTaskbarWindowFocusable(boolean focusable, boolean imeFocusable) {
if (isPhoneMode()) {
return;
}
if (focusable) {
mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
+ if (imeFocusable) {
+ mWindowLayoutParams.flags &= ~FLAG_ALT_FOCUSABLE_IM;
+ } else {
+ mWindowLayoutParams.flags |= FLAG_ALT_FOCUSABLE_IM;
+ }
} else {
mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+ mWindowLayoutParams.flags &= ~FLAG_ALT_FOCUSABLE_IM;
}
notifyUpdateLayoutParams();
}
@@ -1266,8 +1278,12 @@
}
/**
- * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
- * window. If we're now focusable, also move nav buttons to a separate window above IME.
+ * Sets whether the taskbar window should be focusable, as well as IME focusable. If we're now
+ * focusable, also move nav buttons to a separate window above IME.
+ *
+ * @param focusable whether it should be focusable.
+ *
+ * @see WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE
*/
public void setTaskbarWindowFocusableForIme(boolean focusable) {
if (focusable) {
@@ -1275,7 +1291,7 @@
} else {
mControllers.navbarButtonsViewController.moveNavButtonsBackToTaskbarWindow();
}
- setTaskbarWindowFocusable(focusable);
+ setTaskbarWindowFocusable(focusable, true /* imeFocusable */);
}
/** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */
@@ -1710,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/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index b4ffb74..5d1288c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -39,6 +39,7 @@
import com.airbnb.lottie.LottieAnimationView
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.R
+import com.android.launcher3.RemoveAnimationSettingsTracker
import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
@@ -128,6 +129,26 @@
activityContext.dragLayer.post { maybeShowSearchEdu() }
}
+ /**
+ * Turns off auto play of lottie animations if user has opted to remove animation else attaches
+ * click listener to allow user to play or pause animations.
+ */
+ fun handleEduAnimations(animationViews: List<LottieAnimationView>) {
+ for (animationView in animationViews) {
+ if (
+ RemoveAnimationSettingsTracker.INSTANCE.get(animationView.context)
+ .isRemoveAnimationEnabled()
+ ) {
+ animationView.pauseAnimation()
+ } else {
+ animationView.setOnClickListener {
+ if (animationView.isAnimating) animationView.pauseAnimation()
+ else animationView.playAnimation()
+ }
+ }
+ }
+ }
+
/** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
fun maybeShowSwipeEdu() {
if (
@@ -145,7 +166,9 @@
requireViewById(R.id.taskbar_edu_title),
TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
)
- requireViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
+ val swipeAnimation = requireViewById<LottieAnimationView>(R.id.swipe_animation)
+ swipeAnimation.supportLightTheme()
+ handleEduAnimations(listOf(swipeAnimation))
show()
}
}
@@ -174,6 +197,7 @@
splitscreenAnim.supportLightTheme()
suggestionsAnim.supportLightTheme()
pinningAnim.supportLightTheme()
+ handleEduAnimations(listOf(splitscreenAnim, suggestionsAnim, pinningAnim))
if (DisplayController.isTransientTaskbar(activityContext)) {
splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
@@ -249,9 +273,6 @@
tooltip?.run {
allowTouchDismissal = true
- requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
- .supportLightTheme()
-
TypefaceUtils.setTypeface(
requireViewById(R.id.taskbar_edu_title),
TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
@@ -261,6 +282,10 @@
TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
)
+ val pinningAnim =
+ requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
+ pinningAnim.supportLightTheme()
+ handleEduAnimations(listOf(pinningAnim))
updateLayoutParams<BaseDragLayer.LayoutParams> {
if (DisplayController.isTransientTaskbar(activityContext)) {
bottomMargin += activityContext.deviceProfile.taskbarHeight
@@ -304,7 +329,9 @@
inflateTooltip(R.layout.taskbar_edu_search)
tooltip?.run {
allowTouchDismissal = true
- requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
+ val searchEdu = requireViewById<LottieAnimationView>(R.id.search_edu_animation)
+ searchEdu.supportLightTheme()
+ handleEduAnimations(listOf(searchEdu))
val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
TypefaceUtils.setTypeface(
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 6cd2979..34bb6e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -45,6 +45,7 @@
import android.os.Handler;
import android.os.Trace;
import android.provider.Settings;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -75,14 +76,20 @@
import com.android.quickstep.fallback.window.RecentsDisplayModel;
import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.ContextualSearchInvoker;
+import com.android.quickstep.util.GroupTask;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
+import java.util.Set;
import java.util.StringJoiner;
/**
@@ -194,6 +201,65 @@
recreateTaskbar();
};
+ private final PerceptibleTaskListener mTaskStackListener;
+
+ private class PerceptibleTaskListener implements TaskStackChangeListener {
+ private ArraySet<Integer> mPerceptibleTasks = new ArraySet<Integer>();
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ if (mPerceptibleTasks.contains(taskId)) {
+ return;
+ }
+
+ // This listens to any Task, so we filter them by the ones shown in the launcher.
+ // For Tasks restored after startup, they will by default not be Perceptible, and no
+ // need to until user interacts with it by bringing it to the foreground.
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ // get pinned tasks
+ Set<Integer> taskbarPinnedTasks =
+ mTaskbars.valueAt(i).getControllers().taskbarViewController
+ .getTaskIdsForPinnedApps();
+
+ // mark as perceptible if the foregrounded task is in the list of apps shown in
+ // the launcher.
+ if (taskbarPinnedTasks.contains(taskId)
+ && ActivityManagerWrapper.getInstance()
+ .setTaskIsPerceptible(taskId, true)
+ ) {
+ mPerceptibleTasks.add(taskId);
+ }
+ }
+ }
+
+ /**
+ * Launcher also can display recently launched tasks that are not pinned. Also add
+ * these as perceptible
+ */
+ @Override
+ public void onRecentTaskListUpdated() {
+ for (int i = 0; i < mTaskbars.size(); i++) {
+ for (GroupTask gTask : mTaskbars.valueAt(i).getControllers()
+ .taskbarRecentAppsController.getShownTasks()) {
+ for (Task task : gTask.getTasks()) {
+ int taskId = task.key.id;
+
+ if (!mPerceptibleTasks.contains(taskId)) {
+ ActivityManagerWrapper.getInstance()
+ .setTaskIsPerceptible(taskId, true);
+ mPerceptibleTasks.add(taskId);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ mPerceptibleTasks.remove(taskId);
+ }
+ };
+
private boolean mUserUnlocked = false;
private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver;
@@ -296,6 +362,12 @@
mTaskbarBroadcastReceiver.register(RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
});
+ if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
+ mTaskStackListener = new PerceptibleTaskListener();
+ TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+ } else {
+ mTaskStackListener = null;
+ }
debugTaskbarManager("TaskbarManager created");
recreateTaskbar();
}
@@ -709,15 +781,14 @@
return;
}
- // TODO (b/391965805): remove once onDisplayAddSystemDecorations is working.
- WindowManager wm = getWindowManager(displayId);
- if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
- return;
- }
-
Context newWindowContext = createWindowContext(displayId);
if (newWindowContext != null) {
addWindowContextToMap(displayId, newWindowContext);
+ // TODO (b/391965805): remove once onDisplayAddSystemDecorations is working.
+ WindowManager wm = getWindowManager(displayId);
+ if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
+ return;
+ }
createTaskbarRootLayout(displayId);
createNavButtonController(displayId);
createAndRegisterComponentCallbacks(displayId);
@@ -788,6 +859,12 @@
Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
mShutdownReceiver.unregisterReceiverSafely();
+ if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
+ for (Integer taskId: mTaskStackListener.mPerceptibleTasks) {
+ ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
+ }
+ }
+ TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
destroyAllTaskbars();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
index 8b53ff1..e9c62d1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TypefaceUtils.kt
@@ -30,11 +30,14 @@
class TypefaceUtils {
companion object {
- const val FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED = "variable-headline-small-emphasized"
- const val FONT_FAMILY_HEADLINE_LARGE_EMPHASIZED = "variable-headline-large-emphasized"
const val FONT_FAMILY_BODY_SMALL_BASELINE = "variable-body-small"
const val FONT_FAMILY_BODY_MEDIUM_BASELINE = "variable-body-medium"
+ const val FONT_FAMILY_BODY_LARGE_BASELINE = "variable-body-large"
const val FONT_FAMILY_LABEL_LARGE_BASELINE = "variable-label-large"
+ const val FONT_FAMILY_DISPLAY_SMALL_EMPHASIZED = "variable-display-small-emphasized"
+ const val FONT_FAMILY_DISPLAY_MEDIUM_EMPHASIZED = "variable-display-medium-emphasized"
+ const val FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED = "variable-headline-small-emphasized"
+ const val FONT_FAMILY_HEADLINE_LARGE_EMPHASIZED = "variable-headline-large-emphasized"
@JvmStatic
@JvmOverloads
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/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 6e901ee..1f34969 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -144,7 +144,7 @@
override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
- override fun supportsMultiInstance(lai: LauncherActivityInfo) : Boolean {
+ override fun supportsMultiInstance(lai: LauncherActivityInfo): Boolean {
return try {
super.supportsMultiInstance(lai) || lai.supportsMultiInstance()
} catch (e: Exception) {
@@ -202,4 +202,7 @@
(appInfo.sourceDir?.hashCode() ?: 0).toString() + " " + appInfo.longVersionCode
override fun getRoundIconRes(appInfo: ApplicationInfo) = appInfo.roundIconRes
+
+ override fun isFileDrawable(shortcutInfo: ShortcutInfo) =
+ shortcutInfo.hasIconFile() || shortcutInfo.hasIconUri()
}
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/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 10513c0..b27c6e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -153,7 +153,7 @@
return new PageAlphaProvider(DECELERATE_2) {
@Override
public float getPageAlpha(int pageIndex) {
- return launcher.getDeviceProfile().shouldShowAllAppsOnSheet()
+ return launcher.getDeviceProfile().isTablet
? superPageAlphaProvider.getPageAlpha(pageIndex)
: 0;
}
@@ -164,7 +164,7 @@
public int getVisibleElements(Launcher launcher) {
int elements = ALL_APPS_CONTENT | FLOATING_SEARCH_BAR;
// When All Apps is presented on a bottom sheet, HOTSEAT_ICONS are visible.
- if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+ if (launcher.getDeviceProfile().isTablet) {
elements |= HOTSEAT_ICONS;
}
return elements;
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/DesktopFullscreenDrawParams.kt b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
index bafb0b2..444e77d 100644
--- a/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
+++ b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
@@ -17,7 +17,7 @@
package com.android.quickstep
import android.content.Context
-import com.android.systemui.shared.system.QuickStepContract
+import com.android.launcher3.R
// DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
open class DesktopFullscreenDrawParams
@@ -28,6 +28,6 @@
// computeCornerRadius is used as cornerRadiusProvider, so
// QuickStepContract::getWindowCornerRadius can be mocked properly.
private fun computeCornerRadius(context: Context): Float =
- QuickStepContract.getWindowCornerRadius(context)
+ context.resources.getDimension(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index bb72408..2c4c2f9 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -21,7 +21,7 @@
import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM;
+import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_DESK;
import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
import android.app.ActivityManager.RunningTaskInfo;
@@ -32,10 +32,12 @@
import android.os.Process;
import android.os.RemoteException;
import android.util.SparseBooleanArray;
+import android.window.DesktopExperienceFlags;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.quickstep.util.DesktopTask;
@@ -353,9 +355,9 @@
int numVisibleTasks = 0;
for (GroupedTaskInfo rawTask : rawTasks) {
- if (rawTask.isBaseType(TYPE_FREEFORM)) {
- // TYPE_FREEFORM tasks is only created when desktop mode can be entered,
- // leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
+ if (rawTask.isBaseType(TYPE_DESK)) {
+ // TYPE_DESK tasks is only created when desktop mode can be entered,
+ // leftover TYPE_DESK tasks created when flag was on should be ignored.
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
List<DesktopTask> desktopTasks = createDesktopTasks(
rawTask.getBaseGroupedTask());
@@ -442,7 +444,11 @@
Set<Integer> minimizedTaskIds = minimizedTaskIdArray != null
? CollectionsKt.toSet(ArraysKt.asIterable(minimizedTaskIdArray))
: Collections.emptySet();
- if (enableSeparateExternalDisplayTasks()) {
+ if (enableSeparateExternalDisplayTasks()
+ && !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+ // This code is not needed when the multiple desktop feature is enabled, since Shell
+ // will send a single `GroupedTaskInfo` for each desk with a unique `deskId` across
+ // all displays.
Map<Integer, List<Task>> perDisplayTasks = new HashMap<>();
for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
Task task = createTask(taskInfo, minimizedTaskIds);
@@ -450,11 +456,16 @@
k -> new ArrayList<>());
tasks.add(task);
}
- return MapsKt.map(perDisplayTasks, it -> new DesktopTask(it.getValue()));
+ // When the multiple desktop feature is disabled, there can only be up to a single desk
+ // on each display, The desk ID doesn't matter and should not be used.
+ return MapsKt.map(perDisplayTasks,
+ it -> new DesktopTask(DesktopVisibilityController.INACTIVE_DESK_ID,
+ it.getValue()));
} else {
+ final int deskId = recentTaskInfo.getDeskId();
List<Task> tasks = CollectionsKt.map(recentTaskInfo.getTaskInfoList(),
it -> createTask(it, minimizedTaskIds));
- return List.of(new DesktopTask(tasks));
+ return List.of(new DesktopTask(deskId, tasks));
}
}
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/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index c986b88..c1bb250 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -64,6 +64,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.RemoveAnimationSettingsTracker;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -92,6 +93,8 @@
private static final String LOG_TAG = "AllSetActivity";
private static final String URI_SYSTEM_NAVIGATION_SETTING =
"#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S.:settings:fragment_args_key=gesture_system_navigation_input_summary;S.:settings:show_fragment=com.android.settings.gestures.SystemNavigationGestureSettings;end";
+ private static final String INTENT_ACTION_ACTIVITY_CLOSED =
+ "com.android.quickstep.interaction.ACTION_ALL_SET_ACTIVITY_CLOSED";
private static final String EXTRA_ACCENT_COLOR_DARK_MODE = "suwColorAccentDark";
private static final String EXTRA_ACCENT_COLOR_LIGHT_MODE = "suwColorAccentLight";
private static final String EXTRA_DEVICE_NAME = "suwDeviceName";
@@ -105,6 +108,9 @@
private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
+ private static final String KEY_BACKGROUND_ANIMATION_TOGGLED_ON =
+ "background_animation_toggled_on";
+
private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
private final InvariantDeviceProfile.OnIDPChangeListener mOnIDPChangeListener =
@@ -122,6 +128,9 @@
private AnimatorPlaybackController mLauncherStartAnim = null;
+ // Auto play background animation by default
+ private boolean mBackgroundAnimationToggledOn = true;
+
private TextView mHintView;
private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChange;
@@ -198,6 +207,15 @@
LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
getTheme());
+ mBackgroundAnimationToggledOn = savedInstanceState == null
+ || savedInstanceState.getBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, true);
+ // The animated background is behind a scroll view, which intercepts all input.
+ // However, the content view also covers the full screen
+ requireViewById(R.id.content).setOnClickListener(v -> {
+ mBackgroundAnimationToggledOn = !mBackgroundAnimationToggledOn;
+ maybeResumeOrPauseBackgroundAnimation();
+ });
+
setUpBackgroundAnimation(getDP().isTablet);
getIDP().addOnChangeListener(mOnIDPChangeListener);
@@ -206,6 +224,12 @@
ActivityPreloadUtil.preloadOverviewForSUWAllSet(this);
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, mBackgroundAnimationToggledOn);
+ }
+
private InvariantDeviceProfile getIDP() {
return LauncherAppState.getInstance(this).getInvariantDeviceProfile();
}
@@ -332,6 +356,7 @@
mLauncherStartAnim.dispatchOnEnd();
mLauncherStartAnim = null;
}
+ sendBroadcast(new Intent(INTENT_ACTION_ACTIVITY_CLOSED));
}
@Override
@@ -365,8 +390,10 @@
private void maybeResumeOrPauseBackgroundAnimation() {
boolean shouldPlayAnimation =
- getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
- && isResumed();
+ !RemoveAnimationSettingsTracker.INSTANCE.get(this).isRemoveAnimationEnabled()
+ && getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
+ && isResumed()
+ && mBackgroundAnimationToggledOn;
if (mAnimatedBackground.isAnimating() && !shouldPlayAnimation) {
mAnimatedBackground.pauseAnimation();
} else if (!mAnimatedBackground.isAnimating() && shouldPlayAnimation) {
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 0fc95e2..e73fb3b 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -57,6 +57,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.taskbar.TypefaceUtils;
import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
@@ -177,6 +178,7 @@
mFeedbackTitleView.setText(getIntroductionTitle());
mFeedbackSubtitleView.setText(getIntroductionSubtitle());
+ setTitleTypefaces();
mExitingAppView.setClipToOutline(true);
mExitingAppView.setOutlineProvider(new ViewOutlineProvider() {
@@ -434,6 +436,10 @@
if (isGestureSuccessful) {
if (mTutorialFragment.isAtFinalStep()) {
+ TypefaceUtils.setTypeface(
+ mDoneButton,
+ TypefaceUtils.FONT_FAMILY_LABEL_LARGE_BASELINE
+ );
showActionButton();
}
@@ -458,7 +464,8 @@
pauseAndHideLottieAnimation();
mCheckmarkAnimation.setVisibility(View.VISIBLE);
mCheckmarkAnimation.playAnimation();
- mFeedbackTitleView.setTextAppearance(mContext, getSuccessTitleTextAppearance());
+ mFeedbackTitleView.setTextAppearance(getSuccessTitleTextAppearance());
+ setTitleTypefaces();
}
public boolean isGestureCompleted() {
@@ -513,8 +520,10 @@
updateDrawables();
updateLayout();
- mFeedbackTitleView.setTextAppearance(mContext, getTitleTextAppearance());
- mDoneButton.setTextAppearance(mContext, getDoneButtonTextAppearance());
+ mFeedbackTitleView.setTextAppearance(getTitleTextAppearance());
+ mDoneButton.setTextAppearance(getDoneButtonTextAppearance());
+
+ setTitleTypefaces();
mDoneButton.getBackground().setTint(getDoneButtonColor());
mCheckmarkAnimation.setAnimation(mTutorialFragment.isAtFinalStep()
? R.raw.checkmark_animation_end
@@ -533,6 +542,21 @@
}
}
+ /**
+ * Apply expressive typefaces to the feedback title and subtitle views.
+ */
+ private void setTitleTypefaces() {
+ TypefaceUtils.setTypeface(
+ mFeedbackTitleView,
+ mTutorialFragment.isLargeScreen()
+ ? TypefaceUtils.FONT_FAMILY_DISPLAY_MEDIUM_EMPHASIZED
+ : TypefaceUtils.FONT_FAMILY_DISPLAY_SMALL_EMPHASIZED);
+ TypefaceUtils.setTypeface(
+ mFeedbackSubtitleView,
+ TypefaceUtils.FONT_FAMILY_BODY_LARGE_BASELINE
+ );
+ }
+
protected void resetViewsForBackGesture() {
mFakeTaskView.setVisibility(View.VISIBLE);
mFakeTaskView.setBackgroundColor(getFakeTaskViewColor());
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/data/TaskVisualsChangedDelegate.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
index 608fafd..12616a8 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
@@ -17,6 +17,7 @@
package com.android.quickstep.recents.data
import android.os.UserHandle
+import android.util.Log
import com.android.quickstep.HighResLoadingState.HighResLoadingStateChangedCallback
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
@@ -58,7 +59,7 @@
fun onTaskThumbnailChanged(thumbnailData: ThumbnailData?)
/** Informs the listener that the default resolution for loading thumbnails has changed */
- fun onHighResLoadingStateChanged()
+ fun onHighResLoadingStateChanged(highResEnabled: Boolean)
}
}
@@ -91,8 +92,9 @@
}
override fun onHighResLoadingStateChanged(enabled: Boolean) {
+ Log.d(TAG, "onHighResLoadingStateChanged(enabled = $enabled)")
taskThumbnailChangedCallbacks.values.forEach { (_, callback) ->
- callback.onHighResLoadingStateChanged()
+ callback.onHighResLoadingStateChanged(enabled)
}
}
@@ -142,4 +144,8 @@
}
}
}
+
+ companion object {
+ const val TAG = "TaskVisualsChangedDelegateImpl"
+ }
}
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index b1a5920..5274ef3 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -160,7 +160,17 @@
updateThumbnail(task.key.id, thumbnailData)
}
- override fun onHighResLoadingStateChanged() {
+ override fun onHighResLoadingStateChanged(highResEnabled: Boolean) {
+ val isTaskVisible = taskRequests.containsKey(task.key.id)
+ if (!isTaskVisible) return
+
+ val isCurrentThumbnailLowRes =
+ tasks.value[task.key.id]?.thumbnail?.reducedResolution
+ val isRequestedResHigherThanCurrent =
+ isCurrentThumbnailLowRes == null ||
+ (isCurrentThumbnailLowRes && highResEnabled)
+ if (!isRequestedResHigherThanCurrent) return
+
recentsCoroutineScope.launch(dispatcherProvider.background) {
updateThumbnail(task.key.id, getThumbnailFromDataSource(task))
}
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/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index e672ec4..0edbacc 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -20,6 +20,7 @@
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Outline
+import android.graphics.Path
import android.graphics.Rect
import android.graphics.drawable.ShapeDrawable
import android.util.AttributeSet
@@ -52,6 +53,7 @@
private val dimAlpha: MultiPropertyFactory<View> by lazy {
MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
}
+ private val outlinePath = Path()
private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
@@ -96,7 +98,20 @@
outlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(outlineBounds ?: bounds, cornerRadius)
+ val outlineRect = outlineBounds ?: bounds
+ outlinePath.apply {
+ rewind()
+ addRoundRect(
+ outlineRect.left.toFloat(),
+ outlineRect.top.toFloat(),
+ outlineRect.right.toFloat(),
+ outlineRect.bottom.toFloat(),
+ cornerRadius / scaleX,
+ cornerRadius / scaleY,
+ Path.Direction.CW,
+ )
+ }
+ outline.setPath(outlinePath)
}
}
}
@@ -166,8 +181,10 @@
private fun resetViews() {
liveTileView.isInvisible = true
thumbnailView.isInvisible = true
+ thumbnailView.setImageBitmap(null)
splashBackground.alpha = 0f
splashIcon.alpha = 0f
+ splashIcon.setImageDrawable(null)
scrimView.alpha = 0f
setBackgroundColor(Color.BLACK)
taskThumbnailViewHeader?.isInvisible = true
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/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index 53ea022..fbe3bc6 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -20,17 +20,19 @@
/**
* A [Task] container that can contain N number of tasks that are part of the desktop in recent
- * tasks list. Note that desktops can be empty with no tasks in them.
+ * tasks list. Note that desktops can be empty with no tasks in them. The [deskId] makes sense only
+ * when the multiple desks feature is enabled.
*/
-class DesktopTask(tasks: List<Task>) : GroupTask(tasks, TaskViewType.DESKTOP) {
+class DesktopTask(val deskId: Int, tasks: List<Task>) : GroupTask(tasks, TaskViewType.DESKTOP) {
- override fun copy() = DesktopTask(tasks)
+ override fun copy() = DesktopTask(deskId, tasks)
- override fun toString() = "type=$taskViewType tasks=$tasks"
+ override fun toString() = "type=$taskViewType deskId=$deskId tasks=$tasks"
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is DesktopTask) return false
+ if (deskId != o.deskId) return false
return super.equals(o)
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 7d5b471..d2f9652 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -18,6 +18,7 @@
import android.animation.Animator;
import android.animation.RectEvaluator;
+import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -25,6 +26,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
+import android.util.Rational;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
@@ -50,8 +52,6 @@
private static final float END_PROGRESS = 1.0f;
- private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
-
private final int mTaskId;
private final ActivityInfo mActivityInfo;
private final SurfaceControl mLeash;
@@ -158,9 +158,8 @@
// not a valid rectangle to use for cropping app surface
reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint;
sourceRectHint.setEmpty();
- } else if (Math.abs(
- aspectRatio - (sourceRectHint.width() / (float) sourceRectHint.height()))
- > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+ } else if (!PictureInPictureParams.isSameAspectRatio(sourceRectHint,
+ new Rational(destinationBounds.width(), destinationBounds.height()))) {
// The source rect hint does not aspect ratio
reasonForCreateOverlay = "Source rect hint does not match aspect ratio "
+ sourceRectHint + " aspect ratio " + aspectRatio;
diff --git a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
index 91e8376..6e2d469 100644
--- a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
@@ -16,16 +16,11 @@
package com.android.quickstep.util;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
-import android.app.Activity;
import android.app.ActivityManager;
import android.util.Log;
import androidx.annotation.NonNull;
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
-import com.android.quickstep.RecentsModel;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
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/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt
index 6da52d6..cb69b22 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconView.kt
@@ -78,7 +78,8 @@
override fun setDrawable(d: Drawable?) {
drawable?.callback = null
- drawable = d
+ // Copy drawable so that mutations below do not affect other users of the drawable
+ drawable = d?.constantState?.newDrawable()?.mutate()
drawable?.let {
it.callback = this
setDrawableSizeInternal(width, height)
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 a29d302..d17dfb8 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;
@@ -157,6 +159,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StatefulContainer;
@@ -179,6 +182,7 @@
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewPool;
import com.android.launcher3.util.coroutines.DispatcherProvider;
+import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener;
import com.android.quickstep.BaseContainerInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.HighResLoadingState;
@@ -265,7 +269,7 @@
CONTAINER_TYPE extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>,
STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
HighResLoadingState.HighResLoadingStateChangedCallback,
- TaskVisualsChangeListener {
+ TaskVisualsChangeListener, DesktopVisibilityListener {
private static final String TAG = "RecentsView";
private static final boolean DEBUG = false;
@@ -556,6 +560,10 @@
private final Rect mTaskViewDeadZoneRect = new Rect();
private final Rect mTopRowDeadZoneRect = new Rect();
private final Rect mBottomRowDeadZoneRect = new Rect();
+
+ @Nullable
+ private DesktopVisibilityController mDesktopVisibilityController = null;
+
/**
* Reflects if Recents is currently in the middle of a gesture, and if so, which tasks are
* running. If a gesture is not in progress, this will be null.
@@ -668,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()));
}
@@ -913,6 +921,8 @@
mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate(
R.layout.overview_add_desktop_button, this, false);
mAddDesktopButton.setOnClickListener(this::createDesk);
+
+ mDesktopVisibilityController = DesktopVisibilityController.INSTANCE.get(mContext);
}
mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
@@ -1227,13 +1237,16 @@
mSyncTransactionApplier = new SurfaceTransactionApplier(this);
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
.setSyncTransactionApplier(mSyncTransactionApplier));
- RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
+ RecentsModel.INSTANCE.get(mContext).addThumbnailChangeListener(this);
mIPipAnimationListener.setActivityAndRecentsView(mContainer, this);
- SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(
+ SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(
mIPipAnimationListener);
mOrientationState.initListeners();
mTaskOverlayFactory.initListeners();
mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
+ if (mDesktopVisibilityController != null) {
+ mDesktopVisibilityController.registerDesktopVisibilityListener(this);
+ }
}
@Override
@@ -1248,12 +1261,15 @@
runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
.setSyncTransactionApplier(null));
executeSideTaskLaunchCallback();
- RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
- SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(null);
+ RecentsModel.INSTANCE.get(mContext).removeThumbnailChangeListener(this);
+ SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(null);
mIPipAnimationListener.setActivityAndRecentsView(null, null);
mOrientationState.destroyListeners();
mTaskOverlayFactory.removeListeners();
mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
+ if (mDesktopVisibilityController != null) {
+ mDesktopVisibilityController.unregisterDesktopVisibilityListener(this);
+ }
reset();
}
@@ -2896,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);
}
}
@@ -3030,8 +3048,12 @@
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView;
if (needDesktopTask) {
+ final int activeDeskId =
+ DesktopVisibilityController.INSTANCE.get(mContext).getActiveDeskId(
+ mContainer.getDisplay().getDisplayId());
taskView = getTaskViewFromPool(TaskViewType.DESKTOP);
- ((DesktopTaskView) taskView).bind(new DesktopTask(Arrays.asList(runningTasks)),
+ ((DesktopTaskView) taskView).bind(
+ new DesktopTask(activeDeskId, Arrays.asList(runningTasks)),
mOrientationState, mTaskOverlayFactory);
} else if (needGroupTaskView) {
taskView = getTaskViewFromPool(TaskViewType.GROUPED);
@@ -3840,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.
@@ -3885,6 +3908,22 @@
// the only invariant point in landscape split screen.
snapToLastTask = true;
}
+ if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) {
+ TaskView lastLargeTile = mUtils.getLastLargeTaskView();
+ if (lastLargeTile != null) {
+ // Calculate the distance to put last large tile back to middle of the screen.
+ int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
+ int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile));
+ longGridRowWidthDiff = primaryScroll - lastLargeTileScroll;
+
+ if (!isClearAllHidden) {
+ // If ClearAllButton is visible, reduce the distance by scroll difference
+ // between ClearAllButton and the last task.
+ longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0,
+ getPagedOrientationHandler().getPrimarySize(mClearAllButton));
+ }
+ }
+ }
// If we need to animate the grid to compensate the clear all gap, we split the second
// half of the dismiss pending animation (in which the non-dismissed tasks slide into
@@ -4608,17 +4647,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*/);
@@ -4634,7 +4683,7 @@
private void dismissCurrentTask() {
TaskView taskView = getNextPageTaskView();
if (taskView != null) {
- dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
+ dismissTaskView(taskView, true /*animateTaskView*/, true /*removeTask*/);
}
}
@@ -6229,8 +6278,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);
@@ -6859,6 +6908,11 @@
}
}
+ @Override
+ public void onCanCreateDesksChanged(boolean canCreateDesks) {
+ // TODO: b/389209338 - update the AddDesktopButton's visibility on this.
+ }
+
/** Get the color used for foreground scrimming the RecentsView for sharing. */
public static int getForegroundScrimDimColor(Context context) {
return context.getColor(R.color.overview_foreground_scrim_color);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index f742ec3..67318ac 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
@@ -84,6 +85,9 @@
/** Counts [TaskView]s that are large tiles. */
fun getLargeTileCount(): Int = taskViews.count { it.isLargeTile }
+ /** Counts [TaskView]s that are grid tasks. */
+ fun getGridTaskCount(): Int = taskViews.count { it.isGridTask }
+
/** Returns the first TaskView that should be displayed as a large tile. */
fun getFirstLargeTaskView(): TaskView? =
taskViews.firstOrNull {
@@ -294,7 +298,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..2b9d036 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
@@ -108,6 +109,8 @@
overlay.destroy()
if (enableRefactorTaskThumbnail()) {
isThumbnailValid = false
+ thumbnailData = null
+ thumbnailView.onRecycle()
} else {
thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
}
@@ -127,9 +130,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 49ec31d..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(
@@ -789,11 +806,11 @@
private fun updateThumbnailValidity(container: TaskContainer) {
container.isThumbnailValid =
- viewModel!!.isThumbnailValid(
+ viewModel?.isThumbnailValid(
thumbnail = container.thumbnailData,
width = container.thumbnailView.width,
height = container.thumbnailView.height,
- )
+ ) ?: return
applyThumbnailSplashAlpha()
}
@@ -810,7 +827,8 @@
*/
private fun updateThumbnailMatrix(container: TaskContainer, width: Int, height: Int) {
val thumbnailPosition =
- viewModel!!.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
+ viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
+ ?: return
container.updateThumbnailMatrix(thumbnailPosition.matrix)
}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 6f0aaeb..80b2c16 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -213,6 +213,18 @@
}
}
+ @Test
+ fun taskThumbnailView_scaled_roundRoundedCorners() {
+ screenshotRule.screenshotTest("taskThumbnailView_scaledRoundedCorners") { activity ->
+ activity.actionBar?.hide()
+ createTaskThumbnailView(activity).apply {
+ scaleX = 0.75f
+ scaleY = 0.3f
+ setState(BackgroundOnly(Color.YELLOW))
+ }
+ }
+ }
+
private fun createTaskThumbnailView(context: Context): TaskThumbnailView {
val taskThumbnailView =
LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
similarity index 90%
rename from quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
index 9ca9fe4..7ebef45 100644
--- a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
@@ -27,12 +27,10 @@
import android.window.TransitionFilter
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.quickstep.SystemUiProxy
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -43,7 +41,6 @@
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-import org.mockito.quality.Strictness
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -51,12 +48,6 @@
@get:Rule val mSetFlagsRule = SetFlagsRule()
- private val mockitoSession =
- mockitoSession()
- .strictness(Strictness.LENIENT)
- .mockStatic(DesktopModeStatus::class.java)
- .startMocking()
-
private val context = mock<Context>()
private val systemUiProxy = mock<SystemUiProxy>()
private lateinit var transitionManager: DesktopAppLaunchTransitionManager
@@ -68,11 +59,6 @@
transitionManager = DesktopAppLaunchTransitionManager(context, systemUiProxy)
}
- @After
- fun tearDown() {
- mockitoSession.finishMocking()
- }
-
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
fun registerTransitions_appLaunchFlagEnabled_registersTransition() {
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/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index bfd53ef..2cd09cc 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -332,7 +332,7 @@
(0..<tasksToAdd).map {
Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 2000))
}
- recentsModel.updateRecentTasks(listOf(DesktopTask(tasks)))
+ recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
desktopTaskListener?.onTasksVisibilityChanged(
context.virtualDisplay.display.displayId,
tasksToAdd,
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index c792783..002c988 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -877,7 +877,7 @@
val allTasks =
ArrayList<GroupTask>().apply {
if (!runningTasks.isEmpty()) {
- add(DesktopTask(ArrayList(runningTasks)))
+ add(DesktopTask(deskId = 0, ArrayList(runningTasks)))
}
addAll(recentTasks)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index 8b17958..ad9bbb9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -177,8 +177,8 @@
createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
- GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(
- tasks, Collections.emptySet() /* minimizedTaskIds */);
+ GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
+ 0 /* deskId */, tasks, Collections.emptySet() /* minimizedTaskIds */);
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -207,8 +207,8 @@
createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
- GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(
- tasks, Collections.emptySet() /* minimizedTaskIds */);
+ GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
+ 0 /* deskId */, tasks, Collections.emptySet() /* minimizedTaskIds */);
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
@@ -241,7 +241,8 @@
createRecentTaskInfo(5 /* taskId */, DEFAULT_DISPLAY));
Set<Integer> minimizedTaskIds =
Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
- GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(tasks, minimizedTaskIds);
+ GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
+ 0 /* deskId */, tasks, minimizedTaskIds);
when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
.thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
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..99a34ea 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -24,8 +24,11 @@
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;
+import static com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer.MIN_TIME_TO_LOG_ABANDON_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -33,9 +36,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 +52,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 +89,7 @@
private NavHandleLongPressInputConsumer mUnderTest;
private SandboxContext mContext;
private float mScreenWidth;
+ private long mDownTimeMs;
@Mock InputConsumer mDelegate;
@Mock InputMonitorCompat mInputMonitor;
@Mock RecentsAnimationDeviceState mDeviceState;
@@ -91,6 +98,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 +110,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 +139,23 @@
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));
+ sleep(MIN_TIME_TO_LOG_ABANDON_MS);
// 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,46 +165,54 @@
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
public void testLongPressTriggered() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
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
public void testLongPressTriggeredWithSlightVerticalMovement() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
- mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
- -(TOUCH_SLOP - 1)));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE, 1));
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
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
public void testLongPressTriggeredWithSlightHorizontalMovement() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
- mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
- mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE, mScreenWidth / 2f + 1, 0));
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
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
@@ -196,8 +225,7 @@
mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
// We have entered the second stage, so the normal timeout shouldn't trigger.
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
@@ -207,14 +235,15 @@
// After an extended time, the long press should trigger.
float extendedDurationMultiplier =
(DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
- SystemClock.sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
+ sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
* (extendedDurationMultiplier - 1))); // -1 because we already waited 1x
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
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);
}
@@ -228,13 +257,14 @@
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
// We have not entered the second stage, so the normal timeout should trigger.
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
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);
}
@@ -243,48 +273,47 @@
@Test
public void testLongPressAbortedByTouchUp() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(MIN_TIME_TO_LOG_ABANDON_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
// Wait past the long press timeout, to be extra sure it wouldn't have triggered.
- SystemClock.sleep(20);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
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
public void testLongPressAbortedByTouchCancel() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(MIN_TIME_TO_LOG_ABANDON_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_CANCEL));
// Wait past the long press timeout, to be extra sure it wouldn't have triggered.
- SystemClock.sleep(20);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
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
public void testLongPressAbortedByTouchSlopPassedVertically() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(MIN_TIME_TO_LOG_ABANDON_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
@@ -292,20 +321,20 @@
mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
-(TOUCH_SLOP + 1)));
// Wait past the long press timeout, to be extra sure it wouldn't have triggered.
- SystemClock.sleep(20);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
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
public void testLongPressAbortedByTouchSlopPassedHorizontally() {
mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(MIN_TIME_TO_LOG_ABANDON_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
@@ -313,13 +342,14 @@
mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
mScreenWidth / 2f - (TOUCH_SLOP + 1), 0));
// Wait past the long press timeout, to be extra sure it wouldn't have triggered.
- SystemClock.sleep(20);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
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
@@ -333,8 +363,7 @@
mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
-(TOUCH_SLOP - 1)));
// Normal duration shouldn't trigger.
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
@@ -345,15 +374,16 @@
// Wait past the extended long press timeout, to be sure it wouldn't have triggered.
float extendedDurationMultiplier =
(DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
- SystemClock.sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
+ sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
* (extendedDurationMultiplier - 1))); // -1 because we already waited 1x
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
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);
}
@@ -370,8 +400,7 @@
mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
// Normal duration shouldn't trigger.
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
@@ -382,15 +411,16 @@
// Wait past the extended long press timeout, to be sure it wouldn't have triggered.
float extendedDurationMultiplier =
(DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
- SystemClock.sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
+ sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
* (extendedDurationMultiplier - 1))); // -1 because we already waited 1x
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
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);
}
@@ -400,14 +430,16 @@
public void testTouchOutsideNavHandleIgnored() {
// Touch the far left side of the screen. (y=0 is top of navbar region, picked arbitrarily)
mUnderTest.onMotionEvent(generateMotionEvent(ACTION_DOWN, 0, 0));
- SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ sleep(DEFAULT_LPNH_TIMEOUT_MS);
// Should be ignored because the x position was not centered in the navbar region.
assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
assertFalse(mLongPressTriggered.get());
verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
@Test
@@ -422,6 +454,20 @@
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));
+ sleep(10);
+ mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
+ verifyNoMoreInteractions(mStatsLogManager);
+ verifyNoMoreInteractions(mStatsLogger);
+ verifyNoMoreInteractions(mStatsLatencyLogger);
}
private void initializeObjectUnderTest() {
@@ -437,6 +483,13 @@
mUnderTest = new NavHandleLongPressInputConsumer(mContext, mDelegate, mInputMonitor,
mDeviceState, mNavHandle, mGestureState);
mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler);
+ mUnderTest.setStatsLogManager(mStatsLogManager);
+ mDownTimeMs = 0;
+ }
+
+ private static void sleep(long sleepMs) {
+ SystemClock.sleep(sleepMs);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
/** Generate a motion event centered horizontally in the screen. */
@@ -449,8 +502,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/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
index e10afc4..40d5e02 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
@@ -22,7 +22,6 @@
import com.android.systemui.shared.recents.model.ThumbnailData
import kotlinx.coroutines.yield
import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
@@ -31,6 +30,8 @@
private val completionPrevented: MutableSet<Int> = mutableSetOf()
private val getThumbnailCalls = mutableMapOf<Int, Int>()
+ var highResEnabled = true
+
/** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */
override suspend fun getThumbnail(task: Task): ThumbnailData {
getThumbnailCalls[task.key.id] = (getThumbnailCalls[task.key.id] ?: 0) + 1
@@ -38,9 +39,10 @@
while (task.key.id in completionPrevented) {
yield()
}
- return mock<ThumbnailData>().also {
- whenever(it.thumbnail).thenReturn(taskIdToBitmap[task.key.id])
- }
+ return ThumbnailData(
+ thumbnail = taskIdToBitmap[task.key.id],
+ reducedResolution = !highResEnabled,
+ )
}
fun getNumberOfGetThumbnailCalls(taskId: Int): Int = getThumbnailCalls[taskId] ?: 0
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
index 41f6bfd..b91f8bd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
@@ -19,16 +19,19 @@
import android.content.ComponentName
import android.content.Intent
import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
import org.junit.Test
+import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
+@RunWith(AndroidJUnit4::class)
class TaskVisualsChangedDelegateTest {
private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
@@ -83,21 +86,21 @@
// Correct match
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
- expectedListener
+ expectedListener,
)
// 1 out of 2 match
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 2, pkg = PACKAGE_NAME, userId = 1),
- listener
+ listener,
)
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 3, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 2),
- listener
+ listener,
)
// 0 out of 2 match
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 4, pkg = PACKAGE_NAME, userId = 2),
- listener
+ listener,
)
systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
@@ -112,11 +115,11 @@
val newListener = mock<TaskIconChangedCallback>()
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
- replacedListener
+ replacedListener,
)
systemUnderTest.registerTaskIconChangedCallback(
createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
- newListener
+ newListener,
)
systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
@@ -132,11 +135,11 @@
val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 1),
- expectedListener
+ expectedListener,
)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 2),
- additionalListener
+ additionalListener,
)
systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData)
@@ -146,22 +149,41 @@
}
@Test
- fun onHighResLoadingStateChanged_notifiesAllListeners() {
+ fun onHighResLoadingStateChanged_toEnabled_notifiesAllListeners() {
val expectedListener = mock<TaskThumbnailChangedCallback>()
val additionalListener = mock<TaskThumbnailChangedCallback>()
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 1),
- expectedListener
+ expectedListener,
)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 2),
- additionalListener
+ additionalListener,
)
systemUnderTest.onHighResLoadingStateChanged(true)
- verify(expectedListener).onHighResLoadingStateChanged()
- verify(additionalListener).onHighResLoadingStateChanged()
+ verify(expectedListener).onHighResLoadingStateChanged(true)
+ verify(additionalListener).onHighResLoadingStateChanged(true)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_toDisabled_notifiesAllListeners() {
+ val expectedListener = mock<TaskThumbnailChangedCallback>()
+ val additionalListener = mock<TaskThumbnailChangedCallback>()
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 1),
+ expectedListener,
+ )
+ systemUnderTest.registerTaskThumbnailChangedCallback(
+ createTaskKey(id = 2),
+ additionalListener,
+ )
+
+ systemUnderTest.onHighResLoadingStateChanged(false)
+
+ verify(expectedListener).onHighResLoadingStateChanged(false)
+ verify(additionalListener).onHighResLoadingStateChanged(false)
}
@Test
@@ -171,7 +193,7 @@
val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
systemUnderTest.registerTaskThumbnailChangedCallback(
createTaskKey(id = 1),
- replacedListener1
+ replacedListener1,
)
systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), newListener1)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index 823f808..6790567 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -64,7 +64,7 @@
/* snapPosition = */ SNAP_TO_2_50_50,
),
),
- DesktopTask(tasks.subList(3, 6)),
+ DesktopTask(deskId = 0, tasks.subList(3, 6)),
)
private val recentsModel = FakeRecentTasksDataSource()
private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
@@ -326,8 +326,9 @@
}
@Test
- fun onHighResLoadingStateChanged_setsNewThumbnailDataOnTask() =
+ fun onHighResLoadingStateChanged_highResReplacesLowResThumbnail() =
testScope.runTest {
+ taskThumbnailDataSource.highResEnabled = false
recentsModel.seedTasks(defaultTaskList)
systemUnderTest.getAllTaskData(forceRefresh = true)
@@ -337,16 +338,77 @@
val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
val taskDataFlow = systemUnderTest.getTaskDataById(1)
- val task1ThumbnailValues = mutableListOf<Bitmap?>()
+ val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
testScope.backgroundScope.launch {
- taskDataFlow.map { it?.thumbnail?.thumbnail }.toList(task1ThumbnailValues)
+ taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
}
taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+ taskThumbnailDataSource.highResEnabled = true
taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
- assertThat(task1ThumbnailValues.first()).isEqualTo(expectedPreviousBitmap)
- assertThat(task1ThumbnailValues.last()).isEqualTo(expectedBitmap)
+ val firstThumbnailValue = task1ThumbnailValues.first()!!
+ assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(firstThumbnailValue.reducedResolution).isTrue()
+
+ val lastThumbnailValue = task1ThumbnailValues.last()!!
+ assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedBitmap)
+ assertThat(lastThumbnailValue.reducedResolution).isFalse()
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_invisibleTaskIgnored() =
+ testScope.runTest {
+ taskThumbnailDataSource.highResEnabled = false
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(setOf(1))
+
+ val invisibleTaskId = 2
+ val taskDataFlow = systemUnderTest.getTaskDataById(invisibleTaskId)
+
+ val task2ThumbnailValues = mutableListOf<ThumbnailData?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail }.toList(task2ThumbnailValues)
+ }
+
+ taskThumbnailDataSource.highResEnabled = true
+ taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
+
+ assertThat(task2ThumbnailValues.filterNotNull()).isEmpty()
+ assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(2)).isEqualTo(0)
+ }
+
+ @Test
+ fun onHighResLoadingStateChanged_lowResDoesNotReplaceHighResThumbnail() =
+ testScope.runTest {
+ taskThumbnailDataSource.highResEnabled = true
+ recentsModel.seedTasks(defaultTaskList)
+ systemUnderTest.getAllTaskData(forceRefresh = true)
+
+ systemUnderTest.setVisibleTasks(setOf(1))
+
+ val expectedBitmap = mock<Bitmap>()
+ val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
+ val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+ val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
+ testScope.backgroundScope.launch {
+ taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
+ }
+
+ taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+ taskThumbnailDataSource.highResEnabled = false
+ taskVisualsChangedDelegate.onHighResLoadingStateChanged(false)
+
+ val firstThumbnailValue = task1ThumbnailValues.first()!!
+ assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(firstThumbnailValue.reducedResolution).isFalse()
+
+ val lastThumbnailValue = task1ThumbnailValues.last()!!
+ assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+ assertThat(lastThumbnailValue.reducedResolution).isFalse()
}
@Test
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/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
index 7aed579..6fbf482 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
@@ -29,35 +29,42 @@
@Test
fun testDesktopTask_sameInstance_isEqual() {
- val task = DesktopTask(createTasks(1))
+ val task = DesktopTask(deskId = 0, createTasks(1))
assertThat(task).isEqualTo(task)
}
@Test
fun testDesktopTask_identicalConstructor_isEqual() {
- val task1 = DesktopTask(createTasks(1))
- val task2 = DesktopTask(createTasks(1))
+ val task1 = DesktopTask(deskId = 0, createTasks(1))
+ val task2 = DesktopTask(deskId = 0, createTasks(1))
assertThat(task1).isEqualTo(task2)
}
@Test
fun testDesktopTask_copy_isEqual() {
- val task1 = DesktopTask(createTasks(1))
+ val task1 = DesktopTask(deskId = 0, createTasks(1))
val task2 = task1.copy()
assertThat(task1).isEqualTo(task2)
}
@Test
- fun testDesktopTask_differentId_isNotEqual() {
- val task1 = DesktopTask(createTasks(1))
- val task2 = DesktopTask(createTasks(2))
+ fun testDesktopTask_differentDeskIds_isNotEqual() {
+ val task1 = DesktopTask(deskId = 0, createTasks(1))
+ val task2 = DesktopTask(deskId = 1, createTasks(1))
+ assertThat(task1).isNotEqualTo(task2)
+ }
+
+ @Test
+ fun testDesktopTask_differentTaskIds_isNotEqual() {
+ val task1 = DesktopTask(deskId = 0, createTasks(1))
+ val task2 = DesktopTask(deskId = 0, createTasks(2))
assertThat(task1).isNotEqualTo(task2)
}
@Test
fun testDesktopTask_differentLength_isNotEqual() {
- val task1 = DesktopTask(createTasks(1))
- val task2 = DesktopTask(createTasks(1, 2))
+ val task1 = DesktopTask(deskId = 0, createTasks(1))
+ val task2 = DesktopTask(deskId = 0, createTasks(1, 2))
assertThat(task1).isNotEqualTo(task2)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index fa043b9..67fc62f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -98,7 +98,7 @@
@Test
fun testGroupTask_differentType_isNotEqual() {
val task1 = SingleTask(createTask(1))
- val task2 = DesktopTask(listOf(createTask(1)))
+ val task2 = DesktopTask(deskId = 0, listOf(createTask(1)))
assertThat(task1).isNotEqualTo(task2)
}
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/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 76aab39..a523e02 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display.DEFAULT_DISPLAY
import androidx.test.platform.app.InstrumentationRegistry
@@ -106,6 +107,7 @@
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun createDesktopTaskShortcutFactory_transparentTask() {
val baseComponent = ComponentName("", /* class */ "")
val taskKey =
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 818841a..2db94f6 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
@@ -113,6 +114,7 @@
Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
)
+ @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
fun createExternalDisplayTaskShortcut_transparentTask() {
val baseComponent = ComponentName("", /* class */ "")
val taskKey =
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index aaaa0e4..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;
@@ -50,6 +51,7 @@
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
@@ -542,6 +544,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // TODO(b/396447643): Remove screen record.
public void testDismissCancel() throws Exception {
startTestAppsWithCheck();
Overview overview = mLauncher.goHome().switchToOverview();
@@ -579,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/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
index cfec2b1..1e7fe43 100644
--- a/res/drawable/private_space_install_app_icon.xml
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -13,19 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="60dp"
- android:height="60dp"
- android:viewportWidth="60"
- android:viewportHeight="60">
- <group>
- <clip-path
- android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
- <path
- android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
- android:fillColor="@color/material_color_surface_container_lowest" />
- <path
- android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
- android:fillColor="@color/material_color_on_surface" />
- </group>
-</vector>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/material_color_surface_container_lowest"/>
+ <foreground android:drawable="@drawable/private_space_install_app_icon_foreground" />
+</adaptive-icon>
diff --git a/res/drawable/private_space_install_app_icon_foreground.xml b/res/drawable/private_space_install_app_icon_foreground.xml
new file mode 100644
index 0000000..d55abe7
--- /dev/null
+++ b/res/drawable/private_space_install_app_icon_foreground.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="60dp"
+ android:height="60dp"
+ android:viewportWidth="60"
+ android:viewportHeight="60">
+ <path
+ android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
+ android:fillColor="@color/material_color_on_surface" />
+</vector>
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/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 870e6d6..f189549 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -313,7 +313,7 @@
context,
gridName,
displayInfo,
- RestoreDbTask.isPending(mPrefs),
+ (RestoreDbTask.isPending(mPrefs) && !Flags.oneGridSpecs()),
mPrefs.get(FIXED_LANDSCAPE_MODE)
);
@@ -506,6 +506,7 @@
* Updates the current grid, this triggers a new IDP, reloads the database and triggers a grid
* migration.
*/
+ @VisibleForTesting
public void setCurrentGrid(Context context, String newGridName) {
mPrefs.put(GRID_NAME, newGridName);
MAIN_EXECUTOR.execute(() -> {
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/PagedView.java b/src/com/android/launcher3/PagedView.java
index 5072e37..d6abb56 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.app.animation.Interpolators.SCROLL;
+import static com.android.launcher3.RemoveAnimationSettingsTracker.WINDOW_ANIMATION_SCALE_URI;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE;
@@ -33,7 +34,6 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
@@ -1756,8 +1756,8 @@
}
if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) {
- duration *= Settings.Global.getFloat(getContext().getContentResolver(),
- Settings.Global.WINDOW_ANIMATION_SCALE, 1);
+ duration *= RemoveAnimationSettingsTracker.INSTANCE.get(getContext()).getValue(
+ WINDOW_ANIMATION_SCALE_URI);
}
whichPage = validateNewPage(whichPage);
diff --git a/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
new file mode 100644
index 0000000..dbc04f1
--- /dev/null
+++ b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ * 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.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.provider.Settings.Global.ANIMATOR_DURATION_SCALE
+import android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE
+import android.provider.Settings.Global.WINDOW_ANIMATION_SCALE
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/** Tracker Class for when user turns on/off remove animation setting. */
+@LauncherAppSingleton
+class RemoveAnimationSettingsTracker
+@Inject
+constructor(@ApplicationContext val context: Context, tracker: DaggerSingletonTracker) :
+ ContentObserver(Handler(Looper.getMainLooper())) {
+
+ private val contentResolver = context.contentResolver
+
+ /** Caches the last seen value for registered keys. */
+ private val cache: MutableMap<Uri, Float> = ConcurrentHashMap()
+
+ init {
+ UI_HELPER_EXECUTOR.execute {
+ contentResolver.registerContentObserver(WINDOW_ANIMATION_SCALE_URI, false, this)
+ contentResolver.registerContentObserver(TRANSITION_ANIMATION_SCALE_URI, false, this)
+ contentResolver.registerContentObserver(ANIMATOR_DURATION_SCALE_URI, false, this)
+ }
+
+ tracker.addCloseable {
+ UI_HELPER_EXECUTOR.execute { contentResolver.unregisterContentObserver(this) }
+ }
+ }
+
+ /**
+ * Returns the value for this classes key from the cache. If not in cache, will call
+ * [updateValue] to fetch.
+ */
+ fun getValue(uri: Uri): Float {
+ return getValue(uri, 1f)
+ }
+
+ /**
+ * Returns the value for this classes key from the cache. If not in cache, will call
+ * [getValueFromSettingsGlobal] to fetch.
+ */
+ private fun getValue(uri: Uri, defaultValue: Float): Float {
+ return cache.computeIfAbsent(uri) { getValueFromSettingsGlobal(uri, defaultValue) }
+ }
+
+ /** Returns if user has opted into having no animation on their device. */
+ fun isRemoveAnimationEnabled(): Boolean {
+ return getValue(WINDOW_ANIMATION_SCALE_URI) == 0f &&
+ getValue(TRANSITION_ANIMATION_SCALE_URI) == 0f &&
+ getValue(ANIMATOR_DURATION_SCALE_URI) == 0f
+ }
+
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ if (uri == null) return
+ updateValue(uri)
+ }
+
+ private fun getValueFromSettingsGlobal(uri: Uri, defaultValue: Float = 1f): Float {
+ return Settings.Global.getFloat(contentResolver, uri.lastPathSegment, defaultValue)
+ }
+
+ private fun updateValue(uri: Uri, defaultValue: Float = 1f) {
+ val newValue = getValueFromSettingsGlobal(uri, defaultValue)
+ cache[uri] = newValue
+ }
+
+ companion object {
+ @JvmField
+ val INSTANCE =
+ DaggerSingletonObject(LauncherAppComponent::getRemoveAnimationSettingsTracker)
+ @JvmField
+ val WINDOW_ANIMATION_SCALE_URI: Uri = Settings.Global.getUriFor(WINDOW_ANIMATION_SCALE)
+ @JvmField
+ val TRANSITION_ANIMATION_SCALE_URI: Uri =
+ Settings.Global.getUriFor(TRANSITION_ANIMATION_SCALE)
+ @JvmField
+ val ANIMATOR_DURATION_SCALE_URI: Uri = Settings.Global.getUriFor(ANIMATOR_DURATION_SCALE)
+ }
+}
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 31d0da0..150761f 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,8 +18,13 @@
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.RemoveAnimationSettingsTracker;
+import com.android.launcher3.graphics.GridCustomizationsProxy;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.LauncherIcons.IconPool;
import com.android.launcher3.model.ItemInstallQueue;
@@ -43,6 +48,8 @@
import dagger.BindsInstance;
+import javax.inject.Named;
+
/**
* Launcher base component for Dagger injection.
*
@@ -74,10 +81,15 @@
LockedUserState getLockedUserState();
InvariantDeviceProfile getIDP();
IconPool getIconPool();
+ RemoveAnimationSettingsTracker getRemoveAnimationSettingsTracker();
+ 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/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
similarity index 85%
rename from src/com/android/launcher3/graphics/GridCustomizationsProvider.java
rename to src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 12c65c7..40863c4 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -22,14 +22,11 @@
import static java.util.Objects.requireNonNullElse;
-import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder.DeathRecipient;
@@ -45,9 +42,13 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.shapes.IconShapeModel;
import com.android.launcher3.shapes.ShapesProvider;
+import com.android.launcher3.util.ContentProviderProxy.ProxyProvider;
+import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
@@ -62,6 +63,8 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
+import javax.inject.Inject;
+
/**
* Exposes various launcher grid options and allows the caller to change them.
* APIs:
@@ -76,7 +79,7 @@
* rows: number of rows in the grid
* cols: number of columns in the grid
* preview_count: number of previews available for this grid option. The preview uri
- * looks like /preview/<grid-name>/<preview index starting with 0>
+ * looks like /preview/[grid-name]/[preview index starting with 0]
* is_default: true if this grid option is currently set to the system
*
* /get_preview: Open a file stream for the grid preview
@@ -85,7 +88,8 @@
* shape_key: key of the shape to apply
* name: key of the grid to apply
*/
-public class GridCustomizationsProvider extends ContentProvider {
+@LauncherAppSingleton
+public class GridCustomizationsProxy implements ProxyProvider {
private static final String TAG = "GridCustomizationsProvider";
@@ -132,17 +136,31 @@
private final Set<PreviewLifecycleObserver> mActivePreviews =
Collections.newSetFromMap(new ConcurrentHashMap<>());
- @Override
- public boolean onCreate() {
- return true;
+ private final Context mContext;
+ private final ThemeManager mThemeManager;
+ private final LauncherPrefs mPrefs;
+ private final InvariantDeviceProfile mIdp;
+
+ @Inject
+ GridCustomizationsProxy(
+ @ApplicationContext Context context,
+ ThemeManager themeManager,
+ LauncherPrefs prefs,
+ InvariantDeviceProfile idp,
+ DaggerSingletonTracker lifeCycle
+ ) {
+ mContext = context;
+ mThemeManager = themeManager;
+ mPrefs = prefs;
+ mIdp = idp;
+ lifeCycle.addCloseable(() -> mActivePreviews.forEach(PreviewLifecycleObserver::binderDied));
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
- Context context = getContext();
String path = uri.getPath();
- if (context == null || path == null) {
+ if (path == null) {
return null;
}
@@ -151,8 +169,7 @@
if (Flags.newCustomizationPickerUi()) {
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
- String currentShapePath =
- ThemeManager.INSTANCE.get(context).getIconState().getIconMask();
+ String currentShapePath = mThemeManager.getIconState().getIconMask();
Optional<IconShapeModel> selectedShape = ShapesProvider.INSTANCE.getIconShapes()
.values()
.stream()
@@ -180,8 +197,7 @@
MatrixCursor cursor = new MatrixCursor(new String[]{
KEY_NAME, KEY_GRID_TITLE, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT,
KEY_IS_DEFAULT, KEY_GRID_ICON_ID});
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
- List<GridOption> gridOptionList = idp.parseAllGridOptions(getContext());
+ List<GridOption> gridOptionList = mIdp.parseAllGridOptions(mContext);
if (com.android.launcher3.Flags.oneGridSpecs()) {
gridOptionList.sort(Comparator
.comparingInt((GridOption option) -> option.numColumns)
@@ -194,8 +210,8 @@
.add(KEY_ROWS, gridOption.numRows)
.add(KEY_COLS, gridOption.numColumns)
.add(KEY_PREVIEW_COUNT, 1)
- .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
- && idp.numRows == gridOption.numRows)
+ .add(KEY_IS_DEFAULT, mIdp.numColumns == gridOption.numColumns
+ && mIdp.numRows == gridOption.numRows)
.add(KEY_GRID_ICON_ID, gridOption.gridIconId);
}
return cursor;
@@ -203,8 +219,7 @@
case GET_ICON_THEMED:
case ICON_THEMED: {
MatrixCursor cursor = new MatrixCursor(new String[]{BOOLEAN_VALUE});
- cursor.newRow().add(BOOLEAN_VALUE,
- ThemeManager.INSTANCE.get(getContext()).isMonoThemeEnabled() ? 1 : 0);
+ cursor.newRow().add(BOOLEAN_VALUE, mThemeManager.isMonoThemeEnabled() ? 1 : 0);
return cursor;
}
default:
@@ -213,38 +228,21 @@
}
@Override
- public String getType(Uri uri) {
- return "vnd.android.cursor.dir/launcher_grid";
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues initialValues) {
- return null;
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- return 0;
- }
-
- @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
String path = uri.getPath();
- Context context = getContext();
- if (path == null || context == null) {
+ if (path == null) {
return 0;
}
switch (path) {
case KEY_DEFAULT_GRID: {
if (Flags.newCustomizationPickerUi()) {
- LauncherPrefs.INSTANCE.get(context).put(PREF_ICON_SHAPE,
+ mPrefs.put(PREF_ICON_SHAPE,
requireNonNullElse(values.getAsString(KEY_SHAPE_KEY), ""));
}
String gridName = values.getAsString(KEY_NAME);
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
// Verify that this is a valid grid option
GridOption match = null;
- for (GridOption option : idp.parseAllGridOptions(context)) {
+ for (GridOption option : mIdp.parseAllGridOptions(mContext)) {
String name = option.name;
if (name != null && name.equals(gridName)) {
match = option;
@@ -255,23 +253,22 @@
return 0;
}
- idp.setCurrentGrid(context, gridName);
+ mIdp.setCurrentGrid(mContext, gridName);
if (Flags.newCustomizationPickerUi()) {
try {
// Wait for device profile to be fully reloaded and applied to the launcher
- loadModelSync(context);
+ loadModelSync(mContext);
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Fail to load model", e);
}
}
- context.getContentResolver().notifyChange(uri, null);
+ mContext.getContentResolver().notifyChange(uri, null);
return 1;
}
case ICON_THEMED:
case SET_ICON_THEMED: {
- ThemeManager.INSTANCE.get(context)
- .setMonoThemeEnabled(values.getAsBoolean(BOOLEAN_VALUE));
- context.getContentResolver().notifyChange(uri, null);
+ mThemeManager.setMonoThemeEnabled(values.getAsBoolean(BOOLEAN_VALUE));
+ mContext.getContentResolver().notifyChange(uri, null);
return 1;
}
default:
@@ -298,17 +295,6 @@
@Override
public Bundle call(@NonNull String method, String arg, Bundle extras) {
- Context context = getContext();
- if (context == null) {
- return null;
- }
-
- if (context.checkPermission("android.permission.BIND_WALLPAPER",
- Binder.getCallingPid(), Binder.getCallingUid())
- != PackageManager.PERMISSION_GRANTED) {
- return null;
- }
-
if (METHOD_GET_PREVIEW.equals(method)) {
return getPreview(extras);
} else {
@@ -317,14 +303,10 @@
}
private synchronized Bundle getPreview(Bundle request) {
- Context context = getContext();
- if (context == null) {
- return null;
- }
RunnableList lifeCycleTracker = new RunnableList();
try {
PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(
- getContext(), lifeCycleTracker, request);
+ mContext, lifeCycleTracker, request);
PreviewLifecycleObserver observer =
new PreviewLifecycleObserver(lifeCycleTracker, renderer);
@@ -387,7 +369,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/LauncherCustomizationProvider.kt b/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt
new file mode 100644
index 0000000..c949e2e
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.graphics
+
+import android.content.Context
+import android.net.Uri
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
+import com.android.launcher3.util.ContentProviderProxy
+
+/** Provider for various Launcher customizations exposed via a ContentProvider API */
+class LauncherCustomizationProvider : ContentProviderProxy() {
+
+ override fun getProxy(ctx: Context): ProxyProvider? = ctx.appComponent.gridCustomizationsProxy
+
+ override fun getType(uri: Uri) = "vnd.android.cursor.dir/launcher_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 f0d670e..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,11 +119,12 @@
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);
}
- mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
+ mHideQsb = bundle.getBoolean(GridCustomizationsProxy.KEY_HIDE_BOTTOM_ROW);
mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
mWidth = bundle.getInt(KEY_VIEW_WIDTH);
@@ -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/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
index 225e12f..50dd146 100644
--- a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -30,6 +30,7 @@
import com.android.launcher3.icons.cache.BaseIconCache
import com.android.launcher3.icons.cache.CachingLogic
import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ApiWrapper
import com.android.launcher3.util.ApplicationInfoWrapper
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.Themes
@@ -114,7 +115,15 @@
d,
IconOptions()
.setExtractedColor(Themes.getColorAccent(context))
- .setSourceHint(getSourceHint(info, cache)),
+ .setSourceHint(
+ getSourceHint(info, cache)
+ .copy(
+ isFileDrawable =
+ ApiWrapper.INSTANCE[context].isFileDrawable(
+ info.shortcutInfo
+ )
+ )
+ ),
)
} ?: BitmapInfo.LOW_RES_INFO
}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 9f99e8f..119a6b1 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LooperExecutor.CALLER_ICON_CACHE;
import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
import static java.util.stream.Collectors.groupingBy;
@@ -35,7 +36,6 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.os.Looper;
-import android.os.Process;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -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();
}
/**
@@ -182,7 +204,7 @@
Runnable endRunnable;
if (Looper.myLooper() == Looper.getMainLooper()) {
if (mPendingIconRequestCount <= 0) {
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
+ MODEL_EXECUTOR.elevatePriority(CALLER_ICON_CACHE);
}
mPendingIconRequestCount++;
endRunnable = this::onIconRequestEnd;
@@ -199,7 +221,7 @@
private void onIconRequestEnd() {
mPendingIconRequestCount--;
if (mPendingIconRequestCount <= 0) {
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ MODEL_EXECUTOR.restorePriority(CALLER_ICON_CACHE);
}
}
@@ -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..a2ca6b6 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -28,7 +28,6 @@
import static java.util.Collections.emptyList;
-import android.os.Process;
import android.os.Trace;
import android.util.Log;
import android.util.Pair;
@@ -167,7 +166,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();
@@ -352,14 +351,8 @@
onCompleteSignal.executeAllAndDestroy();
}
- executeCallbacksTask(
- c -> {
- if (!enableWorkspaceInflation()) {
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- }
- c.onInitialBindComplete(currentScreenIds, pendingTasks, onCompleteSignal,
- workspaceItemCount, isBindSync);
- }, mUiExecutor);
+ executeCallbacksTask(c -> c.onInitialBindComplete(currentScreenIds, pendingTasks,
+ onCompleteSignal, workspaceItemCount, isBindSync), mUiExecutor);
}
private void setupPendingBind(
@@ -369,12 +362,8 @@
executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor);
executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
- pendingExecutor.execute(
- () -> {
- MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
- ItemInstallQueue.INSTANCE.get(mApp.getContext())
- .resumeModelPush(FLAG_LOADER_RUNNING);
- });
+ pendingExecutor.execute(() -> ItemInstallQueue.INSTANCE.get(mApp.getContext())
+ .resumeModelPush(FLAG_LOADER_RUNNING));
}
/**
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index c1ee69b..d44b289 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -36,6 +36,7 @@
import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LooperExecutor.CALLER_LOADER_TASK;
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.appwidget.AppWidgetProviderInfo;
@@ -251,6 +252,7 @@
}
TraceHelper.INSTANCE.beginSection(TAG);
+ MODEL_EXECUTOR.elevatePriority(CALLER_LOADER_TASK);
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
mIsRestoreFromBackup =
LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE);
@@ -403,6 +405,7 @@
memoryLogger.printLogs();
throw e;
}
+ MODEL_EXECUTOR.restorePriority(CALLER_LOADER_TASK);
TraceHelper.INSTANCE.endSection();
}
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/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d1eceb9..3cdb250 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -239,25 +239,31 @@
boolean isTargetValid = !cn.getClassName().equals(
IconCache.EMPTY_CLASS_NAME);
if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
+ int requestQuery = ShortcutRequest.PINNED;
+ if (Flags.restoreArchivedShortcuts()) {
+ // Avoid race condition where shortcut service has no record of
+ // unarchived shortcut being pinned after restore.
+ // Launcher should be source-of-truth for if shortcut is pinned.
+ requestQuery = ShortcutRequest.ALL;
+ }
List<ShortcutInfo> shortcut =
new ShortcutRequest(context, mUser)
.forPackage(cn.getPackageName(),
itemInfo.getDeepShortcutId())
- .query(ShortcutRequest.PINNED);
- if (shortcut.isEmpty()
- && !(Flags.restoreArchivedShortcuts()
- && !itemInfo.isArchived())
- ) {
+ .query(requestQuery);
+ if (shortcut.isEmpty()) {
isTargetValid = false;
if (DEBUG) {
- Log.d(TAG, "Pinned Shortcut not found for updated"
- + " package=" + itemInfo.getTargetPackage());
- }
- } else if (!shortcut.isEmpty()) {
- if (DEBUG) {
- Log.d(TAG, "Found pinned shortcut for updated"
+ Log.d(TAG, "Shortcut not found for updated"
+ " package=" + itemInfo.getTargetPackage()
- + ", isTargetValid=" + isTargetValid);
+ + ", isArchived=" + itemInfo.isArchived());
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Found shortcut for updated"
+ + " package=" + itemInfo.getTargetPackage()
+ + ", isTargetValid=" + isTargetValid
+ + ", isArchived=" + itemInfo.isArchived());
}
itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
infoUpdated = true;
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 34c9117..23941bb 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -51,6 +51,7 @@
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.Flags;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherFiles;
@@ -130,9 +131,11 @@
removeOldDBs(context, oldPhoneFileName);
// The idp before this contains data about the old phone, after this it becomes the idp
// of the current phone.
- FileLog.d(TAG, "Resetting IDP to default for restore dest device");
- idp.reset(context);
- trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+ if (!Flags.oneGridSpecs()) {
+ FileLog.d(TAG, "Resetting IDP to default for restore dest device");
+ idp.reset(context);
+ trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+ }
}
@@ -535,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/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
index 463c816..5427c89 100644
--- a/src/com/android/launcher3/shapes/ShapesProvider.kt
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -193,15 +193,6 @@
"M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z",
folderPathString = folderShapes["arch"]!!,
),
- "sunny" to
- IconShapeModel(
- key = "sunny",
- title = "sunny",
- pathString =
- "M42.846 4.873C46.084 -.531 53.916 -.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C -.531 53.916 -.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
- folderPathString = folderShapes["clover"]!!,
- iconScale = 72f / 92f,
- ),
)
} else {
mapOf(
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index 17b472a..4b592e7 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -16,61 +16,40 @@
package com.android.launcher3.testing;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
+import android.content.Context;
import android.os.Bundle;
import android.util.Log;
-import com.android.launcher3.Utilities;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
-public class TestInformationProvider extends ContentProvider {
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.ContentProviderProxy;
+
+public class TestInformationProvider extends ContentProviderProxy {
private static final String TAG = "TestInformationProvider";
+ @Nullable
@Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
- return 0;
- }
-
- @Override
- public int delete(Uri uri, String s, String[] strings) {
- return 0;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues contentValues) {
- return null;
- }
-
- @Override
- public String getType(Uri uri) {
- return null;
- }
-
- @Override
- public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
- return null;
- }
-
- @Override
- public Bundle call(String method, String arg, Bundle extras) {
+ public ProxyProvider getProxy(@NonNull Context context) {
if (Utilities.isRunningInTestHarness()) {
- TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
- handler.init(getContext());
+ return new ProxyProvider() {
+ @Nullable
+ @Override
+ public Bundle call(@NonNull String method, @Nullable String arg,
+ @Nullable Bundle extras) {
+ TestInformationHandler handler = TestInformationHandler.newInstance(context);
+ handler.init(context);
- Bundle response = handler.call(method, arg, extras);
- if (response == null) {
- Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
- + handler.getClass().getSimpleName());
- }
- return response;
+ Bundle response = handler.call(method, arg, extras);
+ if (response == null) {
+ Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
+ + handler.getClass().getSimpleName());
+ }
+ return response;
+ }
+ };
}
return null;
}
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 107bcc1..2cc4909 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -207,6 +207,11 @@
}
config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
+ if (launcher.getDeviceProfile().isPhone) {
+ config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
+ config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT);
+ config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+ }
} else {
if (config.isUserControlled()) {
config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
@@ -248,6 +253,11 @@
}
config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
+ if (launcher.getDeviceProfile().isPhone) {
+ config.setInterpolator(ANIM_WORKSPACE_FADE, FINAL_FRAME);
+ config.setInterpolator(ANIM_HOTSEAT_FADE, FINAL_FRAME);
+ config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+ }
} else {
config.setInterpolator(ANIM_DEPTH,
config.isUserControlled() ? BLUR_MANUAL : BLUR_ATOMIC);
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 56337b0..0510d59 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -216,6 +216,13 @@
return 0;
}
+ /**
+ * Checks if the shortcut is using an icon with file or URI source
+ */
+ public boolean isFileDrawable(@NonNull ShortcutInfo shortcutInfo) {
+ return false;
+ }
+
private static class NoopDrawable extends ColorDrawable {
@Override
public int getIntrinsicHeight() {
diff --git a/src/com/android/launcher3/util/ContentProviderProxy.kt b/src/com/android/launcher3/util/ContentProviderProxy.kt
new file mode 100644
index 0000000..db693db
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentProviderProxy.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+
+/** Wrapper around [ContentProvider] which allows delegating all calls to an interface */
+abstract class ContentProviderProxy : ContentProvider() {
+
+ override fun onCreate() = true
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
+ checkGetProxy()?.delete(uri, selection, selectionArgs) ?: 0
+
+ /** Do not route this call through proxy as it doesn't generally require initializing objects */
+ override fun getType(uri: Uri): String? = null
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? =
+ checkGetProxy()?.insert(uri, values)
+
+ override fun query(
+ uri: Uri,
+ projection: Array<out String>?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ sortOrder: String?,
+ ): Cursor? = checkGetProxy()?.query(uri, projection, selection, selectionArgs, sortOrder)
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int = checkGetProxy()?.update(uri, values, selection, selectionArgs) ?: 0
+
+ override fun call(method: String, arg: String?, extras: Bundle?): Bundle? =
+ checkGetProxy()?.call(method, arg, extras)
+
+ private fun checkGetProxy(): ProxyProvider? = context?.let { getProxy(it) }
+
+ abstract fun getProxy(ctx: Context): ProxyProvider?
+
+ /** Interface for handling the actual content provider calls */
+ interface ProxyProvider {
+
+ fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
+
+ fun insert(uri: Uri, values: ContentValues?): Uri? = null
+
+ fun query(
+ uri: Uri,
+ projection: Array<out String>?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ sortOrder: String?,
+ ): Cursor? = null
+
+ fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<out String>?,
+ ): Int = 0
+
+ fun call(method: String, arg: String?, extras: Bundle?): Bundle? = null
+ }
+}
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..6ff528d
--- /dev/null
+++ b/src/com/android/launcher3/util/LooperExecutor.kt
@@ -0,0 +1,118 @@
+/*
+ * 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 android.os.Process.THREAD_PRIORITY_FOREGROUND
+import androidx.annotation.IntDef
+import java.util.concurrent.AbstractExecutorService
+import java.util.concurrent.TimeUnit
+import kotlin.annotation.AnnotationRetention.SOURCE
+
+/** 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
+
+ @ElevationCaller private var elevationFlags: Int = 0
+
+ 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()
+ }
+
+ /**
+ * Increases the priority of the thread for the [caller]. Multiple calls with same caller are
+ * ignored. The priority is reset once wall callers have restored priority
+ */
+ fun elevatePriority(@ElevationCaller caller: Int) {
+ val wasElevated = elevationFlags != 0
+ elevationFlags = elevationFlags.or(caller)
+ if (elevationFlags != 0 && !wasElevated)
+ Process.setThreadPriority(
+ (thread as HandlerThread).threadId,
+ THREAD_PRIORITY_FOREGROUND,
+ )
+ }
+
+ /** Restores to default priority if it was previously elevated */
+ fun restorePriority(@ElevationCaller caller: Int) {
+ val wasElevated = elevationFlags != 0
+ elevationFlags = elevationFlags.and(caller.inv())
+ if (elevationFlags == 0 && wasElevated)
+ Process.setThreadPriority((thread as HandlerThread).threadId, defaultPriority)
+ }
+
+ @Retention(SOURCE)
+ @IntDef(value = [CALLER_LOADER_TASK, CALLER_ICON_CACHE], flag = true)
+ annotation class ElevationCaller
+
+ 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
+
+ const val CALLER_LOADER_TASK = 1 shl 0
+ const val CALLER_ICON_CACHE = 1 shl 1
+ }
+}
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/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 647d170..11f0bc2 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -509,7 +509,17 @@
* @param isInDesktopModeAndNotInOverview True if a desktop is currently active on the given
* display, and Overview is currently inactive.
*/
- void onIsInDesktopModeChanged(int displayId, boolean isInDesktopModeAndNotInOverview);
+ default void onIsInDesktopModeChanged(int displayId,
+ boolean isInDesktopModeAndNotInOverview) {
+ }
+
+ /**
+ * Called whenever the conditions that allow the creation of desks change.
+ *
+ * @param canCreateDesks whether it is possible to create new desks.
+ */
+ default void onCanCreateDesksChanged(boolean canCreateDesks) {
+ }
}
}
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/shapes/ShapesProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
index 508c9a4..49d305b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
@@ -63,15 +63,6 @@
@Test
@EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
- fun `verify valid path sunny`() {
- ShapesProvider.iconShapes["sunny"]?.apply {
- GenericPathShape(pathString)
- PathParser.createPathFromPathData(pathString)
- }
- }
-
- @Test
- @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
fun `verify valid path circle`() {
ShapesProvider.iconShapes["circle"]?.apply {
GenericPathShape(pathString)
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/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index a123170..38970fe 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -23,7 +23,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
@@ -37,6 +36,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.allapps.AllAppsRecyclerView;
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
+import com.android.launcher3.dagger.LauncherComponentProvider;
import com.android.launcher3.icons.mono.ThemedIconDrawable;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.util.BaseLauncherActivityTest;
@@ -139,7 +139,7 @@
return icon;
}
- private void setThemeEnabled(boolean isEnabled) throws Exception {
+ private void setThemeEnabled(boolean isEnabled) {
Uri uri = new Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(targetContext().getPackageName() + ".grid_control")
@@ -147,11 +147,10 @@
.build();
ContentValues values = new ContentValues();
values.put("boolean_value", isEnabled);
- try (ContentProviderClient client = targetContext().getContentResolver()
- .acquireContentProviderClient(uri)) {
- int result = client.update(uri, values, null);
- assertTrue(result > 0);
- }
+
+ int result = LauncherComponentProvider.get(targetContext()).getGridCustomizationsProxy()
+ .update(uri, values, null, null);
+ assertTrue(result > 0);
}
private void switchToAllApps() {
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;
}