Merge "Separate getTaskViewAt usage" into sc-v2-dev
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 0f92274..25b39ed 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS"/>
<uses-permission android:name="android.permission.REMOVE_TASKS"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
<uses-permission android:name="android.permission.STATUS_BAR"/>
<uses-permission android:name="android.permission.STOP_APP_SWITCHES"/>
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index e82c900..f9a0bb1 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -161,8 +161,7 @@
}
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
for (ItemInfo info : itemsIdMap) {
- FolderInfo parent = info.container > 0
- ? (FolderInfo) itemsIdMap.get(info.container) : null;
+ FolderInfo parent = getContainer(info, itemsIdMap);
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
}
additionalSnapshotEvents(instanceId);
@@ -199,8 +198,7 @@
}
for (ItemInfo info : itemsIdMap) {
- FolderInfo parent = info.container > 0
- ? (FolderInfo) itemsIdMap.get(info.container) : null;
+ FolderInfo parent = getContainer(info, itemsIdMap);
LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
Log.d(TAG, itemInfo.toString());
StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
@@ -222,6 +220,22 @@
}
}
+ private static FolderInfo getContainer(ItemInfo info, IntSparseArrayMap<ItemInfo> itemsIdMap) {
+ if (info.container > 0) {
+ ItemInfo containerInfo = itemsIdMap.get(info.container);
+
+ if (!(containerInfo instanceof FolderInfo)) {
+ Log.e(TAG, String.format(
+ "Item info: %s found with invalid container: %s",
+ info,
+ containerInfo));
+ } else {
+ return (FolderInfo) containerInfo;
+ }
+ }
+ return null;
+ }
+
@Override
public void validateData() {
super.validateData();
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 90c035f..f1e6747 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -60,6 +60,7 @@
@Override
protected void onDestroy() {
+ super.onDestroy();
mRecentsActivity.setTaskbarUIController(null);
mRecentsActivity.getStateManager().removeStateListener(mStateListener);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 7d23439..2622700 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -99,6 +99,7 @@
@Override
protected void onDestroy() {
+ super.onDestroy();
onLauncherResumedOrPaused(false);
mTaskbarLauncherStateController.onDestroy();
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index f6e0426..ce1e8b6b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -31,12 +31,14 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.annotation.DrawableRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
+import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.graphics.Rect;
import android.graphics.Region;
@@ -85,6 +87,7 @@
private static final int FLAG_DISABLE_RECENTS = 1 << 8;
private static final int FLAG_DISABLE_BACK = 1 << 9;
private static final int FLAG_NOTIFICATION_SHADE_EXPANDED = 1 << 10;
+ private static final int FLAG_SCREEN_PINNING_ACTIVE = 1 << 10;
private static final int MASK_IME_SWITCHER_VISIBLE = FLAG_SWITCHER_SUPPORTED | FLAG_IME_VISIBLE;
@@ -151,7 +154,9 @@
mPropertyHolders.add(new StatePropertyHolder(
mControllers.taskbarViewController.getTaskbarIconAlpha()
.getProperty(ALPHA_INDEX_KEYGUARD),
- flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0, MultiValueAlpha.VALUE, 1, 0));
+ flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0
+ && (flags & FLAG_SCREEN_PINNING_ACTIVE) == 0,
+ MultiValueAlpha.VALUE, 1, 0));
mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
.getKeyguardBgTaskbar(),
@@ -285,6 +290,7 @@
int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
boolean isNotificationShadeExpanded = (sysUiStateFlags & shadeExpandedFlags) != 0;
+ boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
// TODO(b/202218289) we're getting IME as not visible on lockscreen from system
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
@@ -294,6 +300,7 @@
updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
updateStateForFlag(FLAG_DISABLE_BACK, isBackDisabled);
updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
+ updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
if (mA11yButton != null) {
// Only used in 3 button
@@ -445,6 +452,12 @@
return mFloatingRotationButtonBounds.contains((int) ev.getX(), (int) ev.getY());
}
+ public void onConfigurationChanged(@Config int configChanges) {
+ if (mFloatingRotationButton != null) {
+ mFloatingRotationButton.onConfigurationChanged(configChanges);
+ }
+ }
+
public void onDestroy() {
mPropertyHolders.clear();
mControllers.rotationButtonController.unregisterListeners();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2e1e5bb..692352b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -30,6 +30,7 @@
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ActivityInfo.Config;
import android.content.pm.LauncherApps;
import android.graphics.Insets;
import android.graphics.PixelFormat;
@@ -205,6 +206,10 @@
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
}
+ public void onConfigurationChanged(@Config int configChanges) {
+ mControllers.onConfigurationChanged(configChanges);
+ }
+
public boolean isThreeButtonNav() {
return mNavMode == Mode.THREE_BUTTONS;
}
@@ -356,6 +361,7 @@
mControllers.taskbarStashController.updateStateForSysuiFlags(systemUiStateFlags, fromInit);
mControllers.taskbarScrimViewController.updateStateForSysuiFlags(systemUiStateFlags,
fromInit);
+ mControllers.navButtonController.updateSysuiFlags(systemUiStateFlags);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 56730db..c43fbf9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -15,10 +15,15 @@
*/
package com.android.launcher3.taskbar;
+import android.content.pm.ActivityInfo.Config;
+
import androidx.annotation.NonNull;
import com.android.systemui.shared.rotation.RotationButtonController;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Hosts various taskbar controllers to facilitate passing between one another.
*/
@@ -43,6 +48,9 @@
/** Do not store this controller, as it may change at runtime. */
@NonNull public TaskbarUIController uiController = TaskbarUIController.DEFAULT;
+ private boolean mAreAllControllersInitialized;
+ private final List<Runnable> mPostInitCallbacks = new ArrayList<>();
+
public TaskbarControllers(TaskbarActivityContext taskbarActivityContext,
TaskbarDragController taskbarDragController,
TaskbarNavButtonController navButtonController,
@@ -81,6 +89,8 @@
* in constructors for now, as some controllers may still be waiting for init().
*/
public void init(TaskbarSharedState sharedState) {
+ mAreAllControllersInitialized = false;
+
taskbarDragController.init(this);
navbarButtonsViewController.init(this);
rotationButtonController.init();
@@ -92,6 +102,16 @@
stashedHandleViewController.init(this);
taskbarStashController.init(this, sharedState);
taskbarEduController.init(this);
+
+ mAreAllControllersInitialized = true;
+ for (Runnable postInitCallback : mPostInitCallbacks) {
+ postInitCallback.run();
+ }
+ mPostInitCallbacks.clear();
+ }
+
+ public void onConfigurationChanged(@Config int configChanges) {
+ navbarButtonsViewController.onConfigurationChanged(configChanges);
}
/**
@@ -108,4 +128,17 @@
stashedHandleViewController.onDestroy();
taskbarAutohideSuspendController.onDestroy();
}
+
+ /**
+ * If all controllers are already initialized, runs the given callback immediately. Otherwise,
+ * queues it to run after calling init() on all controllers. This should likely be used in any
+ * case where one controller is telling another controller to do something inside init().
+ */
+ public void runAfterInit(Runnable callback) {
+ if (mAreAllControllersInitialized) {
+ callback.run();
+ } else {
+ mPostInitCallbacks.add(callback);
+ }
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index a65cc56..3cdcdf7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -29,8 +29,8 @@
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.net.Uri;
+import android.os.Handler;
import android.provider.Settings;
-import android.util.Log;
import android.view.Display;
import androidx.annotation.NonNull;
@@ -93,7 +93,8 @@
Display display =
service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY);
mContext = service.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null);
- mNavButtonController = new TaskbarNavButtonController(service);
+ mNavButtonController = new TaskbarNavButtonController(service,
+ SystemUiProxy.INSTANCE.get(mContext), new Handler());
mUserSetupCompleteListener = isUserSetupComplete -> recreateTaskbar();
mComponentCallbacks = new ComponentCallbacks() {
private Configuration mOldConfig = mContext.getResources().getConfiguration();
@@ -106,6 +107,11 @@
if ((configDiff & configsRequiringRecreate) != 0) {
// Color has changed, recreate taskbar to reload background color & icons.
recreateTaskbar();
+ } else {
+ // Config change might be handled without re-creating the taskbar
+ if (mTaskbarActivityContext != null) {
+ mTaskbarActivityContext.onConfigurationChanged(configDiff);
+ }
}
mOldConfig = newConfig;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 6dcfe56..37a9b5e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -184,9 +184,12 @@
}
mContainer.updateHotseatItems(hotseatItemInfos);
- mControllers.taskbarStashController.updateStateForFlag(
- TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, isHotseatEmpty);
- mControllers.taskbarStashController.applyState();
+ final boolean finalIsHotseatEmpty = isHotseatEmpty;
+ mControllers.runAfterInit(() -> {
+ mControllers.taskbarStashController.updateStateForFlag(
+ TaskbarStashController.FLAG_STASHED_IN_APP_EMPTY, finalIsHotseatEmpty);
+ mControllers.taskbarStashController.applyState();
+ });
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index ae23eda..d233365 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -19,8 +19,10 @@
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import android.os.Bundle;
+import android.os.Handler;
import androidx.annotation.IntDef;
@@ -40,6 +42,13 @@
*/
public class TaskbarNavButtonController {
+ /** Allow some time in between the long press for back and recents. */
+ static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200;
+ static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100;
+
+ private long mLastScreenPinLongPress;
+ private boolean mScreenPinned;
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
BUTTON_BACK,
@@ -57,10 +66,20 @@
static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1;
static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1;
- private final TouchInteractionService mService;
+ private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS;
+ private int mLongPressedButtons = 0;
- public TaskbarNavButtonController(TouchInteractionService service) {
+ private final TouchInteractionService mService;
+ private final SystemUiProxy mSystemUiProxy;
+ private final Handler mHandler;
+
+ private final Runnable mResetLongPress = this::resetScreenUnpin;
+
+ public TaskbarNavButtonController(TouchInteractionService service,
+ SystemUiProxy systemUiProxy, Handler handler) {
mService = service;
+ mSystemUiProxy = systemUiProxy;
+ mHandler = handler;
}
public void onButtonClick(@TaskbarButton int buttonType) {
@@ -72,13 +91,13 @@
navigateHome();
break;
case BUTTON_RECENTS:
- navigateToOverview();;
+ navigateToOverview();
break;
case BUTTON_IME_SWITCH:
showIMESwitcher();
break;
case BUTTON_A11Y:
- notifyImeClick(false /* longClick */);
+ notifyA11yClick(false /* longClick */);
break;
}
}
@@ -89,46 +108,98 @@
startAssistant();
return true;
case BUTTON_A11Y:
- notifyImeClick(true /* longClick */);
+ notifyA11yClick(true /* longClick */);
return true;
case BUTTON_BACK:
- case BUTTON_IME_SWITCH:
case BUTTON_RECENTS:
+ mLongPressedButtons |= buttonType;
+ return determineScreenUnpin();
+ case BUTTON_IME_SWITCH:
default:
return false;
}
}
+ /**
+ * Checks if the user has long pressed back and recents buttons
+ * "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms
+ * If so, then requests the system to turn off screen pinning.
+ *
+ * @return true if the long press is a valid user action in attempting to unpin an app
+ * Will always return {@code false} when screen pinning is not active.
+ * NOTE: Returning true does not mean that screen pinning has stopped
+ */
+ private boolean determineScreenUnpin() {
+ long timeNow = System.currentTimeMillis();
+ if (!mScreenPinned) {
+ return false;
+ }
+
+ if (mLastScreenPinLongPress == 0) {
+ // First button long press registered, just mark time and wait for second button press
+ mLastScreenPinLongPress = System.currentTimeMillis();
+ mHandler.postDelayed(mResetLongPress, SCREEN_PIN_LONG_PRESS_RESET);
+ return true;
+ }
+
+ if ((timeNow - mLastScreenPinLongPress) > SCREEN_PIN_LONG_PRESS_THRESHOLD) {
+ // Too long in-between presses, reset the clock
+ resetScreenUnpin();
+ return false;
+ }
+
+ if ((mLongPressedButtons & SCREEN_UNPIN_COMBO) == SCREEN_UNPIN_COMBO) {
+ // Hooray! They did it (finally...)
+ mSystemUiProxy.stopScreenPinning();
+ mHandler.removeCallbacks(mResetLongPress);
+ resetScreenUnpin();
+ }
+ return true;
+ }
+
+ private void resetScreenUnpin() {
+ mLongPressedButtons = 0;
+ mLastScreenPinLongPress = 0;
+ }
+
+ public void updateSysuiFlags(int sysuiFlags) {
+ mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+ }
+
private void navigateHome() {
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
}
private void navigateToOverview() {
+ if (mScreenPinned) {
+ return;
+ }
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE);
}
private void executeBack() {
- SystemUiProxy.INSTANCE.getNoCreate().onBackPressed();
+ mSystemUiProxy.onBackPressed();
}
private void showIMESwitcher() {
- SystemUiProxy.INSTANCE.getNoCreate().onImeSwitcherPressed();
+ mSystemUiProxy.onImeSwitcherPressed();
}
- private void notifyImeClick(boolean longClick) {
- SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+ private void notifyA11yClick(boolean longClick) {
if (longClick) {
- systemUiProxy.notifyAccessibilityButtonLongClicked();
+ mSystemUiProxy.notifyAccessibilityButtonLongClicked();
} else {
- systemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
+ mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId());
}
}
private void startAssistant() {
+ if (mScreenPinned) {
+ return;
+ }
Bundle args = new Bundle();
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
- SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
- systemUiProxy.startAssistant(args);
+ mSystemUiProxy.startAssistant(args);
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f713dca..f6bc785 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -18,6 +18,8 @@
import android.graphics.Rect;
import android.view.View;
+import androidx.annotation.CallSuper;
+
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -33,11 +35,15 @@
// Initialized in init.
protected TaskbarControllers mControllers;
+ @CallSuper
protected void init(TaskbarControllers taskbarControllers) {
mControllers = taskbarControllers;
}
- protected void onDestroy() { }
+ @CallSuper
+ protected void onDestroy() {
+ mControllers = null;
+ }
protected boolean isTaskbarTouchable() {
return true;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index d74b6c5..c85b256 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -34,6 +34,7 @@
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
import android.util.FloatProperty;
+import android.util.Pair;
import androidx.annotation.NonNull;
@@ -101,11 +102,13 @@
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
PagedOrientationHandler orientationHandler =
((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
- FloatProperty taskViewsFloat = orientationHandler.getSplitSelectTaskOffset(
- TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
- mLauncher.getDeviceProfile());
- setter.setFloat(mRecentsView, taskViewsFloat,
+ Pair<FloatProperty, FloatProperty> taskViewsFloat =
+ orientationHandler.getSplitSelectTaskOffset(
+ TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
+ mLauncher.getDeviceProfile());
+ setter.setFloat(mRecentsView, taskViewsFloat.first,
toState.getSplitSelectTranslation(mLauncher), LINEAR);
+ setter.setFloat(mRecentsView, taskViewsFloat.second, 0, LINEAR);
setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 097850f..c5f4a53 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -36,7 +36,6 @@
import com.android.wm.shell.util.GroupedRecentTaskInfo;
import com.android.wm.shell.util.StagedSplitBounds;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
@@ -220,26 +219,6 @@
return newTasks;
}
- public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + "RecentTasksList:");
- writer.println(prefix + " mChangeId=" + mChangeId);
- writer.println(prefix + " mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
- for (GroupTask task : mResultsUi) {
- writer.println(prefix + " t1=" + task.task1.key.id
- + " t2=" + (task.hasMultipleTasks() ? task.task2.key.id : "-1"));
- }
- writer.println(prefix + " ]");
- int currentUserId = Process.myUserHandle().getIdentifier();
- ArrayList<GroupedRecentTaskInfo> rawTasks =
- mSysUiProxy.getRecentTasks(Integer.MAX_VALUE, currentUserId);
- writer.println(prefix + " rawTasks=[");
- for (GroupedRecentTaskInfo task : rawTasks) {
- writer.println(prefix + " t1=" + task.mTaskInfo1.taskId
- + " t2=" + (task.mTaskInfo2 != null ? task.mTaskInfo2.taskId : "-1"));
- }
- writer.println(prefix + " ]");
- }
-
private static class TaskLoadResult extends ArrayList<GroupTask> {
final int mRequestId;
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 5d77a6e..e539a8c 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -43,7 +43,6 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
-import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -221,11 +220,6 @@
mThumbnailChangeListeners.remove(listener);
}
- public void dump(String prefix, PrintWriter writer) {
- writer.println(prefix + "RecentsModel:");
- mTaskList.dump(" ", writer);
- }
-
/**
* Listener for receiving various task properties changes
*/
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index c8abd14..6c623bc 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -83,16 +83,14 @@
MAIN_EXECUTOR.execute(() -> clearProxy());
};
- // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
- // yet, and we'll need to set/register these listeners with SysUI when they do. Note that it is
- // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
- // in case SysUI needs to rebind.
- private IPipAnimationListener mPipAnimationListener;
- private ISplitScreenListener mSplitScreenListener;
- private IStartingWindowListener mStartingWindowListener;
- private ISmartspaceCallback mSmartspaceCallback;
- private IRecentTasksListener mRecentTasksListener;
- private final ArrayList<RemoteTransitionCompat> mRemoteTransitions = new ArrayList<>();
+ // Save the listeners passed into the proxy since when set/register these listeners,
+ // setProxy may not have been called, eg. OverviewProxyService is not connected yet.
+ private IPipAnimationListener mPendingPipAnimationListener;
+ private ISplitScreenListener mPendingSplitScreenListener;
+ private IStartingWindowListener mPendingStartingWindowListener;
+ private ISmartspaceCallback mPendingSmartspaceCallback;
+ private IRecentTasksListener mPendingRecentTasksListener;
+ private final ArrayList<RemoteTransitionCompat> mPendingRemoteTransitions = new ArrayList<>();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -169,23 +167,29 @@
mRecentTasks = recentTasks;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
- if (mPipAnimationListener != null && mPip != null) {
- setPinnedStackAnimationListener(mPipAnimationListener);
+ if (mPendingPipAnimationListener != null && mPip != null) {
+ setPinnedStackAnimationListener(mPendingPipAnimationListener);
+ mPendingPipAnimationListener = null;
}
- if (mSplitScreenListener != null && mSplitScreen != null) {
- registerSplitScreenListener(mSplitScreenListener);
+ if (mPendingSplitScreenListener != null && mSplitScreen != null) {
+ registerSplitScreenListener(mPendingSplitScreenListener);
+ mPendingSplitScreenListener = null;
}
- if (mStartingWindowListener != null && mStartingWindow != null) {
- setStartingWindowListener(mStartingWindowListener);
+ if (mPendingStartingWindowListener != null && mStartingWindow != null) {
+ setStartingWindowListener(mPendingStartingWindowListener);
+ mPendingStartingWindowListener = null;
}
- if (mSmartspaceCallback != null && mSmartspaceTransitionController != null) {
- setSmartspaceCallback(mSmartspaceCallback);
+ if (mPendingSmartspaceCallback != null && mSmartspaceTransitionController != null) {
+ setSmartspaceCallback(mPendingSmartspaceCallback);
+ mPendingSmartspaceCallback = null;
}
- for (int i = mRemoteTransitions.size() - 1; i >= 0; --i) {
- registerRemoteTransition(mRemoteTransitions.get(i));
+ for (int i = mPendingRemoteTransitions.size() - 1; i >= 0; --i) {
+ registerRemoteTransition(mPendingRemoteTransitions.get(i));
}
- if (mRecentTasksListener != null && mRecentTasks != null) {
- registerRecentTasksListener(mRecentTasksListener);
+ mPendingRemoteTransitions.clear();
+ if (mPendingRecentTasksListener != null && mRecentTasks != null) {
+ registerRecentTasksListener(mPendingRecentTasksListener);
+ mPendingRecentTasksListener = null;
}
if (mPendingSetNavButtonAlpha != null) {
@@ -509,8 +513,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
}
+ } else {
+ mPendingPipAnimationListener = listener;
}
- mPipAnimationListener = listener;
}
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
@@ -548,8 +553,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerSplitScreenListener");
}
+ } else {
+ mPendingSplitScreenListener = listener;
}
- mSplitScreenListener = listener;
}
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
@@ -560,17 +566,17 @@
Log.w(TAG, "Failed call unregisterSplitScreenListener");
}
}
- mSplitScreenListener = null;
+ mPendingSplitScreenListener = null;
}
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
- @SplitConfigurationOptions.StagePosition int sidePosition,
+ @SplitConfigurationOptions.StagePosition int sidePosition, float splitRatio,
RemoteTransitionCompat remoteTransition) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasks(mainTaskId, mainOptions, sideTaskId, sideOptions,
- sidePosition, remoteTransition.getTransition());
+ sidePosition, splitRatio, remoteTransition.getTransition());
} catch (RemoteException e) {
Log.w(TAG, "Failed call startTask");
}
@@ -582,22 +588,22 @@
*/
public void startTasksWithLegacyTransition(int mainTaskId, Bundle mainOptions, int sideTaskId,
Bundle sideOptions, @SplitConfigurationOptions.StagePosition int sidePosition,
- RemoteAnimationAdapter adapter) {
+ float splitRatio, RemoteAnimationAdapter adapter) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasksWithLegacyTransition(mainTaskId, mainOptions, sideTaskId,
- sideOptions, sidePosition, adapter);
+ sideOptions, sidePosition, splitRatio, adapter);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startTasksWithLegacyTransition");
}
}
}
- public void startShortcut(String packageName, String shortcutId, int stage, int position,
+ public void startShortcut(String packageName, String shortcutId, int position,
Bundle options, UserHandle user) {
if (mSplitScreen != null) {
try {
- mSplitScreen.startShortcut(packageName, shortcutId, stage, position, options,
+ mSplitScreen.startShortcut(packageName, shortcutId, position, options,
user);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startShortcut");
@@ -605,11 +611,11 @@
}
}
- public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
+ public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
Bundle options) {
if (mSplitScreen != null) {
try {
- mSplitScreen.startIntent(intent, fillInIntent, stage, position, options);
+ mSplitScreen.startIntent(intent, fillInIntent, position, options);
} catch (RemoteException e) {
Log.w(TAG, "Failed call startIntent");
}
@@ -681,8 +687,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRemoteTransition");
}
+ } else {
+ mPendingRemoteTransitions.add(remoteTransition);
}
- mRemoteTransitions.add(remoteTransition);
}
public void unregisterRemoteTransition(RemoteTransitionCompat remoteTransition) {
@@ -693,7 +700,7 @@
Log.w(TAG, "Failed call registerRemoteTransition");
}
}
- mRemoteTransitions.remove(remoteTransition);
+ mPendingRemoteTransitions.remove(remoteTransition);
}
//
@@ -710,8 +717,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
+ } else {
+ mPendingStartingWindowListener = listener;
}
- mStartingWindowListener = listener;
}
//
@@ -725,8 +733,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call setStartingWindowListener", e);
}
+ } else {
+ mPendingSmartspaceCallback = callback;
}
- mSmartspaceCallback = callback;
}
//
@@ -740,8 +749,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Failed call registerRecentTasksListener", e);
}
+ } else {
+ mPendingRecentTasksListener = listener;
}
- mRecentTasksListener = listener;
}
public void unregisterRecentTasksListener(IRecentTasksListener listener) {
@@ -752,7 +762,7 @@
Log.w(TAG, "Failed call unregisterRecentTasksListener");
}
}
- mRecentTasksListener = null;
+ mPendingRecentTasksListener = null;
}
public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index f6f2cf9..2c7fd71 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -973,7 +973,6 @@
pw.println(" resumed=" + resumed);
pw.println(" mConsumer=" + mConsumer.getName());
ActiveGestureLog.INSTANCE.dump("", pw);
- RecentsModel.INSTANCE.get(this).dump("", pw);
pw.println("ProtoTrace:");
pw.println(" file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 22f67d2..169b208 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -31,18 +31,19 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.quickstep.FallbackActivityInterface;
import com.android.quickstep.GestureState;
import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -73,6 +74,7 @@
@Override
public void startHome() {
mActivity.startHome();
+ AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
}
/**
@@ -206,10 +208,6 @@
@Override
public void onStateTransitionStart(RecentsState toState) {
- if (toState == HOME) {
- // Clean-up logic that occurs when recents is no longer in use/visible.
- reset();
- }
setOverviewStateEnabled(true);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.isFullScreen());
@@ -218,6 +216,10 @@
@Override
public void onStateTransitionComplete(RecentsState finalState) {
+ if (finalState == HOME) {
+ // Clean-up logic that occurs when recents is no longer in use/visible.
+ reset();
+ }
boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
setOverlayEnabled(isOverlayEnabled);
setFreezeViewVisibility(false);
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c784d82..d310893 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -30,7 +31,6 @@
import android.view.SurfaceControl;
import android.window.TransitionInfo;
-
import androidx.annotation.Nullable;
import com.android.launcher3.statehandlers.DepthController;
@@ -95,7 +95,7 @@
public void setSecondTaskId(Task task, Consumer<Boolean> callback) {
mSecondTask = task;
launchTasks(mInitialTask, mSecondTask, mStagePosition, callback,
- false /* freezeTaskList */);
+ false /* freezeTaskList */, DEFAULT_SPLIT_RATIO);
}
/**
@@ -107,14 +107,15 @@
TaskView.TaskIdAttributeContainer[] taskIdAttributeContainers =
groupedTaskView.getTaskIdAttributeContainers();
launchTasks(taskIdAttributeContainers[0].getTask(), taskIdAttributeContainers[1].getTask(),
- taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList);
+ taskIdAttributeContainers[0].getStagePosition(), callback, freezeTaskList,
+ groupedTaskView.getSplitRatio());
}
/**
* @param stagePosition representing location of task1
*/
public void launchTasks(Task task1, Task task2, @StagePosition int stagePosition,
- Consumer<Boolean> callback, boolean freezeTaskList) {
+ Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
// Assume initial task is for top/left part of screen
final int[] taskIds = stagePosition == STAGE_POSITION_TOP_OR_LEFT
? new int[]{task1.key.id, task2.key.id}
@@ -123,7 +124,7 @@
RemoteSplitLaunchTransitionRunner animationRunner =
new RemoteSplitLaunchTransitionRunner(task1, task2);
mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
- null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
+ null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT, splitRatio,
new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR,
ActivityThread.currentActivityThread().getApplicationThread()));
} else {
@@ -140,7 +141,7 @@
}
mSystemUiProxy.startTasksWithLegacyTransition(taskIds[0], mainOpts.toBundle(),
taskIds[1], null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
- adapter);
+ splitRatio, adapter);
}
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 4771d1e..b43626b 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -1,5 +1,6 @@
package com.android.quickstep.views;
+import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -122,6 +123,14 @@
invalidate();
}
+ public float getSplitRatio() {
+ if (mSplitBoundsConfig != null) {
+ return mSplitBoundsConfig.appsStackedVertically
+ ? mSplitBoundsConfig.topTaskPercent : mSplitBoundsConfig.leftTaskPercent;
+ }
+ return DEFAULT_SPLIT_RATIO;
+ }
+
@Override
public boolean offerTouchToChildren(MotionEvent event) {
computeAndSetIconTouchDelegate(mIconView2, mIcon2CenterCoords, mIcon2TouchDelegate);
@@ -148,16 +157,27 @@
@Nullable
@Override
public RunnableList launchTaskAnimated() {
- getRecentsView().getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
- null /*callback*/,
+ if (mTask == null || mSecondaryTask == null) {
+ return null;
+ }
+
+ RunnableList endCallback = new RunnableList();
+ RecentsView recentsView = getRecentsView();
+ // Callbacks run from remote animation when recents animation not currently running
+ recentsView.getSplitPlaceholder().launchTasks(this /*groupedTaskView*/,
+ success -> endCallback.executeAllAndDestroy(),
false /* freezeTaskList */);
- return null;
+
+ // Callbacks get run from recentsView for case when recents animation already running
+ recentsView.addSideTaskLaunchCallback(endCallback);
+ return endCallback;
}
@Override
public void launchTask(@NonNull Consumer<Boolean> callback, boolean freezeTaskList) {
getRecentsView().getSplitPlaceholder().launchTasks(mTask, mSecondaryTask,
- STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList);
+ STAGE_POSITION_TOP_OR_LEFT, callback, freezeTaskList,
+ getSplitRatio());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index a2e9e57..3e06f55 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -32,6 +32,7 @@
import androidx.annotation.Nullable;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.statehandlers.DepthController;
@@ -70,6 +71,7 @@
@Override
public void startHome() {
mActivity.getStateManager().goToState(NORMAL);
+ AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
}
@Override
@@ -92,10 +94,6 @@
@Override
public void onStateTransitionStart(LauncherState toState) {
- if (toState == NORMAL || toState == SPRING_LOADED) {
- // Clean-up logic that occurs when recents is no longer in use/visible.
- reset();
- }
setOverviewStateEnabled(toState.overviewUi);
setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
@@ -104,6 +102,10 @@
@Override
public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState == NORMAL || finalState == SPRING_LOADED) {
+ // Clean-up logic that occurs when recents is no longer in use/visible.
+ reset();
+ }
boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
setOverlayEnabled(isOverlayEnabled);
setFreezeViewVisibility(false);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index e270cb7..9cf76b3 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1510,6 +1510,16 @@
}
}
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+ // resetTaskVisuals is called at the end of dismiss animation which could update
+ // primary and secondary translation of the live tile cut out. We will need to do so
+ // here accordingly.
+ runActionOnRemoteHandles(remoteTargetHandle -> {
+ TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator();
+ simulator.taskPrimaryTranslation.value = 0;
+ simulator.taskSecondaryTranslation.value = 0;
+ simulator.fullScreenProgress.value = 0;
+ simulator.recentsViewScale.value = 1;
+ });
// Similar to setRunningTaskHidden below, reapply the state before runningTaskView is
// null.
if (!mRunningTaskShowScreenshot) {
@@ -3187,6 +3197,7 @@
}
}
}
+ pageBeginTransition();
setCurrentPage(pageToSnapTo);
// Update various scroll-dependent UI.
dispatchScrollChanged();
@@ -4034,7 +4045,7 @@
/** TODO(b/181707736) More gracefully handle exiting split selection state */
private void resetFromSplitSelectionState() {
- if (!showAsGrid()) {
+ if (!mActivity.getDeviceProfile().overviewShowAsGrid) {
int pageToSnapTo = mCurrentPage;
if (mSplitHiddenTaskViewIndex <= pageToSnapTo) {
pageToSnapTo += 1;
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index d91669a..da92551 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -19,6 +19,7 @@
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+import static com.android.launcher3.Utilities.comp;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
@@ -290,8 +291,17 @@
float cornerRadius) {
if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
- canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
- canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
+ // TODO(b/189265196): Temporary fix to align the surface with the cutout perfectly.
+ // Round up only when the live tile task is displayed in Overview.
+ float rounding = comp(mFullscreenParams.mFullscreenProgress);
+ float left = x + rounding / 2;
+ float top = y + rounding / 2;
+ float right = width - rounding;
+ float bottom = height - rounding;
+
+ canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
+ mClearPaint);
+ canvas.drawRoundRect(left, top, right, bottom, cornerRadius, cornerRadius,
mDimmingPaintAfterClearing);
return;
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index e8077cf..cca9ecf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -599,7 +599,9 @@
if (confirmSecondSplitSelectApp()) {
return;
}
- if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+ RecentsView recentsView = getRecentsView();
+ RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
+ if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask() && remoteTargetHandles != null) {
if (!mIsClickableAsLiveTile) {
return;
}
@@ -612,9 +614,7 @@
}
mIsClickableAsLiveTile = false;
- RecentsView recentsView = getRecentsView();
RemoteAnimationTargets targets;
- RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
if (remoteTargetHandles.length == 1) {
targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
} else {
@@ -1531,6 +1531,7 @@
private final float mCornerRadius;
private final float mWindowCornerRadius;
+ public float mFullscreenProgress;
public RectF mCurrentDrawnInsets = new RectF();
public float mCurrentDrawnCornerRadius;
/** The current scale we apply to the thumbnail to adjust for new left/right insets. */
@@ -1548,6 +1549,8 @@
*/
public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale,
int previewWidth, DeviceProfile dp, PreviewPositionHelper pph) {
+ mFullscreenProgress = fullscreenProgress;
+
RectF insets = pph.getInsetsToDrawInFullscreen(dp);
float currentInsetsLeft = insets.left * fullscreenProgress;
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
new file mode 100644
index 0000000..ba1a60d
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -0,0 +1,159 @@
+package com.android.launcher3.taskbar;
+
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_A11Y;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS;
+import static com.android.launcher3.taskbar.TaskbarNavButtonController.SCREEN_PIN_LONG_PRESS_THRESHOLD;
+import static com.android.quickstep.OverviewCommandHelper.TYPE_HOME;
+import static com.android.quickstep.OverviewCommandHelper.TYPE_TOGGLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TouchInteractionService;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class TaskbarNavButtonControllerTest {
+
+ private final static int DISPLAY_ID = 2;
+
+ @Mock
+ SystemUiProxy mockSystemUiProxy;
+ @Mock
+ TouchInteractionService mockService;
+ @Mock
+ OverviewCommandHelper mockCommandHelper;
+ @Mock
+ Handler mockHandler;
+
+ private TaskbarNavButtonController mNavButtonController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mockService.getDisplayId()).thenReturn(DISPLAY_ID);
+ when(mockService.getOverviewCommandHelper()).thenReturn(mockCommandHelper);
+ mNavButtonController = new TaskbarNavButtonController(mockService,
+ mockSystemUiProxy, mockHandler);
+ }
+
+ @Test
+ public void testPressBack() {
+ mNavButtonController.onButtonClick(BUTTON_BACK);
+ verify(mockSystemUiProxy, times(1)).onBackPressed();
+ }
+
+ @Test
+ public void testPressImeSwitcher() {
+ mNavButtonController.onButtonClick(BUTTON_IME_SWITCH);
+ verify(mockSystemUiProxy, times(1)).onImeSwitcherPressed();
+ }
+
+ @Test
+ public void testPressA11yShortClick() {
+ mNavButtonController.onButtonClick(BUTTON_A11Y);
+ verify(mockSystemUiProxy, times(1))
+ .notifyAccessibilityButtonClicked(DISPLAY_ID);
+ }
+
+ @Test
+ public void testPressA11yLongClick() {
+ mNavButtonController.onButtonLongClick(BUTTON_A11Y);
+ verify(mockSystemUiProxy, times(1)).notifyAccessibilityButtonLongClicked();
+ }
+
+ @Test
+ public void testLongPressHome() {
+ mNavButtonController.onButtonLongClick(BUTTON_HOME);
+ verify(mockSystemUiProxy, times(1)).startAssistant(any());
+ }
+
+ @Test
+ public void testPressHome() {
+ mNavButtonController.onButtonClick(BUTTON_HOME);
+ verify(mockCommandHelper, times(1)).addCommand(TYPE_HOME);
+ }
+
+ @Test
+ public void testPressRecents() {
+ mNavButtonController.onButtonClick(BUTTON_RECENTS);
+ verify(mockCommandHelper, times(1)).addCommand(TYPE_TOGGLE);
+ }
+
+ @Test
+ public void testPressRecentsWithScreenPinned() {
+ mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
+ mNavButtonController.onButtonClick(BUTTON_RECENTS);
+ verify(mockCommandHelper, times(0)).addCommand(TYPE_TOGGLE);
+ }
+
+ @Test
+ public void testLongPressBackRecentsNotPinned() {
+ mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+ mNavButtonController.onButtonLongClick(BUTTON_BACK);
+ verify(mockSystemUiProxy, times(0)).stopScreenPinning();
+ }
+
+ @Test
+ public void testLongPressBackRecentsPinned() {
+ mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
+ mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+ mNavButtonController.onButtonLongClick(BUTTON_BACK);
+ verify(mockSystemUiProxy, times(1)).stopScreenPinning();
+ }
+
+ @Test
+ public void testLongPressBackRecentsTooLongPinned() {
+ mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
+ mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+ try {
+ Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ mNavButtonController.onButtonLongClick(BUTTON_BACK);
+ verify(mockSystemUiProxy, times(0)).stopScreenPinning();
+ }
+
+ @Test
+ public void testLongPressBackRecentsMultipleAttemptPinned() {
+ mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
+ mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+ try {
+ Thread.sleep(SCREEN_PIN_LONG_PRESS_THRESHOLD + 5);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ mNavButtonController.onButtonLongClick(BUTTON_BACK);
+ verify(mockSystemUiProxy, times(0)).stopScreenPinning();
+
+ // Try again w/in threshold
+ mNavButtonController.onButtonLongClick(BUTTON_RECENTS);
+ mNavButtonController.onButtonLongClick(BUTTON_BACK);
+ verify(mockSystemUiProxy, times(1)).stopScreenPinning();
+ }
+
+ @Test
+ public void testLongPressHomeScreenPinned() {
+ mNavButtonController.updateSysuiFlags(SYSUI_STATE_SCREEN_PINNING);
+ mNavButtonController.onButtonLongClick(BUTTON_HOME);
+ verify(mockSystemUiProxy, times(0)).startAssistant(any());
+ }
+}
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 78f3f11..c689942 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -321,6 +321,9 @@
<!-- Size of the maximum radius for the enforced rounded rectangles. -->
<dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
+<!-- Base Swipe Detector, speed in dp/s -->
+ <dimen name="base_swift_detector_fling_release_velocity">1dp</dimen>
+
<!-- Overview placeholder to compile in Launcher3 without Quickstep -->
<dimen name="task_thumbnail_icon_size">0dp</dimen>
<dimen name="task_thumbnail_icon_drawable_size">0dp</dimen>
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index d844b87..2ebedf7 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -286,7 +286,11 @@
mExtraAttrs = closestProfile.extraAttrs;
iconSize = displayOption.iconSizes;
- iconBitmapSize = ResourceUtils.pxFromDp(iconSize[INDEX_DEFAULT], metrics);
+ float maxIconSize = iconSize[0];
+ for (int i = 1; i < iconSize.length; i++) {
+ maxIconSize = Math.max(maxIconSize, iconSize[i]);
+ }
+ iconBitmapSize = ResourceUtils.pxFromDp(maxIconSize, metrics);
fillResIconDpi = getLauncherIconDensity(iconBitmapSize);
iconTextSize = displayOption.textSizes;
@@ -555,13 +559,10 @@
}
out.multiply(1.0f / weights);
- // Since the bitmaps are persisted, ensure that the default bitmap size is same as
+ // Since the bitmaps are persisted, ensure that all bitmap sizes are not larger than
// predefined size to avoid cache invalidation
- out.iconSizes[INDEX_DEFAULT] =
- closestPoint.iconSizes[INDEX_DEFAULT];
- for (int i = INDEX_DEFAULT + 1; i < COUNT_SIZES; i++) {
- out.iconSizes[i] = Math.min(out.iconSizes[i],
- out.iconSizes[INDEX_DEFAULT]);
+ for (int i = INDEX_DEFAULT; i < COUNT_SIZES; i++) {
+ out.iconSizes[i] = Math.min(out.iconSizes[i], closestPoint.iconSizes[i]);
}
return out;
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 13ad90e..d3351dc 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -31,6 +31,8 @@
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
@@ -215,6 +217,19 @@
}
public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
+ addItem(context, item, newItem, null);
+ }
+
+ public synchronized void addItem(
+ Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
+ if (logger != null) {
+ logger.addLog(
+ Log.DEBUG,
+ TAG,
+ String.format("Adding item to ID map: %s", item.toString()),
+ /* stackTrace= */ null);
+ }
+
itemsIdMap.put(item.id, item);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 47df538..08b38e8 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -383,18 +383,23 @@
info.cellY = getInt(cellYIndex);
}
+ public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
+ checkAndAddItem(info, dataModel, null);
+ }
+
/**
* Adds the {@param info} to {@param dataModel} if it does not overlap with any other item,
* otherwise marks it for deletion.
*/
- public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
+ public void checkAndAddItem(
+ ItemInfo info, BgDataModel dataModel, LoaderMemoryLogger logger) {
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
// Ensure that it is a valid intent. An exception here will
// cause the item loading to get skipped
ShortcutKey.fromItemInfo(info);
}
if (checkItemPlacement(info)) {
- dataModel.addItem(mContext, info, false);
+ dataModel.addItem(mContext, info, false, logger);
} else {
markDeleted("Item position overlap");
}
diff --git a/src/com/android/launcher3/model/LoaderMemoryLogger.java b/src/com/android/launcher3/model/LoaderMemoryLogger.java
new file mode 100644
index 0000000..f48efcb
--- /dev/null
+++ b/src/com/android/launcher3/model/LoaderMemoryLogger.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+
+/**
+ * Helper logger that collects logs while {@code LoaderTask#run} executes and prints them all iff
+ * an exception is caught in {@code LoaderTask#run}.
+ */
+public class LoaderMemoryLogger {
+
+ private static final String TAG = "LoaderMemoryLogger";
+
+ private final ArrayList<LogEntry> mLogEntries = new ArrayList<>();
+
+ protected LoaderMemoryLogger() {}
+
+ protected void addLog(int logLevel, String tag, String log) {
+ addLog(logLevel, tag, log, null);
+ }
+
+ protected void addLog(
+ int logLevel, String tag, String log, Exception stackTrace) {
+ switch (logLevel) {
+ case Log.ASSERT:
+ case Log.ERROR:
+ case Log.DEBUG:
+ case Log.INFO:
+ case Log.VERBOSE:
+ case Log.WARN:
+ mLogEntries.add(new LogEntry(logLevel, tag, log, stackTrace));
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid log level provided: " + logLevel);
+
+ }
+ }
+
+ protected void clearLogs() {
+ mLogEntries.clear();
+ }
+
+ protected void printLogs() {
+ for (LogEntry logEntry : mLogEntries) {
+ String tag = String.format("%s: %s", TAG, logEntry.mLogTag);
+ String logString = logEntry.mStackStrace == null
+ ? logEntry.mLogString
+ : String.format(
+ "%s\n%s",
+ logEntry.mLogString,
+ Log.getStackTraceString(logEntry.mStackStrace));
+
+ Log.println(logEntry.mLogLevel, tag, logString);
+ }
+ clearLogs();
+ }
+
+ private static class LogEntry {
+
+ protected final int mLogLevel;
+ protected final String mLogTag;
+ protected final String mLogString;
+ @Nullable protected final Exception mStackStrace;
+
+ protected LogEntry(
+ int logLevel, String logTag, String logString, @Nullable Exception stackStrace) {
+ mLogLevel = logLevel;
+ mLogTag = logTag;
+ mLogString = logString;
+ mStackStrace = stackStrace;
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index a4f6f7a..2a0f9a6 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -52,6 +52,8 @@
import android.util.LongSparseArray;
import android.util.TimingLogger;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -197,11 +199,12 @@
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
TimingLogger logger = new TimingLogger(TAG, "run");
+ LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
Trace.beginSection("LoadWorkspace");
try {
- loadWorkspace(allShortcuts);
+ loadWorkspace(allShortcuts, memoryLogger);
} finally {
Trace.endSection();
}
@@ -311,9 +314,13 @@
mModelDelegate.modelLoadComplete();
transaction.commit();
+ memoryLogger.clearLogs();
} catch (CancellationException e) {
// Loader stopped, ignore
logASplit(logger, "Cancelled");
+ } catch (Exception e) {
+ memoryLogger.printLogs();
+ throw e;
} finally {
logger.dumpToLog();
}
@@ -325,13 +332,21 @@
this.notify();
}
- private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
+ private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, LoaderMemoryLogger logger) {
loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
- null /* selection */);
+ null /* selection */, logger);
}
- protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri,
- String selection) {
+ protected void loadWorkspace(
+ List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
+ loadWorkspace(allDeepShortcuts, contentUri, selection, null);
+ }
+
+ protected void loadWorkspace(
+ List<ShortcutInfo> allDeepShortcuts,
+ Uri contentUri,
+ String selection,
+ @Nullable LoaderMemoryLogger logger) {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@@ -635,7 +650,7 @@
}
}
- c.checkAndAddItem(info, mBgDataModel);
+ c.checkAndAddItem(info, mBgDataModel, logger);
} else {
throw new RuntimeException("Unexpected null WorkspaceItemInfo");
}
@@ -654,7 +669,7 @@
// no special handling required for restored folders
c.markRestored();
- c.checkAndAddItem(folderInfo, mBgDataModel);
+ c.checkAndAddItem(folderInfo, mBgDataModel, logger);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 1276ece..52c3581 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -17,6 +17,7 @@
import static android.view.MotionEvent.INVALID_POINTER_ID;
+import android.content.Context;
import android.graphics.PointF;
import android.util.Log;
import android.view.MotionEvent;
@@ -26,6 +27,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.R;
+
import java.util.LinkedList;
import java.util.Queue;
@@ -44,10 +47,9 @@
private static final boolean DBG = false;
private static final String TAG = "BaseSwipeDetector";
private static final float ANIMATION_DURATION = 1200;
- /** The minimum release velocity in pixels per millisecond that triggers fling.*/
- private static final float RELEASE_VELOCITY_PX_MS = 1.0f;
private static final PointF sTempPoint = new PointF();
+ private final float mReleaseVelocity;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
protected final boolean mIsRtl;
@@ -64,6 +66,7 @@
private boolean mIsSettingState;
protected boolean mIgnoreSlopWhenSettling;
+ protected Context mContext;
private enum ScrollState {
IDLE,
@@ -71,10 +74,14 @@
SETTLING // onDragEnd
}
- protected BaseSwipeDetector(@NonNull ViewConfiguration config, boolean isRtl) {
+ protected BaseSwipeDetector(@NonNull Context context, @NonNull ViewConfiguration config,
+ boolean isRtl) {
mTouchSlop = config.getScaledTouchSlop();
mMaxVelocity = config.getScaledMaximumFlingVelocity();
mIsRtl = isRtl;
+ mContext = context;
+ mReleaseVelocity = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.base_swift_detector_fling_release_velocity);
}
public static long calculateDuration(float velocity, float progressNeeded) {
@@ -120,7 +127,7 @@
}
public boolean isFling(float velocity) {
- return Math.abs(velocity) > RELEASE_VELOCITY_PX_MS;
+ return Math.abs(velocity) > mReleaseVelocity;
}
public boolean onTouchEvent(MotionEvent ev) {
@@ -236,7 +243,7 @@
} else {
mSubtractDisplacement.x = mDisplacement.x > 0 ? mTouchSlop : -mTouchSlop;
mSubtractDisplacement.y = mDisplacement.y > 0 ? mTouchSlop : -mTouchSlop;
- }
+ }
}
protected abstract boolean shouldScrollStart(PointF displacement);
diff --git a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
index 944391e..6e2f0d8 100644
--- a/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BothAxesSwipeDetector.java
@@ -21,7 +21,6 @@
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Utilities;
@@ -43,13 +42,7 @@
private int mScrollDirections;
public BothAxesSwipeDetector(@NonNull Context context, @NonNull Listener l) {
- this(ViewConfiguration.get(context), l, Utilities.isRtl(context.getResources()));
- }
-
- @VisibleForTesting
- protected BothAxesSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
- boolean isRtl) {
- super(config, isRtl);
+ super(context, ViewConfiguration.get(context), Utilities.isRtl(context.getResources()));
mListener = l;
}
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index e127074..c255225 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -36,6 +36,7 @@
import android.graphics.RectF;
import android.graphics.drawable.ShapeDrawable;
import android.util.FloatProperty;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
@@ -462,8 +463,8 @@
}
@Override
- public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
- DeviceProfile deviceProfile) {
- return primary;
+ public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
+ FloatProperty secondary, DeviceProfile deviceProfile) {
+ return new Pair<>(primary, secondary);
}
}
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index d954552..54b30fb 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -24,6 +24,7 @@
import android.graphics.RectF;
import android.graphics.drawable.ShapeDrawable;
import android.util.FloatProperty;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -113,8 +114,8 @@
float getSecondaryValue(float x, float y);
boolean isLayoutNaturalToLauncher();
- FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
- DeviceProfile deviceProfile);
+ Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
+ FloatProperty secondary, DeviceProfile deviceProfile);
int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
/**
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index fbc335c..e69944a 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -35,6 +35,7 @@
import android.graphics.RectF;
import android.graphics.drawable.ShapeDrawable;
import android.util.FloatProperty;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
@@ -571,12 +572,12 @@
}
@Override
- public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
- DeviceProfile dp) {
- if (dp.isLandscape) { // or seascape
- return primary;
+ public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
+ FloatProperty secondary, DeviceProfile deviceProfile) {
+ if (deviceProfile.isLandscape) { // or seascape
+ return new Pair<>(primary, secondary);
} else {
- return secondary;
+ return new Pair<>(secondary, primary);
}
}
}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index f751b7d..5c599c0 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -106,13 +106,15 @@
public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l,
@NonNull Direction dir) {
- this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
+ super(context, ViewConfiguration.get(context), Utilities.isRtl(context.getResources()));
+ mListener = l;
+ mDir = dir;
}
@VisibleForTesting
- protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
- @NonNull Direction dir, boolean isRtl) {
- super(config, isRtl);
+ protected SingleAxisSwipeDetector(@NonNull Context context, @NonNull ViewConfiguration config,
+ @NonNull Listener l, @NonNull Direction dir, boolean isRtl) {
+ super(context, config, isRtl);
mListener = l;
mDir = dir;
}
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 53b1c3e..cb714b2 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -68,6 +68,11 @@
public @interface StageType {}
///////////////////////////////////
+ /**
+ * Default split ratio for launching app pair from overview.
+ */
+ public static final float DEFAULT_SPLIT_RATIO = 0.5f;
+
public static class SplitPositionOption {
public final int iconResId;
public final int textResId;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 8b7ad46..27cf134 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -460,7 +460,7 @@
if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) {
setVisibility(View.VISIBLE);
}
- if (!mIsOpening) {
+ if (!mIsOpening && mOriginalIcon != null) {
// When closing an app, we want the item on the workspace to be invisible immediately
setIconAndDotVisible(mOriginalIcon, false);
}
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index 49fcd2e..f945819 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -28,8 +28,9 @@
import android.view.MotionEvent;
import android.widget.TextView;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -44,7 +45,7 @@
private static final long HIDE_DURATION_MS = 180;
private static final int TIMEOUT_DURATION_MS = 4000;
- private final BaseDraggingActivity mActivity;
+ private final ActivityContext mActivity;
private Runnable mOnDismissed;
public Snackbar(Context context, AttributeSet attrs) {
@@ -53,12 +54,19 @@
public Snackbar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mActivity = BaseDraggingActivity.fromContext(context);
+ mActivity = ActivityContext.lookupContext(context);
inflate(context, R.layout.snackbar, this);
}
- public static void show(BaseDraggingActivity activity, int labelStringResId,
- int actionStringResId, Runnable onDismissed, Runnable onActionClicked) {
+ /** Show a snackbar with just a label. */
+ public static <T extends Context & ActivityContext> void show(T activity, int labelStringRedId,
+ Runnable onDismissed) {
+ show(activity, labelStringRedId, NO_ID, onDismissed, null);
+ }
+
+ /** Show a snackbar with a label and action. */
+ public static <T extends Context & ActivityContext> void show(T activity, int labelStringResId,
+ int actionStringResId, Runnable onDismissed, @Nullable Runnable onActionClicked) {
closeOpenViews(activity, true, TYPE_SNACKBAR);
Snackbar snackbar = new Snackbar(activity, null);
// Set some properties here since inflated xml only contains the children.
@@ -87,13 +95,30 @@
params.setMargins(0, 0, 0, marginBottom + insets.bottom);
TextView labelView = snackbar.findViewById(R.id.label);
- TextView actionView = snackbar.findViewById(R.id.action);
String labelText = res.getString(labelStringResId);
- String actionText = res.getString(actionStringResId);
- int totalContentWidth = (int) (labelView.getPaint().measureText(labelText)
- + actionView.getPaint().measureText(actionText))
+ labelView.setText(labelText);
+
+ TextView actionView = snackbar.findViewById(R.id.action);
+ float actionWidth;
+ if (actionStringResId != NO_ID) {
+ String actionText = res.getString(actionStringResId);
+ actionWidth = actionView.getPaint().measureText(actionText)
+ + actionView.getPaddingRight() + actionView.getPaddingLeft();
+ actionView.setText(actionText);
+ actionView.setOnClickListener(v -> {
+ if (onActionClicked != null) {
+ onActionClicked.run();
+ }
+ snackbar.mOnDismissed = null;
+ snackbar.close(true);
+ });
+ } else {
+ actionWidth = 0;
+ actionView.setVisibility(GONE);
+ }
+
+ int totalContentWidth = (int) (labelView.getPaint().measureText(labelText) + actionWidth)
+ labelView.getPaddingRight() + labelView.getPaddingLeft()
- + actionView.getPaddingRight() + actionView.getPaddingLeft()
+ padding * 2;
if (totalContentWidth > params.width) {
// The text doesn't fit in our standard width so update width to accommodate.
@@ -113,17 +138,8 @@
params.width = maxWidth;
}
}
- labelView.setText(labelText);
- actionView.setText(actionText);
- actionView.setOnClickListener(v -> {
- if (onActionClicked != null) {
- onActionClicked.run();
- }
- snackbar.mOnDismissed = null;
- snackbar.close(true);
- });
- snackbar.mOnDismissed = onDismissed;
+ snackbar.mOnDismissed = onDismissed;
snackbar.setAlpha(0);
snackbar.setScaleX(0.8f);
snackbar.setScaleY(0.8f);
diff --git a/tests/Android.bp b/tests/Android.bp
index c329ece..3670c37 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -23,7 +23,7 @@
// Source code used for test
filegroup {
name: "launcher-tests-src",
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: ["src/**/*.java"],
}
// Source code used for oop test helpers
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
new file mode 100644
index 0000000..8a4590a
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java
@@ -0,0 +1,201 @@
+package com.android.launcher3.model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSparseArrayMap;
+import com.android.launcher3.util.LauncherModelHelper;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link AddWorkspaceItemsTask}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AddWorkspaceItemsTaskTest {
+
+ private final ComponentName mComponent1 = new ComponentName("a", "b");
+ private final ComponentName mComponent2 = new ComponentName("b", "b");
+
+ private Context mTargetContext;
+ private InvariantDeviceProfile mIdp;
+ private LauncherAppState mAppState;
+ private LauncherModelHelper mModelHelper;
+
+ private IntArray mExistingScreens;
+ private IntArray mNewScreens;
+ private IntSparseArrayMap<GridOccupancy> mScreenOccupancy;
+
+ @Before
+ public void setup() {
+ mModelHelper = new LauncherModelHelper();
+ mTargetContext = mModelHelper.sandboxContext;
+ mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
+ mIdp.numColumns = mIdp.numRows = 5;
+ mAppState = LauncherAppState.getInstance(mTargetContext);
+
+ mExistingScreens = new IntArray();
+ mScreenOccupancy = new IntSparseArrayMap<>();
+ mNewScreens = new IntArray();
+ }
+
+ @After
+ public void tearDown() {
+ mModelHelper.destroy();
+ }
+
+ private AddWorkspaceItemsTask newTask(ItemInfo... items) {
+ List<Pair<ItemInfo, Object>> list = new ArrayList<>();
+ for (ItemInfo item : items) {
+ list.add(Pair.create(item, null));
+ }
+ return new AddWorkspaceItemsTask(list);
+ }
+
+ @Test
+ public void testFindSpaceForItem_prefers_second() throws Exception {
+ // First screen has only one hole of size 1
+ int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ // Second screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ int[] spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1);
+ assertEquals(1, spaceFound[0]);
+ assertTrue(mScreenOccupancy.get(spaceFound[0])
+ .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1));
+
+ // Find a larger space
+ spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3);
+ assertEquals(2, spaceFound[0]);
+ assertTrue(mScreenOccupancy.get(spaceFound[0])
+ .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3));
+ }
+
+ @Test
+ public void testFindSpaceForItem_adds_new_screen() throws Exception {
+ // First screen has 2 holes of sizes 3x2 and 2x3
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
+
+ IntArray oldScreens = mExistingScreens.clone();
+ int[] spaceFound = newTask().findSpaceForItem(
+ mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3);
+ assertFalse(oldScreens.contains(spaceFound[0]));
+ assertTrue(mNewScreens.contains(spaceFound[0]));
+ }
+
+ @Test
+ public void testAddItem_existing_item_ignored() throws Exception {
+ WorkspaceItemInfo info = new WorkspaceItemInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ // Nothing was added
+ assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty());
+ }
+
+ @Test
+ public void testAddItem_some_items_added() throws Exception {
+ Callbacks callbacks = mock(Callbacks.class);
+ Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get();
+
+ WorkspaceItemInfo info = new WorkspaceItemInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+
+ WorkspaceItemInfo info2 = new WorkspaceItemInfo();
+ info2.intent = new Intent().setComponent(mComponent2);
+
+ // Setup a screen with a hole
+ setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
+
+ mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run();
+ ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
+ ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
+
+ // only info2 should be added because info was already added to the workspace
+ // in setupWorkspaceWithHoles()
+ verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(),
+ animated.capture());
+ assertTrue(notAnimated.getValue().isEmpty());
+
+ assertEquals(1, animated.getValue().size());
+ assertTrue(animated.getValue().contains(info2));
+ }
+
+ private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
+ return mModelHelper.executeSimpleTask(
+ model -> writeWorkspaceWithHoles(model, startId, screenId, holes));
+ }
+
+ private int writeWorkspaceWithHoles(
+ BgDataModel bgDataModel, int startId, int screenId, Rect... holes) {
+ GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows);
+ occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true);
+ for (Rect r : holes) {
+ occupancy.markCells(r, false);
+ }
+
+ mExistingScreens.add(screenId);
+ mScreenOccupancy.append(screenId, occupancy);
+
+ for (int x = 0; x < mIdp.numColumns; x++) {
+ for (int y = 0; y < mIdp.numRows; y++) {
+ if (!occupancy.cells[x][y]) {
+ continue;
+ }
+
+ WorkspaceItemInfo info = new WorkspaceItemInfo();
+ info.intent = new Intent().setComponent(mComponent1);
+ info.id = startId++;
+ info.screenId = screenId;
+ info.cellX = x;
+ info.cellY = y;
+ info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
+ bgDataModel.addItem(mTargetContext, info, false);
+
+ ContentWriter writer = new ContentWriter(mTargetContext);
+ info.writeToValues(writer);
+ writer.put(Favorites._ID, info.id);
+ mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI,
+ writer.getValues(mTargetContext));
+ }
+ }
+ return startId;
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
deleted file mode 100644
index e315658..0000000
--- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.model
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.graphics.Rect
-import android.util.Pair
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.launcher3.InvariantDeviceProfile
-import com.android.launcher3.LauncherAppState
-import com.android.launcher3.LauncherSettings
-import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.util.*
-import com.android.launcher3.util.IntArray
-import org.junit.After
-import org.junit.Assert.*
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.*
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.verify
-import kotlin.collections.ArrayList
-
-/**
- * Tests for [AddWorkspaceItemsTask]
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AddWorkspaceItemsTaskTest {
-
- @Captor
- private lateinit var animatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
-
- @Captor
- private lateinit var notAnimatedItemArgumentCaptor: ArgumentCaptor<ArrayList<ItemInfo>>
-
- @Mock
- private lateinit var dataModelCallbacks: BgDataModel.Callbacks
-
- private lateinit var mTargetContext: Context
- private lateinit var mIdp: InvariantDeviceProfile
- private lateinit var mAppState: LauncherAppState
- private lateinit var mModelHelper: LauncherModelHelper
- private lateinit var mExistingScreens: IntArray
- private lateinit var mNewScreens: IntArray
- private lateinit var mScreenOccupancy: IntSparseArrayMap<GridOccupancy>
-
- private val emptyScreenHoles = listOf(Rect(0, 0, 5, 5))
- private val fullScreenHoles = emptyList<Rect>()
-
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- mModelHelper = LauncherModelHelper()
- mTargetContext = mModelHelper.sandboxContext
- mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext]
- mIdp.numRows = 5
- mIdp.numColumns = mIdp.numRows
- mAppState = LauncherAppState.getInstance(mTargetContext)
- mExistingScreens = IntArray()
- mScreenOccupancy = IntSparseArrayMap()
- mNewScreens = IntArray()
- Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(dataModelCallbacks) }.get()
- }
-
- @After
- fun tearDown() {
- mModelHelper.destroy()
- }
-
- @Test
- fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() {
- setupWorkspacesWithHoles(
- screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
- // 2 holes of sizes 3x2 and 2x3
- screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
- )
-
- val spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 1, 1)
- assertEquals(1, spaceFound[0])
- assertTrue(mScreenOccupancy[spaceFound[0]]
- .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1))
- }
-
- @Test
- fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() {
- setupWorkspacesWithHoles(
- screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
- // 2 holes of sizes 3x2 and 2x3
- screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
- )
-
- // Find a larger space
- val spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 2, 3)
- assertEquals(2, spaceFound[0])
- assertTrue(mScreenOccupancy[spaceFound[0]]
- .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3))
- }
-
- @Test
- fun notEnoughSpaceOnExistingScreens_whenFindSpaceForItem_thenReturnNewScreenId() {
- setupWorkspacesWithHoles(
- // 2 holes of sizes 3x2 and 2x3
- screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)),
- // 2 holes of sizes 1x2 and 2x2
- screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)),
- )
-
- val oldScreens = mExistingScreens.clone()
- val spaceFound = newTask().findSpaceForItem(
- mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 3, 3)
- assertFalse(oldScreens.contains(spaceFound[0]))
- assertTrue(mNewScreens.contains(spaceFound[0]))
- }
-
- @Test
- fun enoughSpaceOnFirstScreen_whenTaskRuns_thenAddItemToFirstScreen() {
- val workspaceHoles = createWorkspaceHoles(
- screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space
- screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
- )
- val addedItems = testAddItems(workspaceHoles, getNewItem())
- assertEquals(1, addedItems.size)
- assertEquals(1, addedItems.first().itemInfo.screenId)
- }
-
- @Test
- fun firstPageIsFull_whenTaskRuns_thenAddItemToSecondScreen() {
- val workspaceHoles = createWorkspaceHoles(
- screen1 = fullScreenHoles,
- )
- val addedItems = testAddItems(workspaceHoles, getNewItem())
- assertEquals(1, addedItems.size)
- assertEquals(2, addedItems.first().itemInfo.screenId)
- }
-
- @Test
- fun firstScreenIsEmptyButSecondIsNotEmpty_whenTaskRuns_thenAddItemToSecondScreen() {
- val workspaceHoles = createWorkspaceHoles(
- screen1 = emptyScreenHoles,
- screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space
- )
- val addedItems = testAddItems(workspaceHoles, getNewItem())
- assertEquals(1, addedItems.size)
- assertEquals(2, addedItems.first().itemInfo.screenId)
- }
-
- @Test
- fun twoEmptyMiddleScreens_whenTaskRuns_thenAddItemToThirdScreen() {
- val workspaceHoles = createWorkspaceHoles(
- screen1 = emptyScreenHoles,
- screen2 = emptyScreenHoles,
- screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space
- )
- val addedItems = testAddItems(workspaceHoles, getNewItem())
- assertEquals(1, addedItems.size)
- assertEquals(3, addedItems.first().itemInfo.screenId)
- }
-
- @Test
- fun allPagesAreFull_whenTaskRuns_thenAddItemToNewScreen() {
- val workspaceHoles = createWorkspaceHoles(
- screen1 = fullScreenHoles,
- screen2 = fullScreenHoles,
- )
- val addedItems = testAddItems(workspaceHoles, getNewItem())
- assertEquals(1, addedItems.size)
- assertEquals(3, addedItems.first().itemInfo.screenId)
- }
-
- @Test
- fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_whenTaskRuns_thenAddItemToThirdPage() {
- val workspaceHoles = createWorkspaceHoles(
- screen1 = fullScreenHoles,
- screen2 = fullScreenHoles,
- screen3 = emptyScreenHoles
- )
- val addedItems = testAddItems(workspaceHoles, getNewItem())
- assertEquals(1, addedItems.size)
- assertEquals(3, addedItems.first().itemInfo.screenId)
- }
-
- @Test
- fun itemIsAlreadyAdded_whenTaskRun_thenIgnoreItem() {
- val task = newTask(getExistingItem())
- setupWorkspacesWithHoles(
- screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
- )
-
- // Nothing was added
- assertTrue(mModelHelper.executeTaskForTest(task).isEmpty())
- }
-
- @Test
- fun newAndExistingItems_whenTaskRun_thenAddOnlyTheNewOne() {
- val newItem = getNewItem()
- val workspaceHoles = createWorkspaceHoles(
- screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole
- )
- val addedItems = testAddItems(workspaceHoles, getExistingItem(), newItem)
- assertEquals(1, addedItems.size)
- val addedItem = addedItems.first()
- assert(addedItem.isAnimated)
- val addedItemInfo = addedItem.itemInfo
- assertEquals(1, addedItemInfo.screenId)
- assertEquals(newItem, addedItemInfo)
- }
-
- private fun testAddItems(
- workspaceHoles: List<List<Rect>>,
- vararg itemsToAdd: WorkspaceItemInfo
- ): List<AddedItem> {
- setupWorkspaces(workspaceHoles)
- mModelHelper.executeTaskForTest(newTask(*itemsToAdd))[0].run()
-
- verify(dataModelCallbacks).bindAppsAdded(any(),
- notAnimatedItemArgumentCaptor.capture(), animatedItemArgumentCaptor.capture())
-
- val addedItems = mutableListOf<AddedItem>()
- addedItems.addAll(animatedItemArgumentCaptor.value.map { AddedItem(it, true) })
- addedItems.addAll(notAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) })
- return addedItems
- }
-
- private fun setupWorkspaces(workspaceHoles: List<List<Rect>>) {
- var nextItemId = 1
- var screenId = 1
- workspaceHoles.forEach { holes ->
- nextItemId = setupWorkspace(nextItemId, screenId++, *holes.toTypedArray())
- }
- }
-
- private fun setupWorkspace(startId: Int, screenId: Int, vararg holes: Rect): Int {
- return mModelHelper.executeSimpleTask { dataModel ->
- writeWorkspaceWithHoles(dataModel, startId, screenId, *holes)
- }
- }
-
- private fun writeWorkspaceWithHoles(
- bgDataModel: BgDataModel,
- itemStartId: Int,
- screenId: Int,
- vararg holes: Rect,
- ): Int {
- var itemId = itemStartId
- val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows)
- occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true)
- holes.forEach { holeRect ->
- occupancy.markCells(holeRect, false)
- }
- mExistingScreens.add(screenId)
- mScreenOccupancy.append(screenId, occupancy)
- for (x in 0 until mIdp.numColumns) {
- for (y in 0 until mIdp.numRows) {
- if (!occupancy.cells[x][y]) {
- continue
- }
- val info = getExistingItem()
- info.id = itemId++
- info.screenId = screenId
- info.cellX = x
- info.cellY = y
- info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP
- bgDataModel.addItem(mTargetContext, info, false)
- val writer = ContentWriter(mTargetContext)
- info.writeToValues(writer)
- writer.put(LauncherSettings.Favorites._ID, info.id)
- mTargetContext.contentResolver.insert(LauncherSettings.Favorites.CONTENT_URI,
- writer.getValues(mTargetContext))
- }
- }
- return itemId
- }
-
- private fun setupWorkspacesWithHoles(
- screen1: List<Rect>? = null,
- screen2: List<Rect>? = null,
- screen3: List<Rect>? = null,
- ) = createWorkspaceHoles(screen1, screen2, screen3)
- .let(this::setupWorkspaces)
-
- private fun createWorkspaceHoles(
- screen1: List<Rect>? = null,
- screen2: List<Rect>? = null,
- screen3: List<Rect>? = null,
- ): List<List<Rect>> = listOfNotNull(screen1, screen2, screen3)
-
- private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask =
- items.map { Pair.create(it, Any()) }
- .toMutableList()
- .let(::AddWorkspaceItemsTask)
-
- private fun getExistingItem() = WorkspaceItemInfo()
- .apply { intent = Intent().setComponent(ComponentName("a", "b")) }
-
- private fun getNewItem() = WorkspaceItemInfo()
- .apply { intent = Intent().setComponent(ComponentName("b", "b")) }
-}
-
-private data class AddedItem(
- val itemInfo: ItemInfo,
- val isAnimated: Boolean
-)
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index 472e1a1..260f556 100644
--- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -58,6 +59,7 @@
private TouchEventGenerator mGenerator;
private SingleAxisSwipeDetector mDetector;
private int mTouchSlop;
+ Context mContext;
@Mock
private SingleAxisSwipeDetector.Listener mMockListener;
@@ -69,12 +71,13 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev));
- ViewConfiguration orgConfig = ViewConfiguration
- .get(InstrumentationRegistry.getTargetContext());
+ mContext = InstrumentationRegistry.getTargetContext();
+ ViewConfiguration orgConfig = ViewConfiguration.get(mContext);
doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig)
.getScaledMaximumFlingVelocity();
- mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector = new SingleAxisSwipeDetector(mContext,
+ mMockConfig, mMockListener, VERTICAL, false);
mDetector.setDetectableScrollConditions(DIRECTION_BOTH, false);
mTouchSlop = orgConfig.getScaledTouchSlop();
doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop();
@@ -84,7 +87,8 @@
@Test
public void testDragStart_verticalPositive() {
- mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector = new SingleAxisSwipeDetector(mContext,
+ mMockConfig, mMockListener, VERTICAL, false);
mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 - mTouchSlop);
@@ -94,7 +98,8 @@
@Test
public void testDragStart_verticalNegative() {
- mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, VERTICAL, false);
+ mDetector = new SingleAxisSwipeDetector(mContext,
+ mMockConfig, mMockListener, VERTICAL, false);
mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
@@ -112,7 +117,8 @@
@Test
public void testDragStart_horizontalPositive() {
- mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+ mDetector = new SingleAxisSwipeDetector(mContext,
+ mMockConfig, mMockListener, HORIZONTAL, false);
mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
@@ -123,7 +129,8 @@
@Test
public void testDragStart_horizontalNegative() {
- mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, false);
+ mDetector = new SingleAxisSwipeDetector(mContext,
+ mMockConfig, mMockListener, HORIZONTAL, false);
mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
@@ -134,7 +141,8 @@
@Test
public void testDragStart_horizontalRtlPositive() {
- mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+ mDetector = new SingleAxisSwipeDetector(mContext,
+ mMockConfig, mMockListener, HORIZONTAL, true);
mDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false);
mGenerator.put(0, 100, 100);
@@ -145,7 +153,8 @@
@Test
public void testDragStart_horizontalRtlNegative() {
- mDetector = new SingleAxisSwipeDetector(mMockConfig, mMockListener, HORIZONTAL, true);
+ mDetector = new SingleAxisSwipeDetector(mContext,
+ mMockConfig, mMockListener, HORIZONTAL, true);
mDetector.setDetectableScrollConditions(DIRECTION_NEGATIVE, false);
mGenerator.put(0, 100, 100);
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index d5479fb..3eb8cf1 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -212,7 +212,7 @@
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to get overview actions")) {
verifyActiveContainer();
- UiObject2 overviewActions = mLauncher.waitForLauncherObject("action_buttons");
+ UiObject2 overviewActions = mLauncher.waitForOverviewObject("action_buttons");
return new OverviewActions(overviewActions, mLauncher);
}
}
@@ -224,19 +224,16 @@
return mLauncher.hasLauncherObject(mLauncher.getOverviewObjectSelector("clear_all"));
}
- /* TODO(b/197630182): Once b/188790554 is fixed, remove instanceof check. Currently, when
- swiping from app to overview in Fallback Recents, taskbar remains and no action buttons
- are visible, so we are only testing Overview for now, not BaseOverview. */
private void verifyActionsViewVisibility() {
- if (!(this instanceof Overview) || !hasTasks()) {
+ if (!hasTasks()) {
return;
}
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to assert overview actions view visibility")) {
if (mLauncher.isTablet() && !isOverviewSnappedToFocusedTaskForTablet()) {
- mLauncher.waitUntilLauncherObjectGone("action_buttons");
+ mLauncher.waitUntilOverviewObjectGone("action_buttons");
} else {
- mLauncher.waitForLauncherObject("action_buttons");
+ mLauncher.waitForOverviewObject("action_buttons");
}
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 3485dd1..91b1bc7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1029,6 +1029,10 @@
waitUntilGoneBySelector(getLauncherObjectSelector(resId));
}
+ void waitUntilOverviewObjectGone(String resId) {
+ waitUntilGoneBySelector(getOverviewObjectSelector(resId));
+ }
+
void waitUntilLauncherObjectGone(BySelector selector) {
waitUntilGoneBySelector(makeLauncherSelector(selector));
}