Snap for 11828632 from 2ee174e696 to 24Q3-release

Change-Id: Id105f7e01c7701f3aeb35c94da4a1be04bc9657f
This commit is contained in:
Android Build Coastguard Worker
2024-05-11 01:21:01 +00:00
72 changed files with 639 additions and 282 deletions
@@ -13,12 +13,15 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
<inset xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
<solid android:color="@color/bubblebar_drop_target_bg_color" />
<stroke
android:width="1dp"
android:color="?androidprv:attr/materialColorPrimaryContainer" />
</shape>
android:inset="@dimen/bubble_expanded_view_drop_target_padding">
<shape
android:shape="rectangle">
<corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
<solid android:color="@color/bubblebar_drop_target_bg_color" />
<stroke
android:width="1dp"
android:color="?androidprv:attr/materialColorPrimaryContainer" />
</shape>
</inset>
@@ -16,8 +16,8 @@
<!-- TODO(b/330585402): replace 600dp height with calculated value -->
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="@dimen/bubble_expanded_view_drop_target_width"
android:layout_height="600dp"
android:layout_width="@dimen/bubble_expanded_view_drop_target_default_width"
android:layout_height="@dimen/bubble_expanded_view_drop_target_default_height"
android:layout_margin="@dimen/bubble_expanded_view_drop_target_margin"
android:background="@drawable/bg_bubble_expanded_view_drop_target"
android:elevation="@dimen/bubblebar_elevation" />
+1 -2
View File
@@ -17,8 +17,7 @@
<string name="overscroll_plugin_factory_class" translatable="false" />
<string name="task_overlay_factory_class" translatable="false"/>
<!-- Activities which block home gesture -->
<string-array name="gesture_blocking_activities" translatable="false">
<string-array name="back_gesture_blocking_activities" translatable="false">
<item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
</string-array>
+4 -2
View File
@@ -456,8 +456,10 @@
<!-- Bubble bar drop target -->
<dimen name="bubblebar_drop_target_corner_radius">36dp</dimen>
<dimen name="bubble_expanded_view_drop_target_corner_radius">16dp</dimen>
<dimen name="bubble_expanded_view_drop_target_width">412dp</dimen>
<dimen name="bubble_expanded_view_drop_target_default_width">412dp</dimen>
<dimen name="bubble_expanded_view_drop_target_default_height">600dp</dimen>
<dimen name="bubble_expanded_view_drop_target_corner_radius">28dp</dimen>
<dimen name="bubble_expanded_view_drop_target_padding">24dp</dimen>
<dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>
<!-- Launcher splash screen -->
@@ -58,6 +58,7 @@ import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVIT
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
@@ -1881,7 +1882,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
return new ContainerAnimationRunner(
new ActivityTransitionAnimator.AnimationDelegate(
controller, callback, listener));
MAIN_EXECUTOR, controller, callback, listener));
}
/**
@@ -286,7 +286,7 @@ public class PredictionRowView<T extends Context & ActivityContext>
}
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + this.getClass().getSimpleName());
writer.println(prefix + "PredictionRowView");
writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled);
writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused);
writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow);
@@ -29,6 +29,7 @@ import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.ComponentName;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.View;
import android.view.ViewGroup;
@@ -80,6 +81,7 @@ public class HotseatPredictionController implements DragController.DragListener,
SystemShortcut.Factory<QuickstepLauncher>, DeviceProfile.OnDeviceProfileChangeListener,
DragSource, ViewGroup.OnHierarchyChangeListener {
private static final String TAG = "HotseatPredictionController";
private static final int FLAG_UPDATE_PAUSED = 1 << 0;
private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1;
private static final int FLAG_FILL_IN_PROGRESS = 1 << 2;
@@ -291,6 +293,16 @@ public class HotseatPredictionController implements DragController.DragListener,
}
}
/**
* Ensures that if the flag FLAG_UPDATE_PAUSED is active we set it to false.
*/
public void verifyUIUpdateNotPaused() {
if ((mPauseFlags & FLAG_UPDATE_PAUSED) != 0) {
setPauseUIUpdate(false);
Log.e(TAG, "FLAG_UPDATE_PAUSED should not be set to true (see b/339700174)");
}
}
/**
* Sets or updates the predicted items
*/
@@ -521,7 +533,7 @@ public class HotseatPredictionController implements DragController.DragListener,
}
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + this.getClass().getSimpleName());
writer.println(prefix + "HotseatPredictionController");
writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
@@ -73,6 +73,7 @@ import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ApiWrapper;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.PersistedItemArray;
import com.android.quickstep.logging.SettingsChangeLogger;
import com.android.quickstep.logging.StatsLogCompatManager;
@@ -150,7 +151,8 @@ public class QuickstepModelDelegate extends ModelDelegate {
// TODO: Implement caching and preloading
WorkspaceItemFactory factory =
new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, numColumns, state.containerId);
new WorkspaceItemFactory(mApp, ums, mPmHelper, pinnedShortcuts, numColumns,
state.containerId);
FixedContainerItems fci = new FixedContainerItems(state.containerId,
state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
@@ -530,6 +532,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
private final LauncherAppState mAppState;
private final UserManagerState mUMS;
private final PackageManagerHelper mPmHelper;
private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
private final int mMaxCount;
private final int mContainer;
@@ -537,9 +540,11 @@ public class QuickstepModelDelegate extends ModelDelegate {
private int mReadCount = 0;
protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount, int container) {
PackageManagerHelper pmHelper, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
int maxCount, int container) {
mAppState = appState;
mUMS = ums;
mPmHelper = pmHelper;
mPinnedShortcuts = pinnedShortcuts;
mMaxCount = maxCount;
mContainer = container;
@@ -563,6 +568,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
lai,
UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user),
ApiWrapper.INSTANCE.get(mAppState.getContext()),
mPmHelper,
mUMS.isUserQuiet(user));
info.container = mContainer;
mAppState.getIconCache().getTitleAndIcon(info, lai, false);
@@ -25,7 +25,7 @@ import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
/** {@link SystemShortcut.Factory} implementation to create workspace split shortcuts */
public interface QuickstepSystemShortcut {
String TAG = QuickstepSystemShortcut.class.getSimpleName();
String TAG = "QuickstepSystemShortcut";
static SystemShortcut.Factory<QuickstepLauncher> getSplitSelectShortcutByPosition(
SplitPositionOption position) {
@@ -45,7 +45,7 @@ abstract class SplitShortcut<T>(
) : SystemShortcut<T>(iconResId, labelResId, target, itemInfo, originalView) where
T : Context?,
T : ActivityContext? {
private val TAG = SystemShortcut::class.java.simpleName
private val TAG = "SplitShortcut"
// Initiate splitscreen from the Home screen or Home All Apps
protected val splitSelectSource: SplitSelectSource?
@@ -182,7 +182,7 @@ public class DepthController extends BaseDepthController implements StateHandler
}
public void dump(String prefix, PrintWriter writer) {
writer.println(prefix + this.getClass().getSimpleName());
writer.println(prefix + "DepthController");
writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
writer.println(prefix + "\tmSurface=" + mSurface);
@@ -133,4 +133,9 @@ public class FallbackTaskbarUIController extends TaskbarUIController {
protected TISBindHelper getTISBindHelper() {
return mRecentsActivity.getTISBindHelper();
}
@Override
protected String getTaskbarUIControllerName() {
return "FallbackTaskbarUIController";
}
}
@@ -446,4 +446,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
}
@Override
protected String getTaskbarUIControllerName() {
return "LauncherTaskbarUIController";
}
}
@@ -260,9 +260,9 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
new BubbleDragController(this),
new BubbleDismissController(this, mDragLayer),
new BubbleBarPinController(this, mDragLayer,
() -> getDeviceProfile().getDisplayInfo().currentSize),
() -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
new BubblePinController(this, mDragLayer,
() -> getDeviceProfile().getDisplayInfo().currentSize)
() -> DisplayController.INSTANCE.get(this).getInfo().currentSize)
));
}
@@ -65,7 +65,7 @@ import java.util.StringJoiner;
*/
public class TaskbarLauncherStateController {
private static final String TAG = TaskbarLauncherStateController.class.getSimpleName();
private static final String TAG = "TaskbarLauncherStateController";
private static final boolean DEBUG = false;
/** Launcher activity is visible and focused. */
@@ -66,7 +66,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
/** Allow some time in between the long press for back and recents. */
static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200;
static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100;
private static final String TAG = TaskbarNavButtonController.class.getSimpleName();
private static final String TAG = "TaskbarNavButtonController";
private long mLastScreenPinLongPress;
private boolean mScreenPinned;
@@ -78,7 +78,7 @@ import java.util.function.IntPredicate;
* create a cohesive animation between stashed/unstashed states.
*/
public class TaskbarStashController implements TaskbarControllers.LoggableTaskbarController {
private static final String TAG = TaskbarStashController.class.getSimpleName();
private static final String TAG = "TaskbarStashController";
private static final boolean DEBUG = false;
public static final int FLAG_IN_APP = 1 << 0;
@@ -55,7 +55,6 @@ import java.util.stream.Stream;
* Base class for providing different taskbar UI
*/
public class TaskbarUIController {
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
// Initialized in init.
@@ -91,6 +90,10 @@ public class TaskbarUIController {
*/
protected void onIconLayoutBoundsChanged() { }
protected String getTaskbarUIControllerName() {
return "TaskbarUIController";
}
/** Called when an icon is launched. */
@CallSuper
public void onTaskbarIconLaunched(ItemInfo item) {
@@ -207,7 +210,7 @@ public class TaskbarUIController {
pw.println(String.format(
"%sTaskbarUIController: using an instance of %s",
prefix,
getClass().getSimpleName()));
getTaskbarUIControllerName()));
}
/**
@@ -71,7 +71,7 @@ import java.util.function.Predicate;
*/
public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconParent, Insettable,
DeviceProfile.OnDeviceProfileChangeListener {
private static final String TAG = TaskbarView.class.getSimpleName();
private static final String TAG = "TaskbarView";
private static final Rect sTmpRect = new Rect();
@@ -79,7 +79,7 @@ import java.util.function.Predicate;
*/
public class TaskbarViewController implements TaskbarControllers.LoggableTaskbarController {
private static final String TAG = TaskbarViewController.class.getSimpleName();
private static final String TAG = "TaskbarViewController";
private static final Runnable NO_OP = () -> { };
@@ -94,7 +94,7 @@ import java.util.concurrent.Executors;
*/
public class BubbleBarController extends IBubblesListener.Stub {
private static final String TAG = BubbleBarController.class.getSimpleName();
private static final String TAG = "BubbleBarController";
private static final boolean DEBUG = false;
/**
@@ -150,6 +150,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
private BubblePinController mBubblePinController;
// Keep track of bubble bar bounds sent to shell to avoid sending duplicate updates
private final Rect mLastSentBubbleBarBounds = new Rect();
@@ -169,6 +170,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
BubbleBarLocation bubbleBarLocation;
List<RemovedBubble> removedBubbles;
List<String> bubbleKeysInOrder;
Point expandedViewDropTargetSize;
// These need to be loaded in the background
BubbleBarBubble addedBubble;
@@ -186,6 +188,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
bubbleBarLocation = update.bubbleBarLocation;
removedBubbles = update.removedBubbles;
bubbleKeysInOrder = update.bubbleKeysInOrder;
expandedViewDropTargetSize = update.expandedViewDropTargetSize;
}
}
@@ -216,6 +219,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
mBubblePinController = bubbleControllers.bubblePinController;
bubbleControllers.runAfterInit(() -> {
mBubbleBarViewController.setHiddenForBubbles(
@@ -419,6 +423,9 @@ public class BubbleBarController extends IBubblesListener.Stub {
updateBubbleBarLocationInternal(update.bubbleBarLocation);
}
}
if (update.expandedViewDropTargetSize != null) {
mBubblePinController.setDropTargetSize(update.expandedViewDropTargetSize);
}
}
/** Tells WMShell to show the currently selected bubble. */
@@ -76,7 +76,7 @@ import java.util.function.Consumer;
*/
public class BubbleBarView extends FrameLayout {
private static final String TAG = BubbleBarView.class.getSimpleName();
private static final String TAG = "BubbleBarView";
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
// if it's smaller than 5.
@@ -55,7 +55,7 @@ import java.util.function.Consumer;
*/
public class BubbleBarViewController {
private static final String TAG = BubbleBarViewController.class.getSimpleName();
private static final String TAG = "BubbleBarViewController";
private static final float APP_ICON_SMALL_DP = 44f;
private static final float APP_ICON_MEDIUM_DP = 48f;
private static final float APP_ICON_LARGE_DP = 52f;
@@ -40,7 +40,7 @@ import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
* @see BubbleDragController
*/
public class BubbleDismissController {
private static final String TAG = BubbleDismissController.class.getSimpleName();
private static final String TAG = "BubbleDismissController";
private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
private final TaskbarActivityContext mActivity;
private final TaskbarDragLayer mDragLayer;
@@ -37,12 +37,17 @@ class BubblePinController(
screenSizeProvider: () -> Point
) : BaseBubblePinController(screenSizeProvider) {
var dropTargetSize: Point? = null
private lateinit var bubbleBarViewController: BubbleBarViewController
private lateinit var bubbleStashController: BubbleStashController
private var exclRectWidth: Float = 0f
private var exclRectHeight: Float = 0f
private var dropTargetView: View? = null
// Fallback width and height in case shell has not sent the size over
private var dropTargetDefaultWidth: Int = 0
private var dropTargetDefaultHeight: Int = 0
private var dropTargetMargin: Int = 0
fun init(bubbleControllers: BubbleControllers) {
@@ -50,6 +55,14 @@ class BubblePinController(
bubbleStashController = bubbleControllers.bubbleStashController
exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
dropTargetDefaultWidth =
context.resources.getDimensionPixelSize(
R.dimen.bubble_expanded_view_drop_target_default_width
)
dropTargetDefaultHeight =
context.resources.getDimensionPixelSize(
R.dimen.bubble_expanded_view_drop_target_default_height
)
dropTargetMargin =
context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_drop_target_margin)
}
@@ -75,7 +88,6 @@ class BubblePinController(
return LayoutInflater.from(context)
.inflate(R.layout.bubble_expanded_view_drop_target, container, false)
.also { view ->
// TODO(b/330585402): dynamic height for the drop target based on actual height
dropTargetView = view
container.addView(view)
}
@@ -88,6 +100,8 @@ class BubblePinController(
val bubbleBarBounds = bubbleBarViewController.bubbleBarBounds
dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
width = dropTargetSize?.x ?: dropTargetDefaultWidth
height = dropTargetSize?.y ?: dropTargetDefaultHeight
bottomMargin =
-bubbleStashController.bubbleBarTranslationY.toInt() +
bubbleBarBounds.height() +
@@ -42,7 +42,7 @@ import com.android.wm.shell.shared.animation.PhysicsAnimator;
*/
public class BubbleStashController {
private static final String TAG = BubbleStashController.class.getSimpleName();
private static final String TAG = "BubbleStashController";
/**
* How long to stash/unstash.
@@ -397,6 +397,12 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer
return result;
}
@Override
public void startBinding() {
super.startBinding();
mHotseatPredictionController.verifyUIUpdateNotPaused();
}
@Override
protected void onActivityFlagsChanged(int changeBits) {
if ((changeBits & ACTIVITY_STATE_STARTED) != 0) {
@@ -111,7 +111,7 @@ import java.util.List;
* Holds the reference to SystemUI.
*/
public class SystemUiProxy implements ISystemUiProxy, NavHandle, SafeCloseable {
private static final String TAG = SystemUiProxy.class.getSimpleName();
private static final String TAG = "SystemUiProxy";
public static final MainThreadInitializedObject<SystemUiProxy> INSTANCE =
new MainThreadInitializedObject<>(SystemUiProxy::new);
@@ -335,24 +335,15 @@ public interface TaskShortcutFactory {
recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
boolean shouldShowActionsButtonInstead =
isLargeTileFocusedTask && isInExpectedScrollPosition;
boolean hasUnpinnableApp = taskView.getTaskContainers().stream()
.anyMatch(att -> att != null && att.getItemInfo() != null
&& ((att.getItemInfo().runtimeStatusFlags
& ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
// No "save app pair" menu item if:
// - app pairs feature is not enabled
// - we are in 3p launcher
// - the task in question is a single task
// - at least one app in app pair is unpinnable
// - the Overview Actions Button should be visible
// - the task is not a GroupedTaskView
if (!FeatureFlags.enableAppPairs()
|| !recentsView.supportsAppPairs()
|| !taskView.containsMultipleTasks()
|| hasUnpinnableApp
// - the task view is not a valid save-able split pair
if (!recentsView.supportsAppPairs()
|| shouldShowActionsButtonInstead
|| !(taskView instanceof GroupedTaskView)) {
|| !recentsView.getSplitSelectController().getAppPairsController()
.canSaveAppPair(taskView)) {
return null;
}
@@ -22,6 +22,7 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_PAIR_LAUNCH;
import static com.android.launcher3.model.data.AppInfo.PACKAGE_KEY_COMPARATOR;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SUPPORTS_MULTI_INSTANCE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -32,34 +33,38 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.isPersisten
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.Cuj;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.AppPairInfo;
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.taskbar.TaskbarActivityContext;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
import com.android.launcher3.views.ActivityContext;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.views.GroupedTaskView;
import com.android.quickstep.views.TaskView;
@@ -100,6 +105,55 @@ public class AppPairsController {
mContext = null;
}
/**
* Returns whether the specified GroupedTaskView can be saved as an app pair.
*/
public boolean canSaveAppPair(TaskView taskView) {
if (mContext == null) {
// Can ignore as the activity is already destroyed
return false;
}
// Disallow saving app pairs if:
// - app pairs feature is not enabled
// - the task in question is a single task
// - at least one app in app pair is unpinnable
// - the task is not a GroupedTaskView
// - both tasks in the GroupedTaskView are from the same app and the app does not
// support multi-instance
boolean hasUnpinnableApp = taskView.getTaskContainers().stream()
.anyMatch(att -> att != null && att.getItemInfo() != null
&& ((att.getItemInfo().runtimeStatusFlags
& ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
if (!FeatureFlags.enableAppPairs()
|| !taskView.containsMultipleTasks()
|| hasUnpinnableApp
|| !(taskView instanceof GroupedTaskView)) {
return false;
}
GroupedTaskView gtv = (GroupedTaskView) taskView;
List<TaskView.TaskContainer> containers = gtv.getTaskContainers();
ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask(
containers.get(0).getTask().key);
ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask(
containers.get(1).getTask().key);
AppInfo app1 = resolveAppInfoByComponent(taskKey1);
AppInfo app2 = resolveAppInfoByComponent(taskKey2);
if (app1 == null || app2 == null) {
// Disallow saving app pairs for apps that don't have a front-door in Launcher
return false;
}
if (PackageManagerHelper.isSameAppForMultiInstance(app1, app2)) {
if (!app1.supportsMultiInstance() || !app2.supportsMultiInstance()) {
return false;
}
}
return true;
}
/**
* Creates a new app pair ItemInfo and adds it to the workspace.
* <br>
@@ -119,31 +173,23 @@ public class AppPairsController {
List<TaskView.TaskContainer> containers = gtv.getTaskContainers();
WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
WorkspaceItemInfo app1 = lookupLaunchableItem(recentsInfo1.getComponentKey());
WorkspaceItemInfo app2 = lookupLaunchableItem(recentsInfo2.getComponentKey());
WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);
// If app lookup fails, use the WorkspaceItemInfo that we have, but try to override default
// intent with one from PackageManager.
if (app1 == null) {
Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo1.title
+ " failed. Falling back to the WorkspaceItemInfo from Recents.");
app1 = convertRecentsItemToAppItem(recentsInfo1);
if (app1 == null || app2 == null) {
// This shouldn't happen if canSaveAppPair() is called above, but log an error and do
// not create the app pair if the workspace items can't be resolved
Log.w(TAG, "Failed to save app pair due to invalid apps ("
+ "app1=" + recentsInfo1.getComponentKey().componentName
+ " app2=" + recentsInfo2.getComponentKey().componentName + ")");
return;
}
if (app2 == null) {
Log.w(TAG, "Creating an app pair, but app lookup for " + recentsInfo2.title
+ " failed. Falling back to the WorkspaceItemInfo from Recents.");
app2 = convertRecentsItemToAppItem(recentsInfo2);
}
// WorkspaceItemProcessor won't process these new ItemInfos until the next launcher restart,
// so update some flags now.
updateWorkspaceItemFlags(app1);
updateWorkspaceItemFlags(app2);
@PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
if (!isPersistentSnapPosition(snapPosition)) {
// if we received an illegal snap position, log an error and do not create the app pair.
Log.wtf(TAG, "tried to save an app pair with illegal snapPosition " + snapPosition);
// If we received an illegal snap position, log an error and do not create the app pair
Log.wtf(TAG, "Tried to save an app pair with illegal snapPosition "
+ snapPosition);
return;
}
@@ -228,68 +274,39 @@ public class AppPairsController {
);
}
/**
* Returns an AppInfo associated with the app for the given ComponentKey, or null if no such
* package exists in the AllAppsStore.
*/
@Nullable
private AppInfo resolveAppInfoByComponent(@NonNull ComponentKey key) {
AllAppsStore appsStore = ActivityContext.lookupContext(mContext)
.getAppsView().getAppsStore();
// First look up the app info in order of:
// - The exact activity for the recent task
// - The first(?) loaded activity from the package
AppInfo appInfo = appsStore.getApp(key);
if (appInfo == null) {
appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
}
return appInfo;
}
/**
* Creates a new launchable WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION by looking the
* ComponentKey up in the AllAppsStore. If no app is found, attempts a lookup by package
* instead. If that lookup fails, returns null.
*/
@Nullable
private WorkspaceItemInfo lookupLaunchableItem(@Nullable ComponentKey key) {
if (key == null) {
private WorkspaceItemInfo resolveAppPairWorkspaceInfo(
@NonNull WorkspaceItemInfo recentTaskInfo) {
// ComponentKey should never be null (see TaskView#getItemInfo)
AppInfo appInfo = resolveAppInfoByComponent(recentTaskInfo.getComponentKey());
if (appInfo == null) {
return null;
}
AllAppsStore appsStore = ActivityContext.lookupContext(mContext)
.getAppsView().getAppsStore();
// Lookup by ComponentKey
AppInfo appInfo = appsStore.getApp(key);
if (appInfo == null) {
// Lookup by package
appInfo = appsStore.getApp(key, PACKAGE_KEY_COMPARATOR);
}
return appInfo != null ? appInfo.makeWorkspaceItem(mContext) : null;
}
/**
* Updates flags for newly created WorkspaceItemInfos.
*/
private void updateWorkspaceItemFlags(WorkspaceItemInfo wii) {
PackageManager pm = mContext.getPackageManager();
ActivityInfo ai = null;
try {
ai = pm.getActivityInfo(wii.getTargetComponent(), 0);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "PackageManager lookup failed.");
}
if (ai != null) {
wii.setNonResizeable(ai.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE);
}
}
/**
* Converts a WorkspaceItemInfo of itemType=ITEM_TYPE_TASK (from a Recents task) to a new
* WorkspaceItemInfo of itemType=ITEM_TYPE_APPLICATION.
*/
private WorkspaceItemInfo convertRecentsItemToAppItem(WorkspaceItemInfo recentsItem) {
if (recentsItem.itemType != LauncherSettings.Favorites.ITEM_TYPE_TASK) {
Log.w(TAG, "Expected ItemInfo of type ITEM_TYPE_TASK, but received "
+ recentsItem.itemType);
}
WorkspaceItemInfo launchableItem = recentsItem.clone();
PackageManager p = mContext.getPackageManager();
Intent launchIntent = p.getLaunchIntentForPackage(recentsItem.getTargetPackage());
Log.w(TAG, "Initial intent from Recents: " + launchableItem.intent + "\n"
+ "Intent from PackageManager: " + launchIntent);
if (launchIntent != null) {
// If lookup from PackageManager fails, just use the existing intent
launchableItem.intent = launchIntent;
}
launchableItem.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
return launchableItem;
return appInfo.makeWorkspaceItem(mContext);
}
/**
@@ -38,36 +38,40 @@ import com.android.quickstep.util.SplitSelectDataHolder.Companion.SplitLaunchTyp
import java.io.PrintWriter
/**
* Holds/transforms/signs/seals/delivers information for the transient state of the user
* selecting a first app to start split with and then choosing a second app.
* This class DOES NOT associate itself with drag-and-drop split screen starts because they come
* from the bad part of town.
* Holds/transforms/signs/seals/delivers information for the transient state of the user selecting a
* first app to start split with and then choosing a second app. This class DOES NOT associate
* itself with drag-and-drop split screen starts because they come from the bad part of town.
*
* After setting the correct fields for initial/second.* variables, this converts them into the
* correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary
* data back via [getSplitLaunchData]. Note: there should be only one "initial" field and one
* "second" field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are
* always passed in together as a set, and are converted to a single [PendingIntent] or
* correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary data
* back via [getSplitLaunchData]. Note: there should be only one "initial" field and one "second"
* field set, with the rest remaining null. (Exception: [Intent] and [UserHandle] are always passed
* in together as a set, and are converted to a single [PendingIntent] or
* [ShortcutInfo]+[PendingIntent] before launch.)
*
* [SplitLaunchType] indicates the type of tasks/apps/intents being launched given the provided
* state
*/
class SplitSelectDataHolder(
var context: Context?
) {
class SplitSelectDataHolder(var context: Context?) {
val TAG = SplitSelectDataHolder::class.simpleName
/**
* Order of the constant indicates the order of which task/app was selected.
* Ex. SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
* Order of the constant indicates the order of which task/app was selected. Ex.
* SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
* SPLIT_SHORTCUT_TASK means primary split app is determined by shortcut, secondary is task
*/
companion object {
@IntDef(SPLIT_TASK_TASK, SPLIT_TASK_PENDINGINTENT, SPLIT_TASK_SHORTCUT,
SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK,
SPLIT_SINGLE_TASK_FULLSCREEN, SPLIT_SINGLE_INTENT_FULLSCREEN,
SPLIT_SINGLE_SHORTCUT_FULLSCREEN)
@IntDef(
SPLIT_TASK_TASK,
SPLIT_TASK_PENDINGINTENT,
SPLIT_TASK_SHORTCUT,
SPLIT_PENDINGINTENT_TASK,
SPLIT_PENDINGINTENT_PENDINGINTENT,
SPLIT_SHORTCUT_TASK,
SPLIT_SINGLE_TASK_FULLSCREEN,
SPLIT_SINGLE_INTENT_FULLSCREEN,
SPLIT_SINGLE_SHORTCUT_FULLSCREEN
)
@Retention(AnnotationRetention.SOURCE)
annotation class SplitLaunchType
@@ -84,8 +88,7 @@ class SplitSelectDataHolder(
const val SPLIT_SINGLE_SHORTCUT_FULLSCREEN = 8
}
@StagePosition
private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
@StagePosition private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
private var itemInfo: ItemInfo? = null
private var secondItemInfo: ItemInfo? = null
private var splitEvent: EventEnum? = null
@@ -108,12 +111,16 @@ class SplitSelectDataHolder(
/**
* @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID]
* then @param intent will be used to launch the initial task
* then @param intent will be used to launch the initial task
* @param intent will be ignored if @param alreadyRunningTask is set
*/
fun setInitialTaskSelect(intent: Intent?, @StagePosition stagePosition: Int,
itemInfo: ItemInfo?, splitEvent: EventEnum?,
alreadyRunningTask: Int) {
fun setInitialTaskSelect(
intent: Intent?,
@StagePosition stagePosition: Int,
itemInfo: ItemInfo?,
splitEvent: EventEnum?,
alreadyRunningTask: Int
) {
if (alreadyRunningTask != INVALID_TASK_ID) {
initialTaskId = alreadyRunningTask
} else {
@@ -127,15 +134,21 @@ class SplitSelectDataHolder(
* To be called after first task selected from using a split shortcut from the fullscreen
* running app.
*/
fun setInitialTaskSelect(info: RunningTaskInfo,
@StagePosition stagePosition: Int, itemInfo: ItemInfo?,
splitEvent: EventEnum?) {
fun setInitialTaskSelect(
info: RunningTaskInfo,
@StagePosition stagePosition: Int,
itemInfo: ItemInfo?,
splitEvent: EventEnum?
) {
initialTaskId = info.taskId
setInitialData(stagePosition, splitEvent, itemInfo)
}
private fun setInitialData(@StagePosition stagePosition: Int,
event: EventEnum?, item: ItemInfo?) {
private fun setInitialData(
@StagePosition stagePosition: Int,
event: EventEnum?,
item: ItemInfo?
) {
itemInfo = item
initialStagePosition = stagePosition
splitEvent = event
@@ -143,6 +156,7 @@ class SplitSelectDataHolder(
/**
* 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, itemInfo: ItemInfo) {
@@ -152,6 +166,7 @@ class SplitSelectDataHolder(
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
*
* @param intent The second intent that will be launched.
* @param user The user of that intent.
*/
@@ -162,8 +177,9 @@ class SplitSelectDataHolder(
}
/**
* To be called as soon as user selects the second app (even if animations aren't complete)
* Sets [secondUser] from that of the pendingIntent
* To be called as soon as user selects the second app (even if animations aren't complete) Sets
* [secondUser] from that of the pendingIntent
*
* @param pendingIntent The second PendingIntent that will be launched.
*/
fun setSecondTask(pendingIntent: PendingIntent, itemInfo: ItemInfo) {
@@ -173,9 +189,9 @@ class SplitSelectDataHolder(
}
/**
* Similar to [setSecondTask] except this is to be called for widgets which can pass through
* an extra intent from their RemoteResponse.
* See [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
* Similar to [setSecondTask] except this is to be called for widgets which can pass through an
* extra intent from their RemoteResponse. See
* [android.widget.RemoteViews.RemoteResponse.getLaunchOptions].first
*/
fun setSecondWidget(pendingIntent: PendingIntent, widgetIntent: Intent?, itemInfo: ItemInfo) {
setSecondTask(pendingIntent, itemInfo)
@@ -184,8 +200,7 @@ class SplitSelectDataHolder(
private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
val intentPackage = intent?.getPackage() ?: return null
val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
?: return null
val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) ?: return null
try {
val context: Context =
if (user != null) {
@@ -200,9 +215,7 @@ class SplitSelectDataHolder(
return null
}
/**
* Converts intents to pendingIntents, associating the [user] with the intent if provided
*/
/** Converts intents to pendingIntents, associating the [user] with the intent if provided */
private fun getPendingIntent(intent: Intent?, user: UserHandle?): PendingIntent? {
if (intent != initialIntent && intent != secondIntent) {
throw IllegalStateException("Invalid intent to convert to PendingIntent")
@@ -211,12 +224,21 @@ class SplitSelectDataHolder(
return if (intent == null) {
null
} else if (user != null) {
PendingIntent.getActivityAsUser(context, 0, intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
null /* options */, user)
PendingIntent.getActivityAsUser(
context,
0,
intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
null /* options */,
user
)
} else {
PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT)
PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
)
}
}
@@ -224,7 +246,7 @@ class SplitSelectDataHolder(
* @return [SplitLaunchData] with the necessary fields populated as determined by
* [SplitLaunchData.splitLaunchType]. This is to be used for launching splitscreen
*/
fun getSplitLaunchData() : SplitLaunchData {
fun getSplitLaunchData(): SplitLaunchData {
// Convert all intents to shortcut infos to see if determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getSplitLaunchType()
@@ -241,7 +263,7 @@ class SplitSelectDataHolder(
* [SplitLaunchData.splitLaunchType]. This is to be used for launching an initially selected
* split task in fullscreen
*/
fun getFullscreenLaunchData() : SplitLaunchData {
fun getFullscreenLaunchData(): SplitLaunchData {
// Convert all intents to shortcut infos to determine if we launch shortcut or intent
convertIntentsToFinalTypes()
val splitLaunchType = getFullscreenLaunchType()
@@ -249,21 +271,22 @@ class SplitSelectDataHolder(
return generateSplitLaunchData(splitLaunchType)
}
private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int) : SplitLaunchData {
private fun generateSplitLaunchData(@SplitLaunchType splitLaunchType: Int): SplitLaunchData {
return SplitLaunchData(
splitLaunchType,
initialTaskId,
secondTaskId,
initialPendingIntent,
secondPendingIntent,
widgetSecondIntent,
initialUser?.identifier ?: -1,
secondUser?.identifier ?: -1,
initialShortcut,
secondShortcut,
itemInfo,
splitEvent,
initialStagePosition)
splitLaunchType,
initialTaskId,
secondTaskId,
initialPendingIntent,
secondPendingIntent,
widgetSecondIntent,
initialUser?.identifier ?: -1,
secondUser?.identifier ?: -1,
initialShortcut,
secondShortcut,
itemInfo,
splitEvent,
initialStagePosition
)
}
/**
@@ -273,8 +296,7 @@ class SplitSelectDataHolder(
* Note that both [initialIntent] and [secondIntent] will be nullified on method return
*
* One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to
* convert [secondIntent].
* This also leaves [widgetSecondIntent] untouched.
* convert [secondIntent]. This also leaves [widgetSecondIntent] untouched.
*/
private fun convertIntentsToFinalTypes() {
initialShortcut = getShortcutInfo(initialIntent, initialUser)
@@ -297,8 +319,8 @@ class SplitSelectDataHolder(
}
/**
* Only valid data fields at this point should be tasks, shortcuts, or pendingIntents
* Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method
* Only valid data fields at this point should be tasks, shortcuts, or pendingIntents Intents
* need to be converted in [convertIntentsToFinalTypes] prior to calling this method
*/
@VisibleForTesting
@SplitLaunchType
@@ -354,41 +376,40 @@ class SplitSelectDataHolder(
}
data class SplitLaunchData(
@SplitLaunchType
val splitLaunchType: Int,
var initialTaskId: Int = INVALID_TASK_ID,
var secondTaskId: Int = INVALID_TASK_ID,
var initialPendingIntent: PendingIntent? = null,
var secondPendingIntent: PendingIntent? = null,
var widgetSecondIntent: Intent? = null,
var initialUserId: Int = -1,
var secondUserId: Int = -1,
var initialShortcut: ShortcutInfo? = null,
var secondShortcut: ShortcutInfo? = null,
var itemInfo: ItemInfo? = null,
var splitEvent: EventEnum? = null,
val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
@SplitLaunchType val splitLaunchType: Int,
var initialTaskId: Int = INVALID_TASK_ID,
var secondTaskId: Int = INVALID_TASK_ID,
var initialPendingIntent: PendingIntent? = null,
var secondPendingIntent: PendingIntent? = null,
var widgetSecondIntent: Intent? = null,
var initialUserId: Int = -1,
var secondUserId: Int = -1,
var initialShortcut: ShortcutInfo? = null,
var secondShortcut: ShortcutInfo? = null,
var itemInfo: ItemInfo? = null,
var splitEvent: EventEnum? = null,
val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
)
/**
* @return `true` if first task has been selected and waiting for the second task to be
* chosen
* @return `true` if first task has been selected and waiting for the second task to be chosen
*/
fun isSplitSelectActive(): Boolean {
return isInitialTaskIntentSet() && !isSecondTaskIntentSet()
}
/**
* @return `true` if the first and second task have been chosen and split is waiting to
* be launched
* @return `true` if the first and second task have been chosen and split is waiting to be
* launched
*/
fun isBothSplitAppsConfirmed(): Boolean {
return isInitialTaskIntentSet() && isSecondTaskIntentSet()
}
private fun isInitialTaskIntentSet(): Boolean {
return initialTaskId != INVALID_TASK_ID || initialIntent != null ||
initialPendingIntent != null
return initialTaskId != INVALID_TASK_ID ||
initialIntent != null ||
initialPendingIntent != null
}
fun getInitialTaskId(): Int {
@@ -416,8 +437,9 @@ class SplitSelectDataHolder(
}
private fun isSecondTaskIntentSet(): Boolean {
return secondTaskId != INVALID_TASK_ID || secondIntent != null
|| secondPendingIntent != null
return secondTaskId != INVALID_TASK_ID ||
secondIntent != null ||
secondPendingIntent != null
}
fun resetState() {
@@ -437,7 +459,7 @@ class SplitSelectDataHolder(
}
fun dump(prefix: String, writer: PrintWriter) {
writer.println("$prefix ${javaClass.simpleName}")
writer.println("$prefix SplitSelectDataHolder")
writer.println("$prefix\tinitialStagePosition= $initialStagePosition")
writer.println("$prefix\tinitialTaskId= $initialTaskId")
writer.println("$prefix\tsecondTaskId= $secondTaskId")
@@ -46,7 +46,7 @@ import com.android.wm.shell.pip.PipContentOverlay;
* when swiping up (in gesture navigation mode).
*/
public class SwipePipToHomeAnimator extends RectFSpringAnim {
private static final String TAG = SwipePipToHomeAnimator.class.getSimpleName();
private static final String TAG = "SwipePipToHomeAnimator";
private static final float END_PROGRESS = 1.0f;
@@ -37,7 +37,7 @@ import java.util.function.Predicate;
* @param <V> Type of object stored in the cache
*/
public class TaskKeyByLastActiveTimeCache<V> implements TaskKeyCache<V> {
private static final String TAG = TaskKeyByLastActiveTimeCache.class.getSimpleName();
private static final String TAG = "TaskKeyByLastActiveTimeCache";
private final AtomicInteger mMaxSize;
private final Map<Integer, Entry<V>> mMap;
// To sort task id by last active time
@@ -66,7 +66,7 @@ import java.util.function.Consumer;
// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
public class DesktopTaskView extends TaskView {
private static final String TAG = DesktopTaskView.class.getSimpleName();
private static final String TAG = "DesktopTaskView";
private static final boolean DEBUG = false;
@@ -79,7 +79,7 @@ public final class DigitalWellBeingToast {
static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
static final int MINUTE_MS = 60000;
private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
private static final String TAG = "DigitalWellBeingToast";
private final RecentsViewContainer mContainer;
private final TaskView mTaskView;
@@ -63,7 +63,7 @@ import java.util.function.Consumer;
*/
public class GroupedTaskView extends TaskView {
private static final String TAG = GroupedTaskView.class.getSimpleName();
private static final String TAG = "GroupedTaskView";
// TODO(b/336612373): Support new TTV for GroupedTaskView
private TaskThumbnailViewDeprecated mSnapshotView2;
private TaskViewIcon mIconView2;
@@ -38,6 +38,7 @@ import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.NavigationMode;
import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
import com.android.quickstep.util.AppPairsController;
import com.android.quickstep.util.LayoutUtils;
import java.lang.annotation.Retention;
@@ -139,6 +140,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
protected DeviceProfile mDp;
private final Rect mTaskSize = new Rect();
private boolean mIsGroupedTask = false;
private boolean mCanSaveAppPair = false;
public OverviewActionsView(Context context) {
this(context, null);
@@ -245,9 +247,12 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
* Updates a batch of flags to hide and show actions buttons when a grouped task (split screen)
* is focused.
* @param isGroupedTask True if the focused task is a grouped task.
* @param canSaveAppPair True if the focused task is a grouped task and can be saved as an app
* pair.
*/
public void updateForGroupedTask(boolean isGroupedTask) {
public void updateForGroupedTask(boolean isGroupedTask, boolean canSaveAppPair) {
mIsGroupedTask = isGroupedTask;
mCanSaveAppPair = canSaveAppPair;
updateActionButtonsVisibility();
}
@@ -264,7 +269,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
private void updateActionButtonsVisibility() {
assert mDp != null;
boolean showSingleTaskActions = !mIsGroupedTask;
boolean showGroupActions = mIsGroupedTask && mDp.isTablet;
boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair;
getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
}
@@ -4021,7 +4021,9 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
* * Device is large screen
*/
private void updateCurrentTaskActionsVisibility() {
boolean isCurrentSplit = getCurrentPageTaskView() instanceof GroupedTaskView;
TaskView taskView = getCurrentPageTaskView();
boolean isCurrentSplit = taskView instanceof GroupedTaskView;
GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
// Update flags to see if entire actions bar should be hidden.
if (!FeatureFlags.enableAppPairs()) {
mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
@@ -4029,9 +4031,11 @@ public abstract class RecentsView<CONTAINER_TYPE extends Context & RecentsViewCo
mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
// Update flags to see if actions bar should show buttons for a single task or a pair of
// tasks.
mActionsView.updateForGroupedTask(isCurrentSplit);
boolean canSaveAppPair = isCurrentSplit && supportsAppPairs() &&
getSplitSelectController().getAppPairsController().canSaveAppPair(groupedTaskView);
mActionsView.updateForGroupedTask(isCurrentSplit, canSaveAppPair);
boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
boolean isCurrentDesktop = taskView instanceof DesktopTaskView;
mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
}
@@ -136,7 +136,7 @@ import java.util.stream.Stream;
*/
public class TaskView extends FrameLayout implements Reusable {
private static final String TAG = TaskView.class.getSimpleName();
private static final String TAG = "TaskView";
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;
+7
View File
@@ -223,4 +223,11 @@
<string-array name="skip_private_profile_shortcut_packages" translatable="false">
<item>com.android.settings</item>
</string-array>
<!-- Legacy list of components supporting multiple instances.
DO NOT ADD TO THIS LIST. Apps should use the PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI
property to declare multi-instance support in V+. This resource should match the resource
of the same name in SystemUI. -->
<string-array name="config_appsSupportMultiInstancesSplit">
</string-array>
</resources>
@@ -47,7 +47,7 @@ public class DevicePaddings {
private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding";
private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding";
private static final String TAG = DevicePaddings.class.getSimpleName();
private static final String TAG = "DevicePaddings";
private static final boolean DEBUG = false;
ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
@@ -52,6 +52,7 @@ import com.android.launcher3.pm.InstallSessionTracker;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.SafeCloseable;
@@ -181,7 +182,7 @@ public class LauncherAppState implements SafeCloseable {
mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
iconCacheFileName, mIconProvider);
mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext),
iconCacheFileName != null);
PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
mOnTerminateCallback.add(mIconCache::close);
mOnTerminateCallback.add(mModel::destroy);
}
+6 -3
View File
@@ -98,6 +98,8 @@ public class LauncherModel implements InstallSessionTracker.Callback {
@NonNull
private final LauncherAppState mApp;
@NonNull
private final PackageManagerHelper mPmHelper;
@NonNull
private final ModelDbController mModelDbController;
@NonNull
private final Object mLock = new Object();
@@ -152,12 +154,13 @@ public class LauncherModel implements InstallSessionTracker.Callback {
LauncherModel(@NonNull final Context context, @NonNull final LauncherAppState app,
@NonNull final IconCache iconCache, @NonNull final AppFilter appFilter,
final boolean isPrimaryInstance) {
@NonNull final PackageManagerHelper pmHelper, final boolean isPrimaryInstance) {
mApp = app;
mPmHelper = pmHelper;
mModelDbController = new ModelDbController(context);
mBgAllAppsList = new AllAppsList(iconCache, appFilter);
mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel,
isPrimaryInstance);
mModelDelegate = ModelDelegate.newInstance(context, app, mPmHelper, mBgAllAppsList,
mBgDataModel, isPrimaryInstance);
}
@NonNull
+1 -1
View File
@@ -788,7 +788,7 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
if (mScroller.isFinished() && pageScrollChanged) {
// TODO(b/246283207): Remove logging once root cause of flake detected.
if (Utilities.isRunningInTestHarness() && !(this instanceof Workspace)) {
Log.d("b/246283207", this.getClass().getSimpleName() + "#onLayout() -> "
Log.d("b/246283207", TAG + "#onLayout() -> "
+ "if(mScroller.isFinished() && pageScrollChanged) -> getNextPage(): "
+ getNextPage() + ", getScrollForPage(getNextPage()): "
+ getScrollForPage(getNextPage()));
+4
View File
@@ -134,6 +134,10 @@ public final class Utilities {
@ChecksSdkIntAtLeast(api = VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "U")
public static final boolean ATLEAST_U = Build.VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE;
@ChecksSdkIntAtLeast(api = VERSION_CODES.VANILLA_ICE_CREAM, codename = "V")
public static final boolean ATLEAST_V = Build.VERSION.SDK_INT
>= VERSION_CODES.VANILLA_ICE_CREAM;
/**
* Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
*/
@@ -30,6 +30,8 @@ import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Reorderable;
@@ -85,6 +87,11 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab
: activity.getLayoutInflater();
AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false);
if (Flags.enableFocusOutline() && activity instanceof Launcher) {
icon.setOnFocusChangeListener(((Launcher) activity).getFocusHandler());
icon.setDefaultFocusHighlightEnabled(false);
}
// Sort contents, so that left-hand app comes first
appPairInfo.getContents().sort(Comparator.comparingInt(a -> a.rank));
@@ -60,7 +60,7 @@ public class AllAppsList {
private static final String TAG = "AllAppsList";
private static final Consumer<AppInfo> NO_OP_CONSUMER = a -> { };
private static final boolean DEBUG = true;
public static final int DEFAULT_APPLICATIONS_NUMBER = 42;
@@ -220,6 +220,11 @@ public class AllAppsList {
updatedAppInfos.add(appInfo);
} else if (installInfo.state == PackageInstallInfo.STATUS_FAILED
&& !appInfo.isAppStartable()) {
if (DEBUG) {
Log.w(TAG, "updatePromiseInstallInfo: removing app due to install"
+ " failure and appInfo not startable."
+ " package=" + appInfo.getTargetPackage());
}
removeApp(i);
}
}
@@ -301,6 +306,7 @@ public class AllAppsList {
Context context, String packageName, UserHandle user) {
final ApiWrapper apiWrapper = ApiWrapper.INSTANCE.get(context);
final UserCache userCache = UserCache.getInstance(context);
final PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
final List<LauncherActivityInfo> matches = context.getSystemService(LauncherApps.class)
.getActivityList(packageName, user);
if (matches.size() > 0) {
@@ -311,7 +317,10 @@ public class AllAppsList {
if (user.equals(applicationInfo.user)
&& packageName.equals(applicationInfo.componentName.getPackageName())) {
if (!findActivity(matches, applicationInfo.componentName)) {
Log.w(TAG, "Changing shortcut target due to app component name change.");
if (DEBUG) {
Log.w(TAG, "Changing shortcut target due to app component name change."
+ " package=" + packageName);
}
removeApp(i);
}
}
@@ -330,12 +339,16 @@ public class AllAppsList {
applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
applicationInfo.intent = launchIntent;
AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info,
userCache.getUserInfo(user), apiWrapper);
userCache.getUserInfo(user), apiWrapper, pmHelper);
mDataChanged = true;
}
}
} else {
// Remove all data for this package.
if (DEBUG) {
Log.w(TAG, "updatePromiseInstallInfo: no Activities matched updated package,"
+ " removing all apps from package=" + packageName);
}
for (int i = data.size() - 1; i >= 0; i--) {
final AppInfo applicationInfo = data.get(i);
if (user.equals(applicationInfo.user)
@@ -57,6 +57,7 @@ import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.UserIconInfo;
import java.net.URISyntaxException;
@@ -73,6 +74,7 @@ public class LoaderCursor extends CursorWrapper {
private final LauncherAppState mApp;
private final Context mContext;
private final PackageManagerHelper mPmHelper;
private final IconCache mIconCache;
private final InvariantDeviceProfile mIDP;
private final @Nullable LauncherRestoreEventLogger mRestoreEventLogger;
@@ -114,6 +116,7 @@ public class LoaderCursor extends CursorWrapper {
public int restoreFlag;
public LoaderCursor(Cursor cursor, LauncherAppState app, UserManagerState userManagerState,
PackageManagerHelper pmHelper,
@Nullable LauncherRestoreEventLogger restoreEventLogger) {
super(cursor);
@@ -121,6 +124,7 @@ public class LoaderCursor extends CursorWrapper {
allUsers = userManagerState.allUsers;
mContext = app.getContext();
mIconCache = app.getIconCache();
mPmHelper = pmHelper;
mIDP = app.getInvariantDeviceProfile();
mRestoreEventLogger = restoreEventLogger;
@@ -368,7 +372,7 @@ public class LoaderCursor extends CursorWrapper {
if (mActivityInfo != null) {
AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo,
ApiWrapper.INSTANCE.get(mContext));
ApiWrapper.INSTANCE.get(mContext), mPmHelper);
}
// from the db
@@ -435,7 +435,8 @@ public class LoaderTask implements Runnable {
mShortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(
dbController.query(TABLE_NAME, null, selection, null, null),
mApp, mUserManagerState, mIsRestoreFromBackup ? restoreEventLogger : null);
mApp, mUserManagerState, mPmHelper,
mIsRestoreFromBackup ? restoreEventLogger : null);
final Bundle extras = c.getExtras();
mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME);
try {
@@ -697,7 +698,7 @@ public class LoaderTask implements Runnable {
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
AppInfo appInfo = new AppInfo(app, mUserCache.getUserInfo(user),
ApiWrapper.INSTANCE.get(mApp.getContext()), quietMode);
ApiWrapper.INSTANCE.get(mApp.getContext()), mPmHelper, quietMode);
if (Flags.enableSupportForArchiving() && app.getApplicationInfo().isArchived) {
// For archived apps, include progress info in case there is a pending
// install session post restart of device.
@@ -26,6 +26,7 @@ import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.ResourceBasedOverride;
import java.io.FileDescriptor;
@@ -41,15 +42,16 @@ public class ModelDelegate implements ResourceBasedOverride {
* Creates and initializes a new instance of the delegate
*/
public static ModelDelegate newInstance(
Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel,
boolean isPrimaryInstance) {
Context context, LauncherAppState app, PackageManagerHelper pmHelper,
AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) {
ModelDelegate delegate = Overrides.getObject(
ModelDelegate.class, context, R.string.model_delegate_class);
delegate.init(app, appsList, dataModel, isPrimaryInstance);
delegate.init(app, pmHelper, appsList, dataModel, isPrimaryInstance);
return delegate;
}
protected final Context mContext;
protected PackageManagerHelper mPmHelper;
protected LauncherAppState mApp;
protected AllAppsList mAppsList;
protected BgDataModel mDataModel;
@@ -62,9 +64,10 @@ public class ModelDelegate implements ResourceBasedOverride {
/**
* Initializes the object with the given params.
*/
private void init(LauncherAppState app, AllAppsList appsList,
private void init(LauncherAppState app, PackageManagerHelper pmHelper, AllAppsList appsList,
BgDataModel dataModel, boolean isPrimaryInstance) {
this.mApp = app;
this.mPmHelper = pmHelper;
this.mAppsList = appsList;
this.mDataModel = dataModel;
this.mIsPrimaryInstance = isPrimaryInstance;
@@ -74,8 +74,8 @@ import java.util.stream.Collectors;
public class PackageUpdatedTask implements ModelUpdateTask {
// TODO(b/290090023): Set to false after root causing is done.
private static final boolean DEBUG = true;
private static final String TAG = "PackageUpdatedTask";
private static final boolean DEBUG = true;
public static final int OP_NONE = 0;
public static final int OP_ADD = 1;
@@ -117,29 +117,39 @@ public class PackageUpdatedTask implements ModelUpdateTask {
: ItemInfoMatcher.ofPackages(packageSet, mUser);
final HashSet<ComponentName> removedComponents = new HashSet<>();
final HashMap<String, List<LauncherActivityInfo>> activitiesLists = new HashMap<>();
if (DEBUG) {
Log.d(TAG, "Package updated: mOp=" + getOpString()
+ " packages=" + Arrays.toString(packages));
}
switch (mOp) {
case OP_ADD: {
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
if (DEBUG) {
Log.d(TAG, "OP_ADD: PROMISE_APPS_IN_ALL_APPS enabled:"
+ " removing promise icon apps from package=" + packages[i]);
}
appsList.removePackage(packages[i], mUser);
}
activitiesLists.put(
packages[i], appsList.addPackage(context, packages[i], mUser));
activitiesLists.put(packages[i],
appsList.addPackage(context, packages[i], mUser));
}
flagOp = FlagOp.NO_OP.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
break;
}
case OP_UPDATE:
try (SafeCloseable t =
appsList.trackRemoves(a -> removedComponents.add(a.componentName))) {
try (SafeCloseable t = appsList.trackRemoves(a -> {
Log.d(TAG, "OP_UPDATE - AllAppsList.trackRemoves callback:"
+ " removed component=" + a.componentName
+ " id=" + a.id
+ " Look for earlier AllAppsList logs to find more information.");
removedComponents.add(a.componentName);
})) {
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
iconCache.updateIconsForPkg(packages[i], mUser);
activitiesLists.put(
packages[i], appsList.updatePackage(context, packages[i], mUser));
activitiesLists.put(packages[i],
appsList.updatePackage(context, packages[i], mUser));
}
}
// Since package was just updated, the target must be available now.
@@ -147,14 +157,15 @@ public class PackageUpdatedTask implements ModelUpdateTask {
break;
case OP_REMOVE: {
for (int i = 0; i < N; i++) {
FileLog.d(TAG, "Removing app icon: " + packages[i]);
iconCache.removeIconsForPkg(packages[i], mUser);
}
// Fall through
}
case OP_UNAVAILABLE:
for (int i = 0; i < N; i++) {
if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
if (DEBUG) {
Log.d(TAG, getOpString() + ": removing package=" + packages[i]);
}
appsList.removePackage(packages[i], mUser);
}
flagOp = FlagOp.NO_OP.addFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
@@ -163,7 +174,6 @@ public class PackageUpdatedTask implements ModelUpdateTask {
case OP_UNSUSPEND:
flagOp = FlagOp.NO_OP.setFlag(
WorkspaceItemInfo.FLAG_DISABLED_SUSPENDED, mOp == OP_SUSPEND);
if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
appsList.updateDisabledFlags(matcher, flagOp);
break;
case OP_USER_AVAILABILITY_CHANGE: {
@@ -249,12 +259,21 @@ public class PackageUpdatedTask implements ModelUpdateTask {
infoUpdated = true;
} else if (si.hasPromiseIconUi()) {
removedShortcuts.add(si.id);
if (DEBUG) {
Log.d(TAG, "Removing restored shortcut promise icon"
+ " that no longer points to valid component."
+ " id=" + si.id
+ ", package=" + si.getTargetPackage());
}
return;
}
} else if (!isTargetValid) {
removedShortcuts.add(si.id);
FileLog.e(TAG, "Restored shortcut no longer valid "
+ si.getIntent());
FileLog.e(TAG, "Removing shortcut that no longer points to"
+ " valid component."
+ " id=" + si.id
+ " package=" + si.getTargetPackage()
+ " status=" + si.status);
return;
} else {
si.status = WorkspaceItemInfo.DEFAULT;
@@ -269,6 +288,8 @@ public class PackageUpdatedTask implements ModelUpdateTask {
if (isNewApkAvailable) {
List<LauncherActivityInfo> activities = activitiesLists.get(
packageName);
// TODO: See if we can migrate this to
// AppInfo#updateRuntimeFlagsForActivityTarget
si.setProgressLevel(
activities == null || activities.isEmpty()
? 100
@@ -334,7 +355,8 @@ public class PackageUpdatedTask implements ModelUpdateTask {
if (!removedShortcuts.isEmpty()) {
taskController.deleteAndBindComponentsRemoved(
ItemInfoMatcher.ofItemIds(removedShortcuts),
"removed because the target component is invalid");
"removing shortcuts with invalid target components."
+ " ids=" + removedShortcuts);
}
if (!widgets.isEmpty()) {
@@ -346,6 +368,9 @@ public class PackageUpdatedTask implements ModelUpdateTask {
if (mOp == OP_REMOVE) {
// Mark all packages in the broadcast to be removed
Collections.addAll(removedPackages, packages);
if (DEBUG) {
Log.d(TAG, "OP_REMOVE: removing packages=" + Arrays.toString(packages));
}
// No need to update the removedComponents as
// removedPackages is a super-set of removedComponents
@@ -354,6 +379,10 @@ public class PackageUpdatedTask implements ModelUpdateTask {
final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
for (int i=0; i<N; i++) {
if (!launcherApps.isPackageEnabled(packages[i], mUser)) {
if (DEBUG) {
Log.d(TAG, "OP_UPDATE:"
+ " package " + packages[i] + " is disabled, removing package.");
}
removedPackages.add(packages[i]);
}
}
@@ -399,7 +428,8 @@ public class PackageUpdatedTask implements ModelUpdateTask {
return false;
}
// Try to find the best match activity.
Intent intent = new PackageManagerHelper(context).getAppLaunchIntent(packageName, mUser);
Intent intent = PackageManagerHelper.INSTANCE.get(context)
.getAppLaunchIntent(packageName, mUser);
if (intent != null) {
si.intent = intent;
si.status = WorkspaceItemInfo.DEFAULT;
@@ -407,4 +437,18 @@ public class PackageUpdatedTask implements ModelUpdateTask {
}
return false;
}
private String getOpString() {
return switch (mOp) {
case OP_NONE -> "NONE";
case OP_ADD -> "ADD";
case OP_UPDATE -> "UPDATE";
case OP_REMOVE -> "REMOVE";
case OP_UNAVAILABLE -> "UNAVAILABLE";
case OP_SUSPEND -> "SUSPEND";
case OP_UNSUSPEND -> "UNSUSPEND";
case OP_USER_AVAILABILITY_CHANGE -> "USER_AVAILABILITY_CHANGE";
default -> "UNKNOWN";
};
}
}
@@ -336,7 +336,8 @@ class WorkspaceItemProcessor(
info,
activityInfo,
userCache.getUserInfo(c.user),
ApiWrapper.INSTANCE[app.context]
ApiWrapper.INSTANCE[app.context],
pmHelper
)
}
if (
@@ -90,12 +90,12 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
*/
public AppInfo(Context context, LauncherActivityInfo info, UserHandle user) {
this(info, UserCache.INSTANCE.get(context).getUserInfo(user),
ApiWrapper.INSTANCE.get(context),
ApiWrapper.INSTANCE.get(context), PackageManagerHelper.INSTANCE.get(context),
context.getSystemService(UserManager.class).isQuietModeEnabled(user));
}
public AppInfo(LauncherActivityInfo info, UserIconInfo userIconInfo,
ApiWrapper apiWrapper, boolean quietModeEnabled) {
ApiWrapper apiWrapper, PackageManagerHelper pmHelper, boolean quietModeEnabled) {
this.componentName = info.getComponentName();
this.container = CONTAINER_ALL_APPS;
this.user = userIconInfo.user;
@@ -105,7 +105,7 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
runtimeStatusFlags |= FLAG_DISABLED_QUIET_USER;
}
uid = info.getApplicationInfo().uid;
updateRuntimeFlagsForActivityTarget(this, info, userIconInfo, apiWrapper);
updateRuntimeFlagsForActivityTarget(this, info, userIconInfo, apiWrapper, pmHelper);
}
public AppInfo(AppInfo info) {
@@ -184,7 +184,7 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
*/
public static boolean updateRuntimeFlagsForActivityTarget(
ItemInfoWithIcon info, LauncherActivityInfo lai, UserIconInfo userIconInfo,
ApiWrapper apiWrapper) {
ApiWrapper apiWrapper, PackageManagerHelper pmHelper) {
final int oldProgressLevel = info.getProgressLevel();
final int oldRuntimeStatusFlags = info.runtimeStatusFlags;
ApplicationInfo appInfo = lai.getApplicationInfo();
@@ -216,6 +216,8 @@ public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
PackageManagerHelper.getLoadingProgress(lai),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai));
info.setSupportsMultiInstance(
pmHelper.supportsMultiInstance(lai.getComponentName()));
return (oldProgressLevel != info.getProgressLevel())
|| (oldRuntimeStatusFlags != info.runtimeStatusFlags);
}
@@ -73,6 +73,7 @@ import java.util.Optional;
* Represents an item in the launcher.
*/
public class ItemInfo {
private static final String TAG = "ItemInfo";
public static final boolean DEBUG = false;
public static final int NO_ID = -1;
@@ -285,7 +286,7 @@ public class ItemInfo {
@Override
@NonNull
public final String toString() {
return getClass().getSimpleName() + "(" + dumpProperties() + ")";
return TAG + "(" + dumpProperties() + ")";
}
@NonNull
@@ -126,6 +126,11 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
*/
public static final int FLAG_NOT_RESIZEABLE = 1 << 15;
/**
* Flag indicating whether the package related to the item & user supports multiple instances.
*/
public static final int FLAG_SUPPORTS_MULTI_INSTANCE = 1 << 16;
/**
* Status associated with the system state of the underlying item. This is calculated every
* time a new info is created and not persisted on the disk.
@@ -251,6 +256,24 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
}
}
/**
* Sets whether this app info supports multi-instance.
*/
protected void setSupportsMultiInstance(boolean supportsMultiInstance) {
if (supportsMultiInstance) {
runtimeStatusFlags |= FLAG_SUPPORTS_MULTI_INSTANCE;
} else {
runtimeStatusFlags &= ~FLAG_SUPPORTS_MULTI_INSTANCE;
}
}
/**
* Returns whether this app info supports multi-instance.
*/
public boolean supportsMultiInstance() {
return (runtimeStatusFlags & FLAG_SUPPORTS_MULTI_INSTANCE) != 0;
}
/**
* Sets whether this app info is non-resizeable.
*/
@@ -301,4 +324,11 @@ public abstract class ItemInfoWithIcon extends ItemInfo {
drawable.setIsDisabled(isDisabled());
return drawable;
}
@Override
protected String dumpProperties() {
return super.dumpProperties()
+ " supportsMultiInstance=" + supportsMultiInstance()
+ " nonResizeable=" + isNonResizeable();
}
}
@@ -22,6 +22,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
public final class PackageInstallInfo {
private static final String TAG = "PackageInstallInfo";
public static final int STATUS_INSTALLED = 0;
public static final int STATUS_INSTALLING = 1;
@@ -61,7 +62,7 @@ public final class PackageInstallInfo {
@Override
public String toString() {
return getClass().getSimpleName() + "(" + dumpProperties() + ")";
return TAG + "(" + dumpProperties() + ")";
}
private String dumpProperties() {
@@ -44,6 +44,7 @@ import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.PackageManagerHelper;
/**
* A set of utility methods for Launcher DB used for DB updates and migration.
@@ -107,9 +108,11 @@ public class LauncherDbUtils {
Cursor c = db.query(
Favorites.TABLE_NAME, null, "itemType = 1", null, null, null, null);
UserManagerState ums = new UserManagerState();
PackageManagerHelper pmHelper = PackageManagerHelper.INSTANCE.get(context);
ums.init(UserCache.INSTANCE.get(context),
context.getSystemService(UserManager.class));
LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, null);
LoaderCursor lc = new LoaderCursor(c, LauncherAppState.getInstance(context), ums, pmHelper,
null);
IntSet deletedShortcuts = new IntSet();
while (lc.moveToNext()) {
@@ -135,7 +135,7 @@ data class HotseatSpec(
hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize
private fun logError(message: String) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
Log.e(LOG_TAG, "$LOG_TAG #isValid - $message - $this")
}
companion object {
@@ -31,7 +31,7 @@ class ResponsiveCellSpecsProvider(groupOfSpecs: List<ResponsiveSpecGroup<CellSpe
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isEmpty() && group.heightSpecs.isNotEmpty()) {
"${this::class.simpleName} is invalid, only heightSpecs are allowed - " +
"$LOG_TAG is invalid, only heightSpecs are allowed - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
@@ -65,6 +65,7 @@ class ResponsiveCellSpecsProvider(groupOfSpecs: List<ResponsiveSpecGroup<CellSpe
}
companion object {
private const val LOG_TAG = "ResponsiveCellSpecsProvider"
@JvmStatic
fun create(resourceHelper: ResourceHelper): ResponsiveCellSpecsProvider {
val parser = ResponsiveSpecsParser(resourceHelper)
@@ -137,11 +138,11 @@ data class CellSpec(
}
private fun logError(message: String) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
Log.e(LOG_TAG, "$LOG_TAG#isValid - $message - $this")
}
companion object {
private const val LOG_TAG = "CellSpec"
const val LOG_TAG = "CellSpec"
}
}
@@ -182,6 +183,7 @@ data class CalculatedCellSpec(
)
companion object {
private const val LOG_TAG = "CalculatedCellSpec"
private fun getCalculatedValue(
availableSpace: Int,
spec: SizeSpec,
@@ -191,10 +193,10 @@ data class CalculatedCellSpec(
}
override fun toString(): String {
return "${this::class.simpleName}(" +
return "$LOG_TAG(" +
"availableSpace=$availableSpace, iconSize=$iconSize, " +
"iconTextSize=$iconTextSize, iconDrawablePadding=$iconDrawablePadding, " +
"${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
"${CellSpec.LOG_TAG}.maxAvailableSize=${spec.maxAvailableSize}" +
")"
}
}
@@ -154,7 +154,7 @@ data class ResponsiveSpec(
}
private fun logError(message: String) {
Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this")
Log.e(LOG_TAG, "$LOG_TAG#isValid - $message - $this")
}
enum class DimensionType {
@@ -40,7 +40,7 @@ class ResponsiveSpecsProvider(
groupOfSpecs
.onEach { group ->
check(group.widthSpecs.isNotEmpty() && group.heightSpecs.isNotEmpty()) {
"${this::class.simpleName} is incomplete - " +
"$LOG_TAG is incomplete - " +
"width list size = ${group.widthSpecs.size}; " +
"height list size = ${group.heightSpecs.size}."
}
@@ -124,6 +124,7 @@ class ResponsiveSpecsProvider(
}
companion object {
private const val LOG_TAG = "ResponsiveSpecsProvider"
@JvmStatic
fun create(
resourceHelper: ResourceHelper,
@@ -84,7 +84,8 @@ import java.util.function.Consumer;
*/
public class ItemClickHandler {
private static final String TAG = ItemClickHandler.class.getSimpleName();
private static final String TAG = "ItemClickHandler";
private static final boolean DEBUG = true;
/**
* Instance used for click handling on items
@@ -110,7 +111,19 @@ public class ItemClickHandler {
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher);
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {
if (DEBUG) {
String targetPackage = ((LauncherAppWidgetInfo) tag).getTargetPackage();
Log.d(TAG, "onClick: PendingAppWidgetHostView clicked for"
+ " package=" + targetPackage);
}
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
} else {
if (DEBUG) {
String targetPackage = ((LauncherAppWidgetInfo) tag).getTargetPackage();
Log.d(TAG, "onClick: LauncherAppWidgetInfo clicked,"
+ " but not instance of PendingAppWidgetHostView. Returning."
+ " package=" + targetPackage);
}
}
} else if (tag instanceof ItemClickProxy) {
((ItemClickProxy) tag).onItemClicked(v);
@@ -120,6 +133,10 @@ public class ItemClickHandler {
launcher.getString(R.string.long_accessible_way_to_add_shortcut));
Snackbar.show(launcher, msg, null);
} else if (tag instanceof PendingAddWidgetInfo) {
if (DEBUG) {
String targetPackage = ((PendingAddWidgetInfo) tag).getTargetPackage();
Log.d(TAG, "onClick: PendingAddWidgetInfo clicked for package=" + targetPackage);
}
CharSequence msg = Utilities.wrapForTts(
launcher.getText(R.string.long_press_widget_to_add),
launcher.getString(R.string.long_accessible_way_to_add));
@@ -199,6 +216,9 @@ public class ItemClickHandler {
LauncherAppWidgetProviderInfo appWidgetInfo = new WidgetManagerHelper(launcher)
.findProvider(info.providerName, info.user);
if (appWidgetInfo == null) {
Log.e(TAG, "onClickPendingWidget: Pending widget ready for click setup,"
+ " but LauncherAppWidgetProviderInfo was null. Returning."
+ " component=" + info.getTargetComponent());
return;
}
WidgetAddFlowHandler addFlowHandler = new WidgetAddFlowHandler(appWidgetInfo);
@@ -206,6 +226,10 @@ public class ItemClickHandler {
if (info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
if (!info.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
// This should not happen, as we make sure that an Id is allocated during bind.
Log.e(TAG, "onClickPendingWidget: Pending widget ready for click setup,"
+ " and LauncherAppWidgetProviderInfo was found. However,"
+ " no appWidgetId was allocated. Returning."
+ " component=" + info.getTargetComponent());
return;
}
addFlowHandler.startBindFlow(launcher, info.appWidgetId, info,
@@ -17,6 +17,7 @@
package com.android.launcher3.util;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
@@ -73,10 +74,14 @@ public class PackageManagerHelper implements SafeCloseable{
@NonNull
private final LauncherApps mLauncherApps;
private final String[] mLegacyMultiInstanceSupportedApps;
public PackageManagerHelper(@NonNull final Context context) {
mContext = context;
mPm = context.getPackageManager();
mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray(
R.array.config_appsSupportMultiInstancesSplit);
}
@Override
@@ -159,11 +164,23 @@ public class PackageManagerHelper implements SafeCloseable{
}
}
/**
* Returns the preferred launch activity intent for a given package.
*/
@Nullable
public Intent getAppLaunchIntent(@Nullable final String pkg, @NonNull final UserHandle user) {
LauncherActivityInfo info = getAppLaunchInfo(pkg, user);
return info != null ? AppInfo.makeLaunchIntent(info) : null;
}
/**
* Returns the preferred launch activity for a given package.
*/
@Nullable
public LauncherActivityInfo getAppLaunchInfo(@Nullable final String pkg,
@NonNull final UserHandle user) {
List<LauncherActivityInfo> activities = mLauncherApps.getActivityList(pkg, user);
return activities.isEmpty() ? null :
AppInfo.makeLaunchIntent(activities.get(0));
return activities.isEmpty() ? null : activities.get(0);
}
/**
@@ -285,4 +302,47 @@ public class PackageManagerHelper implements SafeCloseable{
return (info.flags & ApplicationInfo.FLAG_INSTALLED) != 0 || (
Flags.enableSupportForArchiving() && info.isArchived);
}
/**
* Returns whether the given component or its application has the multi-instance property set.
*/
public boolean supportsMultiInstance(@NonNull ComponentName component) {
// Check the legacy hardcoded allowlist first
for (String pkg : mLegacyMultiInstanceSupportedApps) {
if (pkg.equals(component.getPackageName())) {
return true;
}
}
// Check app multi-instance properties after V
if (!Utilities.ATLEAST_V) {
return false;
}
try {
// Check if the component has the multi-instance property
return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component)
.getBoolean();
} catch (PackageManager.NameNotFoundException e1) {
try {
// Check if the application has the multi-instance property
return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI,
component.getPackageName())
.getBoolean();
} catch (PackageManager.NameNotFoundException e2) {
// Fall through
}
}
return false;
}
/**
* Returns whether two apps should be considered the same for multi-instance purposes, which
* requires additional checks to ensure they can be started as multiple instances.
*/
public static boolean isSameAppForMultiInstance(@NonNull ItemInfo app1,
@NonNull ItemInfo app2) {
return app1.getTargetPackage().equals(app2.getTargetPackage())
&& app1.user.equals(app2.user);
}
}
@@ -54,7 +54,7 @@ import com.android.launcher3.graphics.TriangleShape;
*/
public class ArrowTipView extends AbstractFloatingView {
private static final String TAG = ArrowTipView.class.getSimpleName();
private static final String TAG = "ArrowTipView";
private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
private static final long SHOW_DELAY_MS = 200;
private static final long SHOW_DURATION_MS = 300;
@@ -68,7 +68,7 @@ import java.util.function.Supplier;
public class FloatingIconView extends FrameLayout implements
Animator.AnimatorListener, OnGlobalLayoutListener, FloatingView {
private static final String TAG = FloatingIconView.class.getSimpleName();
private static final String TAG = "FloatingIconView";
// Manages loading the icon on a worker thread
private static @Nullable IconLoadResult sIconLoadResult;
@@ -26,6 +26,7 @@ import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
@@ -104,6 +105,8 @@ public class WidgetManagerHelper {
// If exception is thrown because of device is locked, it means a race condition occurs
// that the user got locked again while launcher is processing the event. In this case
// we should return empty list.
Log.e(TAG, "getAllProviders: Error getting installed providers for"
+ " package=" + packageUser.mPackageName, e);
return Collections.emptyList();
}
}
@@ -133,6 +136,8 @@ public class WidgetManagerHelper {
return LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, info);
}
}
Log.w(TAG, "findProvider: No App Widget Provider found for component=" + provider
+ " user=" + user);
return null;
}
@@ -36,6 +36,7 @@ import android.widget.LinearLayout;
import android.widget.ScrollView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import com.android.launcher3.DeviceProfile;
@@ -78,6 +79,7 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
private boolean mOldIsSwipeToDismissInProgress;
private int mActivePage = -1;
@Nullable
private PackageUserKey mSelectedHeader;
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -230,7 +232,8 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
if (mSuggestedWidgetsContainer == null && mRecommendedWidgetsCount > 0) {
setupSuggestedWidgets(LayoutInflater.from(getContext()));
mSuggestedWidgetsHeader.callOnClick();
} else if (mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
} else if (mSelectedHeader != null
&& mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey)) {
// Reselect widget if we are reloading recommendations while it is currently showing.
selectWidgetCell(mWidgetRecommendationsContainer, getLastSelectedWidgetItem());
}
@@ -280,8 +283,8 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
mRightPaneScrollView.setScrollY(0);
mRightPane.setAccessibilityPaneTitle(suggestionsRightPaneTitle);
mSuggestedWidgetsPackageUserKey = PackageUserKey.fromPackageItemInfo(packageItemInfo);
final boolean isChangingHeaders =
!mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey);
final boolean isChangingHeaders = mSelectedHeader == null
|| !mSelectedHeader.equals(mSuggestedWidgetsPackageUserKey);
if (isChangingHeaders) {
// If switching from another header, unselect any WidgetCells. This is necessary
// because we do not clear/recycle the WidgetCells in the recommendations container
+4
View File
@@ -412,5 +412,9 @@
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<property
android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
android:value="true"/>
</application>
</manifest>
@@ -202,6 +202,7 @@ public class PrivateProfileManagerTest {
}
@Test
@TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/339109319
public void openPrivateSpaceSettings_triggersCorrectIntent() {
Intent expectedIntent = ApiWrapper.INSTANCE.get(mContext).getPrivateSpaceSettingsIntent();
ArgumentCaptor<Intent> acIntent = ArgumentCaptor.forClass(Intent.class);
@@ -79,6 +79,7 @@ public class LoaderCursorTest {
private LauncherModelHelper mModelHelper;
private LauncherAppState mApp;
private PackageManagerHelper mPmHelper;
private MatrixCursor mCursor;
private InvariantDeviceProfile mIDP;
@@ -92,6 +93,7 @@ public class LoaderCursorTest {
mContext = mModelHelper.sandboxContext;
mIDP = InvariantDeviceProfile.INSTANCE.get(mContext);
mApp = LauncherAppState.getInstance(mContext);
mPmHelper = PackageManagerHelper.INSTANCE.get(mContext);
mCursor = new MatrixCursor(new String[] {
ICON, TITLE, _ID, CONTAINER, ITEM_TYPE,
@@ -101,7 +103,7 @@ public class LoaderCursorTest {
});
UserManagerState ums = new UserManagerState();
mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, null);
mLoaderCursor = new LoaderCursor(mCursor, mApp, ums, mPmHelper, null);
ums.allUsers.put(0, Process.myUserHandle());
}
@@ -34,6 +34,7 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Rule;
@@ -63,6 +64,8 @@ public final class PackageManagerHelperTest {
mContext = mock(Context.class);
mLauncherApps = mock(LauncherApps.class);
when(mContext.getSystemService(eq(LauncherApps.class))).thenReturn(mLauncherApps);
when(mContext.getResources()).thenReturn(
InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
mPackageManagerHelper = new PackageManagerHelper(mContext);
}
@@ -17,6 +17,7 @@ package com.android.launcher3.tapl;
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.text.TextUtils;
import android.widget.TextView;
import androidx.test.uiautomator.By;
@@ -117,4 +118,28 @@ public class SearchResultFromQsb implements SearchInputSource {
public SearchResultFromQsb getSearchResultForInput() {
return this;
}
/** Verify a tile is present by checking its title and subtitle. */
public void verifyTileIsPresent(String title, String subtitle) {
ArrayList<UiObject2> searchResults =
new ArrayList<>(mLauncher.waitForObjectsInContainer(
mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
By.clazz(TextView.class)));
boolean foundTitle = false;
boolean foundSubtitle = false;
for (UiObject2 uiObject: searchResults) {
String currentString = uiObject.getText();
if (TextUtils.equals(currentString, title)) {
foundTitle = true;
} else if (TextUtils.equals(currentString, subtitle)) {
foundSubtitle = true;
}
}
if (!foundTitle) {
mLauncher.fail("Tile not found for title: " + title);
}
if (!foundSubtitle) {
mLauncher.fail("Tile not found for subtitle: " + subtitle);
}
}
}