Snap for 12052458 from a00c81c173 to 24Q4-release

Change-Id: Ic1df60e993f1a962fe220a951c136c0a12b3d25c
This commit is contained in:
Android Build Coastguard Worker
2024-07-04 23:21:54 +00:00
28 changed files with 472 additions and 216 deletions
@@ -31,6 +31,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.drawable.ColorDrawable;
@@ -47,6 +48,7 @@ import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BaseActivity;
@@ -58,7 +60,6 @@ import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.views.GoOverviewActionsView;
import com.android.quickstep.views.TaskContainer;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.lang.annotation.Retention;
@@ -131,7 +132,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory {
* Called when the current task is interactive for the user
*/
@Override
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
public void initOverlay(Task task, @Nullable Bitmap thumbnail, Matrix matrix,
boolean rotated) {
if (mDialog != null && mDialog.isShowing()) {
// Redraw the dialog in case the layout changed
@@ -245,11 +245,20 @@ public final class KeyboardQuickSwitchController implements
}
void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) {
mModel.getThumbnailCache().updateThumbnailInBackground(task, callback);
mModel.getThumbnailCache().getThumbnailInBackground(task,
thumbnailData -> {
task.thumbnail = thumbnailData;
callback.accept(thumbnailData);
});
}
void updateIconInBackground(Task task, Consumer<Task> callback) {
mModel.getIconCache().updateIconInBackground(task, callback);
mModel.getIconCache().getIconInBackground(task, (icon, contentDescription, title) -> {
task.icon = icon;
task.titleDescription = contentDescription;
task.title = title;
callback.accept(task);
});
}
void onCloseComplete() {
@@ -43,6 +43,8 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import static com.android.wm.shell.Flags.enableTinyTaskbar;
import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
@@ -1515,7 +1517,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return mIsNavBarKidsMode && isThreeButtonNav();
}
protected boolean isNavBarForceVisible() {
@VisibleForTesting(otherwise = PROTECTED)
public boolean isNavBarForceVisible() {
return mIsNavBarForceVisible;
}
@@ -1649,6 +1652,10 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
return mControllers.uiController.canToggleHomeAllApps();
}
boolean isIconAlignedWithHotseat() {
return mControllers.uiController.isIconAlignedWithHotseat();
}
@VisibleForTesting
public TaskbarControllers getControllers() {
return mControllers;
@@ -159,6 +159,10 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
if (mHoverView == null || mToolTipText == null) {
return;
}
// Do not show tooltip if taskbar icons are transitioning to hotseat.
if (mActivity.isIconAlignedWithHotseat()) {
return;
}
if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
return;
}
@@ -115,6 +115,7 @@ public class TaskbarManager {
private WindowManager mWindowManager;
private FrameLayout mTaskbarRootLayout;
private boolean mAddedWindow;
private boolean mIsSuspended;
private final TaskbarNavButtonController mNavButtonController;
private final ComponentCallbacks mComponentCallbacks;
@@ -443,6 +444,8 @@ public class TaskbarManager {
*/
@VisibleForTesting
public synchronized void recreateTaskbar() {
if (mIsSuspended) return;
Trace.beginSection("recreateTaskbar");
try {
DeviceProfile dp = mUserUnlocked ?
@@ -648,8 +651,22 @@ public class TaskbarManager {
}
}
/**
* Removes Taskbar from the window manager and prevents recreation if {@code true}.
* <p>
* Suspending is for testing purposes only; avoid calling this method in production.
*/
@VisibleForTesting
public void addTaskbarRootViewToWindow() {
public void setSuspended(boolean isSuspended) {
mIsSuspended = isSuspended;
if (mIsSuspended) {
removeTaskbarRootViewFromWindow();
} else {
addTaskbarRootViewToWindow();
}
}
private void addTaskbarRootViewToWindow() {
if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
mWindowManager.addView(mTaskbarRootLayout,
mTaskbarActivityContext.getWindowLayoutParams());
@@ -657,8 +674,7 @@ public class TaskbarManager {
}
}
@VisibleForTesting
public void removeTaskbarRootViewFromWindow() {
private void removeTaskbarRootViewFromWindow() {
if (enableTaskbarNoRecreate() && mAddedWindow) {
mWindowManager.removeViewImmediate(mTaskbarRootLayout);
mAddedWindow = false;
@@ -24,11 +24,9 @@ import com.android.launcher3.util.CancellableTask
import com.android.quickstep.RecentsModel
import com.android.quickstep.util.DesktopTask
import com.android.quickstep.util.GroupTask
import com.android.systemui.shared.recents.model.Task
import com.android.window.flags.Flags.enableDesktopWindowingMode
import com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps
import java.io.PrintWriter
import java.util.function.Consumer
/**
* Provides recent apps functionality, when the Taskbar Recent Apps section is enabled. Behavior:
@@ -185,9 +183,16 @@ class TaskbarRecentAppsController(
for (groupTask in shownTasks) {
for (task in groupTask.tasks) {
val callback =
Consumer<Task> { controllers.taskbarViewController.onTaskUpdated(it) }
val cancellableTask = recentsModel.iconCache.updateIconInBackground(task, callback)
val cancellableTask =
recentsModel.iconCache.getIconInBackground(task) {
icon,
contentDescription,
title ->
task.icon = icon
task.titleDescription = contentDescription
task.title = title
controllers.taskbarViewController.onTaskUpdated(task)
}
if (cancellableTask != null) {
iconLoadRequests.add(cancellableTask)
}
@@ -132,6 +132,13 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> implem
* Init drag layer and overview panel views.
*/
protected void setupViews() {
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
// SplitSelectStateController needs to be created before setContentView()
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
null /* depthController */, getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
null /*activityBackCallback*/);
inflateRootView(R.layout.fallback_recents_activity);
setContentView(getRootView());
mDragLayer = findViewById(R.id.drag_layer);
@@ -139,12 +146,6 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> implem
mFallbackRecentsView = findViewById(R.id.overview_panel);
mActionsView = findViewById(R.id.overview_actions_view);
getRootView().getSysUiScrim().getSysUIProgress().updateValue(0);
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
mSplitSelectStateController =
new SplitSelectStateController(this, mHandler, getStateManager(),
null /* depthController */, getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
null /*activityBackCallback*/);
mDragLayer.recreateControllers();
if (enableDesktopWindowingMode()) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
@@ -55,7 +55,6 @@ import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.PackageManagerWrapper;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
* Manages the caching of task icons and related data.
@@ -103,21 +102,21 @@ public class TaskIconCache implements DisplayInfoChangeListener {
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
public CancellableTask updateIconInBackground(Task task, Consumer<Task> callback) {
public CancellableTask getIconInBackground(Task task, GetTaskIconCallback callback) {
Preconditions.assertUIThread();
if (task.icon != null) {
// Nothing to load, the icon is already loaded
callback.accept(task);
callback.onTaskIconReceived(task.icon, task.titleDescription, task.title);
return null;
}
CancellableTask<TaskCacheEntry> request = new CancellableTask<>(
() -> getCacheEntry(task),
MAIN_EXECUTOR,
result -> {
task.icon = result.icon;
task.titleDescription = result.contentDescription;
task.title = result.title;
callback.accept(task);
callback.onTaskIconReceived(
result.icon,
result.contentDescription,
result.title);
dispatchIconUpdate(task.key.id);
}
);
@@ -280,6 +279,12 @@ public class TaskIconCache implements DisplayInfoChangeListener {
public String title = "";
}
/** Callback used when retrieving app icons from cache. */
public interface GetTaskIconCallback {
/** Called when task icon is retrieved. */
void onTaskIconReceived(Drawable icon, String contentDescription, String title);
}
void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) {
mTaskVisualsChangeListener = newListener;
}
@@ -52,7 +52,6 @@ import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import java.util.ArrayList;
import java.util.List;
@@ -185,17 +184,6 @@ public class TaskOverlayFactory implements ResourceBasedOverride {
return mTaskContainer.getTaskView();
}
/**
* Called when the current task is interactive for the user
*
* @deprecated TODO(b/350931107): Remove this interface once TaskOverlayFactoryGo is updated
*/
@Deprecated
public void initOverlay(Task task, ThumbnailData thumbnail, Matrix matrix,
boolean rotated) {
initOverlay(task, thumbnail.getThumbnail(), matrix, rotated);
}
/**
* Called when the current task is interactive for the user
*/
@@ -131,8 +131,7 @@ public class TaskThumbnailCache implements TaskThumbnailDataSource {
Preconditions.assertUIThread();
// Fetch the thumbnail for this task and put it in the cache
if (task.thumbnail == null) {
updateThumbnailInBackground(task.key, lowResolution,
t -> task.thumbnail = t);
getThumbnailInBackground(task.key, lowResolution, t -> task.thumbnail = t);
}
}
@@ -145,13 +144,13 @@ public class TaskThumbnailCache implements TaskThumbnailDataSource {
}
/**
* Asynchronously fetches the icon and other task data for the given {@param task}.
* Asynchronously fetches the thumbnail for the given {@code task}.
*
* @param callback The callback to receive the task after its data has been populated.
* @return A cancelable handle to the request
*/
@Override
public CancellableTask<ThumbnailData> updateThumbnailInBackground(
public CancellableTask<ThumbnailData> getThumbnailInBackground(
Task task, @NonNull Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
@@ -164,10 +163,7 @@ public class TaskThumbnailCache implements TaskThumbnailDataSource {
return null;
}
return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {
task.thumbnail = t;
callback.accept(t);
});
return getThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), callback);
}
/**
@@ -187,7 +183,7 @@ public class TaskThumbnailCache implements TaskThumbnailDataSource {
return newSize > oldSize;
}
private CancellableTask<ThumbnailData> updateThumbnailInBackground(TaskKey key,
private CancellableTask<ThumbnailData> getThumbnailInBackground(TaskKey key,
boolean lowResolution, Consumer<ThumbnailData> callback) {
Preconditions.assertUIThread();
@@ -67,14 +67,15 @@ class TasksRepository(
this.visibleTaskIds.value = visibleTaskIdList.toSet()
}
/** Flow wrapper for [TaskThumbnailDataSource.updateThumbnailInBackground] api */
/** Flow wrapper for [TaskThumbnailDataSource.getThumbnailInBackground] api */
private fun getThumbnailDataRequest(task: Task): ThumbnailDataRequest =
flow {
emit(task.key.id to task.thumbnail)
val thumbnailDataResult: ThumbnailData? =
suspendCancellableCoroutine { continuation ->
val cancellableTask =
taskThumbnailDataSource.updateThumbnailInBackground(task) {
taskThumbnailDataSource.getThumbnailInBackground(task) {
task.thumbnail = it
continuation.resume(it)
}
continuation.invokeOnCancellation { cancellableTask?.cancel() }
@@ -94,12 +95,7 @@ class TasksRepository(
tasks.filter { it.key.id in visibleIds }
}
val visibleThumbnailDataRequests: Flow<List<ThumbnailDataRequest>> =
visibleTasks.map {
it.map { visibleTask ->
val taskCopy = Task(visibleTask).apply { thumbnail = visibleTask.thumbnail }
getThumbnailDataRequest(taskCopy)
}
}
visibleTasks.map { visibleTasksList -> visibleTasksList.map(::getThumbnailDataRequest) }
return visibleThumbnailDataRequests.flatMapLatest {
thumbnailRequestFlows: List<ThumbnailDataRequest> ->
if (thumbnailRequestFlows.isEmpty()) {
@@ -22,7 +22,7 @@ import com.android.systemui.shared.recents.model.ThumbnailData
import java.util.function.Consumer
interface TaskThumbnailDataSource {
fun updateThumbnailInBackground(
fun getThumbnailInBackground(
task: Task,
callback: Consumer<ThumbnailData>
): CancellableTask<ThumbnailData>?
@@ -138,12 +138,13 @@ public class SplitWithKeyboardShortcutController {
mLauncher, mLauncher.getDragLayer(),
controller.screenshotTask(runningTaskInfo.taskId).getThumbnail(),
null /* icon */, startingTaskRect);
Task task = Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo,
false /* isLocked */);
RecentsModel.INSTANCE.get(mLauncher.getApplicationContext())
.getIconCache()
.updateIconInBackground(
Task.from(new Task.TaskKey(runningTaskInfo), runningTaskInfo,
false /* isLocked */),
(task) -> floatingTaskView.setIcon(task.icon));
.getIconInBackground(
task,
(icon, contentDescription, title) -> floatingTaskView.setIcon(icon));
floatingTaskView.setAlpha(1);
floatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect,
false /* fadeWithThumbnail */, true /* isStagedTask */);
@@ -846,7 +846,8 @@ constructor(
taskContainers.forEach {
if (visible) {
recentsModel.thumbnailCache
.updateThumbnailInBackground(it.task) { thumbnailData ->
.getThumbnailInBackground(it.task) { thumbnailData ->
it.task.thumbnail = thumbnailData
it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData)
}
?.also { request -> pendingThumbnailLoadRequests.add(request) }
@@ -862,12 +863,15 @@ constructor(
taskContainers.forEach {
if (visible) {
recentsModel.iconCache
.updateIconInBackground(it.task) { task ->
setIcon(it.iconView, task.icon)
.getIconInBackground(it.task) { icon, contentDescription, title ->
it.task.icon = icon
it.task.titleDescription = contentDescription
it.task.title = title
setIcon(it.iconView, icon)
if (enableOverviewIconMenu()) {
setText(it.iconView, task.title)
setText(it.iconView, title)
}
it.digitalWellBeingToast?.initialize(task)
it.digitalWellBeingToast?.initialize(it.task)
}
?.also { request -> pendingIconLoadRequests.add(request) }
} else {
@@ -20,7 +20,6 @@ import android.animation.AnimatorTestRule
import android.content.ComponentName
import android.content.Intent
import android.os.Process
import androidx.test.annotation.UiThreadTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.BubbleTextView
import com.android.launcher3.appprediction.PredictionRowView
@@ -34,6 +33,7 @@ import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -55,17 +55,17 @@ class TaskbarAllAppsControllerTest {
@InjectController lateinit var overlayController: TaskbarOverlayController
@Test
@UiThreadTest
fun testToggle_once_showsAllApps() {
allAppsController.toggle()
getInstrumentation().runOnMainSync { allAppsController.toggle() }
assertThat(allAppsController.isOpen).isTrue()
}
@Test
@UiThreadTest
fun testToggle_twice_closesAllApps() {
allAppsController.toggle()
allAppsController.toggle()
getInstrumentation().runOnMainSync {
allAppsController.toggle()
allAppsController.toggle()
}
assertThat(allAppsController.isOpen).isFalse()
}
@@ -77,54 +77,62 @@ class TaskbarAllAppsControllerTest {
}
@Test
@UiThreadTest
fun testSetApps_beforeOpened_cachesInfo() {
allAppsController.setApps(TEST_APPS, 0, emptyMap())
allAppsController.toggle()
val overlayContext =
TestUtil.getOnUiThread {
allAppsController.setApps(TEST_APPS, 0, emptyMap())
allAppsController.toggle()
overlayController.requestWindow()
}
val overlayContext = overlayController.requestWindow()
assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
}
@Test
@UiThreadTest
fun testSetApps_afterOpened_updatesStore() {
allAppsController.toggle()
allAppsController.setApps(TEST_APPS, 0, emptyMap())
val overlayContext =
TestUtil.getOnUiThread {
allAppsController.toggle()
allAppsController.setApps(TEST_APPS, 0, emptyMap())
overlayController.requestWindow()
}
val overlayContext = overlayController.requestWindow()
assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
}
@Test
@UiThreadTest
fun testSetPredictedApps_beforeOpened_cachesInfo() {
allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
allAppsController.toggle()
val predictedApps =
overlayController
.requestWindow()
.appsView
.floatingHeaderView
.findFixedRowByType(PredictionRowView::class.java)
.predictedApps
TestUtil.getOnUiThread {
allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
allAppsController.toggle()
overlayController
.requestWindow()
.appsView
.floatingHeaderView
.findFixedRowByType(PredictionRowView::class.java)
.predictedApps
}
assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
}
@Test
@UiThreadTest
fun testSetPredictedApps_afterOpened_cachesInfo() {
allAppsController.toggle()
allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
val predictedApps =
overlayController
.requestWindow()
.appsView
.floatingHeaderView
.findFixedRowByType(PredictionRowView::class.java)
.predictedApps
TestUtil.getOnUiThread {
allAppsController.toggle()
allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
overlayController
.requestWindow()
.appsView
.floatingHeaderView
.findFixedRowByType(PredictionRowView::class.java)
.predictedApps
}
assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
}
@@ -140,36 +148,38 @@ class TaskbarAllAppsControllerTest {
}
// Ensure the recycler view fully inflates before trying to grab an icon.
getInstrumentation().runOnMainSync {
val btv =
val btv =
TestUtil.getOnUiThread {
overlayController
.requestWindow()
.appsView
.activeRecyclerView
.findViewHolderForAdapterPosition(0)
?.itemView as? BubbleTextView
assertThat(btv?.hasDot()).isTrue()
}
}
assertThat(btv?.hasDot()).isTrue()
}
@Test
@UiThreadTest
fun testUpdateNotificationDots_predictedApp_hasDot() {
allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
allAppsController.toggle()
getInstrumentation().runOnMainSync {
allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
allAppsController.toggle()
taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]),
NotificationKeyData("key"),
)
}
taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]),
NotificationKeyData("key"),
)
val predictionRowView =
overlayController
.requestWindow()
.appsView
.floatingHeaderView
.findFixedRowByType(PredictionRowView::class.java)
val btv = predictionRowView.getChildAt(0) as BubbleTextView
val btv =
TestUtil.getOnUiThread {
overlayController
.requestWindow()
.appsView
.floatingHeaderView
.findFixedRowByType(PredictionRowView::class.java)
.getChildAt(0) as BubbleTextView
}
assertThat(btv.hasDot()).isTrue()
}
@@ -18,7 +18,6 @@ package com.android.launcher3.taskbar.overlay
import android.app.ActivityManager.RunningTaskInfo
import android.view.MotionEvent
import androidx.test.annotation.UiThreadTest
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP
@@ -31,7 +30,7 @@ import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.android.launcher3.views.BaseDragLayer
import com.android.launcher3.util.TestUtil.getOnUiThread
import com.android.systemui.shared.system.TaskStackChangeListeners
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -54,74 +53,69 @@ class TaskbarOverlayControllerTest {
get() = taskbarUnitTestRule.activityContext
@Test
@UiThreadTest
fun testRequestWindow_twice_reusesWindow() {
val context1 = overlayController.requestWindow()
val context2 = overlayController.requestWindow()
val (context1, context2) =
getOnUiThread {
Pair(overlayController.requestWindow(), overlayController.requestWindow())
}
assertThat(context1).isSameInstanceAs(context2)
}
@Test
@UiThreadTest
fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() {
val context1 = overlayController.requestWindow()
overlayController.hideWindow()
val context1 = getOnUiThread { overlayController.requestWindow() }
getInstrumentation().runOnMainSync { overlayController.hideWindow() }
val context2 = overlayController.requestWindow()
val context2 = getOnUiThread { overlayController.requestWindow() }
assertThat(context1).isNotSameInstanceAs(context2)
}
@Test
@UiThreadTest
fun testRequestWindow_afterHidingOverlay_createsNewWindow() {
val context1 = overlayController.requestWindow()
TestOverlayView.show(context1)
overlayController.hideWindow()
val context1 = getOnUiThread { overlayController.requestWindow() }
getInstrumentation().runOnMainSync {
TestOverlayView.show(context1)
overlayController.hideWindow()
}
val context2 = overlayController.requestWindow()
val context2 = getOnUiThread { overlayController.requestWindow() }
assertThat(context1).isNotSameInstanceAs(context2)
}
@Test
@UiThreadTest
fun testRequestWindow_addsProxyView() {
TestOverlayView.show(overlayController.requestWindow())
getInstrumentation().runOnMainSync {
TestOverlayView.show(overlayController.requestWindow())
}
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
}
@Test
@UiThreadTest
fun testRequestWindow_closeProxyView_closesOverlay() {
val overlay = TestOverlayView.show(overlayController.requestWindow())
AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
getInstrumentation().runOnMainSync {
AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)
}
assertThat(overlay.isOpen).isFalse()
}
@Test
fun testRequestWindow_attachesDragLayer() {
lateinit var dragLayer: BaseDragLayer<*>
getInstrumentation().runOnMainSync {
dragLayer = overlayController.requestWindow().dragLayer
}
val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer }
// Allow drag layer to attach before checking.
getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() }
}
@Test
@UiThreadTest
fun testHideWindow_closesOverlay() {
val overlay = TestOverlayView.show(overlayController.requestWindow())
overlayController.hideWindow()
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
getInstrumentation().runOnMainSync { overlayController.hideWindow() }
assertThat(overlay.isOpen).isFalse()
}
@Test
fun testHideWindow_detachesDragLayer() {
lateinit var dragLayer: BaseDragLayer<*>
getInstrumentation().runOnMainSync {
dragLayer = overlayController.requestWindow().dragLayer
}
val dragLayer = getOnUiThread { overlayController.requestWindow().dragLayer }
// Wait for drag layer to be attached to window before hiding.
getInstrumentation().runOnMainSync {
@@ -131,26 +125,30 @@ class TaskbarOverlayControllerTest {
}
@Test
@UiThreadTest
fun testTwoOverlays_closeOne_windowStaysOpen() {
val context = overlayController.requestWindow()
val overlay1 = TestOverlayView.show(context)
val overlay2 = TestOverlayView.show(context)
val (overlay1, overlay2) =
getOnUiThread {
val context = overlayController.requestWindow()
Pair(TestOverlayView.show(context), TestOverlayView.show(context))
}
overlay1.close(false)
getInstrumentation().runOnMainSync { overlay1.close(false) }
assertThat(overlay2.isOpen).isTrue()
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
}
@Test
@UiThreadTest
fun testTwoOverlays_closeAll_closesWindow() {
val context = overlayController.requestWindow()
val overlay1 = TestOverlayView.show(context)
val overlay2 = TestOverlayView.show(context)
val (overlay1, overlay2) =
getOnUiThread {
val context = overlayController.requestWindow()
Pair(TestOverlayView.show(context), TestOverlayView.show(context))
}
overlay1.close(false)
overlay2.close(false)
getInstrumentation().runOnMainSync {
overlay1.close(false)
overlay2.close(false)
}
assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
}
@@ -165,11 +163,7 @@ class TaskbarOverlayControllerTest {
@Test
fun testTaskMovedToFront_closesOverlay() {
lateinit var overlay: TestOverlayView
getInstrumentation().runOnMainSync {
overlay = TestOverlayView.show(overlayController.requestWindow())
}
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo())
// Make sure TaskStackChangeListeners' Handler posts the callback before checking state.
getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
@@ -177,9 +171,8 @@ class TaskbarOverlayControllerTest {
@Test
fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() {
lateinit var overlay: TestOverlayView
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
getInstrumentation().runOnMainSync {
overlay = TestOverlayView.show(overlayController.requestWindow())
taskbarContext.controllers.sharedState?.allAppsVisible = false
}
@@ -189,9 +182,8 @@ class TaskbarOverlayControllerTest {
@Test
fun testTaskStackChanged_allAppsOpen_closesOverlay() {
lateinit var overlay: TestOverlayView
val overlay = getOnUiThread { TestOverlayView.show(overlayController.requestWindow()) }
getInstrumentation().runOnMainSync {
overlay = TestOverlayView.show(overlayController.requestWindow())
taskbarContext.controllers.sharedState?.allAppsVisible = true
}
@@ -200,33 +192,39 @@ class TaskbarOverlayControllerTest {
}
@Test
@UiThreadTest
fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() {
val overlayContext = overlayController.requestWindow()
val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP }
val context = getOnUiThread { overlayController.requestWindow() }
val overlay = getOnUiThread {
TestOverlayView.show(context).apply { type = TYPE_OPTIONS_POPUP }
}
overlayController.updateLauncherDeviceProfile(
overlayController.launcherDeviceProfile
.toBuilder(overlayContext)
.setGestureMode(false)
.build()
)
getInstrumentation().runOnMainSync {
overlayController.updateLauncherDeviceProfile(
overlayController.launcherDeviceProfile
.toBuilder(context)
.setGestureMode(false)
.build()
)
}
assertThat(overlay.isOpen).isFalse()
}
@Test
@UiThreadTest
fun testUpdateLauncherDeviceProfile_overlayRebindSafe_overlayStaysOpen() {
val overlayContext = overlayController.requestWindow()
val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_TASKBAR_ALL_APPS }
val context = getOnUiThread { overlayController.requestWindow() }
val overlay = getOnUiThread {
TestOverlayView.show(context).apply { type = TYPE_TASKBAR_ALL_APPS }
}
overlayController.updateLauncherDeviceProfile(
overlayController.launcherDeviceProfile
.toBuilder(overlayContext)
.setGestureMode(false)
.build()
)
getInstrumentation().runOnMainSync {
overlayController.updateLauncherDeviceProfile(
overlayController.launcherDeviceProfile
.toBuilder(context)
.setGestureMode(false)
.build()
)
}
assertThat(overlay.isOpen).isTrue()
}
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar.rules
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode
import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
import com.android.launcher3.util.DisplayController
@@ -59,23 +60,25 @@ class TaskbarModeRule(private val context: TaskbarWindowSandboxContext) : TestRu
override fun evaluate() {
val mode = taskbarMode.mode
context.applicationContext.putObject(
DisplayController.INSTANCE,
object : DisplayController(context) {
override fun getInfo(): Info {
return spy(super.getInfo()) {
on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT)
on { isPinnedTaskbar } doReturn (mode == Mode.PINNED)
on { navigationMode } doReturn
when (mode) {
Mode.TRANSIENT,
Mode.PINNED -> NavigationMode.NO_BUTTON
Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS
}
getInstrumentation().runOnMainSync {
context.applicationContext.putObject(
DisplayController.INSTANCE,
object : DisplayController(context) {
override fun getInfo(): Info {
return spy(super.getInfo()) {
on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT)
on { isPinnedTaskbar } doReturn (mode == Mode.PINNED)
on { navigationMode } doReturn
when (mode) {
Mode.TRANSIENT,
Mode.PINNED -> NavigationMode.NO_BUTTON
Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS
}
}
}
}
},
)
},
)
}
base.evaluate()
}
@@ -20,18 +20,25 @@ import android.app.Instrumentation
import android.app.PendingIntent
import android.content.IIntentSender
import android.content.Intent
import android.provider.Settings
import android.provider.Settings.Secure.NAV_BAR_KIDS_MODE
import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ServiceTestRule
import com.android.launcher3.LauncherAppState
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
import com.android.launcher3.util.ModelTestExtensions.loadModelSync
import com.android.launcher3.util.TestUtil
import com.android.quickstep.AllAppsActionManager
import com.android.quickstep.TouchInteractionService
import com.android.quickstep.TouchInteractionService.TISBinder
import org.junit.Assume.assumeTrue
import org.junit.rules.RuleChain
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
@@ -48,12 +55,11 @@ import org.junit.runners.model.Statement
* that code that is executed on the main thread in production should also happen on that thread
* when tested.
*
* `@UiThreadTest` is a simple way to run an entire test body on the main thread. But if a test
* executes code that appends message(s) to the main thread's `MessageQueue`, the annotation will
* prevent those messages from being processed until after the test body finishes.
* `@UiThreadTest` is incompatible with this rule. The annotation causes this rule to run on the
* main thread, but it needs to be run on the test thread for it to work properly. Instead, only run
* code that requires the main thread using something like [Instrumentation.runOnMainSync] or
* [TestUtil.getOnUiThread].
*
* To test pending messages, instead use something like [Instrumentation.runOnMainSync] to perform
* only sections of the test body on the main thread synchronously:
* ```
* @Test
* fun example() {
@@ -71,6 +77,10 @@ class TaskbarUnitTestRule(
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val serviceTestRule = ServiceTestRule()
private val userSetupCompleteRule = TaskbarSecureSettingRule(USER_SETUP_COMPLETE)
private val kidsModeRule = TaskbarSecureSettingRule(NAV_BAR_KIDS_MODE)
private val settingRules = RuleChain.outerRule(userSetupCompleteRule).around(kidsModeRule)
private lateinit var taskbarManager: TaskbarManager
val activityContext: TaskbarActivityContext
@@ -80,15 +90,34 @@ class TaskbarUnitTestRule(
}
override fun apply(base: Statement, description: Description): Statement {
return settingRules.apply(createStatement(base, description), description)
}
private fun createStatement(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
// Only run test when Taskbar is enabled.
instrumentation.runOnMainSync {
assumeTrue(
LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent
)
}
// Process secure setting annotations.
instrumentation.runOnMainSync {
userSetupCompleteRule.putInt(
if (description.getAnnotation(UserSetupMode::class.java) != null) {
0
} else {
1
}
)
kidsModeRule.putInt(
if (description.getAnnotation(NavBarKidsMode::class.java) != null) 1 else 0
)
}
// Check for existing Taskbar instance from Launcher process.
val launcherTaskbarManager: TaskbarManager? =
if (!isRunningInRobolectric) {
@@ -105,8 +134,8 @@ class TaskbarUnitTestRule(
null
}
instrumentation.runOnMainSync {
taskbarManager =
taskbarManager =
TestUtil.getOnUiThread {
TaskbarManager(
context,
AllAppsActionManager(context, UI_HELPER_EXECUTOR) {
@@ -114,12 +143,14 @@ class TaskbarUnitTestRule(
},
object : TaskbarNavButtonCallbacks {},
)
}
}
try {
LauncherAppState.getInstance(context).model.loadModelSync()
// Replace Launcher Taskbar window with test instance.
instrumentation.runOnMainSync {
launcherTaskbarManager?.removeTaskbarRootViewFromWindow()
launcherTaskbarManager?.setSuspended(true)
taskbarManager.onUserUnlocked() // Required to complete initialization.
}
@@ -129,7 +160,7 @@ class TaskbarUnitTestRule(
// Revert Taskbar window.
instrumentation.runOnMainSync {
taskbarManager.destroy()
launcherTaskbarManager?.addTaskbarRootViewToWindow()
launcherTaskbarManager?.setSuspended(false)
}
}
}
@@ -167,4 +198,35 @@ class TaskbarUnitTestRule(
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class InjectController
/** Overrides [USER_SETUP_COMPLETE] to be `false` for tests. */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class UserSetupMode
/** Overrides [NAV_BAR_KIDS_MODE] to be `true` for tests. */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class NavBarKidsMode
/** Rule for Taskbar integer-based secure settings. */
private inner class TaskbarSecureSettingRule(private val settingName: String) : TestRule {
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
val originalValue =
Settings.Secure.getInt(context.contentResolver, settingName, /* def= */ 0)
try {
base.evaluate()
} finally {
instrumentation.runOnMainSync { putInt(originalValue) }
}
}
}
}
/** Puts [value] into secure settings under [settingName]. */
fun putInt(value: Int) = Settings.Secure.putInt(context.contentResolver, settingName, value)
}
}
@@ -22,6 +22,8 @@ import com.android.launcher3.taskbar.TaskbarKeyguardController
import com.android.launcher3.taskbar.TaskbarManager
import com.android.launcher3.taskbar.TaskbarStashController
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.NavBarKidsMode
import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.UserSetupMode
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
import com.google.common.truth.Truth.assertThat
@@ -125,9 +127,40 @@ class TaskbarUnitTestRuleTest {
}
}
/** Executes [runTest] after the [testRule] setup phase completes. */
@Test
fun testUserSetupMode_default_isComplete() {
onSetup { assertThat(activityContext.isUserSetupComplete).isTrue() }
}
@Test
fun testUserSetupMode_withAnnotation_isIncomplete() {
@UserSetupMode class Mode
onSetup(description = Description.createSuiteDescription(Mode::class.java)) {
assertThat(activityContext.isUserSetupComplete).isFalse()
}
}
@Test
fun testNavBarKidsMode_default_navBarNotForcedVisible() {
onSetup { assertThat(activityContext.isNavBarForceVisible).isFalse() }
}
@Test
fun testNavBarKidsMode_withAnnotation_navBarForcedVisible() {
@NavBarKidsMode class Mode
onSetup(description = Description.createSuiteDescription(Mode::class.java)) {
assertThat(activityContext.isNavBarForceVisible).isTrue()
}
}
/**
* Executes [runTest] after the [testRule] setup phase completes.
*
* A [description] can also be provided to mimic annotating a test or test class.
*/
private fun onSetup(
testRule: TaskbarUnitTestRule = TaskbarUnitTestRule(this, context),
description: Description = DESCRIPTION,
runTest: TaskbarUnitTestRule.() -> Unit,
) {
testRule
@@ -135,7 +168,7 @@ class TaskbarUnitTestRuleTest {
object : Statement() {
override fun evaluate() = runTest(testRule)
},
DESCRIPTION,
description,
)
.evaluate()
}
@@ -32,7 +32,7 @@ class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
var shouldLoadSynchronously: Boolean = true
/** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */
override fun updateThumbnailInBackground(
override fun getThumbnailInBackground(
task: Task,
callback: Consumer<ThumbnailData>
): CancellableTask<ThumbnailData>? {
@@ -87,6 +87,7 @@ public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
when(taskbarActivityContext.getDragLayer()).thenReturn(mTaskbarDragLayer);
when(taskbarActivityContext.getMainLooper()).thenReturn(context.getMainLooper());
when(taskbarActivityContext.getDisplay()).thenReturn(mDisplay);
when(taskbarActivityContext.isIconAlignedWithHotseat()).thenReturn(false);
when(mTaskbarDragLayer.getChildCount()).thenReturn(1);
mSpyFolderView = spy(new Folder(new ActivityContextWrapper(context), null));
@@ -243,6 +244,21 @@ public class TaskbarHoverToolTipControllerTest extends TaskbarBaseTestCase {
false);
}
@Test
public void onHover_hoverEnterIconAlignedWithHotseat_noReveal() {
when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
when(taskbarActivityContext.isIconAlignedWithHotseat()).thenReturn(true);
boolean hoverHandled =
mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
waitForIdleSync();
assertThat(hoverHandled).isTrue();
verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
true);
}
private void waitForIdleSync() {
mTestableLooper.processAllMessages();
}
+1
View File
@@ -771,6 +771,7 @@ public class Launcher extends StatefulActivity<LauncherState>
// initialized properly.
onSaveInstanceState(new Bundle());
mModel.rebindCallbacks();
updateDisallowBack();
} finally {
Trace.endSection();
}
+22
View File
@@ -380,6 +380,28 @@ public final class Utilities {
outPivot.y = dst.top + dst.height() * pivotYPct;
}
/**
* Scales a {@code RectF} in place about a specified pivot point.
*
* <p>This method modifies the given {@code RectF} directly to scale it proportionally
* by the given {@code scale}, while preserving its center at the specified
* {@code (pivotX, pivotY)} coordinates.
*
* @param rectF the {@code RectF} to scale, modified directly.
* @param pivotX the x-coordinate of the pivot point about which to scale.
* @param pivotY the y-coordinate of the pivot point about which to scale.
* @param scale the factor by which to scale the rectangle. Values less than 1 will
* shrink the rectangle, while values greater than 1 will enlarge it.
*/
public static void scaleRectFAboutPivot(RectF rectF, float pivotX, float pivotY, float scale) {
rectF.offset(-pivotX, -pivotY);
rectF.left *= scale;
rectF.top *= scale;
rectF.right *= scale;
rectF.bottom *= scale;
rectF.offset(pivotX, pivotY);
}
/**
* Maps t from one range to another range.
* @param t The value to map.
@@ -18,10 +18,12 @@ package com.android.launcher3.apppairs;
import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -54,6 +56,26 @@ import java.util.function.Predicate;
public class AppPairIcon extends FrameLayout implements DraggableView, Reorderable {
private static final String TAG = "AppPairIcon";
// The duration of the scaling animation on hover enter/exit.
private static final int HOVER_SCALE_DURATION = 150;
// The default scale of the icon when not hovered.
private static final Float HOVER_SCALE_DEFAULT = 1f;
// The max scale of the icon when hovered.
private static final Float HOVER_SCALE_MAX = 1.1f;
// Animates the scale of the icon background on hover.
private static final FloatProperty<AppPairIcon> HOVER_SCALE_PROPERTY =
new FloatProperty<>("hoverScale") {
@Override
public void setValue(AppPairIcon view, float scale) {
view.mIconGraphic.setHoverScale(scale);
}
@Override
public Float get(AppPairIcon view) {
return view.mIconGraphic.getHoverScale();
}
};
// A view that holds the app pair icon graphic.
private AppPairIconGraphic mIconGraphic;
// A view that holds the app pair's title.
@@ -250,4 +272,14 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
public void onHoverChanged(boolean hovered) {
super.onHoverChanged(hovered);
ObjectAnimator
.ofFloat(this, HOVER_SCALE_PROPERTY,
hovered ? HOVER_SCALE_MAX : HOVER_SCALE_DEFAULT)
.setDuration(HOVER_SCALE_DURATION)
.start();
}
}
@@ -26,6 +26,7 @@ import android.os.Build;
import androidx.annotation.NonNull;
import com.android.launcher3.Utilities;
import com.android.launcher3.icons.FastBitmapDrawable;
/**
@@ -128,6 +129,18 @@ public class AppPairIconDrawable extends Drawable {
height - (mP.getStandardIconPadding() + mP.getOuterPadding())
);
// Scale each background from its center edge closest to the center channel.
Utilities.scaleRectFAboutPivot(
leftSide,
leftSide.left + leftSide.width(),
leftSide.top + leftSide.centerY(),
mP.getHoverScale());
Utilities.scaleRectFAboutPivot(
rightSide,
rightSide.left,
rightSide.top + rightSide.centerY(),
mP.getHoverScale());
drawCustomRoundedRect(canvas, leftSide, new float[]{
mP.getBigRadius(), mP.getBigRadius(),
mP.getSmallRadius(), mP.getSmallRadius(),
@@ -163,6 +176,18 @@ public class AppPairIconDrawable extends Drawable {
height - (mP.getStandardIconPadding() + mP.getOuterPadding())
);
// Scale each background from its center edge closest to the center channel.
Utilities.scaleRectFAboutPivot(
topSide,
topSide.left + topSide.centerX(),
topSide.top + topSide.height(),
mP.getHoverScale());
Utilities.scaleRectFAboutPivot(
bottomSide,
bottomSide.left + bottomSide.centerX(),
bottomSide.top,
mP.getHoverScale());
drawCustomRoundedRect(canvas, topSide, new float[]{
mP.getBigRadius(), mP.getBigRadius(),
mP.getBigRadius(), mP.getBigRadius(),
@@ -64,6 +64,8 @@ class AppPairIconDrawingParams(val context: Context, container: Int) {
var isLeftRightSplit: Boolean = true
// The background paint color (based on container).
var bgColor: Int = 0
// The scale of the icon background while hovered.
var hoverScale: Float = 1f
init {
val activity: ActivityContext = ActivityContext.lookupContext(context)
@@ -139,4 +139,19 @@ constructor(context: Context, attrs: AttributeSet? = null) :
super.dispatchDraw(canvas)
drawable.draw(canvas)
}
/**
* Sets the scale of the icon background while hovered.
*/
fun setHoverScale(scale: Float) {
drawParams.hoverScale = scale
redraw()
}
/**
* Gets the scale of the icon background while hovered.
*/
fun getHoverScale(): Float {
return drawParams.hoverScale
}
}
@@ -16,6 +16,8 @@
package com.android.launcher3.ui.workspace;
import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -38,6 +40,7 @@ import com.android.launcher3.tapl.HomeAppIcon;
import com.android.launcher3.tapl.HomeAppIconMenuItem;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -111,6 +114,7 @@ public class TaplThemeIconsTest extends AbstractLauncherUiTest<Launcher> {
}
@Test
@TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/350557998
public void testShortcutIconWithTheme() throws Exception {
setThemeEnabled(true);
initialize(this);