Merge "Cleanup SHOW_DOT_PAGINATION flag" into main
diff --git a/quickstep/src/com/android/launcher3/model/PredictionHelper.java b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
index 738dd83..dbd99e1 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionHelper.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionHelper.java
@@ -67,6 +67,9 @@
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
context.getPackageName(), info.user).build();
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
+ return new AppTarget.Builder(new AppTargetId("app_pair:" + info.id),
+ context.getPackageName(), info.user).build();
}
return null;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 21bbca5..efe9d4b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -90,6 +90,17 @@
}
}
+ repositionContextualButtons()
+ }
+
+ open fun addThreeButtons() {
+ // Swap recents and back button
+ navButtonContainer.addView(recentsButton)
+ navButtonContainer.addView(homeButton)
+ navButtonContainer.addView(backButton)
+ }
+
+ open fun repositionContextualButtons() {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
@@ -108,11 +119,4 @@
rotationButton.currentView.layoutParams = getParamsToCenterView()
}
}
-
- open fun addThreeButtons() {
- // Swap recents and back button
- navButtonContainer.addView(recentsButton)
- navButtonContainer.addView(homeButton)
- navButtonContainer.addView(backButton)
- }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index cde39f3..fd6e991 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -48,7 +48,9 @@
navButtonContainer.addView(backButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
+ }
+ override fun repositionContextualButtons() {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 419824a..56765e5 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -78,7 +78,7 @@
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.desktopmode.IDesktopMode;
import com.android.wm.shell.desktopmode.IDesktopTaskListener;
import com.android.wm.shell.draganddrop.IDragAndDrop;
@@ -799,7 +799,7 @@
/** Start multiple tasks in split-screen simultaneously. */
public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
- @StagePosition int splitPosition, @SnapPosition int snapPosition,
+ @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
@@ -813,7 +813,7 @@
public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
int taskId, Bundle options2, @StagePosition int splitPosition,
- @SnapPosition int snapPosition, RemoteTransition remoteTransition,
+ @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
@@ -828,7 +828,7 @@
public void startIntents(PendingIntent pendingIntent1, int userId1,
@Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
- @StagePosition int splitPosition, @SnapPosition int snapPosition,
+ @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
RemoteTransition remoteTransition, InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
@@ -842,8 +842,9 @@
}
public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
- Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
- RemoteTransition remoteTransition, InstanceId instanceId) {
+ Bundle options2, @StagePosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
@@ -858,8 +859,9 @@
* Start multiple tasks in split-screen simultaneously.
*/
public void startTasksWithLegacyTransition(int taskId1, Bundle options1, int taskId2,
- Bundle options2, @StagePosition int splitPosition, @SnapPosition int snapPosition,
- RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ Bundle options2, @StagePosition int splitPosition,
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startTasksWithLegacyTransition(taskId1, options1, taskId2, options2,
@@ -873,7 +875,8 @@
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent, int userId1,
Bundle options1, int taskId, Bundle options2, @StagePosition int splitPosition,
- @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntentAndTaskWithLegacyTransition(pendingIntent, userId1,
@@ -888,7 +891,8 @@
public void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo, Bundle options1,
int taskId, Bundle options2, @StagePosition int splitPosition,
- @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startShortcutAndTaskWithLegacyTransition(shortcutInfo, options1,
@@ -908,7 +912,8 @@
@Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1,
PendingIntent pendingIntent2, int userId2, @Nullable ShortcutInfo shortcutInfo2,
@Nullable Bundle options2, @StagePosition int sidePosition,
- @SnapPosition int snapPosition, RemoteAnimationAdapter adapter, InstanceId instanceId) {
+ @PersistentSnapPosition int snapPosition, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
if (mSystemUiProxy != null) {
try {
mSplitScreen.startIntentsWithLegacyTransition(pendingIntent1, userId1,
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 2adc790..b3b7be4 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -50,6 +50,7 @@
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
@@ -128,12 +129,12 @@
/**
* A menu item, "Save app pair", that allows the user to preserve the current app combination as
- * a single persistent icon on the Home screen, allowing for quick split screen initialization.
+ * one persistent icon on the Home screen, allowing for quick split screen launching.
*/
class SaveAppPairSystemShortcut extends SystemShortcut<BaseDraggingActivity> {
- private final TaskView mTaskView;
+ private final GroupedTaskView mTaskView;
- public SaveAppPairSystemShortcut(BaseDraggingActivity activity, TaskView taskView) {
+ public SaveAppPairSystemShortcut(BaseDraggingActivity activity, GroupedTaskView taskView) {
super(R.drawable.ic_save_app_pair, R.string.save_app_pair, activity,
taskView.getItemInfo(), taskView);
mTaskView = taskView;
@@ -318,7 +319,8 @@
return null;
}
- return Collections.singletonList(new SaveAppPairSystemShortcut(activity, taskView));
+ return Collections.singletonList(
+ new SaveAppPairSystemShortcut(activity, (GroupedTaskView) taskView));
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 1a7099d..8888831 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -20,46 +20,46 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.isPersistentSnapPosition;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.Arrays;
/**
- * Mini controller class that handles app pair interactions: saving, modifying, deleting, etc.
+ * Controller class that handles app pair interactions: saving, modifying, deleting, etc.
+ * <br>
+ * App pairs contain two "member" apps, which are determined at the time of app pair creation
+ * and never modified. The member apps are WorkspaceItemInfos, but use the "rank" attribute
+ * differently from other ItemInfos -- we use it to store information about the split position and
+ * ratio.
*/
public class AppPairsController {
-
- private static final int POINT_THREE_RATIO = 0;
- private static final int POINT_FIVE_RATIO = 1;
- private static final int POINT_SEVEN_RATIO = 2;
- /**
- * Used to calculate {@link #complement(int)}
- */
- private static final int FULL_RATIO = 2;
-
- private static final int LEFT_TOP = 0;
- private static final int RIGHT_BOTTOM = 1 << 2;
-
- // TODO (jeremysim b/274189428): Support saving different ratios in future.
- public int DEFAULT_RATIO = POINT_FIVE_RATIO;
+ // Used for encoding and decoding the "rank" attribute
+ private static final int BITMASK_SIZE = 16;
+ private static final int BITMASK_FOR_SNAP_POSITION = (1 << BITMASK_SIZE) - 1;
private final Context mContext;
private final SplitSelectStateController mSplitSelectStateController;
@@ -75,17 +75,21 @@
/**
* Creates a new app pair ItemInfo and adds it to the workspace
*/
- public void saveAppPair(TaskView taskView) {
- TaskView.TaskIdAttributeContainer[] attributes = taskView.getTaskIdAttributeContainers();
+ public void saveAppPair(GroupedTaskView gtv) {
+ TaskView.TaskIdAttributeContainer[] attributes = gtv.getTaskIdAttributeContainers();
WorkspaceItemInfo app1 = attributes[0].getItemInfo().clone();
WorkspaceItemInfo app2 = attributes[1].getItemInfo().clone();
app1.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
app2.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
- app1.rank = DEFAULT_RATIO + LEFT_TOP;
- app2.rank = complement(DEFAULT_RATIO) + RIGHT_BOTTOM;
+
+ @PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
+ if (!isPersistentSnapPosition(snapPosition)) {
+ throw new RuntimeException("tried to save an app pair with illegal snapPosition");
+ }
+
+ app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
+ app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
FolderInfo newAppPair = FolderInfo.createAppPair(app1, app2);
- // TODO (jeremysim b/274189428): Generate default title here.
- newAppPair.title = "App pair 1";
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
@@ -94,6 +98,8 @@
member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
iconCache.getTitleAndIcon(member, member.usingLowResIcon());
});
+ newAppPair.title = getDefaultTitle(newAppPair.contents.get(0).title,
+ newAppPair.contents.get(1).title);
MAIN_EXECUTOR.execute(() -> {
LauncherAccessibilityDelegate delegate =
Launcher.getLauncher(mContext).getAccessibilityDelegate();
@@ -128,7 +134,7 @@
}
mSplitSelectStateController.setInitialTaskSelect(task1Intent,
- SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(app1.rank),
app1,
LAUNCHER_APP_PAIR_LAUNCH,
task1Id);
@@ -141,18 +147,42 @@
app2.intent, app2.user);
}
- mSplitSelectStateController.launchSplitTasks();
+ mSplitSelectStateController.launchSplitTasks(
+ AppPairsController.convertRankToSnapPosition(app1.rank));
});
}
/**
- * Used to calculate the "opposite" side of the split ratio, so we can know how big the split
- * apps are supposed to be. This math works because POINT_THREE_RATIO is internally represented
- * by 0, POINT_FIVE_RATIO is represented by 1, and POINT_SEVEN_RATIO is represented by 2. There
- * are no other supported ratios for now.
+ * App pair members have a "rank" attribute that contains information about the split position
+ * and ratio. We implement this by splitting the int in half (e.g. 16 bits each), then use one
+ * half to store splitPosition (left vs right) and the other half to store snapPosition
+ * (30-70 split vs 50-50 split)
*/
- private int complement(int ratio1) {
- int ratio2 = FULL_RATIO - ratio1;
- return ratio2;
+ @VisibleForTesting
+ public int encodeRank(int splitPosition, int snapPosition) {
+ return (splitPosition << BITMASK_SIZE) + snapPosition;
+ }
+
+ /**
+ * Returns the desired stage position for the app pair to be launched in (decoded from the
+ * "rank" integer).
+ */
+ public static int convertRankToStagePosition(int rank) {
+ return rank >> BITMASK_SIZE;
+ }
+
+ /**
+ * Returns the desired split ratio for the app pair to be launched in (decoded from the "rank"
+ * integer).
+ */
+ public static int convertRankToSnapPosition(int rank) {
+ return rank & BITMASK_FOR_SNAP_POSITION;
+ }
+
+ /**
+ * Returns a formatted default title for the app pair.
+ */
+ public String getDefaultTitle(CharSequence app1, CharSequence app2) {
+ return mContext.getString(R.string.app_pair_default_title, app1, app2);
}
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c8831c7..b9829f7 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -98,7 +98,7 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.splitscreen.ISplitSelectListener;
import java.io.PrintWriter;
@@ -289,7 +289,7 @@
* To be called when the both split tasks are ready to be launched. Call after launcher side
* animations are complete.
*/
- public void launchSplitTasks(@SnapPosition int snapPosition,
+ public void launchSplitTasks(@PersistentSnapPosition int snapPosition,
@Nullable Consumer<Boolean> callback) {
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
LogUtils.getShellShareableInstanceId();
@@ -302,6 +302,14 @@
}
/**
+ * A version of {@link #launchTasks(Consumer, boolean, int, InstanceId)} with no success
+ * callback.
+ */
+ public void launchSplitTasks(@PersistentSnapPosition int snapPosition) {
+ launchSplitTasks(snapPosition, /* callback */ null);
+ }
+
+ /**
* A version of {@link #launchSplitTasks(int, Consumer)} that launches with default split ratio.
*/
public void launchSplitTasks(@Nullable Consumer<Boolean> callback) {
@@ -350,7 +358,7 @@
* foreground (quickswitch, launching previous pairs from overview)
*/
public void launchTasks(@Nullable Consumer<Boolean> callback, boolean freezeTaskList,
- @SnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
+ @PersistentSnapPosition int snapPosition, @Nullable InstanceId shellInstanceId) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
final ActivityOptions options1 = ActivityOptions.makeBasic();
@@ -455,7 +463,8 @@
*/
public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
- Consumer<Boolean> callback, boolean freezeTaskList, @SnapPosition int snapPosition) {
+ Consumer<Boolean> callback, boolean freezeTaskList,
+ @PersistentSnapPosition int snapPosition) {
mLaunchingTaskView = groupedTaskView;
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 3d33c87..71758ad 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -35,7 +35,7 @@
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
import java.util.HashMap;
import java.util.function.Consumer;
@@ -200,9 +200,9 @@
}
/**
- * Returns the {@link SnapPosition} of this pair of tasks.
+ * Returns the {@link PersistentSnapPosition} of this pair of tasks.
*/
- public int getSnapPosition() {
+ public @PersistentSnapPosition int getSnapPosition() {
if (mSplitBoundsConfig == null) {
throw new IllegalStateException("mSplitBoundsConfig is null");
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
new file mode 100644
index 0000000..1723844
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.quickstep.util
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidJUnit4::class)
+class AppPairsControllerTest {
+ @Mock lateinit var context: Context
+ @Mock lateinit var splitSelectStateController: SplitSelectStateController
+ @Mock lateinit var statsLogManager: StatsLogManager
+
+ private lateinit var appPairsController: AppPairsController
+
+ private val left30: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_30_70)
+ }
+ private val left50: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_50_50)
+ }
+ private val left70: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_TOP_OR_LEFT, SNAP_TO_70_30)
+ }
+ private val right30: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_30_70)
+ }
+ private val right50: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_50_50)
+ }
+ private val right70: Int by lazy {
+ appPairsController.encodeRank(STAGE_POSITION_BOTTOM_OR_RIGHT, SNAP_TO_70_30)
+ }
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ appPairsController =
+ AppPairsController(context, splitSelectStateController, statsLogManager)
+ }
+
+ @Test
+ fun shouldEncodeRankCorrectly() {
+ assertEquals("left + 30-70 should encode as 0 (0b0)", 0, left30)
+ assertEquals("left + 50-50 should encode as 1 (0b1)", 1, left50)
+ assertEquals("left + 70-30 should encode as 2 (0b10)", 2, left70)
+ // See AppPairsController#BITMASK_SIZE and BITMASK_FOR_SNAP_POSITION for context
+ assertEquals("right + 30-70 should encode as 1 followed by 16 0s", 1 shl 16, right30)
+ assertEquals("right + 50-50 should encode as the above value + 1", (1 shl 16) + 1, right50)
+ assertEquals("right + 70-30 should encode as the above value + 2", (1 shl 16) + 2, right70)
+ }
+
+ @Test
+ fun shouldDecodeRankCorrectly() {
+ assertEquals(
+ "left + 30-70 should decode to left",
+ STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(left30),
+ )
+ assertEquals(
+ "left + 30-70 should decode to 30-70",
+ SNAP_TO_30_70,
+ AppPairsController.convertRankToSnapPosition(left30),
+ )
+
+ assertEquals(
+ "left + 50-50 should decode to left",
+ STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(left50),
+ )
+ assertEquals(
+ "left + 50-50 should decode to 50-50",
+ SNAP_TO_50_50,
+ AppPairsController.convertRankToSnapPosition(left50),
+ )
+
+ assertEquals(
+ "left + 70-30 should decode to left",
+ STAGE_POSITION_TOP_OR_LEFT,
+ AppPairsController.convertRankToStagePosition(left70),
+ )
+ assertEquals(
+ "left + 70-30 should decode to 70-30",
+ SNAP_TO_70_30,
+ AppPairsController.convertRankToSnapPosition(left70),
+ )
+
+ assertEquals(
+ "right + 30-70 should decode to right",
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ AppPairsController.convertRankToStagePosition(right30),
+ )
+ assertEquals(
+ "right + 30-70 should decode to 30-70",
+ SNAP_TO_30_70,
+ AppPairsController.convertRankToSnapPosition(right30),
+ )
+
+ assertEquals(
+ "right + 50-50 should decode to right",
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ AppPairsController.convertRankToStagePosition(right50),
+ )
+ assertEquals(
+ "right + 50-50 should decode to 50-50",
+ SNAP_TO_50_50,
+ AppPairsController.convertRankToSnapPosition(right50),
+ )
+
+ assertEquals(
+ "right + 70-30 should decode to right",
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
+ AppPairsController.convertRankToStagePosition(right70),
+ )
+ assertEquals(
+ "right + 70-30 should decode to 70-30",
+ SNAP_TO_70_30,
+ AppPairsController.convertRankToSnapPosition(right70),
+ )
+ }
+}
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a2f4a61..37bd4f1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -44,6 +44,8 @@
<!-- App pairs -->
<string name="save_app_pair">Save app pair</string>
+ <!-- App pair default title -->
+ <string name="app_pair_default_title"><xliff:g id="app1" example="Chrome">%1$s</xliff:g> | <xliff:g id="app2" example="YouTube">%2$s</xliff:g></string>
<!-- Widgets -->
<!-- Message to tell the user to press and hold on a widget to add it [CHAR_LIMIT=50] -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 879000a..6d14b31 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -386,10 +386,12 @@
setTag(itemInfo);
}
+ @VisibleForTesting
@UiThread
- protected void applyIconAndLabel(ItemInfoWithIcon info) {
+ public void applyIconAndLabel(ItemInfoWithIcon info) {
int flags = shouldUseTheme() ? FLAG_THEMED : 0;
- if (mHideBadge) {
+ // Remove badge on icons smaller than 48dp.
+ if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
flags |= FLAG_NO_BADGE;
}
FastBitmapDrawable iconDrawable = info.newIcon(getContext(), flags);
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index e7e6c92..c20d602 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -72,6 +72,7 @@
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
@@ -2862,6 +2863,10 @@
view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, mLauncher, cellLayout,
(FolderInfo) info);
break;
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
+ view = AppPairIcon.inflateIcon(R.layout.app_pair_icon, mLauncher, cellLayout,
+ (FolderInfo) info);
+ break;
default:
throw new IllegalStateException("Unknown item type: " + info.itemType);
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 0e193d4..92d7a3c 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -137,7 +137,7 @@
// TODO(Block 6): Clean up flags
public static final BooleanFlag ENABLE_ALL_APPS_SEARCH_IN_TASKBAR = getDebugFlag(270393900,
- "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", TEAMFOOD,
+ "ENABLE_ALL_APPS_SEARCH_IN_TASKBAR", ENABLED,
"Enables Search box in Taskbar All Apps.");
public static final BooleanFlag SECONDARY_DRAG_N_DROP_TO_PIN = getDebugFlag(270395140,
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index c06ab8c..eed4ee6 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -468,6 +468,13 @@
}
break;
}
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+ int total = getFolderItemsCount(entry);
+ if (total != 2) {
+ throw new Exception("App pair contains fewer or more than 2 items");
+ }
+ break;
+ }
default:
throw new Exception("Invalid item type");
}
@@ -565,6 +572,13 @@
}
break;
}
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR: {
+ int total = getFolderItemsCount(entry);
+ if (total != 2) {
+ throw new Exception("App pair contains fewer or more than 2 items");
+ }
+ break;
+ }
default:
throw new Exception("Invalid item type");
}
@@ -682,6 +696,7 @@
public String getEntryMigrationId() {
switch (itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+ case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
return getFolderMigrationId();
case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
return mProvider;
diff --git a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index 2cdcf24..6c2950c 100644
--- a/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -20,21 +20,30 @@
import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
+import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT;
+import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_SMALL;
+
+import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Typeface;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.os.UserHandle;
import android.view.ViewGroup;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Flags;
import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.search.StringMatcherUtility;
import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.views.BaseDragLayer;
@@ -55,6 +64,8 @@
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final StringMatcherUtility.StringMatcher
MATCHER = StringMatcherUtility.StringMatcher.getInstance();
+ private static final UserHandle WORK_HANDLE = new UserHandle(13);
+ private static final int WORK_FLAG = 1;
private static final int ONE_LINE = 1;
private static final int TWO_LINE = 2;
private static final String TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT = "Battery Stats";
@@ -81,6 +92,7 @@
private ItemInfoWithIcon mItemInfoWithIcon;
private Context mContext;
private int mLimitedWidth;
+ private AppInfo mGmailAppInfo;
@Before
public void setUp() throws Exception {
@@ -110,6 +122,9 @@
return null;
}
};
+ ComponentName componentName = new ComponentName(mContext,
+ "com.android.launcher3.tests.Activity" + "Gmail");
+ mGmailAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent());
}
@Test
@@ -359,4 +374,28 @@
assertEquals(TWO_LINE, mBubbleTextView.getLineCount());
}
+
+ @Test
+ public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_SMALL_noBadge() {
+ FlagOp op = FlagOp.NO_OP;
+ // apply the WORK bitmap flag to show work badge
+ mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG);
+ mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT_SMALL);
+
+ mBubbleTextView.applyIconAndLabel(mGmailAppInfo);
+
+ assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(false);
+ }
+
+ @Test
+ public void applyIconAndLabel_whenDisplay_DISPLAY_SEARCH_RESULT_hasBadge() {
+ FlagOp op = FlagOp.NO_OP;
+ // apply the WORK bitmap flag to show work badge
+ mGmailAppInfo.bitmap.flags = op.apply(WORK_FLAG);
+ mBubbleTextView.setDisplay(DISPLAY_SEARCH_RESULT);
+
+ mBubbleTextView.applyIconAndLabel(mGmailAppInfo);
+
+ assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true);
+ }
}