Merge "Adjusting weight of taskbar all apps icon or action key" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 25db4d7..147cac6 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -16,6 +16,13 @@
}
flag {
+ name: "enable_twoline_toggle"
+ namespace: "launcher"
+ description: "Enables visibility in home settings to see the toggle to turn on/off two lines in all apps."
+ bug: "316027081"
+}
+
+flag {
name: "enable_grid_only_overview"
namespace: "launcher"
description: "Enable a grid-only overview without a focused task."
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index caf8a0b..e1443d0 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -31,6 +31,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
@@ -137,7 +138,9 @@
int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
// Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
// is not enabled. Otherwise, the extra height will increase by just the textHeight.
- int extraHeight = FeatureFlags.enableTwolineAllapps() ? textHeight : mTopRowExtraHeight;
+ int extraHeight = (FeatureFlags.enableTwolineAllapps() && (!Flags.enableTwolineToggle()
+ || (Flags.enableTwolineToggle() && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
+ getContext())))) ? textHeight : mTopRowExtraHeight;
totalHeight += extraHeight;
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index b69f657..9006df8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -332,7 +332,7 @@
// Update icon size
deviceProfile.iconSizePx = deviceProfile.taskbarIconSize;
- deviceProfile.updateIconSize(1f, getResources());
+ deviceProfile.updateIconSize(1f, this);
};
mDeviceProfile = originDeviceProfile.toBuilder(this)
.withDimensionsOverride(overrideProvider).build();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 8a26054..af1bd2a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -260,7 +260,8 @@
taskAttributes.getThumbnailView(),
taskAttributes.getThumbnailView().getThumbnail(),
null /* intent */,
- null /* user */);
+ null /* user */,
+ info);
return;
}
}
@@ -273,7 +274,8 @@
startingView,
null /* thumbnail */,
intent,
- info.user);
+ info.user,
+ info);
}
);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 9329e16..dbaeafb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -22,6 +22,7 @@
import static com.android.app.animation.Interpolators.INSTANT;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
@@ -111,7 +112,7 @@
boolean exitingOverview = !FeatureFlags.enableSplitContextually() && !toState.overviewUi;
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
- .createPlaceholderDismissAnim(mLauncher));
+ .createPlaceholderDismissAnim(mLauncher, LAUNCHER_SPLIT_SELECTION_EXIT_HOME));
setter.setViewAlpha(
mRecentsView.getSplitInstructionsView(),
0,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f3f36c5..722676a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -39,6 +39,8 @@
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
@@ -596,7 +598,7 @@
list.add(getDragController());
Consumer<AnimatorSet> splitAnimator = animatorSet ->
animatorSet.play(mSplitSelectStateController.getSplitAnimationController()
- .createPlaceholderDismissAnim(this));
+ .createPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_HOME));
switch (mode) {
case NO_BUTTON:
list.add(new NoButtonQuickSwitchTouchController(this));
@@ -767,8 +769,10 @@
// If Launcher pauses before both split apps are selected, exit split screen.
if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
!mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
+ mSplitSelectStateController
+ .logExitReason(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
mSplitSelectStateController.getSplitAnimationController()
- .playPlaceholderDismissAnim(this);
+ .playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
}
}
}
@@ -1042,17 +1046,17 @@
}
@Override
- protected void handleSplitAnimationGoingToHome() {
- super.handleSplitAnimationGoingToHome();
+ protected void handleSplitAnimationGoingToHome(StatsLogManager.EventEnum splitDismissReason) {
+ super.handleSplitAnimationGoingToHome(splitDismissReason);
mSplitSelectStateController.getSplitAnimationController()
- .playPlaceholderDismissAnim(this);
+ .playPlaceholderDismissAnim(this, splitDismissReason);
}
@Override
- public void dismissSplitSelection() {
- super.dismissSplitSelection();
+ public void dismissSplitSelection(StatsLogManager.LauncherEvent splitDismissEvent) {
+ super.dismissSplitSelection(splitDismissEvent);
mSplitSelectStateController.getSplitAnimationController()
- .playPlaceholderDismissAnim(this);
+ .playPlaceholderDismissAnim(this, splitDismissEvent);
}
public <T extends OverviewActionsView> T getActionsView() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 5db5d17..b5914a1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -186,7 +186,7 @@
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
InteractionJankMonitorWrapper.begin(
- mLauncher.getRootView(), Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS);
+ mLauncher.getRootView(), Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS, /*tag=*/ "swipe");
InteractionJankMonitorWrapper.begin(
mLauncher.getRootView(), Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE);
break;
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 4e6b23f..e6febff 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.Flags.enableOverviewIconMenu;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -41,12 +42,12 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.FlagOp;
import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.TaskKeyLruCache;
import com.android.quickstep.util.TaskVisualsChangeListener;
import com.android.systemui.shared.recents.model.Task;
@@ -109,21 +110,17 @@
callback.accept(task);
return null;
}
- CancellableTask<TaskCacheEntry> request = new CancellableTask<TaskCacheEntry>() {
- @Override
- public TaskCacheEntry getResultOnBg() {
- return getCacheEntry(task);
- }
-
- @Override
- public void handleResult(TaskCacheEntry result) {
- task.icon = result.icon;
- task.titleDescription = result.contentDescription;
- task.title = result.title;
- callback.accept(task);
- dispatchIconUpdate(task.key.id);
- }
- };
+ CancellableTask<TaskCacheEntry> request = new CancellableTask<>(
+ () -> getCacheEntry(task),
+ MAIN_EXECUTOR,
+ result -> {
+ task.icon = result.icon;
+ task.titleDescription = result.contentDescription;
+ task.title = result.title;
+ callback.accept(task);
+ dispatchIconUpdate(task.key.id);
+ }
+ );
mBgExecutor.execute(request);
return request;
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index 9e21595..b7cbb47 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -16,6 +16,7 @@
package com.android.quickstep;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
import android.content.res.Resources;
@@ -23,8 +24,8 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.R;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.TaskKeyByLastActiveTimeCache;
import com.android.quickstep.util.TaskKeyCache;
import com.android.quickstep.util.TaskKeyLruCache;
@@ -195,33 +196,29 @@
return null;
}
- CancellableTask<ThumbnailData> request = new CancellableTask<>() {
- @Override
- public ThumbnailData getResultOnBg() {
- ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
- key.id, lowResolution);
- if (thumbnailData.thumbnail != null) {
- return thumbnailData;
- }
- return ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id);
- }
-
- @Override
- public void handleResult(ThumbnailData result) {
- // Avoid an async timing issue that a low res entry replaces an existing high res
- // entry in high res enabled state, so we check before putting it to cache
- if (enableGridOnlyOverview() && result.reducedResolution
- && getHighResLoadingState().isEnabled()) {
- ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
- if (cachedThumbnail != null && cachedThumbnail.thumbnail != null
- && !cachedThumbnail.reducedResolution) {
- return;
+ CancellableTask<ThumbnailData> request = new CancellableTask<>(
+ () -> {
+ ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance()
+ .getTaskThumbnail(key.id, lowResolution);
+ return thumbnailData.thumbnail != null ? thumbnailData
+ : ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id);
+ },
+ MAIN_EXECUTOR,
+ result -> {
+ // Avoid an async timing issue that a low res entry replaces an existing high
+ // res entry in high res enabled state, so we check before putting it to cache
+ if (enableGridOnlyOverview() && result.reducedResolution
+ && getHighResLoadingState().isEnabled()) {
+ ThumbnailData newCachedThumbnail = mCache.getAndInvalidateIfModified(key);
+ if (newCachedThumbnail != null && newCachedThumbnail.thumbnail != null
+ && !newCachedThumbnail.reducedResolution) {
+ return;
+ }
}
+ mCache.put(key, result);
+ callback.accept(result);
}
- mCache.put(key, result);
- callback.accept(result);
- }
- };
+ );
mBgExecutor.execute(request);
return request;
}
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 8f9395f..b846323 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -174,10 +174,10 @@
@Nullable Task foundTask2 = foundTasks[1];
if (foundTask2 != null) {
- mSplitSelectStateController.setSecondTask(foundTask2);
+ mSplitSelectStateController.setSecondTask(foundTask2, app2);
} else {
mSplitSelectStateController.setSecondTask(
- app2.intent, app2.user);
+ app2.intent, app2.user, app2);
}
mSplitSelectStateController.setLaunchingIconView(appPairIcon);
diff --git a/quickstep/src/com/android/quickstep/util/CancellableTask.java b/quickstep/src/com/android/quickstep/util/CancellableTask.java
deleted file mode 100644
index a6e2e81..0000000
--- a/quickstep/src/com/android/quickstep/util/CancellableTask.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 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 static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-/**
- * Utility class to executore a task on background and post the result on UI thread
- */
-public abstract class CancellableTask<T> implements Runnable {
-
- private boolean mCancelled = false;
-
- @Override
- public final void run() {
- if (mCancelled) {
- return;
- }
- T result = getResultOnBg();
- if (mCancelled) {
- return;
- }
- MAIN_EXECUTOR.execute(() -> {
- if (mCancelled) {
- return;
- }
- handleResult(result);
- });
- }
-
- /**
- * Called on the worker thread to process the request. The return object is passed to
- * {@link #handleResult(Object)}
- */
- @WorkerThread
- public abstract T getResultOnBg();
-
- /**
- * Called on the UI thread to handle the final result.
- * @param result
- */
- @UiThread
- public abstract void handleResult(T result);
-
- /**
- * Cancels the request. If it is called before {@link #handleResult(Object)}, that method
- * will not be called
- */
- public void cancel() {
- mCancelled = true;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 0ee27be..1eb71fc 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -40,22 +40,26 @@
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags.enableOverviewIconMenu
import com.android.launcher3.Launcher
import com.android.launcher3.QuickstepTransitionManager
import com.android.launcher3.Utilities
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.logging.StatsLogManager.EventEnum
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
import com.android.launcher3.statemanager.StatefulActivity
import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
import com.android.launcher3.views.BaseDragLayer
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.views.FloatingAppPairView
import com.android.quickstep.views.FloatingTaskView
import com.android.quickstep.views.GroupedTaskView
+import com.android.quickstep.views.IconAppChipView
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.SplitInstructionsView
import com.android.quickstep.views.TaskThumbnailView
@@ -155,16 +159,36 @@
val iconView: View = taskIdAttributeContainer.iconView.asView()
builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailView.SPLASH_ALPHA, 1f))
thumbnail.setShowSplashForSplitSelection(true)
+ // With the new `IconAppChipView`, we always want to keep the chip pinned to the
+ // top left of the task / thumbnail.
+ if (enableOverviewIconMenu()) {
+ builder.add(
+ ObjectAnimator.ofFloat(
+ (iconView as IconAppChipView).splitTranslationX,
+ MULTI_PROPERTY_VALUE,
+ 0f
+ )
+ )
+ builder.add(
+ ObjectAnimator.ofFloat(
+ iconView.splitTranslationY,
+ MULTI_PROPERTY_VALUE,
+ 0f
+ )
+ )
+ }
if (deviceProfile.isLeftRightSplit) {
// Center view first so scaling happens uniformly, alternatively we can move pivotX to 0
val centerThumbnailTranslationX: Float = (taskViewWidth - thumbnail.width) / 2f
- val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
val finalScaleX: Float = taskViewWidth.toFloat() / thumbnail.width
builder.add(ObjectAnimator.ofFloat(thumbnail,
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_X, centerThumbnailTranslationX))
- // icons are anchored from Gravity.END, so need to use negative translation
- builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
+ if (!enableOverviewIconMenu()) {
+ // icons are anchored from Gravity.END, so need to use negative translation
+ val centerIconTranslationX: Float = (taskViewWidth - iconView.width) / 2f
+ builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
-centerIconTranslationX))
+ }
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_X, finalScaleX))
// Reset other dimensions
@@ -183,7 +207,6 @@
// asymmetry causes problems..
// Icon defaults to center | horizontal, we add additional translation for split
- val centerIconTranslationX = 0f
var centerThumbnailTranslationY: Float
// TODO(b/271468547), primary thumbnail has layout margin above it, so secondary
@@ -200,9 +223,10 @@
builder.add(ObjectAnimator.ofFloat(thumbnail,
TaskThumbnailView.SPLIT_SELECT_TRANSLATE_Y, centerThumbnailTranslationY))
- // icons are anchored from Gravity.END, so need to use negative translation
- builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X,
- centerIconTranslationX))
+ if (!enableOverviewIconMenu()) {
+ // icons are anchored from Gravity.END, so need to use negative translation
+ builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
+ }
builder.add(ObjectAnimator.ofFloat(thumbnail, View.SCALE_Y, finalScaleY))
// Reset other dimensions
@@ -213,17 +237,21 @@
}
/** Does not play any animation if user is not currently in split selection state. */
- fun playPlaceholderDismissAnim(launcher: StatefulActivity<*>) {
+ fun playPlaceholderDismissAnim(launcher: StatefulActivity<*>, splitDismissEvent: EventEnum) {
if (!splitSelectStateController.isSplitSelectActive) {
return
}
- val anim = createPlaceholderDismissAnim(launcher)
+ val anim = createPlaceholderDismissAnim(launcher, splitDismissEvent)
anim.start()
}
- /** Returns [AnimatorSet] which slides initial split placeholder view offscreen. */
- fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>) : AnimatorSet {
+ /**
+ * Returns [AnimatorSet] which slides initial split placeholder view offscreen and logs an event
+ * for why split is being dismissed
+ */
+ fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>,
+ splitDismissEvent: EventEnum) : AnimatorSet {
val animatorSet = AnimatorSet()
val recentsView : RecentsView<*, *> = launcher.getOverviewPanel()
val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView
@@ -260,6 +288,7 @@
splitSelectStateController.splitInstructionsView)
}
})
+ splitSelectStateController.logExitReason(splitDismissEvent)
return animatorSet
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
index c013483..06edb14 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -87,6 +87,7 @@
@StagePosition
private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
private var itemInfo: ItemInfo? = null
+ private var secondItemInfo: ItemInfo? = null
private var splitEvent: EventEnum? = null
private var initialTaskId: Int = INVALID_TASK_ID
@@ -144,8 +145,9 @@
* To be called as soon as user selects the second task (even if animations aren't complete)
* @param taskId The second task that will be launched.
*/
- fun setSecondTask(taskId: Int) {
+ fun setSecondTask(taskId: Int, itemInfo: ItemInfo) {
secondTaskId = taskId
+ secondItemInfo = itemInfo
}
/**
@@ -153,9 +155,10 @@
* @param intent The second intent that will be launched.
* @param user The user of that intent.
*/
- fun setSecondTask(intent: Intent, user: UserHandle) {
+ fun setSecondTask(intent: Intent, user: UserHandle, itemInfo: ItemInfo) {
secondIntent = intent
secondUser = user
+ secondItemInfo = itemInfo
}
/**
@@ -163,9 +166,10 @@
* Sets [secondUser] from that of the pendingIntent
* @param pendingIntent The second PendingIntent that will be launched.
*/
- fun setSecondTask(pendingIntent: PendingIntent) {
+ fun setSecondTask(pendingIntent: PendingIntent, itemInfo: ItemInfo) {
secondPendingIntent = pendingIntent
secondUser = pendingIntent.creatorUserHandle
+ secondItemInfo = itemInfo
}
/**
@@ -173,8 +177,8 @@
* an extra intent from their RemoteResponse.
* See [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
*/
- fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?) {
- setSecondTask(pendingIntent)
+ fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?, itemInfo: ItemInfo) {
+ setSecondTask(pendingIntent, itemInfo)
widgetSecondIntent = widgetIntent
}
@@ -407,6 +411,10 @@
return itemInfo
}
+ fun getSecondItemInfo(): ItemInfo? {
+ return secondItemInfo
+ }
+
private fun isSecondTaskIntentSet(): Boolean {
return secondTaskId != INVALID_TASK_ID || secondIntent != null
|| secondPendingIntent != null
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 3c90e0c..f06418b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -19,6 +19,10 @@
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_LEFT_TOP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTED_SECOND_APP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_COMPLETE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_INITIATED;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -151,6 +155,11 @@
private SplitInstructionsView mSplitInstructionsView;
private final List<SplitSelectionListener> mSplitSelectionListeners = new ArrayList<>();
+ /**
+ * Tracks metrics from when first app is selected to split launch or cancellation. This also
+ * gets passed over to shell when attempting to invoke split.
+ */
+ private Pair<InstanceId, com.android.launcher3.logging.InstanceId> mSessionInstanceIds;
private final BackPressHandler mSplitBackHandler = new BackPressHandler() {
@Override
@@ -162,7 +171,8 @@
public void onBackInvoked() {
// When exiting from split selection, leave current context to go to
// homescreen as well
- getSplitAnimationController().playPlaceholderDismissAnim(mContext);
+ getSplitAnimationController().playPlaceholderDismissAnim(mContext,
+ LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
if (mActivityBackCallback != null) {
mActivityBackCallback.run();
}
@@ -203,6 +213,7 @@
int alreadyRunningTask) {
mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent,
alreadyRunningTask);
+ createAndLogInstanceIdsForSession();
}
/**
@@ -213,6 +224,7 @@
@StagePosition int stagePosition, @NonNull ItemInfo itemInfo,
StatsLogManager.EventEnum splitEvent) {
mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent);
+ createAndLogInstanceIdsForSession();
}
/**
@@ -330,14 +342,12 @@
*/
public void launchSplitTasks(@PersistentSnapPosition int snapPosition,
@Nullable Consumer<Boolean> callback) {
- Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
- LogUtils.getShellShareableInstanceId();
- launchTasks(callback, false /* freezeTaskList */, snapPosition, instanceIds.first);
+ launchTasks(callback, false /* freezeTaskList */, snapPosition, mSessionInstanceIds.first);
mStatsLogManager.logger()
- .withItemInfo(mSplitSelectDataHolder.getItemInfo())
- .withInstanceId(instanceIds.second)
- .log(mSplitSelectDataHolder.getSplitEvent());
+ .withItemInfo(mSplitSelectDataHolder.getSecondItemInfo())
+ .withInstanceId(mSessionInstanceIds.second)
+ .log(LAUNCHER_SPLIT_SELECTED_SECOND_APP);
}
/**
@@ -364,11 +374,26 @@
}
/**
+ * Use to log an event when user exists split selection when the second app **IS NOT** selected.
+ * This must be called before playing any exit animations since most animations will call
+ * {@link #resetState()} which removes {@link #mSessionInstanceIds}.
+ */
+ public void logExitReason(StatsLogManager.EventEnum splitExitEvent) {
+ StatsLogManager.StatsLogger logger = mStatsLogManager.logger();
+ if (mSessionInstanceIds != null) {
+ logger.withInstanceId(mSessionInstanceIds.second);
+ } else {
+ Log.w(TAG, "Missing session instanceIds");
+ }
+ logger.log(splitExitEvent);
+ }
+
+ /**
* To be called as soon as user selects the second task (even if animations aren't complete)
* @param task The second task that will be launched.
*/
- public void setSecondTask(Task task) {
- mSplitSelectDataHolder.setSecondTask(task.key.id);
+ public void setSecondTask(Task task, ItemInfo itemInfo) {
+ mSplitSelectDataHolder.setSecondTask(task.key.id, itemInfo);
}
/**
@@ -376,20 +401,20 @@
* @param intent The second intent that will be launched.
* @param user The user of that intent.
*/
- public void setSecondTask(Intent intent, UserHandle user) {
- mSplitSelectDataHolder.setSecondTask(intent, user);
+ public void setSecondTask(Intent intent, UserHandle user, ItemInfo itemInfo) {
+ mSplitSelectDataHolder.setSecondTask(intent, user, itemInfo);
}
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
* @param pendingIntent The second PendingIntent that will be launched.
*/
- public void setSecondTask(PendingIntent pendingIntent) {
- mSplitSelectDataHolder.setSecondTask(pendingIntent);
+ public void setSecondTask(PendingIntent pendingIntent, ItemInfo itemInfo) {
+ mSplitSelectDataHolder.setSecondTask(pendingIntent, itemInfo);
}
public void setSecondWidget(PendingIntent pendingIntent, Intent widgetIntent) {
- mSplitSelectDataHolder.setSecondWidget(pendingIntent, widgetIntent);
+ mSplitSelectDataHolder.setSecondWidget(pendingIntent, widgetIntent, null /*itemInfo*/);
}
/**
@@ -578,7 +603,7 @@
final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
ActivityThread.currentActivityThread().getApplicationThread(),
"LaunchAppFullscreen");
- InstanceId instanceId = LogUtils.getShellShareableInstanceId().first;
+ InstanceId instanceId = mSessionInstanceIds.first;
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
switch (launchData.getSplitLaunchType()) {
case SPLIT_SINGLE_TASK_FULLSCREEN -> mSystemUiProxy.startTasks(firstTaskId,
@@ -630,6 +655,26 @@
ActivityThread.currentActivityThread().getApplicationThread());
}
+ /**
+ * Will initialize {@link #mSessionInstanceIds} if null and log the first split event from
+ * {@link #mSplitSelectDataHolder}
+ */
+ private void createAndLogInstanceIdsForSession() {
+ if (mSessionInstanceIds != null) {
+ Log.w(TAG, "SessionIds should be null");
+ }
+ // Log separately the start of the session and then the first app selected
+ mSessionInstanceIds = LogUtils.getShellShareableInstanceId();
+ mStatsLogManager.logger()
+ .withInstanceId(mSessionInstanceIds.second)
+ .log(LAUNCHER_SPLIT_SELECTION_INITIATED);
+
+ mStatsLogManager.logger()
+ .withItemInfo(mSplitSelectDataHolder.getItemInfo())
+ .withInstanceId(mSessionInstanceIds.second)
+ .log(mSplitSelectDataHolder.getSplitEvent());
+ }
+
public @StagePosition int getActiveSplitStagePosition() {
return mSplitSelectDataHolder.getInitialStagePosition();
}
@@ -804,6 +849,13 @@
mFirstFloatingTaskView = null;
mSplitInstructionsView = null;
mLaunchingFirstAppFullscreen = false;
+
+ if (mSessionInstanceIds != null) {
+ mStatsLogManager.logger()
+ .withInstanceId(mSessionInstanceIds.second)
+ .log(LAUNCHER_SPLIT_SELECTION_COMPLETE);
+ }
+ mSessionInstanceIds = null;
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 50a5a83..445a540 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -45,6 +45,7 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.quickstep.views.FloatingTaskView;
@@ -134,7 +135,7 @@
return false;
}
- mController.setSecondTask(intent, user);
+ mController.setSecondTask(intent, user, (ItemInfo) tag);
startWorkspaceAnimation(view, null /*bitmap*/, bitmapInfo.newIcon(mLauncher));
return true;
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 6b00473..4c7d5c4 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -44,10 +44,10 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -63,7 +63,6 @@
import java.util.List;
import java.util.function.Consumer;
-
/**
* TaskView that contains all tasks that are part of the desktop.
*/
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index ad4b292..10ef47c 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -24,6 +24,7 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
@@ -31,7 +32,6 @@
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.systemui.shared.recents.model.Task;
@@ -45,7 +45,6 @@
import java.util.HashMap;
import java.util.function.Consumer;
-
/**
* TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
*
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
index 6bb98e1..ba42594 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
@@ -97,11 +97,25 @@
private final MultiPropertyFactory<View> mViewTranslationY;
/**
+ * Gets the view split x-axis translation
+ */
+ public MultiPropertyFactory<View>.MultiProperty getSplitTranslationX() {
+ return mViewTranslationX.get(INDEX_SPLIT_TRANSLATION);
+ }
+
+ /**
* Sets the view split x-axis translation
* @param translationX x-axis translation
*/
public void setSplitTranslationX(float translationX) {
- mViewTranslationX.get(INDEX_SPLIT_TRANSLATION).setValue(translationX);
+ getSplitTranslationX().setValue(translationX);
+ }
+
+ /**
+ * Gets the view split y-axis translation
+ */
+ public MultiPropertyFactory<View>.MultiProperty getSplitTranslationY() {
+ return mViewTranslationY.get(INDEX_SPLIT_TRANSLATION);
}
/**
@@ -109,7 +123,7 @@
* @param translationY y-axis translation
*/
public void setSplitTranslationY(float translationY) {
- mViewTranslationY.get(INDEX_SPLIT_TRANSLATION).setValue(translationY);
+ getSplitTranslationY().setValue(translationY);
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index a27875a..97f3d81 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -25,6 +25,7 @@
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
import android.annotation.TargetApi;
@@ -90,7 +91,7 @@
stateManager.goToState(NORMAL, animated);
if (FeatureFlags.enableSplitContextually()) {
mSplitSelectStateController.getSplitAnimationController()
- .playPlaceholderDismissAnim(mActivity);
+ .playPlaceholderDismissAnim(mActivity, LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
}
AbstractFloatingView.closeAllOpenViews(mActivity, animated);
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ecff6b4..99c42a7 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -147,14 +147,15 @@
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.desktop.DesktopRecentsTransitionController;
-import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.DynamicResource;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
@@ -589,8 +590,7 @@
return;
}
Task.TaskKey taskKey = taskView.getTask().key;
- UI_HELPER_EXECUTOR.execute(new HandlerRunnable<>(
- UI_HELPER_EXECUTOR.getHandler(),
+ UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
() -> PackageManagerWrapper.getInstance()
.getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
MAIN_EXECUTOR,
@@ -4815,7 +4815,8 @@
* false otherwise
*/
public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable,
- View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user) {
+ View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user,
+ ItemInfo itemInfo) {
if (canLaunchFullscreenTask()) {
return false;
}
@@ -4834,9 +4835,9 @@
+ ") is not dockable / does not support splitscreen"));
return true;
}
- mSplitSelectStateController.setSecondTask(task);
+ mSplitSelectStateController.setSecondTask(task, itemInfo);
} else {
- mSplitSelectStateController.setSecondTask(intent, user);
+ mSplitSelectStateController.setSecondTask(intent, user, itemInfo);
}
RectF secondTaskStartingBounds = new RectF();
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index c4b93b7..a11a913 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -16,6 +16,8 @@
package com.android.quickstep.views;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
@@ -39,6 +41,7 @@
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.quickstep.util.SplitSelectStateController;
/**
* A rounded rectangular component containing a single TextView.
@@ -130,8 +133,11 @@
}
private void exitSplitSelection() {
- ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController()
- .getSplitAnimationController().playPlaceholderDismissAnim(mLauncher);
+ SplitSelectStateController splitSelectController =
+ ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController();
+
+ splitSelectController.getSplitAnimationController().playPlaceholderDismissAnim(mLauncher,
+ LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON);
mLauncher.getStateManager().goToState(LauncherState.NORMAL);
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 55da160..085c502 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -54,7 +54,6 @@
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -89,6 +88,7 @@
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.ActivityOptionsWrapper;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SplitConfigurationOptions;
@@ -107,7 +107,6 @@
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.BorderAnimator;
-import com.android.quickstep.util.CancellableTask;
import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.TaskCornerRadius;
@@ -135,8 +134,6 @@
private static final String TAG = TaskView.class.getSimpleName();
private static final boolean DEBUG = false;
- private static final RectF EMPTY_RECT_F = new RectF();
-
public static final int FLAG_UPDATE_ICON = 1;
public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1;
@@ -836,7 +833,7 @@
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView().getDrawable(), container.getThumbnailView(),
container.getThumbnailView().getThumbnail(), /* intent */ null,
- /* user */ null);
+ /* user */ null, container.getItemInfo());
}
return false;
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index cbb4894..1bc2c67 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -305,6 +305,7 @@
@NavigationModeSwitch
@PortraitLandscape
@ScreenRecord // b/313464374
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406
public void testQuickSwitchFromApp() throws Exception {
startTestActivity(2);
startTestActivity(3);
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
index c6c5be4..b4f1692 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectDataHolderTest.kt
@@ -60,6 +60,7 @@
private val sampleShortcut = Intent()
private val sampleShortcut2 = Intent()
private val sampleItemInfo = ItemInfo()
+ private val sampleItemInfo2 = ItemInfo()
private val samplePackage =
AbstractLauncherUiTest.resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)
@@ -133,7 +134,7 @@
null,
INVALID_TASK_ID
)
- splitSelectDataHolder.setSecondTask(sampleTaskId)
+ splitSelectDataHolder.setSecondTask(sampleTaskId, sampleItemInfo2)
assertTrue(splitSelectDataHolder.isBothSplitAppsConfirmed())
}
@@ -145,7 +146,7 @@
null,
null
)
- splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+ splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser, sampleItemInfo2)
assertTrue(splitSelectDataHolder.isBothSplitAppsConfirmed())
}
@@ -158,7 +159,7 @@
null,
INVALID_TASK_ID
)
- splitSelectDataHolder.setSecondTask(sampleShortcut, sampleUser)
+ splitSelectDataHolder.setSecondTask(sampleShortcut, sampleUser, sampleItemInfo2)
assertTrue(splitSelectDataHolder.isBothSplitAppsConfirmed())
}
@@ -170,7 +171,7 @@
sampleItemInfo,
null
)
- splitSelectDataHolder.setSecondTask(sampleTaskId2)
+ splitSelectDataHolder.setSecondTask(sampleTaskId2, sampleItemInfo2)
val launchData = splitSelectDataHolder.getSplitLaunchData()
assertEquals(launchData.splitLaunchType, SPLIT_TASK_TASK)
@@ -194,7 +195,7 @@
sampleItemInfo,
null
)
- splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+ splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser, sampleItemInfo2)
val launchData = splitSelectDataHolder.getSplitLaunchData()
assertEquals(launchData.splitLaunchType, SPLIT_TASK_PENDINGINTENT)
@@ -218,7 +219,7 @@
sampleItemInfo,
null
)
- splitSelectDataHolder.setSecondTask(sampleShortcut, sampleUser)
+ splitSelectDataHolder.setSecondTask(sampleShortcut, sampleUser, sampleItemInfo2)
val launchData = splitSelectDataHolder.getSplitLaunchData()
assertEquals(launchData.splitLaunchType, SPLIT_TASK_SHORTCUT)
@@ -243,7 +244,7 @@
null,
INVALID_TASK_ID
)
- splitSelectDataHolder.setSecondTask(sampleTaskId)
+ splitSelectDataHolder.setSecondTask(sampleTaskId, sampleItemInfo2)
val launchData = splitSelectDataHolder.getSplitLaunchData()
assertEquals(launchData.splitLaunchType, SPLIT_PENDINGINTENT_TASK)
@@ -268,7 +269,7 @@
null,
INVALID_TASK_ID
)
- splitSelectDataHolder.setSecondTask(sampleTaskId)
+ splitSelectDataHolder.setSecondTask(sampleTaskId, sampleItemInfo2)
val launchData = splitSelectDataHolder.getSplitLaunchData()
assertEquals(launchData.splitLaunchType, SPLIT_SHORTCUT_TASK)
@@ -293,7 +294,7 @@
null,
INVALID_TASK_ID
)
- splitSelectDataHolder.setSecondTask(sampleIntent2, sampleUser)
+ splitSelectDataHolder.setSecondTask(sampleIntent2, sampleUser, sampleItemInfo2)
val launchData = splitSelectDataHolder.getSplitLaunchData()
assertEquals(launchData.splitLaunchType, SPLIT_PENDINGINTENT_PENDINGINTENT)
@@ -388,7 +389,7 @@
null,
null
)
- splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+ splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser, sampleItemInfo2)
splitSelectDataHolder.resetState()
assertFalse(splitSelectDataHolder.isSplitSelectActive())
}
@@ -402,7 +403,7 @@
null,
INVALID_TASK_ID
)
- splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser)
+ splitSelectDataHolder.setSecondTask(sampleIntent, sampleUser, sampleItemInfo2)
splitSelectDataHolder.resetState()
assertFalse(splitSelectDataHolder.isSplitSelectActive())
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 1e39a34..18b1ea0 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.LauncherState
import com.android.launcher3.logging.StatsLogManager
+import com.android.launcher3.logging.StatsLogManager.StatsLogger
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
@@ -45,6 +46,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.`when`
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
@@ -56,13 +59,14 @@
private val systemUiProxy: SystemUiProxy = mock()
private val depthController: DepthController = mock()
private val statsLogManager: StatsLogManager = mock()
+ private val statsLogger: StatsLogger = mock()
private val stateManager: StateManager<LauncherState> = mock()
private val handler: Handler = mock()
private val context: StatefulActivity<*> = mock()
private val recentsModel: RecentsModel = mock()
private val pendingIntent: PendingIntent = mock()
- lateinit var splitSelectStateController: SplitSelectStateController
+ private lateinit var splitSelectStateController: SplitSelectStateController
private val primaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId)
private val nonPrimaryUserHandle = UserHandle(ActivityManager.RunningTaskInfo().userId + 10)
@@ -74,6 +78,9 @@
@Before
fun setup() {
+ `when`(statsLogManager.logger()).thenReturn(statsLogger)
+ `when`(statsLogger.withInstanceId(any())).thenReturn(statsLogger)
+ `when`(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
splitSelectStateController =
SplitSelectStateController(
context,
@@ -593,9 +600,10 @@
@Test
fun secondPendingIntentSet() {
val itemInfo = ItemInfo()
+ val itemInfo2 = ItemInfo()
whenever(pendingIntent.creatorUserHandle).thenReturn(primaryUserHandle)
splitSelectStateController.setInitialTaskSelect(null, 0, itemInfo, null, 1)
- splitSelectStateController.setSecondTask(pendingIntent)
+ splitSelectStateController.setSecondTask(pendingIntent, itemInfo2)
assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index f72c556..e7b88dc 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -124,7 +124,8 @@
/** Type of popups that should get exclusive accessibility focus. */
public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
- & ~TYPE_ALL_APPS_EDU & ~TYPE_TASKBAR_ALL_APPS & ~TYPE_PIN_IME_POPUP;
+ & ~TYPE_ALL_APPS_EDU & ~TYPE_TASKBAR_ALL_APPS & ~TYPE_PIN_IME_POPUP
+ & ~TYPE_WIDGET_RESIZE_FRAME;
// These view all have particular operation associated with swipe down interaction.
public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 38ddfec..259ddbd 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -69,13 +69,13 @@
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.PlaceHolderIconDrawable;
-import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.search.StringMatcherUtility;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.util.SafeCloseable;
@@ -190,7 +190,7 @@
@ViewDebug.ExportedProperty(category = "launcher")
private boolean mDisableRelayout = false;
- private HandlerRunnable mIconLoadRequest;
+ private CancellableTask mIconLoadRequest;
private boolean mEnableIconUpdateAnimation = false;
@@ -425,11 +425,13 @@
}
/**
- * Only if actual text can be displayed in two line, the {@code true} value will be effective.
+ * Only if actual text can be displayed in two line, the {@code true} value will be effective.
*/
protected boolean shouldUseTwoLine() {
- return (FeatureFlags.enableTwolineAllapps() && isCurrentLanguageEnglish())
- && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW);
+ return FeatureFlags.enableTwolineAllapps() && isCurrentLanguageEnglish()
+ && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW)
+ && (!Flags.enableTwolineToggle() || (Flags.enableTwolineToggle()
+ && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext())));
}
protected boolean isCurrentLanguageEnglish() {
@@ -581,12 +583,12 @@
/**
* Find the appropriate text spacing to display the provided text
- * @param paint the paint used by the text view
- * @param text the text to display
- * @param allowedWidthPx available space to render the text
- * @param minSpacingEm minimum spacing allowed between characters
- * @return the final textSpacing value
*
+ * @param paint the paint used by the text view
+ * @param text the text to display
+ * @param allowedWidthPx available space to render the text
+ * @param minSpacingEm minimum spacing allowed between characters
+ * @return the final textSpacing value
* @see #setLetterSpacing(float)
*/
private float findBestSpacingValue(TextPaint paint, String text, float allowedWidthPx,
@@ -814,13 +816,13 @@
* iterating through the list of break points and determining if the strings between the break
* points can fit within the line it is in. We will show the modified string if there is enough
* horizontal and vertical space, otherwise this method will just return the original string.
- * Example assuming each character takes up one spot:
- * title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7
- * We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery,
- * now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth
- * at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking
- * if the first char is a SPACE, we trim to append "Stats". So resulting string would be
- * "Battery\nStats"
+ * Example assuming each character takes up one spot:
+ * title = "Battery Stats", breakpoint = [6], stringPtr = 0, limitedWidth = 7
+ * We get the current word -> from sublist(0, breakpoint[i]+1) so sublist (0,7) -> Battery,
+ * now stringPtr = 7 then from sublist(7) the current string is " Stats" and the runningWidth
+ * at this point exceeds limitedWidth and so we put " Stats" onto the next line (after checking
+ * if the first char is a SPACE, we trim to append "Stats". So resulting string would be
+ * "Battery\nStats"
*/
public static CharSequence modifyTitleToSupportMultiLine(int limitedWidth, int limitedHeight,
CharSequence title, TextPaint paint, IntArray breakPoints, float spacingMultiplier,
@@ -834,25 +836,25 @@
StringBuilder newString = new StringBuilder();
paint.setLetterSpacing(MIN_LETTER_SPACING);
int stringPtr = 0;
- for (int i = 0; i < breakPoints.size()+1; i++) {
+ for (int i = 0; i < breakPoints.size() + 1; i++) {
if (i < breakPoints.size()) {
- currentWord = title.subSequence(stringPtr, breakPoints.get(i)+1);
+ currentWord = title.subSequence(stringPtr, breakPoints.get(i) + 1);
} else {
// last word from recent breakpoint until the end of the string
currentWord = title.subSequence(stringPtr, title.length());
}
- currentWordWidth = paint.measureText(currentWord,0, currentWord.length());
+ currentWordWidth = paint.measureText(currentWord, 0, currentWord.length());
runningWidth += currentWordWidth;
if (runningWidth <= limitedWidth) {
newString.append(currentWord);
} else {
- if (i != 0) {
+ if (i != 0) {
// If putting word onto a new line, make sure there is no space or new line
// character in the beginning of the current word and just put in the rest of
// the characters.
CharSequence lastCharacters = title.subSequence(stringPtr, title.length());
int beginningLetterType =
- Character.getType(Character.codePointAt(lastCharacters,0));
+ Character.getType(Character.codePointAt(lastCharacters, 0));
if (beginningLetterType == Character.SPACE_SEPARATOR
|| beginningLetterType == Character.LINE_SEPARATOR) {
lastCharacters = lastCharacters.length() > 1
@@ -873,7 +875,7 @@
// no need to look forward into the string if we've already finished processing
break;
}
- stringPtr = breakPoints.get(i)+1;
+ stringPtr = breakPoints.get(i) + 1;
}
return newString.toString();
}
@@ -963,12 +965,12 @@
return preloadDrawable;
}
- private boolean isIconDisabled(ItemInfoWithIcon info) {
- if (info.isArchived()) {
- return info.getProgressLevel() == 0
- && (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0;
- }
- return info.getProgressLevel() == 0;
+ /**
+ * Returns true to grey the icon if the icon is either suspended or if the icon is pending
+ * download
+ */
+ public boolean isIconDisabled(ItemInfoWithIcon info) {
+ return info.isDisabled() || info.isPendingDownload();
}
public void applyDotState(ItemInfo itemInfo, boolean animate) {
@@ -1015,12 +1017,12 @@
if ((info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
setContentDescription(getContext()
.getString(
- R.string.app_installing_title, info.title, percentageString));
+ R.string.app_installing_title, info.title, percentageString));
} else if ((info.runtimeStatusFlags
& FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) {
setContentDescription(getContext()
.getString(
- R.string.app_downloading_title, info.title, percentageString));
+ R.string.app_downloading_title, info.title, percentageString));
}
}
}
@@ -1187,7 +1189,8 @@
public SafeCloseable prepareDrawDragView() {
resetIconScale();
setForceHideDot(true);
- return () -> { };
+ return () -> {
+ };
}
private void resetIconScale() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4afa8e0..4dd2fe3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -717,7 +717,7 @@
}
// Calculate all of the remaining variables.
- extraSpace = updateAvailableDimensions(res);
+ extraSpace = updateAvailableDimensions(context);
calculateAndSetWorkspaceVerticalPadding(context, inv, extraSpace);
@@ -1009,14 +1009,14 @@
/**
* Returns the amount of extra (or unused) vertical space.
*/
- private int updateAvailableDimensions(Resources res) {
+ private int updateAvailableDimensions(Context context) {
iconCenterVertically = (mIsScalableGrid || mIsResponsiveGrid) && isVerticalBarLayout();
if (mIsResponsiveGrid) {
iconSizePx = mResponsiveWorkspaceCellSpec.getIconSize();
iconTextSizePx = mResponsiveWorkspaceCellSpec.getIconTextSize();
mIconDrawablePaddingOriginalPx = mResponsiveWorkspaceCellSpec.getIconDrawablePadding();
- updateIconSize(1f, res);
+ updateIconSize(1f, context);
updateWorkspacePadding();
return 0;
}
@@ -1026,7 +1026,7 @@
iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics));
iconTextSizePx = pxFromSp(invIconTextSizeSp, mMetrics);
- updateIconSize(1f, res);
+ updateIconSize(1f, context);
updateWorkspacePadding();
// Check to see if the icons fit within the available height.
@@ -1050,7 +1050,7 @@
if (shouldScale) {
float scale = Math.min(scaleX, scaleY);
- updateIconSize(scale, res);
+ updateIconSize(scale, context);
extraHeight = Math.max(0, maxHeight - getCellLayoutHeightSpecification());
}
@@ -1096,7 +1096,7 @@
* iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants,
* hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx.
*/
- public void updateIconSize(float scale, Resources res) {
+ public void updateIconSize(float scale, Context context) {
// Icon scale should never exceed 1, otherwise pixellation may occur.
iconScale = Math.min(1f, scale);
cellScaleToFit = scale;
@@ -1216,13 +1216,15 @@
if (mIsResponsiveGrid) {
updateAllAppsWithResponsiveMeasures();
} else {
- updateAllAppsIconSize(scale, res);
+ updateAllAppsIconSize(scale, context.getResources());
}
updateAllAppsContainerWidth();
if (isVerticalLayout && !mIsResponsiveGrid) {
hideWorkspaceLabelsIfNotEnoughSpace();
}
- if (FeatureFlags.enableTwolineAllapps()) {
+ if (FeatureFlags.enableTwolineAllapps()
+ && (!Flags.enableTwolineToggle() || (Flags.enableTwolineToggle()
+ && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context)))) {
// Add extra textHeight to the existing allAppsCellHeight.
allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 07eea32..edfef5e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -78,6 +78,7 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_EXIT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RECONFIGURED;
@@ -1622,7 +1623,7 @@
}
if (FeatureFlags.enableSplitContextually()) {
- handleSplitAnimationGoingToHome();
+ handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
}
mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
handleGestureContract(intent);
@@ -1638,7 +1639,7 @@
}
/** Handle animating away split placeholder view when user taps on home button */
- protected void handleSplitAnimationGoingToHome() {
+ protected void handleSplitAnimationGoingToHome(EventEnum splitDismissReason) {
// Overridden
}
@@ -2735,7 +2736,7 @@
}
/** Call to dismiss the intermediary split selection state. */
- public void dismissSplitSelection() {
+ public void dismissSplitSelection(StatsLogManager.LauncherEvent splitDismissEvent) {
// Overridden; move this into ActivityContext if necessary for Taskbar
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 3b62ae1..b0a644b 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -349,6 +349,8 @@
@JvmField
val PRIVATE_SPACE_APPS =
nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
+ @JvmField val ENABLE_TWOLINE_ALLAPPS_TOGGLE =
+ backedUpItem("pref_enable_two_line_toggle", false)
@JvmField
val THEMED_ICONS =
backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 0e38007..9867556 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -132,7 +132,7 @@
launcher.viewCache.setCacheSize(R.layout.folder_page, 2)
TraceHelper.INSTANCE.endSection()
launcher.workspace.removeExtraEmptyScreen(/* stripEmptyScreens= */ true)
- launcher.workspace.pageIndicator.setAreScreensBinding(false, deviceProfile.isTwoPanels)
+ launcher.workspace.pageIndicator.setPauseScroll(/*pause=*/ false, deviceProfile.isTwoPanels)
}
/**
@@ -299,8 +299,8 @@
}
override fun bindScreens(orderedScreenIds: LIntArray) {
- launcher.workspace.pageIndicator.setAreScreensBinding(
- true,
+ launcher.workspace.pageIndicator.setPauseScroll(
+ /*pause=*/ true,
launcher.deviceProfile.isTwoPanels
)
val firstScreenPosition = 0
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 55416af..ca34dd1 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -2226,8 +2226,7 @@
private Runnable getWidgetResizeFrameRunnable(DragOptions options,
LauncherAppWidgetHostView hostView, CellLayout cellLayout) {
AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
- if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
- && !options.isAccessibleDrag) {
+ if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) {
return () -> {
if (!isPageInTransition()) {
AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 27092f6..ca587c1 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -36,6 +36,8 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.allapps.search.SearchAdapterProvider;
import com.android.launcher3.config.FeatureFlags;
@@ -221,8 +223,11 @@
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_TYPE_ICON:
- int layout = !FeatureFlags.enableTwolineAllapps() ? R.layout.all_apps_icon
- : R.layout.all_apps_icon_twoline;
+ int layout = (FeatureFlags.enableTwolineAllapps() &&
+ (!Flags.enableTwolineToggle() || (Flags.enableTwolineToggle()
+ && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
+ mActivityContext.getApplicationContext()))))
+ ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon;
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
layout, parent, false);
icon.setLongPressTimeoutFactor(1f);
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 871643e..9001a0c 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -119,7 +119,7 @@
final Runnable onCompleteRunnable) {
return reverse
? new FolderPreviewItemAnim(this, mFirstPageParams.get(0), 0, 2, -1, -1,
- FINAL_ITEM_ANIMATION_DURATION, onCompleteRunnable)
+ FINAL_ITEM_ANIMATION_DURATION, onCompleteRunnable)
: new FolderPreviewItemAnim(this, mFirstPageParams.get(0), -1, -1, 0, 2,
INITIAL_ITEM_ANIMATION_DURATION, onCompleteRunnable);
}
@@ -219,9 +219,9 @@
/**
* Draws each preview item.
*
- * @param offset The offset needed to draw the preview items.
+ * @param offset The offset needed to draw the preview items.
* @param shouldClipPath Iff true, clip path using {@param clipPath}.
- * @param clipPath The clip path of the folder icon.
+ * @param clipPath The clip path of the folder icon.
*/
private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params, PointF offset,
boolean shouldClipPath, Path clipPath) {
@@ -362,13 +362,13 @@
/**
* Handles the case where items in the preview are either:
- * - Moving into the preview
- * - Moving into a new position
- * - Moving out of the preview
+ * - Moving into the preview
+ * - Moving into a new position
+ * - Moving out of the preview
*
* @param oldItems The list of items in the old preview.
* @param newItems The list of items in the new preview.
- * @param dropped The item that was dropped onto the FolderIcon.
+ * @param dropped The item that was dropped onto the FolderIcon.
*/
public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems,
WorkspaceItemInfo dropped) {
@@ -433,9 +433,8 @@
@VisibleForTesting
public void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
if (item.hasPromiseIconUi() || (item.runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
+ & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
PreloadIconDrawable drawable = newPendingIcon(mContext, item);
- drawable.setLevel(item.getProgressLevel());
p.drawable = drawable;
} else {
p.drawable = item.newIcon(mContext,
@@ -443,7 +442,6 @@
}
p.drawable.setBounds(0, 0, mIconSize, mIconSize);
p.item = item;
-
// Set the callback to FolderIcon as it is responsible to drawing the icon. The
// callback will be released when the folder is opened.
p.drawable.setCallback(mIcon);
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 3e77c78..9fffcc1 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -173,6 +173,8 @@
mIconScaleMultiplier.updateValue(info.getProgressLevel() == 0 ? 0 : 1);
setLevel(info.getProgressLevel());
+ // Set a disabled icon color if the app is suspended or if the app is pending download
+ setIsDisabled(info.isDisabled() || info.isPendingDownload());
}
@Override
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index d8fa90a..ee66a60 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -54,7 +54,6 @@
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.cache.BaseIconCache;
import com.android.launcher3.icons.cache.CachingLogic;
-import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -63,6 +62,7 @@
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Preconditions;
@@ -100,7 +100,7 @@
private final UserCache mUserManager;
private final InstantAppResolver mInstantAppResolver;
private final IconProvider mIconProvider;
- private final HandlerRunnable mCancelledRunnable;
+ private final CancellableTask mCancelledTask;
private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
@@ -119,9 +119,8 @@
mIconProvider = iconProvider;
mWidgetCategoryBitmapInfos = new SparseArray<>();
- mCancelledRunnable = new HandlerRunnable(
- mWorkerHandler, () -> null, MAIN_EXECUTOR, c -> { });
- mCancelledRunnable.cancel();
+ mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { });
+ mCancelledTask.cancel();
}
@Override
@@ -174,7 +173,7 @@
*
* @return a request ID that can be used to cancel the request.
*/
- public HandlerRunnable updateIconInBackground(final ItemInfoUpdateReceiver caller,
+ public CancellableTask updateIconInBackground(final ItemInfoUpdateReceiver caller,
final ItemInfoWithIcon info) {
Preconditions.assertUIThread();
Supplier<ItemInfoWithIcon> task;
@@ -191,7 +190,7 @@
} else {
Log.i(TAG, "Icon update not supported for "
+ info == null ? "null" : info.getClass().getName());
- return mCancelledRunnable;
+ return mCancelledTask;
}
if (mPendingIconRequestCount <= 0) {
@@ -199,7 +198,7 @@
}
mPendingIconRequestCount++;
- HandlerRunnable<ItemInfoWithIcon> request = new HandlerRunnable<>(mWorkerHandler,
+ CancellableTask<ItemInfoWithIcon> request = new CancellableTask<>(
task, MAIN_EXECUTOR, caller::reapplyItemInfo, this::onIconRequestEnd);
Utilities.postAsyncCallback(mWorkerHandler, request);
return request;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2aabb7c..5cb1540 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -199,6 +199,11 @@
@UiEvent(doc = "User tapped on app info system shortcut.")
LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP(515),
+ /**
+ * @deprecated Use {@link #LAUNCHER_APP_ICON_MENU_SPLIT_LEFT_TOP} or
+ * {@link #LAUNCHER_APP_ICON_MENU_SPLIT_RIGHT_BOTTOM}
+ */
+ @Deprecated
@UiEvent(doc = "User tapped on split screen icon on a task menu.")
LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP(518),
@@ -725,6 +730,27 @@
@UiEvent(doc = "User tapped on private space uninstall system shortcut.")
LAUNCHER_PRIVATE_SPACE_UNINSTALL_SYSTEM_SHORTCUT_TAP(1608),
+ @UiEvent(doc = "User initiated split selection")
+ LAUNCHER_SPLIT_SELECTION_INITIATED(1618),
+
+ @UiEvent(doc = "User finished a split selection session")
+ LAUNCHER_SPLIT_SELECTION_COMPLETE(1619),
+
+ @UiEvent(doc = "User selected both apps for split screen")
+ LAUNCHER_SPLIT_SELECTED_SECOND_APP(1609),
+
+ @UiEvent(doc = "User exited split selection by going home via swipe, button, or state "
+ + "transition")
+ LAUNCHER_SPLIT_SELECTION_EXIT_HOME(1610),
+
+ @UiEvent(doc = "User exited split selection by tapping cancel in split instructions view")
+ LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON(1611),
+
+ @UiEvent(doc = "User exited split selection when another activity/app came to foreground"
+ + " after first app had been selected OR if user long-pressed on home. Default exit"
+ + " metric.")
+ LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED(1612),
+
// ADD MORE
;
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index e46c502..352c363 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -139,7 +139,8 @@
*/
private int mProgressLevel = 100;
- protected ItemInfoWithIcon() { }
+ protected ItemInfoWithIcon() {
+ }
protected ItemInfoWithIcon(ItemInfoWithIcon info) {
super(info);
@@ -155,7 +156,20 @@
}
/**
- * Returns true if the app corresponding to the item is archived. */
+ * @return {@code true} if the app is pending download (0 progress) or if the app is archived
+ * and its install session is active
+ */
+ public boolean isPendingDownload() {
+ if (isArchived()) {
+ return this.getProgressLevel() == 0
+ && (this.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0;
+ }
+ return getProgressLevel() == 0;
+ }
+
+ /**
+ * Returns true if the app corresponding to the item is archived.
+ */
public boolean isArchived() {
if (!Utilities.enableSupportForArchiving()) {
return false;
@@ -179,7 +193,7 @@
public boolean isAppStartable() {
return ((runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) == 0)
&& (((runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0)
- || mProgressLevel == 100 || isArchived());
+ || mProgressLevel == 100 || isArchived());
}
/**
@@ -242,7 +256,7 @@
return targetPackage != null
? ApiWrapper.getAppMarketActivityIntent(
- context, targetPackage, Process.myUserHandle())
+ context, targetPackage, Process.myUserHandle())
: null;
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicator.java b/src/com/android/launcher3/pageindicators/PageIndicator.java
index 30156c8..0640bf3 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicator.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicator.java
@@ -27,10 +27,12 @@
void setMarkersCount(int numMarkers);
/**
- * Sets flag to indicate when the screens are in the process of binding so that we don't animate
- * during that period.
+ * Sets a flag indicating whether to pause scroll.
+ * <p>Should be set to {@code true} while the screen is binding or new data is being applied,
+ * and to {@code false} once done. This prevents animation conflicts due to scrolling during
+ * those periods.</p>
*/
- default void setAreScreensBinding(boolean areScreensBinding, boolean isTwoPanels) {
+ default void setPauseScroll(boolean pause, boolean isTwoPanels) {
// No-op by default
}
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index 1b5abaa..77effca 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -130,7 +130,7 @@
*/
private float mCurrentPosition;
private float mFinalPosition;
- private boolean mAreScreensBinding;
+ private boolean mIsScrollPaused;
private boolean mIsTwoPanels;
private ObjectAnimator mAnimator;
private @Nullable ObjectAnimator mAlphaAnimator;
@@ -172,7 +172,7 @@
}
// Skip scroll update during binding. We will update it when binding completes.
- if (mAreScreensBinding) {
+ if (mIsScrollPaused) {
return;
}
@@ -376,15 +376,15 @@
}
@Override
- public void setAreScreensBinding(boolean areScreensBinding, boolean isTwoPanels) {
+ public void setPauseScroll(boolean pause, boolean isTwoPanels) {
mIsTwoPanels = isTwoPanels;
// Reapply correct current position which was skipped during setScroll.
- if (mAreScreensBinding && !areScreensBinding) {
+ if (mIsScrollPaused && !pause) {
CURRENT_POSITION.set(this, (float) mActivePage);
}
- mAreScreensBinding = areScreensBinding;
+ mIsScrollPaused = pause;
}
@Override
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index 71957e1..43027da 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -23,7 +23,7 @@
import com.android.launcher3.BubbleTextView
import com.android.launcher3.allapps.BaseAllAppsAdapter
import com.android.launcher3.config.FeatureFlags
-import com.android.launcher3.util.ExecutorRunnable
+import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
import com.android.launcher3.views.ActivityContext
@@ -39,7 +39,7 @@
class AllAppsRecyclerViewPool<T> : RecycledViewPool() {
var hasWorkProfile = false
- var executorRunnable: ExecutorRunnable<List<ViewHolder>>? = null
+ private var mCancellableTask: CancellableTask<List<ViewHolder>>? = null
/**
* Preinflate app icons. If all apps RV cannot be scrolled down, we don't need to preinflate.
@@ -62,14 +62,14 @@
override fun getLayoutManager(): RecyclerView.LayoutManager? = null
}
- executorRunnable?.cancel(/* interrupt= */ false)
- executorRunnable =
- ExecutorRunnable.createAndExecute(
- VIEW_PREINFLATION_EXECUTOR,
+ mCancellableTask?.cancel()
+ var task: CancellableTask<List<ViewHolder>>? = null
+ task =
+ CancellableTask(
{
val list: ArrayList<ViewHolder> = ArrayList()
for (i in 0 until preInflateCount) {
- if (Thread.interrupted()) {
+ if (task?.canceled == true) {
break
}
list.add(
@@ -85,6 +85,8 @@
}
}
)
+ mCancellableTask = task
+ VIEW_PREINFLATION_EXECUTOR.submit(mCancellableTask)
}
/**
@@ -93,7 +95,7 @@
*/
override fun clear() {
super.clear()
- executorRunnable?.cancel(/* interrupt= */ true)
+ mCancellableTask?.cancel()
}
/**
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 8c43f75..0ff10c2 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -24,6 +24,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_LONGPRESS;
import android.graphics.PointF;
@@ -207,7 +208,7 @@
mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
if (FeatureFlags.enableSplitContextually() && mLauncher.isSplitSelectionActive()) {
- mLauncher.dismissSplitSelection();
+ mLauncher.dismissSplitSelection(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
}
} else {
cancelLongPress();
diff --git a/src/com/android/launcher3/util/CancellableTask.kt b/src/com/android/launcher3/util/CancellableTask.kt
new file mode 100644
index 0000000..49ef020
--- /dev/null
+++ b/src/com/android/launcher3/util/CancellableTask.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.launcher3.util
+
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import java.util.function.Supplier
+
+/** A [Runnable] that can be posted to a [Executor] that can be cancelled. */
+class CancellableTask<T>
+@JvmOverloads
+constructor(
+ private val task: Supplier<T>,
+ // Executor where consumer needs to be executed on. Typically UI executor.
+ private val callbackExecutor: Executor,
+ // Consumer that needs to be accepted upon completion of the task. Typically work that needs to
+ // be done in UI thread after task completes.
+ private val callback: Consumer<T>,
+ // Callback to be executed on callbackExecutor at the end irrespective of the task being
+ // completed or cancelled
+ private val endRunnable: Runnable = Runnable {}
+) : Runnable {
+
+ // flag to cancel the callback
+ var canceled = false
+ private set
+
+ private var ended = false
+
+ override fun run() {
+ if (canceled) return
+ val value = task.get()
+ callbackExecutor.execute {
+ if (!canceled) {
+ callback.accept(value)
+ }
+ onEnd()
+ }
+ }
+
+ /**
+ * Cancel the [CancellableTask] if not scheduled. If [CancellableTask] has started execution at
+ * this time, we will try to cancel the callback if not executed yet.
+ */
+ fun cancel() {
+ canceled = true
+ callbackExecutor.execute(this::onEnd)
+ }
+
+ private fun onEnd() {
+ if (!ended) {
+ ended = true
+ endRunnable.run()
+ }
+ }
+}
diff --git a/src/com/android/launcher3/util/ExecutorRunnable.kt b/src/com/android/launcher3/util/ExecutorRunnable.kt
deleted file mode 100644
index 49cf592..0000000
--- a/src/com/android/launcher3/util/ExecutorRunnable.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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.launcher3.util
-
-import java.util.concurrent.Executor
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Future
-import java.util.function.Consumer
-import java.util.function.Supplier
-
-/** A [Runnable] that can be posted to a [Executor] that can be cancelled. */
-class ExecutorRunnable<T>
-private constructor(
- private val task: Supplier<T>,
- // Executor where consumer needs to be executed on. Typically UI executor.
- private val callbackExecutor: Executor,
- // Consumer that needs to be accepted upon completion of the task. Typically work that needs to
- // be done in UI thread after task completes.
- private val callback: Consumer<T>
-) : Runnable {
-
- // future of this runnable that will used for cancellation.
- lateinit var future: Future<*>
-
- // flag to cancel the callback
- var canceled = false
-
- override fun run() {
- val value: T = task.get()
- callbackExecutor.execute {
- if (!canceled) {
- callback.accept(value)
- }
- }
- }
-
- /**
- * Cancel the [ExecutorRunnable] if not scheduled. If [ExecutorRunnable] has started execution
- * at this time, we will try to cancel the callback if not executed yet.
- */
- fun cancel(interrupt: Boolean) {
- future.cancel(interrupt)
- canceled = true
- }
-
- companion object {
- /**
- * Create [ExecutorRunnable] and execute it on task [Executor]. It will also save the
- * [Future] into this [ExecutorRunnable] to be used for cancellation.
- */
- fun <T> createAndExecute(
- // Executor where task will be executed, typically an Executor running on background
- // thread.
- taskExecutor: ExecutorService,
- task: Supplier<T>,
- callbackExecutor: Executor,
- callback: Consumer<T>
- ): ExecutorRunnable<T> {
- val executorRunnable = ExecutorRunnable(task, callbackExecutor, callback)
- executorRunnable.future = taskExecutor.submit(executorRunnable)
- return executorRunnable
- }
- }
-}
diff --git a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
index 99485be..aab78bd 100644
--- a/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
+++ b/src/com/android/launcher3/widget/DatabaseWidgetPreviewLoader.java
@@ -41,9 +41,9 @@
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShadowGenerator;
-import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.pm.ShortcutConfigActivityInfo;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.util.Executors;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.util.WidgetSizes;
@@ -74,12 +74,12 @@
* @return a request id which can be used to cancel the request.
*/
@NonNull
- public HandlerRunnable loadPreview(
+ public CancellableTask loadPreview(
@NonNull WidgetItem item,
@NonNull Size previewSize,
@NonNull Consumer<Bitmap> callback) {
Handler handler = Executors.UI_HELPER_EXECUTOR.getHandler();
- HandlerRunnable<Bitmap> request = new HandlerRunnable<>(handler,
+ CancellableTask<Bitmap> request = new CancellableTask<>(
() -> generatePreview(item, previewSize.getWidth(), previewSize.getHeight()),
MAIN_EXECUTOR,
callback);
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 94c630a..7345511 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -51,8 +51,8 @@
import com.android.launcher3.R;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.RoundDrawableWrapper;
-import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.views.ActivityContext;
import java.util.function.Consumer;
@@ -90,7 +90,7 @@
private final DatabaseWidgetPreviewLoader mWidgetPreviewLoader;
- protected HandlerRunnable mActiveRequest;
+ protected CancellableTask mActiveRequest;
private boolean mAnimatePreview = true;
protected final ActivityContext mActivity;
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index 738d74e..426a3ae 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -29,7 +29,6 @@
import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
@@ -88,6 +87,7 @@
* Displays all the provided recommendations in a single table if they fit.
*
* @param recommendedWidgets list of widgets to be displayed in recommendation section.
+ * @param deviceProfile the current {@link DeviceProfile}
* @param availableHeight height in px that can be used to display the recommendations;
* recommendations that don't fit in this height won't be shown
* @param availableWidth width in px that the recommendations should display in
@@ -96,12 +96,13 @@
* @return {@code false} if no recommendations could fit in the available space.
*/
public boolean setRecommendations(
- List<WidgetItem> recommendedWidgets, final @Px float availableHeight,
- final @Px int availableWidth, final @Px int cellPadding) {
+ List<WidgetItem> recommendedWidgets, DeviceProfile deviceProfile,
+ final @Px float availableHeight, final @Px int availableWidth,
+ final @Px int cellPadding) {
this.mAvailableHeight = availableHeight;
removeAllViews();
- maybeDisplayInTable(recommendedWidgets, availableWidth, cellPadding);
+ maybeDisplayInTable(recommendedWidgets, deviceProfile, availableWidth, cellPadding);
updateTitleAndIndicator();
return getChildCount() > 0;
}
@@ -111,6 +112,7 @@
* <p>In case of a single category, no title is displayed for it.</p>
*
* @param recommendations a map of widget items per recommendation category
+ * @param deviceProfile the current {@link DeviceProfile}
* @param availableHeight height in px that can be used to display the recommendations;
* recommendations that don't fit in this height won't be shown
* @param availableWidth width in px that the recommendations should display in
@@ -120,10 +122,12 @@
*/
public boolean setRecommendations(
Map<WidgetRecommendationCategory, List<WidgetItem>> recommendations,
+ DeviceProfile deviceProfile,
final @Px float availableHeight, final @Px int availableWidth,
final @Px int cellPadding) {
this.mAvailableHeight = availableHeight;
Context context = getContext();
+ mPageIndicator.setPauseScroll(true, deviceProfile.isTwoPanels);
removeAllViews();
int displayedCategories = 0;
@@ -133,7 +137,7 @@
new TreeMap<>(recommendations).entrySet()) {
// If none of the recommendations for the category could fit in the mAvailableHeight, we
// don't want to add that category; and we look for the next one.
- if (maybeDisplayInTable(entry.getValue(), availableWidth, cellPadding)) {
+ if (maybeDisplayInTable(entry.getValue(), deviceProfile, availableWidth, cellPadding)) {
mCategoryTitles.add(
context.getResources().getString(entry.getKey().categoryTitleRes));
displayedCategories++;
@@ -145,14 +149,21 @@
}
updateTitleAndIndicator();
+ mPageIndicator.setPauseScroll(false, deviceProfile.isTwoPanels);
return getChildCount() > 0;
}
/** Displays the page title and paging indicator if there are multiple pages. */
private void updateTitleAndIndicator() {
- int titleAndIndicatorVisibility = getPageCount() > 1 ? View.VISIBLE : View.GONE;
+ boolean showPaginatedView = getPageCount() > 1;
+ int titleAndIndicatorVisibility = showPaginatedView ? View.VISIBLE : View.GONE;
mRecommendationPageTitle.setVisibility(titleAndIndicatorVisibility);
mPageIndicator.setVisibility(titleAndIndicatorVisibility);
+ if (showPaginatedView) {
+ mPageIndicator.setActiveMarker(0);
+ setCurrentPage(0);
+ mRecommendationPageTitle.setText(mCategoryTitles.get(0));
+ }
}
@Override
@@ -218,9 +229,9 @@
* <p>Returns false if none of the recommendations could fit.</p>
*/
private boolean maybeDisplayInTable(List<WidgetItem> recommendedWidgets,
+ DeviceProfile deviceProfile,
final @Px int availableWidth, final @Px int cellPadding) {
Context context = getContext();
- DeviceProfile deviceProfile = Launcher.getLauncher(context).getDeviceProfile();
LayoutInflater inflater = LayoutInflater.from(context);
List<ArrayList<WidgetItem>> rows = groupWidgetItemsUsingRowPxWithoutReordering(
@@ -239,7 +250,7 @@
recommendationsTable.setWidgetCellLongClickListener(mWidgetCellOnLongClickListener);
boolean displayedAtLeastOne = recommendationsTable.setRecommendedWidgets(rows,
- mAvailableHeight);
+ deviceProfile, mAvailableHeight);
if (displayedAtLeastOne) {
addView(recommendationsTable);
}
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 237078e..f5742af 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -590,6 +590,7 @@
if (enableCategorizedWidgetSuggestions()) {
mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
mActivityContext.getPopupDataProvider().getCategorizedRecommendedWidgets(),
+ mDeviceProfile,
/* availableHeight= */ getMaxAvailableHeightForRecommendations(),
/* availableWidth= */ mMaxSpanPerRow,
/* cellPadding= */ mWidgetCellHorizontalPadding
@@ -597,6 +598,7 @@
} else {
mHasRecommendedWidgets = mWidgetRecommendationsView.setRecommendations(
mActivityContext.getPopupDataProvider().getRecommendedWidgets(),
+ mDeviceProfile,
/* availableHeight= */ getMaxAvailableHeightForRecommendations(),
/* availableWidth= */ mMaxSpanPerRow,
/* cellPadding= */ mWidgetCellHorizontalPadding
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index 3383299..d373a3b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -35,9 +35,9 @@
import com.android.launcher3.R;
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
import com.android.launcher3.icons.PlaceHolderIconDrawable;
-import com.android.launcher3.icons.cache.HandlerRunnable;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.CancellableTask;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
@@ -57,7 +57,7 @@
* widget apps aren't collapsable.
*/
private final boolean mIsCollapsable;
- @Nullable private HandlerRunnable mIconLoadRequest;
+ @Nullable private CancellableTask mIconLoadRequest;
@Nullable private Drawable mIconDrawable;
@Nullable private WidgetsListDrawableState mListDrawableState;
private ImageView mAppIcon;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index ce1f4e0..47750a5 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -32,7 +32,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.widget.WidgetCell;
@@ -89,9 +88,11 @@
* <p>Returns {@code false} if none of the widgets could fit</p>
*/
public boolean setRecommendedWidgets(List<ArrayList<WidgetItem>> recommendedWidgets,
+ DeviceProfile deviceProfile,
float recommendationTableMaxHeight) {
mRecommendationTableMaxHeight = recommendationTableMaxHeight;
RecommendationTableData data = fitRecommendedWidgetsToTableSpace(/* previewScale= */ 1f,
+ deviceProfile,
recommendedWidgets);
bindData(data);
return !data.mRecommendationTable.isEmpty();
@@ -144,6 +145,7 @@
private RecommendationTableData fitRecommendedWidgetsToTableSpace(
float previewScale,
+ DeviceProfile deviceProfile,
List<ArrayList<WidgetItem>> recommendedWidgetsInTable) {
if (previewScale < MAX_DOWN_SCALE_RATIO) {
Log.w(TAG, "Hide recommended widgets. Can't down scale previews to " + previewScale);
@@ -151,7 +153,6 @@
}
// A naive estimation of the widgets recommendation table height without inflation.
float totalHeight = mWidgetsRecommendationTableVerticalPadding;
- DeviceProfile deviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
for (int i = 0; i < recommendedWidgetsInTable.size(); i++) {
List<WidgetItem> widgetItems = recommendedWidgetsInTable.get(i);
float rowHeight = 0;
@@ -175,12 +176,14 @@
// num of row by 1 to see if it fits.
return fitRecommendedWidgetsToTableSpace(
previewScale,
+ deviceProfile,
recommendedWidgetsInTable.subList(/* fromIndex= */0,
/* toIndex= */recommendedWidgetsInTable.size() - 1));
}
float nextPreviewScale = previewScale * DOWN_SCALE_RATIO;
- return fitRecommendedWidgetsToTableSpace(nextPreviewScale, recommendedWidgetsInTable);
+ return fitRecommendedWidgetsToTableSpace(nextPreviewScale, deviceProfile,
+ recommendedWidgetsInTable);
}
/** Data class for the widgets recommendation table and widgets preview scaling. */
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ExecutorRunnableTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/CancellableTaskTest.kt
similarity index 92%
rename from tests/multivalentTests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/CancellableTaskTest.kt
index 6634577..eb58096 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/CancellableTaskTest.kt
@@ -29,12 +29,12 @@
import org.junit.Test
import org.junit.runner.RunWith
-/** Unit test for [ExecutorRunnable] */
+/** Unit test for [CancellableTask] */
@SmallTest
@RunWith(AndroidJUnit4::class)
-class ExecutorRunnableTest {
+class CancellableTaskTest {
- private lateinit var underTest: ExecutorRunnable<Int>
+ private lateinit var underTest: CancellableTask<Int>
private val lock = ReentrantLock()
private var result: Int = -1
@@ -51,8 +51,7 @@
private fun submitJob() {
underTest =
- ExecutorRunnable.createAndExecute(
- Executors.UI_HELPER_EXECUTOR,
+ CancellableTask(
{
isTaskExecuted = true
1
@@ -63,6 +62,7 @@
result = it + 1
}
)
+ Executors.UI_HELPER_EXECUTOR.execute(underTest)
}
@Test
@@ -82,7 +82,7 @@
Executors.UI_HELPER_EXECUTOR.submit { lock.lock() }
submitJob()
- underTest.cancel(false)
+ underTest.cancel()
lock.unlock() // unblock task on UI_HELPER_EXECUTOR
awaitAllExecutorCompleted()
@@ -101,7 +101,7 @@
awaitExecutorCompleted(Executors.UI_HELPER_EXECUTOR)
assertTrue("task should be executed.", isTaskExecuted)
- underTest.cancel(false)
+ underTest.cancel()
lock.unlock() // unblock callback on VIEW_PREINFLATION_EXECUTOR
awaitExecutorCompleted(Executors.VIEW_PREINFLATION_EXECUTOR)
@@ -113,7 +113,7 @@
fun run_and_cancelAfterCompletion_executeAll() {
awaitAllExecutorCompleted()
- underTest.cancel(false)
+ underTest.cancel()
assertTrue("task should be executed", isTaskExecuted)
assertTrue("callback should be executed", isCallbackExecuted)
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 20ba04b..a5d5884 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -121,6 +121,7 @@
@Test
@PortraitLandscape
@PlatinumTest(focusArea = "launcher")
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/326130648
public void testUninstallFromAllApps() throws Exception {
installDummyAppAndWaitForUIUpdate();
try {
diff --git a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 5283c6b..8631f03 100644
--- a/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -11,11 +11,11 @@
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2;
import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3;
import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.launcher3.util.TestUtil.DUMMY_CLASS_NAME;
import static com.android.launcher3.util.TestUtil.DUMMY_PACKAGE;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.google.common.truth.Truth.assertThat;
@@ -40,13 +40,14 @@
import com.android.launcher3.util.LauncherLayoutBuilder;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import java.io.IOException;
@@ -61,6 +62,9 @@
@RunWith(AndroidJUnit4.class)
public class CacheDataUpdatedTaskTest {
+ @Rule(order = 0)
+ public TestRule testStabilityRule = new TestStabilityRule();
+
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -157,6 +161,7 @@
}
@Test
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325283522
public void testSessionUpdate_updates_pending_apps() {
// Run on model executor so that no other task runs in the middle.
runOnExecutorSync(MODEL_EXECUTOR, () -> {
diff --git a/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt b/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
new file mode 100644
index 0000000..cfbde98
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/data/ItemInfoWithIconTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 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.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.util.LauncherModelHelper
+import com.google.common.truth.Truth
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ItemInfoWithIconTest {
+
+ private var context = LauncherModelHelper.SandboxModelContext()
+ private lateinit var itemInfoWithIcon: ItemInfoWithIcon
+
+ @Before
+ fun setup() {
+ itemInfoWithIcon =
+ object : ItemInfoWithIcon() {
+ override fun clone(): ItemInfoWithIcon? {
+ return null
+ }
+ }
+ }
+
+ @After
+ fun tearDown() {
+ context.destroy()
+ }
+
+ @Test
+ fun itemInfoWithIconDefaultParamsTest() {
+ Truth.assertThat(itemInfoWithIcon.isDisabled).isFalse()
+ Truth.assertThat(itemInfoWithIcon.isPendingDownload).isFalse()
+ Truth.assertThat(itemInfoWithIcon.isArchived).isFalse()
+ }
+
+ @Test
+ fun isDisabledOrPendingTest() {
+ itemInfoWithIcon.setProgressLevel(0, PackageInstallInfo.STATUS_INSTALLING)
+ Truth.assertThat(itemInfoWithIcon.isDisabled).isFalse()
+ Truth.assertThat(itemInfoWithIcon.isPendingDownload).isTrue()
+
+ itemInfoWithIcon.setProgressLevel(1, PackageInstallInfo.STATUS_INSTALLING)
+ Truth.assertThat(itemInfoWithIcon.isDisabled).isFalse()
+ Truth.assertThat(itemInfoWithIcon.isPendingDownload).isFalse()
+ }
+}