Snap for 11828632 from 2ee174e696 to 24Q3-release
Change-Id: Id105f7e01c7701f3aeb35c94da4a1be04bc9657f
This commit is contained in:
@@ -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" />
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user