diff options
Diffstat (limited to 'libs')
17 files changed, 866 insertions, 195 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt index a727b54b3a3f..4cc81a9e6f8f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt @@ -17,7 +17,6 @@ package com.android.wm.shell.apptoweb import android.app.ActivityManager.RunningTaskInfo -import android.app.TaskInfo import android.content.Context import android.content.pm.verify.domain.DomainVerificationManager import android.graphics.Bitmap @@ -36,8 +35,17 @@ import android.widget.TextView import android.window.TaskConstants import com.android.wm.shell.R import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import java.util.function.Supplier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.MainCoroutineDispatcher +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** @@ -45,13 +53,14 @@ import java.util.function.Supplier */ internal class OpenByDefaultDialog( private val context: Context, - private val taskInfo: TaskInfo, + private val taskInfo: RunningTaskInfo, private val taskSurface: SurfaceControl, private val displayController: DisplayController, + private val taskResourceLoader: WindowDecorTaskResourceLoader, private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, + @ShellMainThread private val mainDispatcher: MainCoroutineDispatcher, + @ShellBackgroundThread private val bgScope: CoroutineScope, private val listener: DialogLifecycleListener, - appIconBitmap: Bitmap?, - appName: CharSequence? ) { private lateinit var dialog: OpenByDefaultDialogView private lateinit var viewHost: SurfaceControlViewHost @@ -67,11 +76,20 @@ internal class OpenByDefaultDialog( context.getSystemService(DomainVerificationManager::class.java)!! private val packageName = taskInfo.baseActivity?.packageName!! + private var loadAppInfoJob: Job? = null init { createDialog() initializeRadioButtons() - bindAppInfo(appIconBitmap, appName) + loadAppInfoJob = bgScope.launch { + if (!isActive) return@launch + val name = taskResourceLoader.getName(taskInfo) + val icon = taskResourceLoader.getHeaderIcon(taskInfo) + withContext(mainDispatcher.immediate) { + if (!isActive) return@withContext + bindAppInfo(icon, name) + } + } } /** Creates an open by default settings dialog. */ @@ -147,14 +165,15 @@ internal class OpenByDefaultDialog( } private fun closeMenu() { + loadAppInfoJob?.cancel() dialogContainer?.releaseView() dialogContainer = null listener.onDialogDismissed() } private fun bindAppInfo( - appIconBitmap: Bitmap?, - appName: CharSequence? + appIconBitmap: Bitmap, + appName: CharSequence ) { appIconView.setImageBitmap(appIconBitmap) appNameView.text = appName diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 6ede016e3653..ace7f078bb10 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -151,6 +151,7 @@ import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer; +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader; import com.android.wm.shell.windowdecor.common.viewhost.DefaultWindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.common.viewhost.PooledWindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; @@ -769,6 +770,8 @@ public abstract class WMShellModule { @WMSingleton @Provides static DesktopTilingDecorViewModel provideDesktopTilingViewModel(Context context, + @ShellMainThread MainCoroutineDispatcher mainDispatcher, + @ShellBackgroundThread CoroutineScope bgScope, DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, @@ -777,9 +780,12 @@ public abstract class WMShellModule { ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, ReturnToDragStartAnimator returnToDragStartAnimator, @DynamicOverride DesktopUserRepositories desktopUserRepositories, - DesktopModeEventLogger desktopModeEventLogger) { + DesktopModeEventLogger desktopModeEventLogger, + WindowDecorTaskResourceLoader windowDecorTaskResourceLoader) { return new DesktopTilingDecorViewModel( context, + mainDispatcher, + bgScope, displayController, rootTaskDisplayAreaOrganizer, syncQueue, @@ -788,7 +794,8 @@ public abstract class WMShellModule { toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, desktopUserRepositories, - desktopModeEventLogger + desktopModeEventLogger, + windowDecorTaskResourceLoader ); } @@ -905,6 +912,8 @@ public abstract class WMShellModule { @ShellMainThread ShellExecutor shellExecutor, @ShellMainThread Handler mainHandler, @ShellMainThread Choreographer mainChoreographer, + @ShellMainThread MainCoroutineDispatcher mainDispatcher, + @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -931,13 +940,15 @@ public abstract class WMShellModule { Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, FocusTransitionObserver focusTransitionObserver, DesktopModeEventLogger desktopModeEventLogger, - DesktopModeUiEventLogger desktopModeUiEventLogger + DesktopModeUiEventLogger desktopModeUiEventLogger, + WindowDecorTaskResourceLoader taskResourceLoader ) { if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) { return Optional.empty(); } return Optional.of(new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler, - mainChoreographer, bgExecutor, shellInit, shellCommandHandler, windowManager, + mainChoreographer, mainDispatcher, bgScope, bgExecutor, + shellInit, shellCommandHandler, windowManager, taskOrganizer, desktopUserRepositories, displayController, shellController, displayInsetsController, syncQueue, transitions, desktopTasksController, desktopImmersiveController.get(), @@ -945,7 +956,18 @@ public abstract class WMShellModule { assistContentRequester, windowDecorViewHostSupplier, multiInstanceHelper, desktopTasksLimiter, appHandleEducationController, appToWebEducationController, windowDecorCaptionHandleRepository, activityOrientationChangeHandler, - focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger)); + focusTransitionObserver, desktopModeEventLogger, desktopModeUiEventLogger, + taskResourceLoader)); + } + + @WMSingleton + @Provides + static WindowDecorTaskResourceLoader provideWindowDecorTaskResourceLoader( + @NonNull Context context, @NonNull ShellInit shellInit, + @NonNull ShellController shellController, + @NonNull ShellCommandHandler shellCommandHandler) { + return new WindowDecorTaskResourceLoader(context, shellInit, shellController, + shellCommandHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index aea4bda527b4..5a05861c3a88 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -140,6 +140,7 @@ import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.FocusTransitionObserver; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener; +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.InsetsStateKt; @@ -150,7 +151,9 @@ import kotlin.Pair; import kotlin.Unit; import kotlin.jvm.functions.Function1; +import kotlinx.coroutines.CoroutineScope; import kotlinx.coroutines.ExperimentalCoroutinesApi; +import kotlinx.coroutines.MainCoroutineDispatcher; import java.io.PrintWriter; import java.util.ArrayList; @@ -177,6 +180,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final ShellController mShellController; private final Context mContext; private final @ShellMainThread Handler mMainHandler; + private final @ShellMainThread MainCoroutineDispatcher mMainDispatcher; + private final @ShellBackgroundThread CoroutineScope mBgScope; private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; @@ -241,12 +246,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, private final FocusTransitionObserver mFocusTransitionObserver; private final DesktopModeEventLogger mDesktopModeEventLogger; private final DesktopModeUiEventLogger mDesktopModeUiEventLogger; + private final WindowDecorTaskResourceLoader mTaskResourceLoader; public DesktopModeWindowDecorViewModel( Context context, ShellExecutor shellExecutor, @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, + @ShellMainThread MainCoroutineDispatcher mainDispatcher, + @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -273,12 +281,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, FocusTransitionObserver focusTransitionObserver, DesktopModeEventLogger desktopModeEventLogger, - DesktopModeUiEventLogger desktopModeUiEventLogger) { + DesktopModeUiEventLogger desktopModeUiEventLogger, + WindowDecorTaskResourceLoader taskResourceLoader) { this( context, shellExecutor, mainHandler, mainChoreographer, + mainDispatcher, + bgScope, bgExecutor, shellInit, shellCommandHandler, @@ -311,7 +322,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, new TaskPositionerFactory(), focusTransitionObserver, desktopModeEventLogger, - desktopModeUiEventLogger); + desktopModeUiEventLogger, + taskResourceLoader); } @VisibleForTesting @@ -320,6 +332,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, ShellExecutor shellExecutor, @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, + @ShellMainThread MainCoroutineDispatcher mainDispatcher, + @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -352,11 +366,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, TaskPositionerFactory taskPositionerFactory, FocusTransitionObserver focusTransitionObserver, DesktopModeEventLogger desktopModeEventLogger, - DesktopModeUiEventLogger desktopModeUiEventLogger) { + DesktopModeUiEventLogger desktopModeUiEventLogger, + WindowDecorTaskResourceLoader taskResourceLoader) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; mMainChoreographer = mainChoreographer; + mMainDispatcher = mainDispatcher; + mBgScope = bgScope; mBgExecutor = bgExecutor; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mTaskOrganizer = taskOrganizer; @@ -418,6 +435,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, mFocusTransitionObserver = focusTransitionObserver; mDesktopModeEventLogger = desktopModeEventLogger; mDesktopModeUiEventLogger = desktopModeUiEventLogger; + mTaskResourceLoader = taskResourceLoader; shellInit.addInitCallback(this::onInit, this); } @@ -1640,12 +1658,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel, : mContext, mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */), mDisplayController, + mTaskResourceLoader, mSplitScreenController, mDesktopUserRepositories, mTaskOrganizer, taskInfo, taskSurface, mMainHandler, + mMainExecutor, + mMainDispatcher, + mBgScope, mBgExecutor, mMainChoreographer, mSyncQueue, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 01319fb8c713..febf5669d12d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -28,7 +28,6 @@ import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_ import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; -import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode; import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopModeOrShowAppHandle; import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON; @@ -49,9 +48,6 @@ import android.app.assist.AssistContent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -60,13 +56,11 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Handler; import android.os.Trace; import android.os.UserHandle; import android.util.Size; -import android.util.Slog; import android.view.Choreographer; import android.view.InsetsState; import android.view.MotionEvent; @@ -81,8 +75,6 @@ import android.window.TaskSnapshot; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; -import com.android.launcher3.icons.BaseIconFactory; -import com.android.launcher3.icons.IconProvider; import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; @@ -102,10 +94,12 @@ import com.android.wm.shell.desktopmode.DesktopModeUtils; import com.android.wm.shell.desktopmode.DesktopUserRepositories; import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; @@ -118,7 +112,11 @@ import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; +import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.MainCoroutineDispatcher; + import java.util.List; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; @@ -134,12 +132,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @VisibleForTesting static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L; - private final Handler mHandler; + private final @ShellMainThread Handler mHandler; + private final @ShellMainThread ShellExecutor mMainExecutor; + private final @ShellMainThread MainCoroutineDispatcher mMainDispatcher; + private final @ShellBackgroundThread CoroutineScope mBgScope; private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mChoreographer; private final SyncTransactionQueue mSyncQueue; private final SplitScreenController mSplitScreenController; private final WindowManagerWrapper mWindowManagerWrapper; + private final @NonNull WindowDecorTaskResourceLoader mTaskResourceLoader; private WindowDecorationViewHolder mWindowDecorViewHolder; private View.OnClickListener mOnCaptionButtonClickListener; @@ -175,10 +177,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private OpenByDefaultDialog mOpenByDefaultDialog; private ResizeVeil mResizeVeil; - private Bitmap mAppIconBitmap; - private Bitmap mResizeVeilBitmap; - private CharSequence mAppName; private CapturedLink mCapturedLink; private Uri mGenericLink; private Uri mWebUri; @@ -205,16 +204,23 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository; private final DesktopUserRepositories mDesktopUserRepositories; + private Runnable mLoadAppInfoRunnable; + private Runnable mSetAppInfoRunnable; + public DesktopModeWindowDecoration( Context context, @NonNull Context userContext, DisplayController displayController, + @NonNull WindowDecorTaskResourceLoader taskResourceLoader, SplitScreenController splitScreenController, DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, - Handler handler, + @ShellMainThread Handler handler, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread MainCoroutineDispatcher mainDispatcher, + @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, @@ -226,12 +232,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger) { - this (context, userContext, displayController, splitScreenController, + this (context, userContext, displayController, taskResourceLoader, splitScreenController, desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler, - bgExecutor, choreographer, syncQueue, appHeaderViewHolderFactory, - rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, - SurfaceControl.Builder::new, SurfaceControl.Transaction::new, - WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( + mainExecutor, mainDispatcher, bgScope, bgExecutor, choreographer, syncQueue, + appHeaderViewHolderFactory, rootTaskDisplayAreaOrganizer, genericLinksParser, + assistContentRequester, SurfaceControl.Builder::new, + SurfaceControl.Transaction::new, WindowContainerTransaction::new, + SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier, @@ -244,12 +251,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Context context, @NonNull Context userContext, DisplayController displayController, + @NonNull WindowDecorTaskResourceLoader taskResourceLoader, SplitScreenController splitScreenController, DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, - Handler handler, + @ShellMainThread Handler handler, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread MainCoroutineDispatcher mainDispatcher, + @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, @@ -275,6 +286,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin surfaceControlViewHostFactory, windowDecorViewHostSupplier, desktopModeEventLogger); mSplitScreenController = splitScreenController; mHandler = handler; + mMainExecutor = mainExecutor; + mMainDispatcher = mainDispatcher; + mBgScope = bgScope; mBgExecutor = bgExecutor; mChoreographer = choreographer; mSyncQueue = syncQueue; @@ -288,6 +302,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mWindowManagerWrapper = windowManagerWrapper; mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository; mDesktopUserRepositories = desktopUserRepositories; + mTaskResourceLoader = taskResourceLoader; + mTaskResourceLoader.onWindowDecorCreated(taskInfo); } /** @@ -504,6 +520,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (oldRootView != mResult.mRootView) { disposeStatusBarInputLayer(); mWindowDecorViewHolder = createViewHolder(); + // Load these only when first creating the view. + loadTaskNameAndIconInBackground((name, icon) -> { + final AppHeaderViewHolder appHeader = asAppHeader(mWindowDecorViewHolder); + if (appHeader != null) { + appHeader.setAppName(name); + appHeader.setAppIcon(icon); + } + }); } final Point position = new Point(); @@ -542,6 +566,33 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Trace.endSection(); // DesktopModeWindowDecoration#relayout } + /** + * Loads the task's name and icon in a background thread and posts the results back in the + * main thread. + */ + private void loadTaskNameAndIconInBackground(BiConsumer<CharSequence, Bitmap> onResult) { + if (mWindowDecorViewHolder == null) return; + if (asAppHeader(mWindowDecorViewHolder) == null) { + // Only needed when drawing a header. + return; + } + if (mLoadAppInfoRunnable != null) { + mBgExecutor.removeCallbacks(mLoadAppInfoRunnable); + } + if (mSetAppInfoRunnable != null) { + mMainExecutor.removeCallbacks(mSetAppInfoRunnable); + } + mLoadAppInfoRunnable = () -> { + final CharSequence name = mTaskResourceLoader.getName(mTaskInfo); + final Bitmap icon = mTaskResourceLoader.getHeaderIcon(mTaskInfo); + mSetAppInfoRunnable = () -> { + onResult.accept(name, icon); + }; + mMainExecutor.execute(mSetAppInfoRunnable); + }; + mBgExecutor.execute(mLoadAppInfoRunnable); + } + private boolean isCaptionVisible() { return mTaskInfo.isVisible && mIsCaptionVisible; } @@ -761,15 +812,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin ); } else if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_header) { - loadAppInfoIfNeeded(); return mAppHeaderViewHolderFactory.create( mResult.mRootView, mOnCaptionTouchListener, mOnCaptionButtonClickListener, mOnCaptionLongClickListener, mOnCaptionGenericMotionListener, - mAppName, - mAppIconBitmap, mOnMaximizeHoverListener); } throw new IllegalArgumentException("Unexpected layout resource id"); @@ -1033,7 +1081,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mTaskInfo, mTaskSurface, mDisplayController, + mTaskResourceLoader, mSurfaceControlTransactionSupplier, + mMainDispatcher, + mBgScope, new OpenByDefaultDialog.DialogLifecycleListener() { @Override public void onDialogCreated() { @@ -1044,9 +1095,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin public void onDialogDismissed() { mOpenByDefaultDialog = null; } - }, - mAppIconBitmap, - mAppName + } ); } @@ -1058,50 +1107,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize(); } - private void loadAppInfoIfNeeded() { - // TODO(b/337370277): move this to another thread. - try { - Trace.beginSection("DesktopModeWindowDecoration#loadAppInfoIfNeeded"); - if (mAppIconBitmap != null && mAppName != null) { - return; - } - if (mTaskInfo.baseIntent == null) { - Slog.e(TAG, "Base intent not found in task"); - return; - } - final PackageManager pm = mUserContext.getPackageManager(); - final ActivityInfo activityInfo = - pm.getActivityInfo(mTaskInfo.baseIntent.getComponent(), 0 /* flags */); - final IconProvider provider = new IconProvider(mContext); - final Drawable appIconDrawable = provider.getIcon(activityInfo); - final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, - UserHandle.of(mTaskInfo.userId)); - final BaseIconFactory headerIconFactory = createIconFactory(mContext, - R.dimen.desktop_mode_caption_icon_radius); - mAppIconBitmap = headerIconFactory.createIconBitmap(badgedAppIconDrawable, - 1f /* scale */); - - final BaseIconFactory resizeVeilIconFactory = createIconFactory(mContext, - R.dimen.desktop_mode_resize_veil_icon_size); - mResizeVeilBitmap = resizeVeilIconFactory - .createScaledBitmap(appIconDrawable, MODE_DEFAULT); - - final ApplicationInfo applicationInfo = activityInfo.applicationInfo; - mAppName = pm.getApplicationLabel(applicationInfo); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Base activity's component name cannot be found on the system", e); - } finally { - Trace.endSection(); - } - } - - private BaseIconFactory createIconFactory(Context context, int dimensions) { - final Resources resources = context.getResources(); - final int densityDpi = resources.getDisplayMetrics().densityDpi; - final int iconSize = resources.getDimensionPixelSize(dimensions); - return new BaseIconFactory(context, densityDpi, iconSize); - } - private void closeDragResizeListener() { if (mDragResizeListener == null) { return; @@ -1116,9 +1121,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin */ private void createResizeVeilIfNeeded() { if (mResizeVeil != null) return; - loadAppInfoIfNeeded(); - mResizeVeil = new ResizeVeil(mContext, mDisplayController, mResizeVeilBitmap, - mTaskSurface, mSurfaceControlTransactionSupplier, mTaskInfo); + mResizeVeil = new ResizeVeil(mContext, mDisplayController, mTaskResourceLoader, + mMainDispatcher, mBgScope, mTaskSurface, + mSurfaceControlTransactionSupplier, mTaskInfo); } /** @@ -1318,7 +1323,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @VisibleForTesting void onAssistContentReceived(@Nullable AssistContent assistContent) { mWebUri = assistContent == null ? null : AppToWebUtils.getSessionWebUri(assistContent); - loadAppInfoIfNeeded(); updateGenericLink(); final boolean supportsMultiInstance = mMultiInstanceHelper .supportsMultiInstanceSplit(mTaskInfo.baseActivity) @@ -1331,11 +1335,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .isTaskInFullImmersiveState(mTaskInfo.taskId); final boolean isBrowserApp = isBrowserApp(); mHandleMenu = mHandleMenuFactory.create( + mMainDispatcher, + mBgScope, this, mWindowManagerWrapper, + mTaskResourceLoader, mRelayoutParams.mLayoutResId, - mAppIconBitmap, - mAppName, mSplitScreenController, canEnterDesktopModeOrShowAppHandle(mContext), supportsMultiInstance, @@ -1624,12 +1629,20 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin @Override public void close() { + if (mLoadAppInfoRunnable != null) { + mBgExecutor.removeCallbacks(mLoadAppInfoRunnable); + } + if (mSetAppInfoRunnable != null) { + mMainExecutor.removeCallbacks(mSetAppInfoRunnable); + } + mTaskResourceLoader.onWindowDecorClosed(mTaskInfo); closeDragResizeListener(); closeHandleMenu(); closeManageWindowsMenu(); mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); disposeStatusBarInputLayer(); + mWindowDecorViewHolder = null; if (canEnterDesktopMode(mContext) && isEducationEnabled()) { notifyNoCaptionHandle(); } @@ -1753,12 +1766,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Context context, @NonNull Context userContext, DisplayController displayController, + @NonNull WindowDecorTaskResourceLoader appResourceProvider, SplitScreenController splitScreenController, DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, - Handler handler, + @ShellMainThread Handler handler, + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread MainCoroutineDispatcher mainDispatcher, + @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, @@ -1775,12 +1792,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin context, userContext, displayController, + appResourceProvider, splitScreenController, desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler, + mainExecutor, + mainDispatcher, + bgScope, bgExecutor, choreographer, syncQueue, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt index 1179b0cd226c..159759ede368 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt @@ -47,15 +47,25 @@ import androidx.compose.ui.graphics.toArgb import androidx.core.view.isGone import com.android.window.flags.Flags import com.android.wm.shell.R +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.shared.split.SplitScreenConstants import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.calculateMenuPosition +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.extension.isFullscreen import com.android.wm.shell.windowdecor.extension.isMultiWindow import com.android.wm.shell.windowdecor.extension.isPinned +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.MainCoroutineDispatcher +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Handle menu opened when the appropriate button is clicked on. @@ -66,11 +76,12 @@ import com.android.wm.shell.windowdecor.extension.isPinned * Additional Options: Miscellaneous functions including screenshot and closing task. */ class HandleMenu( + @ShellMainThread private val mainDispatcher: CoroutineDispatcher, + @ShellBackgroundThread private val bgScope: CoroutineScope, private val parentDecor: DesktopModeWindowDecoration, private val windowManagerWrapper: WindowManagerWrapper, + private val taskResourceLoader: WindowDecorTaskResourceLoader, private val layoutResId: Int, - private val appIconBitmap: Bitmap?, - private val appName: CharSequence?, private val splitScreenController: SplitScreenController, private val shouldShowWindowingPill: Boolean, private val shouldShowNewWindowButton: Boolean, @@ -103,7 +114,8 @@ class HandleMenu( @VisibleForTesting var handleMenuViewContainer: AdditionalViewContainer? = null - private var handleMenuView: HandleMenuView? = null + @VisibleForTesting + var handleMenuView: HandleMenuView? = null // Position of the handle menu used for laying out the handle view. @VisibleForTesting @@ -122,6 +134,8 @@ class HandleMenu( get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton || shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton + private var loadAppInfoJob: Job? = null + init { updateHandleMenuPillPositions(captionX, captionY) } @@ -190,7 +204,7 @@ class HandleMenu( shouldShowDesktopModeButton = shouldShowDesktopModeButton, isBrowserApp = isBrowserApp ).apply { - bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill) + bind(taskInfo, shouldShowMoreActionsPill) this.onToDesktopClickListener = onToDesktopClickListener this.onToFullscreenClickListener = onToFullscreenClickListener this.onToSplitScreenClickListener = onToSplitScreenClickListener @@ -204,7 +218,16 @@ class HandleMenu( this.onCloseMenuClickListener = onCloseMenuClickListener this.onOutsideTouchListener = onOutsideTouchListener } - + loadAppInfoJob = bgScope.launch { + if (!isActive) return@launch + val name = taskResourceLoader.getName(taskInfo) + val icon = taskResourceLoader.getHeaderIcon(taskInfo) + withContext(mainDispatcher) { + if (!isActive) return@withContext + handleMenuView.setAppName(name) + handleMenuView.setAppIcon(icon) + } + } val x = handleMenuPosition.x.toInt() val y = handleMenuPosition.y.toInt() handleMenuViewContainer = @@ -412,6 +435,7 @@ class HandleMenu( resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL fun close() { + loadAppInfoJob?.cancel() handleMenuView?.animateCloseMenu { handleMenuViewContainer?.releaseView() handleMenuViewContainer = null @@ -439,8 +463,10 @@ class HandleMenu( private val appInfoPill = rootView.requireViewById<View>(R.id.app_info_pill) private val collapseMenuButton = appInfoPill.requireViewById<HandleMenuImageButton>( R.id.collapse_menu_button) - private val appIconView = appInfoPill.requireViewById<ImageView>(R.id.application_icon) - private val appNameView = appInfoPill.requireViewById<TextView>(R.id.application_name) + @VisibleForTesting + val appIconView = appInfoPill.requireViewById<ImageView>(R.id.application_icon) + @VisibleForTesting + val appNameView = appInfoPill.requireViewById<TextView>(R.id.application_name) // Windowing Pill. private val windowingPill = rootView.requireViewById<View>(R.id.windowing_pill) @@ -509,14 +535,12 @@ class HandleMenu( /** Binds the menu views to the new data. */ fun bind( taskInfo: RunningTaskInfo, - appIconBitmap: Bitmap?, - appName: CharSequence?, shouldShowMoreActionsPill: Boolean ) { this.taskInfo = taskInfo this.style = calculateMenuStyle(taskInfo) - bindAppInfoPill(style, appIconBitmap, appName) + bindAppInfoPill(style) if (shouldShowWindowingPill) { bindWindowingPill(style) } @@ -527,6 +551,16 @@ class HandleMenu( bindOpenInAppOrBrowserPill(style) } + /** Sets the app's name. */ + fun setAppName(name: CharSequence) { + appNameView.text = name + } + + /** Sets the app's icon. */ + fun setAppIcon(icon: Bitmap) { + appIconView.setImageBitmap(icon) + } + /** Animates the menu openInAppOrBrowserg. */ fun animateOpenMenu() { if (taskInfo.isFullscreen || taskInfo.isMultiWindow) { @@ -593,22 +627,14 @@ class HandleMenu( ) } - private fun bindAppInfoPill( - style: MenuStyle, - appIconBitmap: Bitmap?, - appName: CharSequence? - ) { + private fun bindAppInfoPill(style: MenuStyle) { appInfoPill.background.setTint(style.backgroundColor) collapseMenuButton.apply { imageTintList = ColorStateList.valueOf(style.textColor) this.taskInfo = this@HandleMenuView.taskInfo } - appIconView.setImageBitmap(appIconBitmap) - appNameView.apply { - text = appName - setTextColor(style.textColor) - } + appNameView.setTextColor(style.textColor) } private fun bindWindowingPill(style: MenuStyle) { @@ -698,11 +724,12 @@ class HandleMenu( /** A factory interface to create a [HandleMenu]. */ interface HandleMenuFactory { fun create( + @ShellMainThread mainDispatcher: MainCoroutineDispatcher, + @ShellBackgroundThread bgScope: CoroutineScope, parentDecor: DesktopModeWindowDecoration, windowManagerWrapper: WindowManagerWrapper, + taskResourceLoader: WindowDecorTaskResourceLoader, layoutResId: Int, - appIconBitmap: Bitmap?, - appName: CharSequence?, splitScreenController: SplitScreenController, shouldShowWindowingPill: Boolean, shouldShowNewWindowButton: Boolean, @@ -721,11 +748,12 @@ interface HandleMenuFactory { /** A [HandleMenuFactory] implementation that creates a [HandleMenu]. */ object DefaultHandleMenuFactory : HandleMenuFactory { override fun create( + @ShellMainThread mainDispatcher: MainCoroutineDispatcher, + @ShellBackgroundThread bgScope: CoroutineScope, parentDecor: DesktopModeWindowDecoration, windowManagerWrapper: WindowManagerWrapper, + taskResourceLoader: WindowDecorTaskResourceLoader, layoutResId: Int, - appIconBitmap: Bitmap?, - appName: CharSequence?, splitScreenController: SplitScreenController, shouldShowWindowingPill: Boolean, shouldShowNewWindowButton: Boolean, @@ -740,11 +768,12 @@ object DefaultHandleMenuFactory : HandleMenuFactory { captionY: Int, ): HandleMenu { return HandleMenu( + mainDispatcher, + bgScope, parentDecor, windowManagerWrapper, + taskResourceLoader, layoutResId, - appIconBitmap, - appName, splitScreenController, shouldShowWindowingPill, shouldShowNewWindowButton, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt index 8770d35cb85c..96839ce47725 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.kt @@ -20,7 +20,6 @@ import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.app.ActivityManager.RunningTaskInfo import android.content.Context -import android.graphics.Bitmap import android.graphics.Color import android.graphics.PixelFormat import android.graphics.PointF @@ -38,13 +37,23 @@ import android.window.TaskConstants import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.ui.graphics.toArgb +import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.R import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.DecorThemeUtil import com.android.wm.shell.windowdecor.common.Theme import java.util.function.Supplier +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Creates and updates a veil that covers task contents on resize. @@ -52,7 +61,9 @@ import java.util.function.Supplier public class ResizeVeil @JvmOverloads constructor( private val context: Context, private val displayController: DisplayController, - private val appIcon: Bitmap, + private val taskResourceLoader: WindowDecorTaskResourceLoader, + @ShellMainThread private val mainDispatcher: CoroutineDispatcher, + @ShellBackgroundThread private val bgScope: CoroutineScope, private var parentSurface: SurfaceControl, private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>, private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory = @@ -65,7 +76,8 @@ public class ResizeVeil @JvmOverloads constructor( private val lightColors = dynamicLightColorScheme(context) private val darkColors = dynamicDarkColorScheme(context) - private lateinit var iconView: ImageView + @VisibleForTesting + lateinit var iconView: ImageView private var iconSize = 0 /** A container surface to host the veil background and icon child surfaces. */ @@ -77,6 +89,7 @@ public class ResizeVeil @JvmOverloads constructor( private var viewHost: SurfaceControlViewHost? = null private var display: Display? = null private var veilAnimator: ValueAnimator? = null + private var loadAppInfoJob: Job? = null /** * Whether the resize veil is currently visible. @@ -142,7 +155,6 @@ public class ResizeVeil @JvmOverloads constructor( val root = LayoutInflater.from(context) .inflate(R.layout.desktop_mode_resize_veil, null /* root */) iconView = root.requireViewById(R.id.veil_application_icon) - iconView.setImageBitmap(appIcon) val lp = WindowManager.LayoutParams( iconSize, iconSize, @@ -156,6 +168,14 @@ public class ResizeVeil @JvmOverloads constructor( iconSurface, null /* hostInputToken */) viewHost = surfaceControlViewHostFactory.create(context, display, wwm, "ResizeVeil") viewHost?.setView(root, lp) + loadAppInfoJob = bgScope.launch { + if (!isActive) return@launch + val icon = taskResourceLoader.getVeilIcon(taskInfo) + withContext(mainDispatcher) { + if (!isActive) return@withContext + iconView.setImageBitmap(icon) + } + } Trace.endSection() } @@ -401,6 +421,7 @@ public class ResizeVeil @JvmOverloads constructor( cancelAnimation() veilAnimator = null isVisible = false + loadAppInfoJob?.cancel() viewHost?.release() viewHost = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt new file mode 100644 index 000000000000..d87da092cccf --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoader.kt @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2024 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.wm.shell.windowdecor.common + +import android.annotation.DimenRes +import android.app.ActivityManager +import android.app.ActivityManager.RunningTaskInfo +import android.content.Context +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.os.UserHandle +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting +import com.android.launcher3.icons.BaseIconFactory +import com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT +import com.android.launcher3.icons.IconProvider +import com.android.wm.shell.R +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.sysui.ShellCommandHandler +import com.android.wm.shell.sysui.ShellController +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.sysui.UserChangeListener +import java.io.PrintWriter +import java.util.concurrent.ConcurrentHashMap + +/** + * A utility and cache for window decoration UI resources. + */ +class WindowDecorTaskResourceLoader( + private val context: Context, + shellInit: ShellInit, + private val shellController: ShellController, + private val shellCommandHandler: ShellCommandHandler, + private val iconProvider: IconProvider, + private val headerIconFactory: BaseIconFactory, + private val veilIconFactory: BaseIconFactory, +) { + constructor( + context: Context, + shellInit: ShellInit, + shellController: ShellController, + shellCommandHandler: ShellCommandHandler, + ) : this( + context, + shellInit, + shellController, + shellCommandHandler, + IconProvider(context), + headerIconFactory = context.createIconFactory(R.dimen.desktop_mode_caption_icon_radius), + veilIconFactory = context.createIconFactory(R.dimen.desktop_mode_resize_veil_icon_size), + ) + + /** + * A map of task -> resources to prevent unnecessary binder calls and resource loading + * when multiple window decorations need the same resources, for example, the app name or icon + * used in the header and menu. + */ + @VisibleForTesting + val taskToResourceCache = ConcurrentHashMap<Int, AppResources>() + /** + * Keeps track of existing tasks with a window decoration. Useful to verify that requests to + * get resources occur within the lifecycle of a window decoration, otherwise it'd be possible + * to load a tasks resources into memory without a future signal to clean up the resource. + * See [onWindowDecorClosed]. + */ + private val existingTasks = mutableSetOf<Int>() + + @VisibleForTesting + lateinit var currentUserContext: Context + + init { + shellInit.addInitCallback(this::onInit, this) + } + + private fun onInit() { + shellCommandHandler.addDumpCallback(this::dump, this) + shellController.addUserChangeListener(object : UserChangeListener { + override fun onUserChanged(newUserId: Int, userContext: Context) { + currentUserContext = userContext + // No need to hold on to resources for tasks of another profile. + taskToResourceCache.clear() + } + }) + currentUserContext = context.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), /* flags= */ 0 + ) + } + + /** Returns the user readable name for this task. */ + @ShellBackgroundThread + fun getName(taskInfo: RunningTaskInfo): CharSequence { + checkWindowDecorExists(taskInfo) + val cachedResources = taskToResourceCache[taskInfo.taskId] + if (cachedResources != null) { + return cachedResources.appName + } + val resources = loadAppResources(taskInfo) + taskToResourceCache[taskInfo.taskId] = resources + return resources.appName + } + + /** Returns the icon for use by the app header and menus for this task. */ + @ShellBackgroundThread + fun getHeaderIcon(taskInfo: RunningTaskInfo): Bitmap { + checkWindowDecorExists(taskInfo) + val cachedResources = taskToResourceCache[taskInfo.taskId] + if (cachedResources != null) { + return cachedResources.appIcon + } + val resources = loadAppResources(taskInfo) + taskToResourceCache[taskInfo.taskId] = resources + return resources.appIcon + } + + /** Returns the icon for use by the resize veil for this task. */ + @ShellBackgroundThread + fun getVeilIcon(taskInfo: RunningTaskInfo): Bitmap { + checkWindowDecorExists(taskInfo) + val cachedResources = taskToResourceCache[taskInfo.taskId] + if (cachedResources != null) { + return cachedResources.veilIcon + } + val resources = loadAppResources(taskInfo) + taskToResourceCache[taskInfo.taskId] = resources + return resources.veilIcon + } + + /** Called when a window decoration for this task is created. */ + fun onWindowDecorCreated(taskInfo: RunningTaskInfo) { + existingTasks.add(taskInfo.taskId) + } + + /** Called when a window decoration for this task is closed. */ + fun onWindowDecorClosed(taskInfo: RunningTaskInfo) { + existingTasks.remove(taskInfo.taskId) + taskToResourceCache.remove(taskInfo.taskId) + } + + private fun checkWindowDecorExists(taskInfo: RunningTaskInfo) { + check(existingTasks.contains(taskInfo.taskId)) { + "Attempt to obtain resource for non-existent decoration" + } + } + + private fun loadAppResources(taskInfo: RunningTaskInfo): AppResources { + Trace.beginSection("$TAG#loadAppResources") + val pm = currentUserContext.packageManager + val activityInfo = getActivityInfo(taskInfo, pm) + val appName = pm.getApplicationLabel(activityInfo.applicationInfo) + val appIconDrawable = iconProvider.getIcon(activityInfo) + val badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable, taskInfo.userHandle()) + val appIcon = headerIconFactory.createIconBitmap(badgedAppIconDrawable, /* scale= */ 1f) + val veilIcon = veilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT) + Trace.endSection() + return AppResources(appName = appName, appIcon = appIcon, veilIcon = veilIcon) + } + + private fun getActivityInfo(taskInfo: RunningTaskInfo, pm: PackageManager): ActivityInfo { + return pm.getActivityInfo(taskInfo.component(), /* flags= */ 0) + } + + private fun RunningTaskInfo.component() = baseIntent.component!! + + private fun RunningTaskInfo.userHandle() = UserHandle.of(userId) + + data class AppResources(val appName: CharSequence, val appIcon: Bitmap, val veilIcon: Bitmap) + + private fun dump(pw: PrintWriter, prefix: String) { + val innerPrefix = "$prefix " + pw.println("${prefix}$TAG") + pw.println(innerPrefix + "appResourceCache=$taskToResourceCache") + pw.println(innerPrefix + "existingTasks=$existingTasks") + } + + companion object { + private const val TAG = "AppResourceProvider" + } +} + +/** Creates an icon factory with the provided [dimensions]. */ +fun Context.createIconFactory(@DimenRes dimensions: Int): BaseIconFactory { + val densityDpi = resources.displayMetrics.densityDpi + val iconSize = resources.getDimensionPixelSize(dimensions) + return BaseIconFactory(this, densityDpi, iconSize) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index 9db69d5c1bc5..d72da3a08de5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -31,17 +31,23 @@ import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger -import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainCoroutineDispatcher /** Manages tiling for each displayId/userId independently. */ class DesktopTilingDecorViewModel( private val context: Context, + @ShellMainThread private val mainDispatcher: MainCoroutineDispatcher, + @ShellBackgroundThread private val bgScope: CoroutineScope, private val displayController: DisplayController, private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, private val syncQueue: SyncTransactionQueue, @@ -51,6 +57,7 @@ class DesktopTilingDecorViewModel( private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val desktopUserRepositories: DesktopUserRepositories, private val desktopModeEventLogger: DesktopModeEventLogger, + private val taskResourceLoader: WindowDecorTaskResourceLoader, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() @@ -74,8 +81,11 @@ class DesktopTilingDecorViewModel( val newHandler = DesktopTilingWindowDecoration( context, + mainDispatcher, + bgScope, syncQueue, displayController, + taskResourceLoader, displayId, rootTdaOrganizer, transitions, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index 7ceac52dd2a1..6f2323347468 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -20,11 +20,9 @@ import android.app.ActivityManager.RunningTaskInfo import android.content.Context import android.content.res.Configuration import android.content.res.Resources -import android.graphics.Bitmap import android.graphics.Rect import android.os.IBinder import android.os.UserHandle -import android.util.Slog import android.view.MotionEvent import android.view.SurfaceControl import android.view.SurfaceControl.Transaction @@ -37,8 +35,6 @@ import android.window.TransitionRequestInfo import android.window.WindowContainerTransaction import com.android.internal.annotations.VisibleForTesting import com.android.launcher3.icons.BaseIconFactory -import com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT -import com.android.launcher3.icons.IconProvider import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer @@ -47,11 +43,12 @@ import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger -import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.DesktopUserRepositories import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler +import com.android.wm.shell.shared.annotations.ShellBackgroundThread +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_MINIMIZE import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration @@ -60,13 +57,19 @@ import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility.DragEvent import com.android.wm.shell.windowdecor.DragResizeWindowGeometry import com.android.wm.shell.windowdecor.DragResizeWindowGeometry.DisabledEdge.NONE import com.android.wm.shell.windowdecor.ResizeVeil +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.extension.isFullscreen import java.util.function.Supplier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainCoroutineDispatcher class DesktopTilingWindowDecoration( private var context: Context, + @ShellMainThread private val mainDispatcher: MainCoroutineDispatcher, + @ShellBackgroundThread private val bgScope: CoroutineScope, private val syncQueue: SyncTransactionQueue, private val displayController: DisplayController, + private val taskResourceLoader: WindowDecorTaskResourceLoader, private val displayId: Int, private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer, private val transitions: Transitions, @@ -110,6 +113,9 @@ class DesktopTilingWindowDecoration( context, destinationBounds, displayController, + taskResourceLoader, + mainDispatcher, + bgScope, transactionSupplier, ) val isFirstTiledApp = leftTaskResizingHelper == null && rightTaskResizingHelper == null @@ -408,11 +414,13 @@ class DesktopTilingWindowDecoration( val context: Context, val bounds: Rect, val displayController: DisplayController, + private val taskResourceLoader: WindowDecorTaskResourceLoader, + @ShellMainThread val mainDispatcher: MainCoroutineDispatcher, + @ShellBackgroundThread val bgScope: CoroutineScope, val transactionSupplier: Supplier<Transaction>, ) { var isInitialised = false var newBounds = Rect(bounds) - private lateinit var resizeVeilBitmap: Bitmap private lateinit var resizeVeil: ResizeVeil private val displayContext = displayController.getDisplayContext(taskInfo.displayId) private val userContext = @@ -426,26 +434,14 @@ class DesktopTilingWindowDecoration( } private fun initVeil() { - val baseActivity = taskInfo.baseActivity - if (baseActivity == null) { - Slog.e(TAG, "Base activity component not found in task") - return - } - val resizeVeilIconFactory = - displayContext?.let { - createIconFactory(displayContext, R.dimen.desktop_mode_resize_veil_icon_size) - } ?: return - val pm = userContext.getPackageManager() - val activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */) - val provider = IconProvider(displayContext) - val appIconDrawable = provider.getIcon(activityInfo) - resizeVeilBitmap = - resizeVeilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT) + displayContext ?: return resizeVeil = ResizeVeil( context = displayContext, displayController = displayController, - appIcon = resizeVeilBitmap, + taskResourceLoader = taskResourceLoader, + mainDispatcher = mainDispatcher, + bgScope = bgScope, parentSurface = desktopModeWindowDecoration.getLeash(), surfaceControlTransactionSupplier = transactionSupplier, taskInfo = taskInfo, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index f3a8b206867d..dc4fa3788778 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -72,8 +72,6 @@ class AppHeaderViewHolder( onCaptionButtonClickListener: View.OnClickListener, private val onLongClickListener: OnLongClickListener, onCaptionGenericMotionListener: View.OnGenericMotionListener, - appName: CharSequence, - appIconBitmap: Bitmap, onMaximizeHoverAnimationFinishedListener: () -> Unit ) : WindowDecorationViewHolder<AppHeaderViewHolder.HeaderData>(rootView) { @@ -154,8 +152,6 @@ class AppHeaderViewHolder( closeWindowButton.setOnTouchListener(onCaptionTouchListener) minimizeWindowButton.setOnClickListener(onCaptionButtonClickListener) minimizeWindowButton.setOnTouchListener(onCaptionTouchListener) - appNameTextView.text = appName - appIconImageView.setImageBitmap(appIconBitmap) maximizeButtonView.onHoverAnimationFinishedListener = onMaximizeHoverAnimationFinishedListener } @@ -170,6 +166,16 @@ class AppHeaderViewHolder( ) } + /** Sets the app's name in the header. */ + fun setAppName(name: CharSequence) { + appNameTextView.text = name + } + + /** Sets the app's icon in the header. */ + fun setAppIcon(icon: Bitmap) { + appIconImageView.setImageBitmap(icon) + } + private fun bindData( taskInfo: RunningTaskInfo, isTaskMaximized: Boolean, @@ -628,8 +634,6 @@ class AppHeaderViewHolder( onCaptionButtonClickListener: View.OnClickListener, onLongClickListener: OnLongClickListener, onCaptionGenericMotionListener: View.OnGenericMotionListener, - appName: CharSequence, - appIconBitmap: Bitmap, onMaximizeHoverAnimationFinishedListener: () -> Unit, ): AppHeaderViewHolder = AppHeaderViewHolder( rootView, @@ -637,8 +641,6 @@ class AppHeaderViewHolder( onCaptionButtonClickListener, onLongClickListener, onCaptionGenericMotionListener, - appName, - appIconBitmap, onMaximizeHoverAnimationFinishedListener, ) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt index 7a37c5eec604..b5e8cebc1277 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt @@ -74,6 +74,7 @@ import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.StubTransaction import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder @@ -91,6 +92,8 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.Optional import java.util.function.Supplier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainCoroutineDispatcher /** * Utility class for tests of [DesktopModeWindowDecorViewModel] @@ -181,6 +184,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { testShellExecutor, mockMainHandler, mockMainChoreographer, + mock<MainCoroutineDispatcher>(), + mock<CoroutineScope>(), bgExecutor, shellInit, mockShellCommandHandler, @@ -213,7 +218,8 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { mockTaskPositionerFactory, mockFocusTransitionObserver, desktopModeEventLogger, - mock<DesktopModeUiEventLogger>() + mock<DesktopModeUiEventLogger>(), + mock<WindowDecorTaskResourceLoader>() ) desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -293,8 +299,9 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() { val decoration = Mockito.mock(DesktopModeWindowDecoration::class.java) whenever( mockDesktopModeWindowDecorFactory.create( - any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), - any(), any(), any(), any(), any(), any(), any(), any(), any()) + any(), any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), + any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.user).thenReturn(mockUserHandle) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index db7b1f22768f..8a1a9b5ef80b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -114,6 +114,7 @@ import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost; import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; @@ -122,6 +123,9 @@ import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; +import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.MainCoroutineDispatcher; + import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -173,6 +177,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private Choreographer mMockChoreographer; @Mock + private MainCoroutineDispatcher mMockMainCoroutineDispatcher; + @Mock + private CoroutineScope mMockBgCoroutineScope; + @Mock private SyncTransactionQueue mMockSyncQueue; @Mock private AppHeaderViewHolder.Factory mMockAppHeaderViewHolderFactory; @@ -224,6 +232,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private DesktopModeEventLogger mDesktopModeEventLogger; @Mock private DesktopRepository mDesktopRepository; + @Mock + private WindowDecorTaskResourceLoader mMockTaskResourceLoader; @Captor private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener; @Captor @@ -234,6 +244,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private StaticMockitoSession mMockitoSession; private TestableContext mTestableContext; private final ShellExecutor mBgExecutor = new TestShellExecutor(); + private final ShellExecutor mMainExecutor = new TestShellExecutor(); private final AssistContent mAssistContent = new AssistContent(); private final Region mExclusionRegion = Region.obtain(); @@ -273,13 +284,13 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { final Display defaultDisplay = mock(Display.class); doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY); doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt()); - when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(), + when(mMockHandleMenuFactory.create(any(), any(), any(), any(), any(), anyInt(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); - when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(), - any())).thenReturn(mMockAppHeaderViewHolder); + when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any())) + .thenReturn(mMockAppHeaderViewHolder); when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository); when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository); when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay))) @@ -1692,7 +1703,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private void verifyHandleMenuCreated(@Nullable Uri uri) { - verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(), + verify(mMockHandleMenuFactory).create(any(), any(), any(), any(), any(), anyInt(), any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)), @@ -1760,12 +1771,14 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { MaximizeMenuFactory maximizeMenuFactory, boolean relayout) { final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext, - mContext, mMockDisplayController, mMockSplitScreenController, - mMockDesktopUserRepositories, mMockShellTaskOrganizer, taskInfo, - mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer, mMockSyncQueue, - mMockAppHeaderViewHolderFactory, mMockRootTaskDisplayAreaOrganizer, - mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, - mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, + mContext, mMockDisplayController, mMockTaskResourceLoader, + mMockSplitScreenController, mMockDesktopUserRepositories, mMockShellTaskOrganizer, + taskInfo, mMockSurfaceControl, mMockHandler, mMainExecutor, + mMockMainCoroutineDispatcher, mMockBgCoroutineScope, mBgExecutor, + mMockChoreographer, mMockSyncQueue, mMockAppHeaderViewHolderFactory, + mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser, + mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier, + WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt index 3bcbcbdd9105..cbfb57edc72d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt @@ -24,6 +24,7 @@ import android.graphics.Bitmap import android.graphics.Color import android.graphics.Point import android.graphics.Rect +import android.graphics.drawable.BitmapDrawable import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner @@ -49,6 +50,13 @@ import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UND import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before @@ -68,6 +76,7 @@ import org.mockito.kotlin.whenever * Build/Install/Run: * atest WMShellUnitTests:HandleMenuTest */ +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner::class) @@ -81,14 +90,6 @@ class HandleMenuTest : ShellTestCase() { @Mock private lateinit var mockWindowManager: WindowManager @Mock - private lateinit var onClickListener: View.OnClickListener - @Mock - private lateinit var onTouchListener: View.OnTouchListener - @Mock - private lateinit var appIcon: Bitmap - @Mock - private lateinit var appName: CharSequence - @Mock private lateinit var displayController: DisplayController @Mock private lateinit var splitScreenController: SplitScreenController @@ -96,6 +97,10 @@ class HandleMenuTest : ShellTestCase() { private lateinit var displayLayout: DisplayLayout @Mock private lateinit var mockSurfaceControlViewHost: SurfaceControlViewHost + @Mock + private lateinit var mockTaskResourceLoader: WindowDecorTaskResourceLoader + @Mock + private lateinit var mockAppIcon: Bitmap private lateinit var handleMenu: HandleMenu @@ -136,7 +141,7 @@ class HandleMenuTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HANDLE_INPUT_FIX) - fun testFullscreenMenuUsesSystemViewContainer() { + fun testFullscreenMenuUsesSystemViewContainer() = runTest { createTaskInfo(WINDOWING_MODE_FULLSCREEN, SPLIT_POSITION_UNDEFINED) val handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED) assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) @@ -148,7 +153,7 @@ class HandleMenuTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HANDLE_INPUT_FIX) - fun testFreeformMenu_usesViewHostViewContainer() { + fun testFreeformMenu_usesViewHostViewContainer() = runTest { createTaskInfo(WINDOWING_MODE_FREEFORM, SPLIT_POSITION_UNDEFINED) handleMenu = createAndShowHandleMenu(SPLIT_POSITION_UNDEFINED) assertTrue(handleMenu.handleMenuViewContainer is AdditionalViewHostViewContainer) @@ -159,7 +164,7 @@ class HandleMenuTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HANDLE_INPUT_FIX) - fun testSplitLeftMenu_usesSystemViewContainer() { + fun testSplitLeftMenu_usesSystemViewContainer() = runTest { createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_TOP_OR_LEFT) handleMenu = createAndShowHandleMenu(SPLIT_POSITION_TOP_OR_LEFT) assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) @@ -174,7 +179,7 @@ class HandleMenuTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_HANDLE_INPUT_FIX) - fun testSplitRightMenu_usesSystemViewContainer() { + fun testSplitRightMenu_usesSystemViewContainer() = runTest { createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, SPLIT_POSITION_BOTTOM_OR_RIGHT) handleMenu = createAndShowHandleMenu(SPLIT_POSITION_BOTTOM_OR_RIGHT) assertTrue(handleMenu.handleMenuViewContainer is AdditionalSystemViewContainer) @@ -188,7 +193,7 @@ class HandleMenuTest : ShellTestCase() { } @Test - fun testCreate_forceShowSystemBars_usesSystemViewContainer() { + fun testCreate_forceShowSystemBars_usesSystemViewContainer() = runTest { createTaskInfo(WINDOWING_MODE_FREEFORM) handleMenu = createAndShowHandleMenu(forceShowSystemBars = true) @@ -198,7 +203,7 @@ class HandleMenuTest : ShellTestCase() { } @Test - fun testCreate_forceShowSystemBars() { + fun testCreate_forceShowSystemBars() = runTest { createTaskInfo(WINDOWING_MODE_FREEFORM) handleMenu = createAndShowHandleMenu(forceShowSystemBars = true) @@ -208,6 +213,18 @@ class HandleMenuTest : ShellTestCase() { assertTrue((types and systemBars()) != 0) } + @Test + fun testCreate_loadsAppInfoInBackground() = runTest { + createTaskInfo(WINDOWING_MODE_FREEFORM) + + handleMenu = createAndShowHandleMenu() + advanceUntilIdle() + + assertThat(handleMenu.handleMenuView!!.appNameView.text).isEqualTo(APP_NAME) + val drawable = handleMenu.handleMenuView!!.appIconView.drawable as BitmapDrawable + assertThat(drawable.bitmap).isEqualTo(mockAppIcon) + } + private fun createTaskInfo(windowingMode: Int, splitPosition: Int? = null) { val taskDescriptionBuilder = ActivityManager.TaskDescription.Builder() .setBackgroundColor(Color.YELLOW) @@ -238,9 +255,13 @@ class HandleMenuTest : ShellTestCase() { (it.arguments[1] as Rect).set(SPLIT_RIGHT_BOUNDS) } } + whenever(mockTaskResourceLoader.getName(mockDesktopWindowDecoration.mTaskInfo)) + .thenReturn(APP_NAME) + whenever(mockTaskResourceLoader.getHeaderIcon(mockDesktopWindowDecoration.mTaskInfo)) + .thenReturn(mockAppIcon) } - private fun createAndShowHandleMenu( + private fun TestScope.createAndShowHandleMenu( splitPosition: Int? = null, forceShowSystemBars: Boolean = false ): HandleMenu { @@ -262,12 +283,22 @@ class HandleMenuTest : ShellTestCase() { } else -> error("Invalid windowing mode") } - val handleMenu = HandleMenu(mockDesktopWindowDecoration, + val handleMenu = HandleMenu( + StandardTestDispatcher(testScheduler), + this, + mockDesktopWindowDecoration, WindowManagerWrapper(mockWindowManager), - layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true, - shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false, - shouldShowChangeAspectRatioButton = false, shouldShowDesktopModeButton = true, - isBrowserApp = false, null /* openInAppOrBrowserIntent */, captionWidth = HANDLE_WIDTH, + mockTaskResourceLoader, + layoutId, + splitScreenController, + shouldShowWindowingPill = true, + shouldShowNewWindowButton = true, + shouldShowManageWindowsButton = false, + shouldShowChangeAspectRatioButton = false, + shouldShowDesktopModeButton = true, + isBrowserApp = false, + null /* openInAppOrBrowserIntent */, + captionWidth = HANDLE_WIDTH, captionHeight = 50, captionX = captionX, captionY = 0, @@ -300,5 +331,6 @@ class HandleMenuTest : ShellTestCase() { private const val MENU_PILL_ELEVATION = 2 private const val MENU_PILL_SPACING_MARGIN = 4 private const val HANDLE_WIDTH = 80 + private const val APP_NAME = "Test App" } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt index e0d16aab1e07..fa3d3e4016e9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeVeilTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.windowdecor import android.graphics.Bitmap import android.graphics.Rect +import android.graphics.drawable.BitmapDrawable import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Display @@ -29,6 +30,13 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -54,6 +62,7 @@ import org.mockito.kotlin.whenever * Build/Install/Run: * atest WMShellUnitTests:ResizeVeilTest */ +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper @@ -85,6 +94,8 @@ class ResizeVeilTest : ShellTestCase() { private lateinit var mockIconSurface: SurfaceControl @Mock private lateinit var mockTransaction: SurfaceControl.Transaction + @Mock + private lateinit var mockTaskResourceLoader: WindowDecorTaskResourceLoader private val taskInfo = TestRunningTaskInfoBuilder().build() @@ -115,7 +126,7 @@ class ResizeVeilTest : ShellTestCase() { } @Test - fun init_displayAvailable_viewHostCreated() { + fun init_displayAvailable_viewHostCreated() = runTest { createResizeVeil(withDisplayAvailable = true) verify(mockSurfaceControlViewHostFactory) @@ -123,7 +134,7 @@ class ResizeVeilTest : ShellTestCase() { } @Test - fun init_displayUnavailable_viewHostNotCreatedUntilDisplayAppears() { + fun init_displayUnavailable_viewHostNotCreatedUntilDisplayAppears() = runTest { createResizeVeil(withDisplayAvailable = false) verify(mockSurfaceControlViewHostFactory, never()) @@ -140,14 +151,14 @@ class ResizeVeilTest : ShellTestCase() { } @Test - fun dispose_removesDisplayWindowListener() { + fun dispose_removesDisplayWindowListener() = runTest { createResizeVeil().dispose() verify(mockDisplayController).removeDisplayWindowListener(any()) } @Test - fun showVeil() { + fun showVeil() = runTest { val veil = createResizeVeil() veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */) @@ -159,7 +170,7 @@ class ResizeVeilTest : ShellTestCase() { } @Test - fun showVeil_displayUnavailable_doesNotShow() { + fun showVeil_displayUnavailable_doesNotShow() = runTest { val veil = createResizeVeil(withDisplayAvailable = false) veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */) @@ -171,7 +182,7 @@ class ResizeVeilTest : ShellTestCase() { } @Test - fun showVeil_alreadyVisible_doesNotShowAgain() { + fun showVeil_alreadyVisible_doesNotShowAgain() = runTest { val veil = createResizeVeil() veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */) @@ -184,7 +195,7 @@ class ResizeVeilTest : ShellTestCase() { } @Test - fun showVeil_reparentsVeilToNewParent() { + fun showVeil_reparentsVeilToNewParent() = runTest { val veil = createResizeVeil(parent = mock()) val newParent = mock<SurfaceControl>() @@ -200,7 +211,7 @@ class ResizeVeilTest : ShellTestCase() { } @Test - fun hideVeil_alreadyHidden_doesNothing() { + fun hideVeil_alreadyHidden_doesNothing() = runTest { val veil = createResizeVeil() veil.hideVeil() @@ -208,16 +219,41 @@ class ResizeVeilTest : ShellTestCase() { verifyZeroInteractions(mockTransaction) } - private fun createResizeVeil( + @Test + fun showVeil_loadsIconInBackground() = runTest { + val veil = createResizeVeil() + veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */) + + advanceUntilIdle() + + verify(mockTaskResourceLoader).getVeilIcon(taskInfo) + assertThat((veil.iconView.drawable as BitmapDrawable).bitmap).isEqualTo(mockAppIcon) + } + + @Test + fun dispose_iconLoading_cancelsJob() = runTest { + val veil = createResizeVeil() + veil.showVeil(mockTransaction, mock(), Rect(0, 0, 100, 100), taskInfo, false /* fadeIn */) + + veil.dispose() + advanceUntilIdle() + + assertThat(veil.iconView.drawable).isNull() + } + + private fun TestScope.createResizeVeil( withDisplayAvailable: Boolean = true, parent: SurfaceControl = mock() ): ResizeVeil { whenever(mockDisplayController.getDisplay(taskInfo.displayId)) .thenReturn(if (withDisplayAvailable) mockDisplay else null) + whenever(mockTaskResourceLoader.getVeilIcon(taskInfo)).thenReturn(mockAppIcon) return ResizeVeil( context, mockDisplayController, - mockAppIcon, + mockTaskResourceLoader, + StandardTestDispatcher(testScheduler), + this, parent, { mockTransaction }, mockSurfaceControlBuilderFactory, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt new file mode 100644 index 000000000000..1ec0fe794d0a --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/common/WindowDecorTaskResourceLoaderTest.kt @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2024 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.wm.shell.windowdecor.common + +import android.app.ActivityManager +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.graphics.drawable.Drawable +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import android.testing.TestableContext +import androidx.test.filters.SmallTest +import com.android.launcher3.icons.BaseIconFactory +import com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT +import com.android.launcher3.icons.IconProvider +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.TestShellExecutor +import com.android.wm.shell.sysui.ShellController +import com.android.wm.shell.sysui.ShellInit +import com.android.wm.shell.sysui.UserChangeListener +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader.AppResources +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions +import org.mockito.kotlin.whenever + +/** + * Tests for [WindowDecorTaskResourceLoader]. + * + * Build/Install/Run: atest WindowDecorTaskResourceLoaderTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class WindowDecorTaskResourceLoaderTest : ShellTestCase() { + private val testExecutor = TestShellExecutor() + private val shellInit = ShellInit(testExecutor) + private val mockShellController = mock<ShellController>() + private val mockPackageManager = mock<PackageManager>() + private val mockIconProvider = mock<IconProvider>() + private val mockHeaderIconFactory = mock<BaseIconFactory>() + private val mockVeilIconFactory = mock<BaseIconFactory>() + + private lateinit var spyContext: TestableContext + private lateinit var loader: WindowDecorTaskResourceLoader + + private val userChangeListenerCaptor = argumentCaptor<UserChangeListener>() + private val userChangeListener: UserChangeListener by lazy { + userChangeListenerCaptor.firstValue + } + + @Before + fun setUp() { + spyContext = spy(mContext) + spyContext.setMockPackageManager(mockPackageManager) + doReturn(spyContext).whenever(spyContext).createContextAsUser(any(), anyInt()) + loader = + WindowDecorTaskResourceLoader( + context = spyContext, + shellInit = shellInit, + shellController = mockShellController, + shellCommandHandler = mock(), + iconProvider = mockIconProvider, + headerIconFactory = mockHeaderIconFactory, + veilIconFactory = mockVeilIconFactory, + ) + shellInit.init() + testExecutor.flushAll() + verify(mockShellController).addUserChangeListener(userChangeListenerCaptor.capture()) + } + + @Test + fun testGetName_notCached_loadsResourceAndCaches() { + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + + loader.getName(task) + + verify(mockPackageManager).getApplicationLabel(task.topActivityInfo!!.applicationInfo) + assertThat(loader.taskToResourceCache[task.taskId]?.appName).isNotNull() + } + + @Test + fun testGetName_cached_returnsFromCache() { + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock()) + + loader.getName(task) + + verifyZeroInteractions( + mockPackageManager, + mockIconProvider, + mockHeaderIconFactory, + mockVeilIconFactory, + ) + } + + @Test + fun testGetHeaderIcon_notCached_loadsResourceAndCaches() { + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + + loader.getHeaderIcon(task) + + verify(mockHeaderIconFactory).createIconBitmap(any(), anyFloat()) + assertThat(loader.taskToResourceCache[task.taskId]?.appIcon).isNotNull() + } + + @Test + fun testGetHeaderIcon_cached_returnsFromCache() { + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock()) + + loader.getHeaderIcon(task) + + verifyZeroInteractions(mockPackageManager, mockIconProvider, mockHeaderIconFactory) + } + + @Test + fun testGetVeilIcon_notCached_loadsResourceAndCaches() { + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + + loader.getVeilIcon(task) + + verify(mockVeilIconFactory).createScaledBitmap(any(), anyInt()) + assertThat(loader.taskToResourceCache[task.taskId]?.veilIcon).isNotNull() + } + + @Test + fun testGetVeilIcon_cached_returnsFromCache() { + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + loader.taskToResourceCache[task.taskId] = AppResources("App Name", mock(), mock()) + + loader.getVeilIcon(task) + + verifyZeroInteractions(mockPackageManager, mockIconProvider, mockVeilIconFactory) + } + + @Test + fun testUserChange_updatesContext() { + val newUser = 5000 + val newContext = mock<Context>() + + userChangeListener.onUserChanged(newUser, newContext) + + assertThat(loader.currentUserContext).isEqualTo(newContext) + } + + @Test + fun testUserChange_clearsCache() { + val newUser = 5000 + val newContext = mock<Context>() + val task = createTaskInfo(context.userId) + loader.onWindowDecorCreated(task) + loader.getName(task) + + userChangeListener.onUserChanged(newUser, newContext) + + assertThat(loader.taskToResourceCache[task.taskId]?.appName).isNull() + } + + @Test + fun testGet_nonexistentDecor_throws() { + val task = createTaskInfo(context.userId) + + assertThrows(Exception::class.java) { loader.getName(task) } + } + + private fun createTaskInfo(userId: Int): ActivityManager.RunningTaskInfo { + val appIconDrawable = mock<Drawable>() + val badgedAppIconDrawable = mock<Drawable>() + val activityInfo = ActivityInfo().apply { applicationInfo = ApplicationInfo() } + val componentName = ComponentName("com.foo", "BarActivity") + whenever(mockPackageManager.getActivityInfo(eq(componentName), anyInt())) + .thenReturn(activityInfo) + whenever(mockPackageManager.getApplicationLabel(activityInfo.applicationInfo)) + .thenReturn("Test App") + whenever(mockPackageManager.getUserBadgedIcon(appIconDrawable, UserHandle.of(userId))) + .thenReturn(badgedAppIconDrawable) + whenever(mockIconProvider.getIcon(activityInfo)).thenReturn(appIconDrawable) + whenever(mockHeaderIconFactory.createIconBitmap(badgedAppIconDrawable, 1f)) + .thenReturn(mock()) + whenever(mockVeilIconFactory.createScaledBitmap(appIconDrawable, MODE_DEFAULT)) + .thenReturn(mock()) + return TestRunningTaskInfoBuilder() + .setUserId(userId) + .setBaseIntent(Intent().apply { component = componentName }) + .build() + .apply { topActivityInfo = activityInfo } + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index 193c2c25d26d..997ece6ecadc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -32,7 +32,10 @@ import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainCoroutineDispatcher import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -47,6 +50,8 @@ import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) class DesktopTilingDecorViewModelTest : ShellTestCase() { private val contextMock: Context = mock() + private val mainDispatcher: MainCoroutineDispatcher = mock() + private val bgScope: CoroutineScope = mock() private val displayControllerMock: DisplayController = mock() private val rootTdaOrganizerMock: RootTaskDisplayAreaOrganizer = mock() private val syncQueueMock: SyncTransactionQueue = mock() @@ -61,6 +66,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val desktopModeWindowDecorationMock: DesktopModeWindowDecoration = mock() private val desktopTilingDecoration: DesktopTilingWindowDecoration = mock() + private val taskResourceLoader: WindowDecorTaskResourceLoader = mock() private lateinit var desktopTilingDecorViewModel: DesktopTilingDecorViewModel @Before @@ -68,6 +74,8 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { desktopTilingDecorViewModel = DesktopTilingDecorViewModel( contextMock, + mainDispatcher, + bgScope, displayControllerMock, rootTdaOrganizerMock, syncQueueMock, @@ -77,6 +85,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { returnToDragStartAnimatorMock, userRepositories, desktopModeEventLogger, + taskResourceLoader, ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index 95e2151be96c..2f15c2e38855 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -45,7 +45,10 @@ import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration import com.android.wm.shell.windowdecor.DragResizeWindowGeometry +import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainCoroutineDispatcher import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -99,6 +102,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock() private val motionEvent: MotionEvent = mock() private val desktopRepository: DesktopRepository = mock() + private val mainDispatcher: MainCoroutineDispatcher = mock() + private val bgScope: CoroutineScope = mock() + private val taskResourceLoader: WindowDecorTaskResourceLoader = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -110,8 +116,11 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { tilingDecoration = DesktopTilingWindowDecoration( context, + mainDispatcher, + bgScope, syncQueue, displayController, + taskResourceLoader, displayId, rootTdaOrganizer, transitions, |