Snap for 12052458 from a00c81c173 to 24Q4-release
Change-Id: Ic1df60e993f1a962fe220a951c136c0a12b3d25c
This commit is contained in:
@@ -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 {
|
||||
|
||||
+63
-53
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+67
-69
@@ -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()
|
||||
}
|
||||
|
||||
+19
-16
@@ -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()
|
||||
}
|
||||
|
||||
+72
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+35
-2
@@ -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()
|
||||
}
|
||||
|
||||
+1
-1
@@ -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>? {
|
||||
|
||||
+16
@@ -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();
|
||||
}
|
||||
|
||||
@@ -771,6 +771,7 @@ public class Launcher extends StatefulActivity<LauncherState>
|
||||
// initialized properly.
|
||||
onSaveInstanceState(new Bundle());
|
||||
mModel.rebindCallbacks();
|
||||
updateDisallowBack();
|
||||
} finally {
|
||||
Trace.endSection();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user