Merge "Moving InvariantDeviceProfile to Dagger" into main
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 4ba4e2b..c748385 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -411,7 +411,8 @@
@NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) {
TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
nonAppTargets, launcherClosing, mLauncher.getStateManager(),
- mLauncher.getOverviewPanel(), mLauncher.getDepthController());
+ mLauncher.getOverviewPanel(), mLauncher.getDepthController(),
+ /* transitionInfo= */ null);
}
private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTarget[] targets) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index f704254..df3869e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -629,6 +629,7 @@
*/
public void setSetupUIVisible(boolean isVisible) {
mSharedState.setupUIVisible = isVisible;
+ mAllAppsActionManager.setSetupUiVisible(isVisible);
TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
if (taskbar != null) {
taskbar.setSetupUIVisible(isVisible);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 2111a80..23f4f67 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -86,7 +86,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -247,7 +246,6 @@
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
private SplitToWorkspaceController mSplitToWorkspaceController;
private BubbleBarLocation mBubbleBarLocation;
- private static final String TRACKING_BUG = "b/395214062";
/**
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
@@ -563,7 +561,6 @@
@Override
public void onDestroy() {
- Log.d(TRACKING_BUG, "onDestroy: " + this.hashCode());
if (mAppTransitionManager != null) {
mAppTransitionManager.onActivityDestroyed();
}
@@ -589,10 +586,7 @@
RecentsView recentsView = getOverviewPanel();
if (recentsView != null) {
- Log.d(TRACKING_BUG, "onDestroy - recentsView.destroy(): " + this.hashCode());
recentsView.destroy();
- } else {
- Log.d(TRACKING_BUG, "onDestroy - recentsView is null: " + this.hashCode());
}
super.onDestroy();
@@ -719,7 +713,6 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Log.d(TRACKING_BUG, "onCreate: " + this.hashCode());
if (savedInstanceState != null) {
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
savedInstanceState.getIBinder(PENDING_SPLIT_SELECT_INFO));
@@ -832,7 +825,7 @@
@Override
protected void onResume() {
super.onResume();
- Log.d(TRACKING_BUG, "onResume: " + this.hashCode());
+
if (mLauncherUnfoldAnimationController != null) {
mLauncherUnfoldAnimationController.onResume();
}
@@ -867,7 +860,6 @@
@Override
protected void onStop() {
super.onStop();
- Log.d(TRACKING_BUG, "onStop: " + this.hashCode());
if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
mTaskbarUIController.onLauncherStop();
}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index f46f9ae..b43c3ac 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -2453,7 +2453,8 @@
}
@Override
- public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+ public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+ @Nullable TransitionInfo transitionInfo) {
if (mRecentsAnimationController == null) {
return;
}
diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
index 6fd68d5..b807a4b 100644
--- a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
+++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
@@ -21,10 +21,16 @@
import android.app.RemoteAction
import android.content.Context
import android.graphics.drawable.Icon
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import android.view.accessibility.AccessibilityManager
import com.android.launcher3.R
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCache.OnChangeListener
import java.util.concurrent.Executor
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
+
/**
* Registers a [RemoteAction] for toggling All Apps if needed.
*
@@ -38,6 +44,12 @@
private val createAllAppsPendingIntent: () -> PendingIntent,
) {
+ private val onSettingsChangeListener = OnChangeListener { v -> isUserSetupComplete = v }
+
+ init {
+ SettingsCache.INSTANCE[context].register(USER_SETUP_COMPLETE_URI, onSettingsChangeListener)
+ }
+
/** `true` if home and overview are the same Activity. */
var isHomeAndOverviewSame = false
set(value) {
@@ -52,12 +64,27 @@
updateSystemAction()
}
+ /** `true` if the setup UI is visible. */
+ var isSetupUiVisible = false
+ set(value) {
+ field = value
+ updateSystemAction()
+ }
+
+ private var isUserSetupComplete =
+ SettingsCache.INSTANCE[context].getValue(USER_SETUP_COMPLETE_URI, 0)
+ set(value) {
+ field = value
+ updateSystemAction()
+ }
+
/** `true` if the action should be registered. */
var isActionRegistered = false
private set
private fun updateSystemAction() {
- val shouldRegisterAction = isHomeAndOverviewSame || isTaskbarPresent
+ val isInSetupFlow = isSetupUiVisible || !isUserSetupComplete
+ val shouldRegisterAction = (isHomeAndOverviewSame || isTaskbarPresent) && !isInSetupFlow
if (isActionRegistered == shouldRegisterAction) return
isActionRegistered = shouldRegisterAction
@@ -84,8 +111,10 @@
isActionRegistered = false
context
.getSystemService(AccessibilityManager::class.java)
- ?.unregisterSystemAction(
- GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
- )
+ ?.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS)
+ SettingsCache.INSTANCE[context].unregister(
+ USER_SETUP_COMPLETE_URI,
+ onSettingsChangeListener,
+ )
}
}
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 331580c..7d8a53d 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -50,6 +50,7 @@
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -172,14 +173,15 @@
}
@Override
- public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+ public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+ @Nullable TransitionInfo transitionInfo) {
if (mActiveAnimationFactory != null && mActiveAnimationFactory.handleHomeTaskAppeared(
appearedTaskTargets)) {
mActiveAnimationFactory = null;
return;
}
- super.onTasksAppeared(appearedTaskTargets);
+ super.onTasksAppeared(appearedTaskTargets, transitionInfo);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index fca67c3..3d12fdf 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -319,7 +319,7 @@
/**
* Composes the animations for a launch from the recents list if possible.
*/
- private AnimatorSet composeRecentsLaunchAnimator(
+ private AnimatorSet composeRecentsLaunchAnimator(
@NonNull RecentsView recentsView,
@NonNull TaskView taskView,
RemoteAnimationTarget[] appTargets,
@@ -329,7 +329,8 @@
boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets,
- wallpaperTargets, nonAppTargets, null /* depthController */, pa);
+ wallpaperTargets, nonAppTargets, /* depthController= */ null ,
+ /* transitionInfo= */ null, pa);
target.play(pa.buildAnim());
// Found a visible recents task that matches the opening app, lets launch the app from there
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index c6b858b..c276447 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -163,11 +163,12 @@
@BinderThread
@Override
- public void onTasksAppeared(RemoteAnimationTarget[] apps) {
+ public void onTasksAppeared(
+ RemoteAnimationTarget[] apps, @Nullable TransitionInfo transitionInfo) {
Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
ActiveGestureProtoLogProxy.logRecentsAnimationCallbacksOnTasksAppeared();
for (RecentsAnimationListener listener : getListeners()) {
- listener.onTasksAppeared(apps);
+ listener.onTasksAppeared(apps, transitionInfo);
}
});
}
@@ -225,6 +226,7 @@
/**
* Callback made when a task started from the recents is ready for an app transition.
*/
- default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {}
+ default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget,
+ @Nullable TransitionInfo transitionInfo) {}
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index a1ac39e..1f3eb2a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -1218,8 +1218,12 @@
override fun onAnimationCanceled(taskIds: IntArray?, taskSnapshots: Array<TaskSnapshot>?) =
listener.onAnimationCanceled(wrap(taskIds, taskSnapshots))
- override fun onTasksAppeared(apps: Array<RemoteAnimationTarget>?) =
- listener.onTasksAppeared(apps)
+ override fun onTasksAppeared(
+ apps: Array<RemoteAnimationTarget>?,
+ transitionInfo: TransitionInfo?,
+ ) {
+ listener.onTasksAppeared(apps, transitionInfo)
+ }
}
//
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 63b8aaf..9810308 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -231,7 +231,8 @@
}
@Override
- public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
+ public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets,
+ @Nullable TransitionInfo transitionInfo) {
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
BaseContainerInterface containerInterface =
mLastGestureState.getContainerInterface();
@@ -264,7 +265,8 @@
recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
appearedTaskTargets,
new RemoteAnimationTarget[0] /* wallpaper */,
- nonAppTargets /* nonApps */);
+ nonAppTargets /* nonApps */,
+ transitionInfo);
return;
} else {
ActiveGestureProtoLogProxy.logLaunchingSideTaskFailed();
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index e47223b..37841e8 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -169,6 +169,7 @@
@NonNull RemoteAnimationTarget[] wallpaperTargets,
@NonNull RemoteAnimationTarget[] nonAppTargets,
@Nullable DepthController depthController,
+ @Nullable TransitionInfo transitionInfo,
PendingAnimation out) {
boolean isQuickSwitch = v.isEndQuickSwitchCuj();
v.setEndQuickSwitchCuj(false);
@@ -191,8 +192,7 @@
RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
recentsView.getSizeStrategy(), targets, forDesktop);
if (forDesktop) {
- remoteTargetHandles =
- gluer.assignTargetsForDesktop(targets, /* transitionInfo=*/ null);
+ remoteTargetHandles = gluer.assignTargetsForDesktop(targets, transitionInfo);
} else if (v.containsMultipleTasks()) {
remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets,
((GroupedTaskView) v).getSplitBoundsConfig());
@@ -462,7 +462,7 @@
final RecentsView recentsView = launchingTaskView.getRecentsView();
composeRecentsLaunchAnimator(animatorSet, launchingTaskView, appTargets, wallpaperTargets,
nonAppTargets, /* launcherClosing */ true, stateManager, recentsView,
- depthController);
+ depthController, /* transitionInfo= */ null);
t.apply();
animatorSet.start();
@@ -501,7 +501,7 @@
composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
appTargets, wallpaperTargets, nonAppTargets,
true, stateManager,
- recentsView, depthController);
+ recentsView, depthController, /* transitionInfo= */ null);
animatorSet.start();
return;
}
@@ -593,7 +593,7 @@
composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps,
true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(),
- depthController);
+ depthController, transitionInfo);
return animatorSet;
}
@@ -603,13 +603,13 @@
@NonNull RemoteAnimationTarget[] wallpaperTargets,
@NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing,
@NonNull StateManager stateManager, @NonNull RecentsView recentsView,
- @Nullable DepthController depthController) {
+ @Nullable DepthController depthController, @Nullable TransitionInfo transitionInfo) {
boolean skipLauncherChanges = !launcherClosing;
TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets,
- wallpaperTargets, nonAppTargets, depthController, pa);
+ wallpaperTargets, nonAppTargets, depthController, transitionInfo, pa);
if (launcherClosing) {
// TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/,
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 977629f..d2f10b6 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -237,7 +237,7 @@
fun initialize(view: View): RecentsDependencies = initialize(view.context)
fun initialize(context: Context): RecentsDependencies {
- Log.d(TAG, "initializing: $activeRecentsCount + 1 more")
+ Log.d(TAG, "initializing")
synchronized(this) {
activeRecentsCount++
instance = RecentsDependencies(context.applicationContext)
@@ -277,7 +277,7 @@
Log.d(
TAG,
"RecentsDependencies was not destroyed. " +
- "There is still an active RecentsView instance: $activeRecentsCount",
+ "There is still an active RecentsView instance.",
)
}
}
diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
index da26622..35e90f2 100644
--- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
@@ -137,4 +137,12 @@
return currentPageTaskViewId;
}
}
+
+ /**
+ * Returns the column of a task's id in the grid.
+ */
+ public int getColumn(int taskViewId) {
+ return mTopRowIds.contains(taskViewId) ? mTopRowIds.indexOf(taskViewId)
+ : mBottomRowIds.indexOf(taskViewId);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 88850ab..424271a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -132,6 +132,7 @@
import android.widget.Toast;
import android.window.DesktopModeFlags;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -1395,12 +1396,13 @@
RemoteAnimationTargets targets = params.getTargetSet();
if (targets != null && targets.findTask(taskId) != null) {
launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
- targets.nonApps);
+ targets.nonApps, /* transitionInfo= */ null);
}
}
public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps) {
+ RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps,
+ @Nullable TransitionInfo transitionInfo) {
AnimatorSet anim = new AnimatorSet();
TaskView taskView = getTaskViewByTaskId(taskId);
if (taskView == null || !isTaskViewVisible(taskView)) {
@@ -1452,7 +1454,7 @@
} else {
TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps,
true /* launcherClosing */, getStateManager(), this,
- getDepthController());
+ getDepthController(), transitionInfo);
}
anim.start();
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
index d37a3f9..c7fc448 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -392,47 +392,72 @@
// Add tasks before dragged index, fanning out from the dragged task.
// The order they are added matters, as each spring drives the next.
var previousNeighbor = neighborsToSettle
- getTasksAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
- previousNeighbor = createNeighboringTaskViewSpringAnimation(it, previousNeighbor)
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
+ (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ )
}
// Add tasks after dragged index, fanning out from the dragged task.
// The order they are added matters, as each spring drives the next.
previousNeighbor = neighborsToSettle
- getTasksAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
- previousNeighbor = createNeighboringTaskViewSpringAnimation(it, previousNeighbor)
+ getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
+ (taskView, offset) ->
+ previousNeighbor =
+ createNeighboringTaskViewSpringAnimation(
+ taskView,
+ offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+ previousNeighbor,
+ )
}
}
- /** Gets adjacent tasks either before or after the dragged task in visual order. */
- private fun getTasksAdjacentToDraggedTask(
+ /**
+ * Gets pairs of (TaskView, offset) adjacent the dragged task in visual order.
+ *
+ * <p>Gets tasks either before or after the dragged task along with their offset from it. The
+ * offset is the distance between indices for carousels, or distance between columns for grids.
+ */
+ private fun getTasksOffsetPairAdjacentToDraggedTask(
draggedTaskView: TaskView,
towardsStart: Boolean,
- ): Sequence<TaskView> {
+ ): Sequence<Pair<TaskView, Int>> {
if (recentsView.showAsGrid()) {
- return gridTaskViewInTabOrderSequence(draggedTaskView, towardsStart)
+ return gridTaskOffsetPairInTabOrderSequence(draggedTaskView, towardsStart)
} else {
val taskViewList = taskViews.toList()
val draggedTaskViewIndex = taskViewList.indexOf(draggedTaskView)
return if (towardsStart) {
- taskViewList.take(draggedTaskViewIndex).reversed().asSequence()
+ taskViewList
+ .take(draggedTaskViewIndex)
+ .reversed()
+ .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+ .asSequence()
} else {
- taskViewList.takeLast(taskViewList.size - draggedTaskViewIndex - 1).asSequence()
+ taskViewList
+ .takeLast(taskViewList.size - draggedTaskViewIndex - 1)
+ .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+ .asSequence()
}
}
}
/**
- * Returns a sequence of TaskViews in the grid, ordered according to tab navigation, starting
- * from the dragged TaskView, in the direction of the provided delta.
+ * Returns a sequence of pairs of (TaskViews, offsets) in the grid, ordered according to tab
+ * navigation, starting from the dragged TaskView, towards the start or end of the grid.
*
* <p>A positive delta moves forward in the tab order towards the end of the grid, while a
- * negative value moves backward towards the beginning.
+ * negative value moves backward towards the beginning. The offset is the distance between
+ * columns the tasks are in.
*/
- private fun gridTaskViewInTabOrderSequence(
+ private fun gridTaskOffsetPairInTabOrderSequence(
draggedTaskView: TaskView,
towardsStart: Boolean,
- ): Sequence<TaskView> = sequence {
+ ): Sequence<Pair<TaskView, Int>> = sequence {
val taskGridNavHelper =
TaskGridNavHelper(
recentsView.topRowIdArray,
@@ -440,6 +465,7 @@
getLargeTaskViewIds(),
/* hasAddDesktopButton= */ false,
)
+ val draggedTaskViewColumn = taskGridNavHelper.getColumn(draggedTaskView.taskViewId)
var nextTaskView: TaskView? = draggedTaskView
var previousTaskView: TaskView? = null
while (nextTaskView != previousTaskView && nextTaskView != null) {
@@ -454,7 +480,11 @@
)
)
if (nextTaskView != null && nextTaskView != previousTaskView) {
- yield(nextTaskView)
+ val columnOffset =
+ abs(
+ taskGridNavHelper.getColumn(nextTaskView.taskViewId) - draggedTaskViewColumn
+ )
+ yield(Pair(nextTaskView, columnOffset))
}
}
}
@@ -462,6 +492,7 @@
/** Creates a neighboring task view spring, driven by the spring of its neighbor. */
private fun createNeighboringTaskViewSpringAnimation(
taskView: TaskView,
+ dampingOffsetRatio: Float,
previousNeighborSpringAnimation: SpringAnimation,
): SpringAnimation {
val neighboringTaskViewSpringAnimation =
@@ -471,7 +502,7 @@
taskView.secondaryDismissTranslationProperty
),
)
- .setSpring(createExpressiveDismissSpringForce())
+ .setSpring(createExpressiveDismissSpringForce(dampingOffsetRatio))
// Update live tile on spring animation.
if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ ->
@@ -489,11 +520,12 @@
return neighboringTaskViewSpringAnimation
}
- private fun createExpressiveDismissSpringForce(): SpringForce {
+ private fun createExpressiveDismissSpringForce(dampingRatioOffset: Float = 0f): SpringForce {
val resourceProvider = DynamicResource.provider(recentsView.mContainer)
return SpringForce()
.setDampingRatio(
- resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio)
+ resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio) +
+ dampingRatioOffset
)
.setStiffness(
resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_stiffness)
@@ -502,5 +534,8 @@
companion object {
val TEMP_RECT = Rect()
+
+ // The additional damping to apply to tasks further from the dismissed task.
+ const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f
}
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 609262f..56a35bd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -1220,6 +1220,7 @@
recentsView.stateManager,
recentsView,
recentsView.depthController,
+ /* transitionInfo= */ null,
)
addListener(
object : AnimatorListenerAdapter() {
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 232a08a..c3b4d15 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -24,6 +24,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,6 +48,11 @@
private val taskThumbnailViewModel = FakeTaskThumbnailViewModel()
+ @After
+ fun tearDown() {
+ RecentsDependencies.destroy()
+ }
+
@Test
fun taskThumbnailView_uninitializedByDefault() {
screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index de0da64..adfbca5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -47,6 +47,7 @@
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +79,11 @@
RecentsDependencies.initialize(context)
}
+ @After
+ fun tearDown() {
+ RecentsDependencies.destroy()
+ }
+
@Test
fun singleTask() {
val taskContainers = listOf(createTaskContainer(createTask(1)))
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
index dcd5352..52238c8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
@@ -17,19 +17,22 @@
package com.android.launcher3.util
import android.net.Uri
+import com.android.launcher3.util.SettingsCache.OnChangeListener
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
-/**
- * Provides a sandboxed [SettingsCache] for testing.
- *
- * Note that listeners registered to [cache] will never be invoked.
- */
+/** Provides [SettingsCache] sandboxed from system settings for testing. */
class SettingsCacheSandbox {
private val values = mutableMapOf<Uri, Int>()
+ private val listeners = mutableMapOf<Uri, MutableSet<OnChangeListener>>()
- /** Fake cache that delegates [SettingsCache.getValue] to [values]. */
+ /**
+ * Fake cache that delegates:
+ * - [SettingsCache.getValue] to [values]
+ * - [SettingsCache.mListenerMap] to [listeners].
+ */
val cache =
mock<SettingsCache> {
on { getValue(any<Uri>()) } doAnswer { mock.getValue(it.getArgument(0), 1) }
@@ -37,11 +40,22 @@
{
values.getOrDefault(it.getArgument(0), it.getArgument(1)) == 1
}
+
+ doAnswer {
+ listeners.getOrPut(it.getArgument(0)) { mutableSetOf() }.add(it.getArgument(1))
+ }
+ .whenever(mock)
+ .register(any(), any())
+ doAnswer { listeners[it.getArgument(0)]?.remove(it.getArgument(1)) }
+ .whenever(mock)
+ .unregister(any(), any())
}
operator fun get(key: Uri): Int? = values[key]
operator fun set(key: Uri, value: Int) {
+ if (value == values[key]) return
values[key] = value
+ listeners[key]?.forEach { it.onSettingsChanged(value == 1) }
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
index 73b35e8..a1bd107 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
@@ -18,32 +18,59 @@
import android.app.PendingIntent
import android.content.IIntentSender
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCacheSandbox
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit.SECONDS
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val TIMEOUT = 5L
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
@RunWith(AndroidJUnit4::class)
class AllAppsActionManagerTest {
private val callbackSemaphore = Semaphore(0)
private val bgExecutor = UI_HELPER_EXECUTOR
- private val allAppsActionManager =
- AllAppsActionManager(
- InstrumentationRegistry.getInstrumentation().targetContext,
- bgExecutor,
- ) {
- callbackSemaphore.release()
- PendingIntent(IIntentSender.Default())
+ @get:Rule val context = SandboxApplication()
+
+ private val settingsCacheSandbox =
+ SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 }
+
+ private val allAppsActionManager by
+ lazy(LazyThreadSafetyMode.NONE) {
+ AllAppsActionManager(context, bgExecutor) {
+ callbackSemaphore.release()
+ PendingIntent(IIntentSender.Default())
+ }
}
+ @Before
+ fun initDaggerComponent() {
+ context.initDaggerComponent(
+ DaggerAllAppsActionManagerTestComponent.builder()
+ .bindSettingsCache(settingsCacheSandbox.cache)
+ )
+ }
+
+ @After fun destroyManager() = allAppsActionManager.onDestroy()
+
@Test
fun taskbarPresent_actionRegistered() {
allAppsActionManager.isTaskbarPresent = true
@@ -88,4 +115,50 @@
assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
assertThat(allAppsActionManager.isActionRegistered).isTrue()
}
+
+ @Test
+ fun taskbarPresent_userSetupIncomplete_actionUnregistered() {
+ settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+ allAppsActionManager.isTaskbarPresent = true
+ assertThat(allAppsActionManager.isActionRegistered).isFalse()
+ }
+
+ @Test
+ fun taskbarPresent_setupUiVisible_actionUnregistered() {
+ allAppsActionManager.isSetupUiVisible = true
+ allAppsActionManager.isTaskbarPresent = true
+ assertThat(allAppsActionManager.isActionRegistered).isFalse()
+ }
+
+ @Test
+ fun taskbarPresent_userSetupCompleted_actionRegistered() {
+ settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+ allAppsActionManager.isTaskbarPresent = true
+
+ settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1
+ assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+ assertThat(allAppsActionManager.isActionRegistered).isTrue()
+ }
+
+ @Test
+ fun taskbarPresent_setupUiDismissed_actionRegistered() {
+ allAppsActionManager.isSetupUiVisible = true
+ allAppsActionManager.isTaskbarPresent = true
+
+ allAppsActionManager.isSetupUiVisible = false
+ assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+ assertThat(allAppsActionManager.isActionRegistered).isTrue()
+ }
+}
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class])
+interface AllAppsActionManagerTestComponent : LauncherAppComponent {
+
+ @Component.Builder
+ interface Builder : LauncherAppComponent.Builder {
+ @BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder
+
+ override fun build(): AllAppsActionManagerTestComponent
+ }
}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
deleted file mode 100644
index 5c6debe..0000000
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.icons;
-
-import static android.graphics.Color.BLACK;
-
-import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.Flags;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.ThemeManager;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.UserIconInfo;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
-
- private static final float SEVEN_SIDED_COOKIE_SCALE = 72f / 80f;
- private static final float FOUR_SIDED_COOKIE_SCALE = 72f / 83.4f;
- private static final float VERY_SUNNY_SCALE = 72f / 92f;
- private static final float DEFAULT_ICON_SCALE = 1f;
-
-
- private static final MainThreadInitializedObject<Pool> POOL =
- new MainThreadInitializedObject<>(Pool::new);
-
- /**
- * Return a new Message instance from the global pool. Allows us to
- * avoid allocating new objects in many cases.
- */
- public static LauncherIcons obtain(Context context) {
- return POOL.get(context).obtain();
- }
-
- public static void clearPool(Context context) {
- POOL.get(context).close();
- }
-
- private final ConcurrentLinkedQueue<LauncherIcons> mPool;
-
- protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
- ConcurrentLinkedQueue<LauncherIcons> pool) {
- super(context, fillResIconDpi, iconBitmapSize);
- mThemeController = ThemeManager.INSTANCE.get(context).getThemeController();
- mPool = pool;
- }
-
- /**
- * Recycles a LauncherIcons that may be in-use.
- */
- public void recycle() {
- clear();
- mPool.add(this);
- }
-
- @NonNull
- @Override
- protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
- return UserCache.INSTANCE.get(mContext).getUserInfo(user);
- }
-
- @NonNull
- @Override
- public Path getShapePath(AdaptiveIconDrawable drawable, Rect iconBounds) {
- if (!Flags.enableLauncherIconShapes()) return drawable.getIconMask();
- return IconShape.INSTANCE.get(mContext).getShape().getPath(iconBounds);
- }
-
- @Override
- protected void drawAdaptiveIcon(
- @NonNull Canvas canvas,
- @NonNull AdaptiveIconDrawable drawable,
- @NonNull Path overridePath
- ) {
- if (!Flags.enableLauncherIconShapes()) {
- super.drawAdaptiveIcon(canvas, drawable, overridePath);
- return;
- }
- String shapeKey = LauncherPrefs.get(mContext).get(PREF_ICON_SHAPE);
- float iconScale = switch (shapeKey) {
- case "seven_sided_cookie" -> SEVEN_SIDED_COOKIE_SCALE;
- case "four_sided_cookie" -> FOUR_SIDED_COOKIE_SCALE;
- case "sunny" -> VERY_SUNNY_SCALE;
- default -> DEFAULT_ICON_SCALE;
- };
- canvas.clipPath(overridePath);
- canvas.drawColor(BLACK);
- canvas.save();
- canvas.scale(iconScale, iconScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
- if (drawable.getBackground() != null) {
- drawable.getBackground().draw(canvas);
- }
- if (drawable.getForeground() != null) {
- drawable.getForeground().draw(canvas);
- }
- canvas.restore();
- }
-
- @Override
- public void close() {
- recycle();
- }
-
- private static class Pool implements SafeCloseable {
-
- private final Context mContext;
-
- @NonNull
- private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>();
-
- private Pool(Context context) {
- mContext = context;
- }
-
- public LauncherIcons obtain() {
- ConcurrentLinkedQueue<LauncherIcons> pool = mPool;
- LauncherIcons m = pool.poll();
-
- if (m == null) {
- InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
- return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool);
- } else {
- return m;
- }
- }
-
- @Override
- public void close() {
- mPool = new ConcurrentLinkedQueue<>();
- }
- }
-}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.kt b/src/com/android/launcher3/icons/LauncherIcons.kt
new file mode 100644
index 0000000..518f29d
--- /dev/null
+++ b/src/com/android/launcher3/icons/LauncherIcons.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.icons
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.os.UserHandle
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.graphics.IconShape
+import com.android.launcher3.graphics.ThemeManager
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.util.UserIconInfo
+import java.util.concurrent.ConcurrentLinkedQueue
+
+/**
+ * Wrapper class to provide access to [BaseIconFactory] and also to provide pool of this class that
+ * are threadsafe.
+ */
+class LauncherIcons
+protected constructor(
+ context: Context,
+ fillResIconDpi: Int,
+ iconBitmapSize: Int,
+ private val pool: ConcurrentLinkedQueue<LauncherIcons>,
+) : BaseIconFactory(context, fillResIconDpi, iconBitmapSize), AutoCloseable {
+
+ init {
+ mThemeController = ThemeManager.INSTANCE[context].themeController
+ }
+
+ /** Recycles a LauncherIcons that may be in-use. */
+ fun recycle() {
+ clear()
+ pool.add(this)
+ }
+
+ override fun getUserInfo(user: UserHandle): UserIconInfo {
+ return UserCache.INSTANCE[mContext].getUserInfo(user)
+ }
+
+ public override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
+ if (!Flags.enableLauncherIconShapes()) return drawable.iconMask
+ return IconShape.INSTANCE[mContext].shape.getPath(iconBounds)
+ }
+
+ override fun drawAdaptiveIcon(
+ canvas: Canvas,
+ drawable: AdaptiveIconDrawable,
+ overridePath: Path,
+ ) {
+ if (!Flags.enableLauncherIconShapes()) {
+ super.drawAdaptiveIcon(canvas, drawable, overridePath)
+ return
+ }
+ val shapeKey = LauncherPrefs.get(mContext).get(ThemeManager.PREF_ICON_SHAPE)
+ val iconScale =
+ when (shapeKey) {
+ "seven_sided_cookie" -> SEVEN_SIDED_COOKIE_SCALE
+ "four_sided_cookie" -> FOUR_SIDED_COOKIE_SCALE
+ "sunny" -> VERY_SUNNY_SCALE
+ else -> DEFAULT_ICON_SCALE
+ }
+ canvas.clipPath(overridePath)
+ canvas.drawColor(Color.BLACK)
+ canvas.save()
+ canvas.scale(iconScale, iconScale, canvas.width / 2f, canvas.height / 2f)
+ if (drawable.background != null) {
+ drawable.background.draw(canvas)
+ }
+ if (drawable.foreground != null) {
+ drawable.foreground.draw(canvas)
+ }
+ canvas.restore()
+ }
+
+ override fun close() {
+ recycle()
+ }
+
+ private class Pool(private val context: Context) : SafeCloseable {
+ private var pool = ConcurrentLinkedQueue<LauncherIcons>()
+
+ fun obtain(): LauncherIcons {
+ val pool = pool
+ return pool.poll()
+ ?: InvariantDeviceProfile.INSTANCE[context].let {
+ LauncherIcons(context, it.fillResIconDpi, it.iconBitmapSize, pool)
+ }
+ }
+
+ override fun close() {
+ pool = ConcurrentLinkedQueue()
+ }
+ }
+
+ companion object {
+ private const val SEVEN_SIDED_COOKIE_SCALE = 72f / 80f
+ private const val FOUR_SIDED_COOKIE_SCALE = 72f / 83.4f
+ private const val VERY_SUNNY_SCALE = 72f / 92f
+ private const val DEFAULT_ICON_SCALE = 1f
+
+ private val POOL = MainThreadInitializedObject { Pool(it) }
+
+ /**
+ * Return a new Message instance from the global pool. Allows us to avoid allocating new
+ * objects in many cases.
+ */
+ @JvmStatic
+ fun obtain(context: Context): LauncherIcons {
+ return POOL[context].obtain()
+ }
+
+ @JvmStatic
+ fun clearPool(context: Context) {
+ POOL[context].close()
+ }
+ }
+}