Merge "Switch to PictureInPictureParams#isSameAspectRatio" 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..fe57da1 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -136,7 +136,7 @@
TODO: Add proper permissions
-->
<provider
- android:name="com.android.launcher3.graphics.GridCustomizationsProvider"
+ android:name="com.android.launcher3.graphics.LauncherCustomizationProvider"
android:authorities="${applicationId}.grid_control"
android:exported="true" />
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/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..67feb6a 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -226,7 +226,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,7 +243,13 @@
private final StartingWindowListener mStartingWindowListener =
new StartingWindowListener(this);
- private ContentObserver mAnimationRemovalObserver = new ContentObserver(
+
+ // 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 final ContentObserver mAnimationRemovalObserver = new ContentObserver(
ORDERED_BG_EXECUTOR.getHandler()) {
@Override
public void onChange(boolean selfChange) {
@@ -338,7 +343,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);
@@ -1210,6 +1222,12 @@
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();
}
/**
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
index adbcc75..533a86a 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()
@@ -189,4 +202,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/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
index 37e8d4d..6ee43ff 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>()
@@ -393,7 +398,10 @@
}
}
- private fun onListenerConnected(displayDeskStates: Array<DisplayDeskState>) {
+ private fun onListenerConnected(
+ displayDeskStates: Array<DisplayDeskState>,
+ canCreateDesks: Boolean,
+ ) {
if (!DesktopModeStatus.enableMultipleDesktops(context)) {
return
}
@@ -404,23 +412,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 +534,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 +577,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)
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index d1e63f3..2eb4412 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;
@@ -876,7 +877,7 @@
@Override
public void onPopupVisibilityChanged(boolean isVisible) {
- setTaskbarWindowFocusable(isVisible);
+ setTaskbarWindowFocusable(isVisible /* focusable */, false /* imeFocusable */);
}
@Override
@@ -1235,17 +1236,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 +1279,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 +1292,7 @@
} else {
mControllers.navbarButtonsViewController.moveNavButtonsBackToTaskbarWindow();
}
- setTaskbarWindowFocusable(focusable);
+ setTaskbarWindowFocusable(focusable, true /* imeFocusable */);
}
/** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */
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/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/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/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/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index c986b88..953b0c5 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -92,6 +92,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";
@@ -332,6 +334,7 @@
mLauncherStartAnim.dispatchOnEnd();
mLauncherStartAnim = null;
}
+ sendBroadcast(new Intent(INTENT_ACTION_ACTIVITY_CLOSED));
}
@Override
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/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index e672ec4..e91073a 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)
}
}
}
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/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/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index a29d302..c5a76cb 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -157,6 +157,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 +180,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 +267,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 +558,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.
@@ -913,6 +919,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 +1235,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 +1259,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();
}
@@ -6859,6 +6873,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/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 49ec31d..ba54232 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -789,11 +789,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 +810,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/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..10be6fd 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
@@ -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/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..1c7f51c 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -50,6 +50,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 +543,7 @@
@Test
@PortraitLandscape
+ @ScreenRecordRule.ScreenRecord // TODO(b/396447643): Remove screen record.
public void testDismissCancel() throws Exception {
startTestAppsWithCheck();
Overview overview = mLauncher.goHome().switchToOverview();
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/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 31d0da0..f6acda4 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -20,6 +20,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherPrefs;
+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;
@@ -75,6 +76,8 @@
InvariantDeviceProfile getIDP();
IconPool getIconPool();
+ GridCustomizationsProxy getGridCustomizationsProxy();
+
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@BindsInstance Builder appContext(@ApplicationContext Context context);
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
similarity index 87%
rename from src/com/android/launcher3/graphics/GridCustomizationsProvider.java
rename to src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 12c65c7..01c9d7e 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -22,7 +22,6 @@
import static java.util.Objects.requireNonNullElse;
-import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -45,9 +44,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 +65,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 +81,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 +90,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 +138,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 +171,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 +199,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 +212,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 +221,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 +230,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 +255,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,12 +297,7 @@
@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",
+ if (mContext.checkPermission("android.permission.BIND_WALLPAPER",
Binder.getCallingPid(), Binder.getCallingUid())
!= PackageManager.PERMISSION_GRANTED) {
return null;
@@ -317,14 +311,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);
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/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index f0d670e..6fe5804 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -124,7 +124,7 @@
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);
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/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 34c9117..0da431f 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);
+ }
}
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/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/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)