diff options
127 files changed, 2823 insertions, 2011 deletions
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist index a413bbd68f60..88b9c05733a8 100644 --- a/config/preloaded-classes-denylist +++ b/config/preloaded-classes-denylist @@ -1,13 +1,17 @@ +android.car.Car android.content.AsyncTaskLoader$LoadTask +android.media.MediaCodecInfo$CodecCapabilities$FeatureList android.net.ConnectivityThread$Singleton android.os.FileObserver android.os.NullVibrator +android.permission.PermissionManager +android.provider.MediaStore android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask +android.view.HdrRenderState android.widget.Magnifier +com.android.internal.jank.InteractionJankMonitor$InstanceHolder +com.android.internal.util.LatencyTracker$SLatencyTrackerHolder gov.nist.core.net.DefaultNetworkLayer -android.net.rtp.AudioGroup -android.net.rtp.AudioStream -android.net.rtp.RtpStream java.util.concurrent.ThreadLocalRandom java.util.ImmutableCollections -com.android.internal.jank.InteractionJankMonitor$InstanceHolder +sun.nio.fs.UnixChannelFactory
\ No newline at end of file diff --git a/core/api/current.txt b/core/api/current.txt index d9ab273888db..aa58fe42fa26 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1074,6 +1074,7 @@ package android { field public static final int layout = 16842994; // 0x10100f2 field public static final int layoutAnimation = 16842988; // 0x10100ec field public static final int layoutDirection = 16843698; // 0x10103b2 + field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel; field public static final int layoutMode = 16843738; // 0x10103da field public static final int layout_above = 16843140; // 0x1010184 field public static final int layout_alignBaseline = 16843142; // 0x1010186 @@ -20986,6 +20987,7 @@ package android.inputmethodservice { method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public android.view.View onCreateInputView(); method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean); method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]); method public boolean onEvaluateFullscreenMode(); method @CallSuper public boolean onEvaluateInputViewShown(); @@ -56975,6 +56977,9 @@ package android.view.inputmethod { method public String getExtraValueOf(String); method public int getIconResId(); method @NonNull public String getLanguageTag(); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutDisplayName(@NonNull android.content.Context, @NonNull android.content.pm.ApplicationInfo); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutLabelNonLocalized(); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @StringRes public int getLayoutLabelResource(); method @Deprecated @NonNull public String getLocale(); method public String getMode(); method @NonNull public CharSequence getNameOverride(); @@ -56994,6 +56999,8 @@ package android.view.inputmethod { method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(String); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelNonLocalized(@NonNull CharSequence); + method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelResource(@StringRes int); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean); method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable android.icu.util.ULocale, @NonNull String); method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(String); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 36fc65a76d53..b447897733e1 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2764,14 +2764,19 @@ public class ActivityManager { /** * Information of organized child tasks. * + * @deprecated No longer used * @hide */ + @Deprecated public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>(); /** * Information about the last snapshot taken for this task. + * + * @deprecated No longer used * @hide */ + @Deprecated public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData(); public RecentTaskInfo() { @@ -2793,7 +2798,7 @@ public class ActivityManager { lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR); lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR); lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR); - super.readFromParcel(source); + super.readTaskFromParcel(source); } @Override @@ -2804,7 +2809,7 @@ public class ActivityManager { dest.writeTypedObject(lastSnapshotData.taskSize, flags); dest.writeTypedObject(lastSnapshotData.contentInsets, flags); dest.writeTypedObject(lastSnapshotData.bufferSize, flags); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } public static final @android.annotation.NonNull Creator<RecentTaskInfo> CREATOR @@ -2988,13 +2993,13 @@ public class ActivityManager { public void readFromParcel(Parcel source) { id = source.readInt(); - super.readFromParcel(source); + super.readTaskFromParcel(source); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(id); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } public static final @android.annotation.NonNull Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() { diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 799df1f9227a..16dcf2ad7e45 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -534,10 +534,9 @@ public class ActivityTaskManager { dest.writeIntArray(childTaskUserIds); dest.writeInt(visible ? 1 : 0); dest.writeInt(position); - super.writeToParcel(dest, flags); + super.writeTaskToParcel(dest, flags); } - @Override void readFromParcel(Parcel source) { bounds = source.readTypedObject(Rect.CREATOR); childTaskIds = source.createIntArray(); @@ -546,7 +545,7 @@ public class ActivityTaskManager { childTaskUserIds = source.createIntArray(); visible = source.readInt() > 0; position = source.readInt(); - super.readFromParcel(source); + super.readTaskFromParcel(source); } public static final @NonNull Creator<RootTaskInfo> CREATOR = new Creator<>() { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ca98da76b78f..60b8f80d8f2d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3100,6 +3100,19 @@ public final class ActivityThread extends ClientTransactionHandler mResourcesManager = ResourcesManager.getInstance(); } + /** + * Creates and initialize a new system activity thread, to be used for testing. This does not + * call {@link #attach}, so it does not modify static state. + */ + @VisibleForTesting + @NonNull + public static ActivityThread createSystemActivityThreadForTesting() { + final var thread = new ActivityThread(); + thread.mSystemThread = true; + initializeSystemThread(thread); + return thread; + } + @UnsupportedAppUsage public ApplicationThread getApplicationThread() { @@ -6806,6 +6819,16 @@ public final class ActivityThread extends ClientTransactionHandler LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths); resApk.updateApplicationInfo(ai, oldPaths); } + if (android.content.res.Flags.systemContextHandleAppInfoChanged() && mSystemThread) { + final var systemContext = getSystemContext(); + if (systemContext.getPackageName().equals(ai.packageName)) { + // The system package is not tracked directly, but still needs to receive updates to + // its application info. + final ArrayList<String> oldPaths = new ArrayList<>(); + LoadedApk.makePaths(this, systemContext.getApplicationInfo(), oldPaths); + systemContext.mPackageInfo.updateApplicationInfo(ai, oldPaths); + } + } ResourcesImpl beforeImpl = getApplication().getResources().getImpl(); @@ -8560,17 +8583,7 @@ public final class ActivityThread extends ClientTransactionHandler // we can't display an alert, we just want to die die die. android.ddm.DdmHandleAppName.setAppName("system_process", UserHandle.myUserId()); - try { - mInstrumentation = new Instrumentation(); - mInstrumentation.basicInit(this); - ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mPackageInfo); - mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null); - mInitialApplication.onCreate(); - } catch (Exception e) { - throw new RuntimeException( - "Unable to instantiate Application():" + e.toString(), e); - } + initializeSystemThread(this); } ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> { @@ -8595,6 +8608,28 @@ public final class ActivityThread extends ClientTransactionHandler ViewRootImpl.addConfigCallback(configChangedCallback); } + /** + * Initializes the given system activity thread, setting up its instrumentation and initial + * application. This only has an effect if the given thread is a {@link #mSystemThread}. + * + * @param thread the given system activity thread to initialize. + */ + private static void initializeSystemThread(@NonNull ActivityThread thread) { + if (!thread.mSystemThread) { + return; + } + try { + thread.mInstrumentation = new Instrumentation(); + thread.mInstrumentation.basicInit(thread); + ContextImpl context = ContextImpl.createAppContext( + thread, thread.getSystemContext().mPackageInfo); + thread.mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null); + thread.mInitialApplication.onCreate(); + } catch (Exception e) { + throw new RuntimeException("Unable to instantiate Application():" + e, e); + } + } + @UnsupportedAppUsage public static ActivityThread systemMain() { ThreadedRenderer.initForSystemProcess(); diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 8370c2e522f3..68794588afc5 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -167,10 +167,11 @@ public class AppCompatTaskInfo implements Parcelable { } /** - * @return {@code true} if top activity is pillarboxed. + * @return {@code true} if the top activity bounds are letterboxed with width <= height. */ - public boolean isTopActivityPillarboxed() { - return topActivityLetterboxWidth < topActivityLetterboxHeight; + public boolean isTopActivityPillarboxShaped() { + return isTopActivityLetterboxed() + && topActivityLetterboxWidth <= topActivityLetterboxHeight; } /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0629b8a58b42..4c860fac9871 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -63,6 +63,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; +import android.os.IpcDataCache; import android.os.Looper; import android.os.PackageTagsList; import android.os.Parcel; @@ -78,12 +79,14 @@ import android.permission.PermissionGroupUsage; import android.permission.PermissionUsageHelper; import android.permission.flags.Flags; import android.provider.DeviceConfig; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.LongSparseArray; import android.util.LongSparseLongArray; import android.util.Pools; import android.util.SparseArray; +import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.Immutable; @@ -7797,6 +7800,116 @@ public class AppOpsManager { } } + private static final String APP_OP_MODE_CACHING_API = "getAppOpMode"; + private static final String APP_OP_MODE_CACHING_NAME = "appOpModeCache"; + private static final int APP_OP_MODE_CACHING_SIZE = 2048; + + private static final IpcDataCache.QueryHandler<AppOpModeQuery, Integer> sGetAppOpModeQuery = + new IpcDataCache.QueryHandler<>() { + @Override + public Integer apply(AppOpModeQuery query) { + IAppOpsService service = getService(); + try { + return service.checkOperationRawForDevice(query.op, query.uid, + query.packageName, query.attributionTag, query.virtualDeviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public boolean shouldBypassCache(@NonNull AppOpModeQuery query) { + // If the flag to enable the new caching behavior is off, bypass the cache. + return !Flags.appopModeCachingEnabled(); + } + }; + + // A LRU cache on binder clients that caches AppOp mode by uid, packageName, virtualDeviceId + // and attributionTag. + private static final IpcDataCache<AppOpModeQuery, Integer> sAppOpModeCache = + new IpcDataCache<>(APP_OP_MODE_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM, + APP_OP_MODE_CACHING_API, APP_OP_MODE_CACHING_NAME, sGetAppOpModeQuery); + + // Ops that we don't want to cache due to: + // 1) Discrepancy of attributionTag support in checkOp and noteOp that determines if a package + // can bypass user restriction of an op: b/240617242. COARSE_LOCATION and FINE_LOCATION are + // the only two ops that are impacted. + private static final SparseBooleanArray OPS_WITHOUT_CACHING = new SparseBooleanArray(); + static { + OPS_WITHOUT_CACHING.put(OP_COARSE_LOCATION, true); + OPS_WITHOUT_CACHING.put(OP_FINE_LOCATION, true); + } + + private static boolean isAppOpModeCachingEnabled(int opCode) { + if (!Flags.appopModeCachingEnabled()) { + return false; + } + return !OPS_WITHOUT_CACHING.get(opCode, false); + } + + /** + * @hide + */ + public static void invalidateAppOpModeCache() { + if (Flags.appopModeCachingEnabled()) { + IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, APP_OP_MODE_CACHING_API); + } + } + + /** + * Bypass AppOpModeCache in the local process + * + * @hide + */ + public static void disableAppOpModeCache() { + if (Flags.appopModeCachingEnabled()) { + sAppOpModeCache.disableLocal(); + } + } + + private static final class AppOpModeQuery { + final int op; + final int uid; + final String packageName; + final int virtualDeviceId; + final String attributionTag; + final String methodName; + + AppOpModeQuery(int op, int uid, @Nullable String packageName, int virtualDeviceId, + @Nullable String attributionTag, @Nullable String methodName) { + this.op = op; + this.uid = uid; + this.packageName = packageName; + this.virtualDeviceId = virtualDeviceId; + this.attributionTag = attributionTag; + this.methodName = methodName; + } + + @Override + public String toString() { + return TextUtils.formatSimple("AppOpModeQuery(op=%d, uid=%d, packageName=%s, " + + "virtualDeviceId=%d, attributionTag=%s, methodName=%s", op, uid, + packageName, virtualDeviceId, attributionTag, methodName); + } + + @Override + public int hashCode() { + return Objects.hash(op, uid, packageName, virtualDeviceId, attributionTag); + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null) return false; + if (this.getClass() != o.getClass()) return false; + + AppOpModeQuery other = (AppOpModeQuery) o; + return op == other.op && uid == other.uid && Objects.equals(packageName, + other.packageName) && virtualDeviceId == other.virtualDeviceId + && Objects.equals(attributionTag, other.attributionTag); + } + } + AppOpsManager(Context context, IAppOpsService service) { mContext = context; mService = service; @@ -8851,12 +8964,16 @@ public class AppOpsManager { private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName, int virtualDeviceId) { try { - if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { - return mService.checkOperationRaw(op, uid, packageName, null); + int mode; + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + "unsafeCheckOpRawNoThrow")); } else { - return mService.checkOperationRawForDevice( + mode = mService.checkOperationRawForDevice( op, uid, packageName, null, virtualDeviceId); } + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -9041,7 +9158,7 @@ public class AppOpsManager { SyncNotedAppOp syncOp; if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { syncOp = mService.noteOperation(op, uid, packageName, attributionTag, - collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); + collectionMode == COLLECT_ASYNC, message, shouldCollectMessage); } else { syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag, virtualDeviceId, collectionMode == COLLECT_ASYNC, message, @@ -9284,8 +9401,21 @@ public class AppOpsManager { @UnsupportedAppUsage public int checkOp(int op, int uid, String packageName) { try { - int mode = mService.checkOperationForDevice(op, uid, packageName, - Context.DEVICE_ID_DEFAULT); + int mode; + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, Context.DEVICE_ID_DEFAULT, null, + "checkOp")); + if (mode == MODE_FOREGROUND) { + // We only cache raw mode. If the mode is FOREGROUND, we need another binder + // call to fetch translated value based on the process state. + mode = mService.checkOperationForDevice(op, uid, packageName, + Context.DEVICE_ID_DEFAULT); + } + } else { + mode = mService.checkOperationForDevice(op, uid, packageName, + Context.DEVICE_ID_DEFAULT); + } if (mode == MODE_ERRORED) { throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName)); } @@ -9324,13 +9454,19 @@ public class AppOpsManager { private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) { try { int mode; - if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) { - mode = mService.checkOperation(op, uid, packageName); + if (isAppOpModeCachingEnabled(op)) { + mode = sAppOpModeCache.query( + new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null, + "checkOpNoThrow")); + if (mode == MODE_FOREGROUND) { + // We only cache raw mode. If the mode is FOREGROUND, we need another binder + // call to fetch translated value based on the process state. + mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); + } } else { mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId); } - - return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode; + return mode; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index c4a6decd982f..aac963ade726 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -364,8 +364,9 @@ public class TaskInfo { // Do nothing } - private TaskInfo(Parcel source) { - readFromParcel(source); + /** @hide */ + public TaskInfo(Parcel source) { + readTaskFromParcel(source); } /** @@ -524,7 +525,7 @@ public class TaskInfo { /** * Reads the TaskInfo from a parcel. */ - void readFromParcel(Parcel source) { + void readTaskFromParcel(Parcel source) { userId = source.readInt(); taskId = source.readInt(); effectiveUid = source.readInt(); @@ -577,8 +578,9 @@ public class TaskInfo { /** * Writes the TaskInfo to a parcel. + * @hide */ - void writeToParcel(Parcel dest, int flags) { + public void writeTaskToParcel(Parcel dest, int flags) { dest.writeInt(userId); dest.writeInt(taskId); dest.writeInt(effectiveUid); diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig index e98fc0c9d02c..26ecbd1982d5 100644 --- a/core/java/android/content/res/flags.aconfig +++ b/core/java/android/content/res/flags.aconfig @@ -83,3 +83,15 @@ flag { bug: "364035303" } +flag { + name: "system_context_handle_app_info_changed" + is_exported: true + namespace: "resource_manager" + description: "Feature flag for allowing system context to handle application info changes" + bug: "362420029" + # This flag is read at boot time. + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig index 2d18d2672dce..d43a66904af8 100644 --- a/core/java/android/database/sqlite/flags.aconfig +++ b/core/java/android/database/sqlite/flags.aconfig @@ -2,13 +2,6 @@ package: "android.database.sqlite" container: "system" flag { - name: "oneway_finalizer_close" - namespace: "system_performance" - description: "Make BuildCursorNative.close oneway if in the the finalizer" - bug: "368221351" -} - -flag { name: "oneway_finalizer_close_fixed" namespace: "system_performance" is_fixed_read_only: true diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index c4566981d3b5..96f6ad117035 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -30,6 +30,7 @@ import static com.android.hardware.input.Flags.mouseSwapPrimaryButton; import static com.android.hardware.input.Flags.touchpadTapDragging; import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut; import static com.android.hardware.input.Flags.touchpadVisualizer; +import static com.android.hardware.input.Flags.useKeyGestureEventHandler; import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS; import static com.android.input.flags.Flags.enableInputFilterRustImpl; import static com.android.input.flags.Flags.keyboardRepeatKeys; @@ -386,7 +387,7 @@ public class InputSettings { * @hide */ public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() { - return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut(); + return isCustomizableInputGesturesFeatureFlagEnabled() && touchpadThreeFingerTapShortcut(); } /** @@ -1132,4 +1133,18 @@ public class InputSettings { Settings.Secure.KEY_REPEAT_DELAY_MS, delayTimeMillis, UserHandle.USER_CURRENT); } + + /** + * Whether "Customizable key gestures" feature flag is enabled. + * + * <p> + * ‘Customizable key gestures’ is a feature which allows users to customize key based + * shortcuts on the physical keyboard. + * </p> + * + * @hide + */ + public static boolean isCustomizableInputGesturesFeatureFlagEnabled() { + return enableCustomizableInputGestures() && useKeyGestureEventHandler(); + } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8c3f0ef08039..ae8366817f2b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -55,6 +55,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER; import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED; import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; +import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.predictiveBackIme; @@ -4392,6 +4393,39 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Called when the requested visibility of a custom IME Switcher button changes. + * + * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher + * button inside this bar. However, the IME can request hiding the bar provided by the system + * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides + * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful, + * then it becomes the IME's responsibility to provide a custom IME Switcher button in its + * input view, with equivalent functionality.</p> + * + * <p>This custom button is only requested to be visible when the system provides the IME + * navigation bar, both the bar and the IME Switcher button inside it should be visible, + * but the IME successfully requested to hide the bar. This does not depend on the current + * visibility of the IME. It could be called with {@code true} while the IME is hidden, in + * which case the IME should prepare to show the button as soon as the IME itself is shown.</p> + * + * <p>This is only called when the requested visibility changes. The default value is + * {@code false} and as such, this will not be called initially if the resulting value is + * {@code false}.</p> + * + * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently + * visible. However, this is not guaranteed to be called before the IME is shown, as it depends + * on when the IME requested hiding the IME navigation bar. If the request is sent during + * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after + * {@link #onWindowShown}, but before the first IME frame is drawn.</p> + * + * @param visible whether the button is requested visible or not. + */ + @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API) + public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) { + // Intentionally empty + } + + /** * Called when the IME switch button was clicked from the client. Depending on the number of * enabled IME subtypes, this will either switch to the next IME/subtype, or show the input * method picker dialog. diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java index b08454dd7f8f..38be8d9f772d 100644 --- a/core/java/android/inputmethodservice/NavigationBarController.java +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -41,6 +41,7 @@ import android.view.WindowInsets; import android.view.WindowInsetsController.Appearance; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.FrameLayout; @@ -178,6 +179,9 @@ final class NavigationBarController { private boolean mDrawLegacyNavigationBarBackground; + /** Whether a custom IME Switcher button should be visible. */ + private boolean mCustomImeSwitcherVisible; + private final Rect mTempRect = new Rect(); private final int[] mTempPos = new int[2]; @@ -265,6 +269,7 @@ final class NavigationBarController { // IME navigation bar. boolean visible = insets.isVisible(captionBar()); mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE); + checkCustomImeSwitcherVisibility(); } return view.onApplyWindowInsets(insets); }); @@ -491,6 +496,8 @@ final class NavigationBarController { mShouldShowImeSwitcherWhenImeIsShown; mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown; + checkCustomImeSwitcherVisibility(); + mService.mWindow.getWindow().getDecorView().getWindowInsetsController() .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar)); @@ -616,12 +623,33 @@ final class NavigationBarController { && mNavigationBarFrame.getVisibility() == View.VISIBLE; } + /** + * Checks if a custom IME Switcher button should be visible, and notifies the IME when this + * state changes. This can only be {@code true} if three conditions are met: + * + * <li>The IME should draw the IME navigation bar.</li> + * <li>The IME Switcher button should be visible when the IME is visible.</li> + * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li> + */ + private void checkCustomImeSwitcherVisibility() { + if (!Flags.imeSwitcherRevampApi()) { + return; + } + final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown + && mNavigationBarFrame != null && !isShown(); + if (visible != mCustomImeSwitcherVisible) { + mCustomImeSwitcherVisible = visible; + mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible); + } + } + @Override public String toDebugString() { return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar + " mNavigationBarFrame=" + mNavigationBarFrame + " mShouldShowImeSwitcherWhenImeIsShown=" + mShouldShowImeSwitcherWhenImeIsShown + + " mCustomImeSwitcherVisible=" + mCustomImeSwitcherVisible + " mAppearance=0x" + Integer.toHexString(mAppearance) + " mDarkIntensity=" + mDarkIntensity + " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index 6a4932211f27..dce4d11aaef3 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -332,3 +332,11 @@ flag { description: "Enables ExtServices to leverage TextClassifier for OTP detection" bug: "351976749" } + +flag { + name: "health_connect_backup_restore_permission_enabled" + is_fixed_read_only: true + namespace: "health_connect" + description: "This flag protects the permission that is required to call Health Connect backup and restore apis" + bug: "376014879" # android_fr bug +} diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS index 0809de25b45c..ce79f5d0c669 100644 --- a/core/java/android/print/OWNERS +++ b/core/java/android/print/OWNERS @@ -2,3 +2,4 @@ anothermark@google.com kumarashishg@google.com +bmgordon@google.com diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS index 0809de25b45c..ce79f5d0c669 100644 --- a/core/java/android/printservice/OWNERS +++ b/core/java/android/printservice/OWNERS @@ -2,3 +2,4 @@ anothermark@google.com kumarashishg@google.com +bmgordon@google.com diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index be91cfb9ebf8..a67ae7c96a54 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -17,8 +17,10 @@ package android.view.inputmethod; import android.annotation.AnyThread; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; @@ -87,8 +89,17 @@ public final class InputMethodSubtype implements Parcelable { private final boolean mIsAsciiCapable; private final int mSubtypeHashCode; private final int mSubtypeIconResId; + /** The subtype name resource identifier. */ private final int mSubtypeNameResId; + /** The untranslatable name of the subtype. */ + @NonNull private final CharSequence mSubtypeNameOverride; + /** The layout label string resource identifier. */ + @StringRes + private final int mLayoutLabelResId; + /** The non-localized layout label. */ + @NonNull + private final CharSequence mLayoutLabelNonLocalized; private final String mPkLanguageTag; private final String mPkLayoutType; private final int mSubtypeId; @@ -176,6 +187,7 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameResId = subtypeNameResId; return this; } + /** The subtype name resource identifier. */ private int mSubtypeNameResId = 0; /** @@ -191,9 +203,56 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameOverride = nameOverride; return this; } + /** The untranslatable name of the subtype. */ + @NonNull private CharSequence mSubtypeNameOverride = ""; /** + * Sets the layout label string resource identifier. + * + * @param layoutLabelResId the layout label string resource identifier. + * + * @see #getLayoutDisplayName + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public InputMethodSubtypeBuilder setLayoutLabelResource( + @StringRes int layoutLabelResId) { + if (!Flags.imeSwitcherRevampApi()) { + return this; + } + mLayoutLabelResId = layoutLabelResId; + return this; + } + /** The layout label string resource identifier. */ + @StringRes + private int mLayoutLabelResId = 0; + + /** + * Sets the non-localized layout label. This is used as the layout display name if the + * {@link #getLayoutLabelResource layoutLabelResource} is not set ({@code 0}). + * + * @param layoutLabelNonLocalized the non-localized layout label. + * + * @see #getLayoutDisplayName + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public InputMethodSubtypeBuilder setLayoutLabelNonLocalized( + @NonNull CharSequence layoutLabelNonLocalized) { + if (!Flags.imeSwitcherRevampApi()) { + return this; + } + Objects.requireNonNull(layoutLabelNonLocalized, + "layoutLabelNonLocalized cannot be null"); + mLayoutLabelNonLocalized = layoutLabelNonLocalized; + return this; + } + /** The non-localized layout label. */ + @NonNull + private CharSequence mLayoutLabelNonLocalized = ""; + + /** * Sets the physical keyboard hint information, such as language and layout. * * The system can use the hint information to automatically configure the physical keyboard @@ -350,6 +409,8 @@ public final class InputMethodSubtype implements Parcelable { private InputMethodSubtype(InputMethodSubtypeBuilder builder) { mSubtypeNameResId = builder.mSubtypeNameResId; mSubtypeNameOverride = builder.mSubtypeNameOverride; + mLayoutLabelResId = builder.mLayoutLabelResId; + mLayoutLabelNonLocalized = builder.mLayoutLabelNonLocalized; mPkLanguageTag = builder.mPkLanguageTag; mPkLayoutType = builder.mPkLayoutType; mSubtypeIconResId = builder.mSubtypeIconResId; @@ -376,6 +437,9 @@ public final class InputMethodSubtype implements Parcelable { mSubtypeNameResId = source.readInt(); CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); mSubtypeNameOverride = cs != null ? cs : ""; + mLayoutLabelResId = source.readInt(); + cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + mLayoutLabelNonLocalized = cs != null ? cs : ""; s = source.readString8(); mPkLanguageTag = s != null ? s : ""; s = source.readString8(); @@ -412,6 +476,24 @@ public final class InputMethodSubtype implements Parcelable { } /** + * Returns the layout label string resource identifier. + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @StringRes + public int getLayoutLabelResource() { + return mLayoutLabelResId; + } + + /** + * Returns the non-localized layout label. + */ + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + @NonNull + public CharSequence getLayoutLabelNonLocalized() { + return mLayoutLabelNonLocalized; + } + + /** * Returns the physical keyboard BCP-47 language tag. * * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag @@ -643,9 +725,47 @@ public final class InputMethodSubtype implements Parcelable { try { return String.format(subtypeNameString, replacementString); } catch (IllegalFormatException e) { - Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e); + Slog.w(TAG, "Found illegal format in subtype name(" + subtypeName + "): " + e); + return ""; + } + } + + /** + * Returns the layout display name. + * + * <p>If {@code layoutLabelResource} is non-zero (specified through + * {@link InputMethodSubtypeBuilder#setLayoutLabelResource setLayoutLabelResource}), the + * text generated from that resource will be returned. The localized string resource of the + * label should be capitalized for inclusion in UI lists. + * + * <p>If {@code layoutLabelResource} is zero, the framework returns the non-localized + * layout label, if specified through + * {@link InputMethodSubtypeBuilder#setLayoutLabelNonLocalized setLayoutLabelNonLocalized}. + * + * @param context The context used for getting the + * {@link android.content.pm.PackageManager PackageManager}. + * @param imeAppInfo The {@link ApplicationInfo} of the input method. + * @return the layout display name. + */ + @NonNull + @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API) + public CharSequence getLayoutDisplayName(@NonNull Context context, + @NonNull ApplicationInfo imeAppInfo) { + if (!Flags.imeSwitcherRevampApi()) { + return ""; + } + Objects.requireNonNull(context, "context cannot be null"); + Objects.requireNonNull(imeAppInfo, "imeAppInfo cannot be null"); + if (mLayoutLabelResId == 0) { + return mLayoutLabelNonLocalized; + } + + final CharSequence subtypeLayoutName = context.getPackageManager().getText( + imeAppInfo.packageName, mLayoutLabelResId, imeAppInfo); + if (TextUtils.isEmpty(subtypeLayoutName)) { return ""; } + return subtypeLayoutName; } @Nullable @@ -778,6 +898,8 @@ public final class InputMethodSubtype implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(mSubtypeNameResId); TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags); + dest.writeInt(mLayoutLabelResId); + TextUtils.writeToParcel(mLayoutLabelNonLocalized, dest, parcelableFlags); dest.writeString8(mPkLanguageTag); dest.writeString8(mPkLayoutType); dest.writeInt(mSubtypeIconResId); @@ -794,6 +916,7 @@ public final class InputMethodSubtype implements Parcelable { void dump(@NonNull Printer pw, @NonNull String prefix) { pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride + + " mLayoutLabelNonLocalized=" + mLayoutLabelNonLocalized + " mPkLanguageTag=" + mPkLanguageTag + " mPkLayoutType=" + mPkLayoutType + " mSubtypeId=" + mSubtypeId diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml index f8710cc45358..7b241aff3fb1 100644 --- a/core/res/res/layout/input_method_switch_item_new.xml +++ b/core/res/res/layout/input_method_switch_item_new.xml @@ -18,18 +18,20 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/list_item" android:layout_width="match_parent" - android:layout_height="72dp" + android:layout_height="wrap_content" + android:minHeight="72dp" android:background="@drawable/input_method_switch_item_background" android:gravity="center_vertical" android:orientation="horizontal" android:layout_marginHorizontal="16dp" android:layout_marginBottom="8dp" android:paddingStart="20dp" - android:paddingEnd="24dp"> + android:paddingEnd="24dp" + android:paddingVertical="8dp"> <LinearLayout android:layout_width="0dp" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:layout_weight="1" android:gravity="start|center_vertical" android:orientation="vertical"> @@ -39,11 +41,26 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="marquee" + android:marqueeRepeatLimit="1" android:singleLine="true" android:fontFamily="google-sans-text" android:textColor="@color/input_method_switch_on_item" android:textAppearance="?attr/textAppearanceListItem"/> + <TextView + android:id="@+id/text2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ellipsize="marquee" + android:marqueeRepeatLimit="1" + android:singleLine="true" + android:fontFamily="google-sans-text" + android:textColor="?attr/materialColorOnSurfaceVariant" + android:textAppearance="?attr/textAppearanceListItemSecondary" + android:textAllCaps="true" + android:visibility="gone"/> + </LinearLayout> <ImageView diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 092d2a72580a..e6dedce8feaf 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4418,6 +4418,10 @@ <declare-styleable name="InputMethod_Subtype"> <!-- The name of the subtype. --> <attr name="label" /> + <!-- The layout label of the subtype. + {@link android.view.inputmethod.InputMethodSubtype#getLayoutDisplayName} returns the + value specified in this attribute. --> + <attr name="layoutLabel" format="reference" /> <!-- The icon of the subtype. --> <attr name="icon" /> <!-- The locale of the subtype. This string should be a locale (for example en_US and fr_FR) diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 0c28ea406aa2..70cc5f14391d 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -125,6 +125,8 @@ <public name="supplementalDescription"/> <!-- @FlaggedApi("android.security.enable_intent_matching_flags") --> <public name="intentMatchingFlags"/> + <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) --> + <public name="layoutLabel"/> </staging-public-group> <staging-public-group type="id" first-id="0x01b60000"> diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java index 24f6ceaf786c..8d045f87063b 100644 --- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java +++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java @@ -26,6 +26,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -59,6 +60,7 @@ import android.app.servertransaction.ResumeActivityItem; import android.app.servertransaction.StopActivityItem; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; @@ -67,7 +69,11 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.os.Bundle; import android.os.IBinder; +import android.os.Looper; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import android.util.DisplayMetrics; import android.util.Log; @@ -129,6 +135,9 @@ public class ActivityThreadTest { @Rule(order = 1) public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private ActivityWindowInfoListener mActivityWindowInfoListener; private WindowTokenClientController mOriginalWindowTokenClientController; private Configuration mOriginalAppConfig; @@ -912,6 +921,32 @@ public class ActivityThreadTest { } /** + * Verifies that {@link ActivityThread#handleApplicationInfoChanged} does send updates to the + * system context, when given the system application info. + */ + @RequiresFlagsEnabled(android.content.res.Flags.FLAG_SYSTEM_CONTEXT_HANDLE_APP_INFO_CHANGED) + @Test + public void testHandleApplicationInfoChanged_systemContext() { + Looper.prepare(); + final var systemThread = ActivityThread.createSystemActivityThreadForTesting(); + + final Context systemContext = systemThread.getSystemContext(); + final var appInfo = systemContext.getApplicationInfo(); + // sourceDir must not be null, and contain at least a '/', for handleApplicationInfoChanged. + appInfo.sourceDir = "/"; + + // Create a copy of the application info. + final var newAppInfo = new ApplicationInfo(appInfo); + newAppInfo.sourceDir = "/"; + assertWithMessage("New application info is a separate instance") + .that(systemContext.getApplicationInfo()).isNotSameInstanceAs(newAppInfo); + + systemThread.handleApplicationInfoChanged(newAppInfo); + assertWithMessage("Application info was updated successfully") + .that(systemContext.getApplicationInfo()).isSameInstanceAs(newAppInfo); + } + + /** * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord, * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the * activity for the given sequence number. diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl index e21bf8fb723c..93e635dd937c 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl @@ -16,4 +16,4 @@ package com.android.wm.shell.shared; -parcelable GroupedRecentTaskInfo;
\ No newline at end of file +parcelable GroupedTaskInfo;
\ No newline at end of file diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java index 65e079ef4f72..03e0ab0591a1 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java @@ -17,7 +17,8 @@ package com.android.wm.shell.shared; import android.annotation.IntDef; -import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; +import android.app.TaskInfo; import android.app.WindowConfiguration; import android.os.Parcel; import android.os.Parcelable; @@ -27,69 +28,91 @@ import androidx.annotation.Nullable; import com.android.wm.shell.shared.split.SplitBounds; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; /** - * Simple container for recent tasks. May contain either a single or pair of tasks. + * Simple container for recent tasks which should be presented as a single task within the + * Overview UI. */ -public class GroupedRecentTaskInfo implements Parcelable { +public class GroupedTaskInfo implements Parcelable { - public static final int TYPE_SINGLE = 1; + public static final int TYPE_FULLSCREEN = 1; public static final int TYPE_SPLIT = 2; public static final int TYPE_FREEFORM = 3; @IntDef(prefix = {"TYPE_"}, value = { - TYPE_SINGLE, + TYPE_FULLSCREEN, TYPE_SPLIT, TYPE_FREEFORM }) public @interface GroupType {} + /** + * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or + * TYPE_FREEFORM. + */ + @GroupType + protected final int mType; + + /** + * The list of tasks associated with this single recent task info. + * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview + * TYPE_SPLIT: Contains the two split roots of each side + * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode + */ @NonNull - private final ActivityManager.RecentTaskInfo[] mTasks; + protected final List<TaskInfo> mTasks; + + /** + * Only set for TYPE_SPLIT. + * + * Information about the split bounds. + */ @Nullable - private final SplitBounds mSplitBounds; - @GroupType - private final int mType; - // TODO(b/348332802): move isMinimized inside each Task object instead once we have a - // replacement for RecentTaskInfo - private final int[] mMinimizedTaskIds; + protected final SplitBounds mSplitBounds; /** - * Create new for a single task + * Only set for TYPE_FREEFORM. + * + * TODO(b/348332802): move isMinimized inside each Task object instead once we have a + * replacement for RecentTaskInfo */ - public static GroupedRecentTaskInfo forSingleTask( - @NonNull ActivityManager.RecentTaskInfo task) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null, - TYPE_SINGLE, null /* minimizedFreeformTasks */); + @Nullable + protected final int[] mMinimizedTaskIds; + + /** + * Create new for a stack of fullscreen tasks + */ + public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) { + return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN, + null /* minimizedFreeformTasks */); } /** * Create new for a pair of tasks in split screen */ - public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1, - @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) { - return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2}, - splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */); + public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1, + @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) { + return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT, + null /* minimizedFreeformTasks */); } /** * Create new for a group of freeform tasks */ - public static GroupedRecentTaskInfo forFreeformTasks( - @NonNull ActivityManager.RecentTaskInfo[] tasks, - @NonNull Set<Integer> minimizedFreeformTasks) { - return new GroupedRecentTaskInfo( - tasks, - null /* splitBounds */, - TYPE_FREEFORM, + public static GroupedTaskInfo forFreeformTasks( + @NonNull List<TaskInfo> tasks, + @NonNull Set<Integer> minimizedFreeformTasks) { + return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM, minimizedFreeformTasks.stream().mapToInt(i -> i).toArray()); } - private GroupedRecentTaskInfo( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private GroupedTaskInfo( + @NonNull List<TaskInfo> tasks, @Nullable SplitBounds splitBounds, @GroupType int type, @Nullable int[] minimizedFreeformTaskIds) { @@ -100,52 +123,56 @@ public class GroupedRecentTaskInfo implements Parcelable { ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds); } - private static void ensureAllMinimizedIdsPresent( - @NonNull ActivityManager.RecentTaskInfo[] tasks, + private void ensureAllMinimizedIdsPresent( + @NonNull List<TaskInfo> tasks, @Nullable int[] minimizedFreeformTaskIds) { if (minimizedFreeformTaskIds == null) { return; } if (!Arrays.stream(minimizedFreeformTaskIds).allMatch( - taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) { + taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) { throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID."); } } - GroupedRecentTaskInfo(Parcel parcel) { - mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR); + protected GroupedTaskInfo(@NonNull Parcel parcel) { + mTasks = new ArrayList(); + final int numTasks = parcel.readInt(); + for (int i = 0; i < numTasks; i++) { + mTasks.add(new TaskInfo(parcel)); + } mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR); mType = parcel.readInt(); mMinimizedTaskIds = parcel.createIntArray(); } /** - * Get primary {@link ActivityManager.RecentTaskInfo} + * Get primary {@link RecentTaskInfo} */ @NonNull - public ActivityManager.RecentTaskInfo getTaskInfo1() { - return mTasks[0]; + public TaskInfo getTaskInfo1() { + return mTasks.getFirst(); } /** - * Get secondary {@link ActivityManager.RecentTaskInfo}. + * Get secondary {@link RecentTaskInfo}. * * Used in split screen. */ @Nullable - public ActivityManager.RecentTaskInfo getTaskInfo2() { - if (mTasks.length > 1) { - return mTasks[1]; + public TaskInfo getTaskInfo2() { + if (mTasks.size() > 1) { + return mTasks.get(1); } return null; } /** - * Get all {@link ActivityManager.RecentTaskInfo}s grouped together. + * Get all {@link RecentTaskInfo}s grouped together. */ @NonNull - public List<ActivityManager.RecentTaskInfo> getTaskInfoList() { - return Arrays.asList(mTasks); + public List<TaskInfo> getTaskInfoList() { + return mTasks; } /** @@ -164,28 +191,46 @@ public class GroupedRecentTaskInfo implements Parcelable { return mType; } + @Nullable public int[] getMinimizedTaskIds() { return mMinimizedTaskIds; } @Override + public boolean equals(Object obj) { + if (!(obj instanceof GroupedTaskInfo)) { + return false; + } + GroupedTaskInfo other = (GroupedTaskInfo) obj; + return mType == other.mType + && Objects.equals(mTasks, other.mTasks) + && Objects.equals(mSplitBounds, other.mSplitBounds) + && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds); + } + + @Override + public int hashCode() { + return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds)); + } + + @Override public String toString() { StringBuilder taskString = new StringBuilder(); - for (int i = 0; i < mTasks.length; i++) { + for (int i = 0; i < mTasks.size(); i++) { if (i == 0) { taskString.append("Task"); } else { taskString.append(", Task"); } - taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i])); + taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i))); } if (mSplitBounds != null) { taskString.append(", SplitBounds: ").append(mSplitBounds); } taskString.append(", Type="); switch (mType) { - case TYPE_SINGLE: - taskString.append("TYPE_SINGLE"); + case TYPE_FULLSCREEN: + taskString.append("TYPE_FULLSCREEN"); break; case TYPE_SPLIT: taskString.append("TYPE_SPLIT"); @@ -199,7 +244,7 @@ public class GroupedRecentTaskInfo implements Parcelable { return taskString.toString(); } - private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) { + private String getTaskInfo(TaskInfo taskInfo) { if (taskInfo == null) { return null; } @@ -213,7 +258,12 @@ public class GroupedRecentTaskInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeTypedArray(mTasks, flags); + // We don't use the parcel list methods because we want to only write the TaskInfo state + // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated + parcel.writeInt(mTasks.size()); + for (int i = 0; i < mTasks.size(); i++) { + mTasks.get(i).writeTaskToParcel(parcel, flags); + } parcel.writeTypedObject(mSplitBounds, flags); parcel.writeInt(mType); parcel.writeIntArray(mMinimizedTaskIds); @@ -224,13 +274,15 @@ public class GroupedRecentTaskInfo implements Parcelable { return 0; } - public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR = - new Creator<GroupedRecentTaskInfo>() { - public GroupedRecentTaskInfo createFromParcel(Parcel source) { - return new GroupedRecentTaskInfo(source); + public static final Creator<GroupedTaskInfo> CREATOR = new Creator() { + @Override + public GroupedTaskInfo createFromParcel(Parcel in) { + return new GroupedTaskInfo(in); } - public GroupedRecentTaskInfo[] newArray(int size) { - return new GroupedRecentTaskInfo[size]; + + @Override + public GroupedTaskInfo[] newArray(int size) { + return new GroupedTaskInfo[size]; } }; -}
\ No newline at end of file +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 886330f3264a..0200e18b5c50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -286,7 +286,7 @@ public class CompatUIController implements OnDisplaysChangedListener, // we need to ignore all the incoming TaskInfo until the education // completes. If we come from a double tap we follow the normal flow. final boolean topActivityPillarboxed = - taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + taskInfo.appCompatTaskInfo.isTopActivityPillarboxShaped(); final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 706a67821a30..a472f79c98e6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -779,7 +779,8 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler, ReturnToDragStartAnimator returnToDragStartAnimator, - @DynamicOverride DesktopRepository desktopRepository) { + @DynamicOverride DesktopRepository desktopRepository, + DesktopModeEventLogger desktopModeEventLogger) { return new DesktopTilingDecorViewModel( context, displayController, @@ -789,7 +790,8 @@ public abstract class WMShellModule { shellTaskOrganizer, toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, - desktopRepository + desktopRepository, + desktopModeEventLogger ); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt index 48bb2a8b4a74..cefcb757690f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt @@ -205,6 +205,11 @@ class DesktopMixedTransitionHandler( finishTransaction: SurfaceControl.Transaction, finishCallback: TransitionFinishCallback, ): Boolean { + val launchChange = findDesktopTaskChange(info, pending.launchingTask) + if (launchChange == null) { + logV("No launch Change, returning") + return false + } // Check if there's also an immersive change during this launch. val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask -> findDesktopTaskChange(info, exitingTask) @@ -212,8 +217,6 @@ class DesktopMixedTransitionHandler( val minimizeChange = pending.minimizingTask?.let { minimizingTask -> findDesktopTaskChange(info, minimizingTask) } - val launchChange = findDesktopTaskChange(info, pending.launchingTask) - ?: error("Should have pending launching task change") var subAnimationCount = -1 var combinedWct: WindowContainerTransaction? = null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt index edcc877ef58e..c7cf31081c8b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt @@ -275,7 +275,7 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean { } // Then check if the activity is portrait when letterboxed - appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed + appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped // Then check if the activity is portrait appBounds != null -> appBounds.height() > appBounds.width() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 9a93371621c9..d3f537b8f904 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -376,18 +376,20 @@ public class PipController implements ConfigurationChangeListener, private void setLauncherKeepClearAreaHeight(boolean visible, int height) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height); - if (visible) { - Rect rect = new Rect( - 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, - mPipDisplayLayoutState.getDisplayBounds().right, - mPipDisplayLayoutState.getDisplayBounds().bottom); - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); - } else { - mPipBoundsState.setNamedUnrestrictedKeepClearArea( - PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); - } - mPipTouchHandler.onShelfVisibilityChanged(visible, height); + mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { + if (visible) { + Rect rect = new Rect( + 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height, + mPipDisplayLayoutState.getDisplayBounds().right, + mPipDisplayLayoutState.getDisplayBounds().bottom); + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect); + } else { + mPipBoundsState.setNamedUnrestrictedKeepClearArea( + PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null); + } + mPipTouchHandler.onShelfVisibilityChanged(visible, height); + }); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index 19d293e829ad..9af1aba51c57 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -231,17 +231,15 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // KCA triggered movement to wait for other transitions (e.g. due to IME changes). return; } - mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> { - boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() - || mPipBoundsState.hasUserResizedPip()); - int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top - - mPipBoundsState.getBounds().top; + boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip() + || mPipBoundsState.hasUserResizedPip()); + int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top + - mPipBoundsState.getBounds().top; - if (!mIsImeShowing && !hasUserInteracted && delta != 0) { - // If the user hasn't interacted with PiP, we respect the keep clear areas - mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); - } - }); + if (!mIsImeShowing && !hasUserInteracted && delta != 0) { + // If the user hasn't interacted with PiP, we respect the keep clear areas + mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta); + } }; if (PipUtils.isPip2ExperimentEnabled()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index 799028a5507a..4a301cc0b603 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -24,7 +24,7 @@ import android.os.Bundle; import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.recents.IRecentTasksListener; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; /** * Interface that is exposed to remote callers to fetch recent tasks. @@ -44,7 +44,7 @@ interface IRecentTasks { /** * Gets the set of recent tasks. */ - GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; + GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3; /** * Gets the set of running tasks. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl index 371bdd5c6469..b58f0681c571 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl @@ -18,6 +18,8 @@ package com.android.wm.shell.recents; import android.app.ActivityManager.RunningTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; + /** * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks. */ @@ -44,8 +46,8 @@ oneway interface IRecentTasksListener { void onRunningTaskChanged(in RunningTaskInfo taskInfo); /** A task has moved to front. */ - oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo); + void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks); /** A task info has changed. */ - oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo); + void onTaskInfoChanged(in RunningTaskInfo taskInfo); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index 8c5d1e7e069d..364a087211c5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -19,7 +19,7 @@ package com.android.wm.shell.recents; import android.annotation.Nullable; import android.graphics.Color; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import java.util.List; @@ -35,7 +35,7 @@ public interface RecentTasks { * Gets the set of recent tasks. */ default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index faa20159f64a..9911669d2cb8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,10 +24,12 @@ import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_R import android.Manifest; import android.annotation.RequiresPermission; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.KeyguardManager; import android.app.PendingIntent; +import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -55,7 +57,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -379,7 +381,8 @@ public class RecentTasksController implements TaskStackListenerCallback, return; } try { - mListener.onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask }); } catch (RemoteException e) { Slog.w(TAG, "Failed call onTaskMovedToFront", e); } @@ -407,27 +410,27 @@ public class RecentTasksController implements TaskStackListenerCallback, } @VisibleForTesting - ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { + ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is from the most-recent to least-recent order - final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( + final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks( maxNum, flags, userId); // Make a mapping of task id -> task info - final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>(); + final SparseArray<TaskInfo> rawMapping = new SparseArray<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final TaskInfo taskInfo = rawList.get(i); rawMapping.put(taskInfo.taskId, taskInfo); } - ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>(); + ArrayList<TaskInfo> freeformTasks = new ArrayList<>(); Set<Integer> minimizedFreeformTasks = new HashSet<>(); int mostRecentFreeformTaskIndex = Integer.MAX_VALUE; // Pull out the pairs as we iterate back in the list - ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>(); + ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>(); for (int i = 0; i < rawList.size(); i++) { - final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i); + final RecentTaskInfo taskInfo = rawList.get(i); if (!rawMapping.contains(taskInfo.taskId)) { // If it's not in the mapping, then it was already paired with another task continue; @@ -460,20 +463,20 @@ public class RecentTasksController implements TaskStackListenerCallback, final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID); if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains( pairedTaskId)) { - final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); + final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId); rawMapping.remove(pairedTaskId); - recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, + recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo, mTaskSplitBoundsMap.get(pairedTaskId))); } else { - recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo)); + recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo)); } } // Add a special entry for freeform tasks if (!freeformTasks.isEmpty()) { recentTasks.add(mostRecentFreeformTaskIndex, - GroupedRecentTaskInfo.forFreeformTasks( - freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]), + GroupedTaskInfo.forFreeformTasks( + freeformTasks, minimizedFreeformTasks)); } @@ -514,16 +517,16 @@ public class RecentTasksController implements TaskStackListenerCallback, * {@param ignoreTaskToken} if it is non-null. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName, + public RecentTaskInfo findTaskInBackground(ComponentName componentName, int userId, @Nullable WindowContainerToken ignoreTaskToken) { if (componentName == null) { return null; } - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -541,12 +544,12 @@ public class RecentTasksController implements TaskStackListenerCallback, * Find the background task that match the given taskId. */ @Nullable - public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) { - List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + public RecentTaskInfo findTaskInBackground(int taskId) { + List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < tasks.size(); i++) { - final ActivityManager.RecentTaskInfo task = tasks.get(i); + final RecentTaskInfo task = tasks.get(i); if (task.isVisible) { continue; } @@ -570,7 +573,7 @@ public class RecentTasksController implements TaskStackListenerCallback, pw.println(prefix + TAG); pw.println(prefix + " mListener=" + mListener); pw.println(prefix + "Tasks:"); - ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, + ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser()); for (int i = 0; i < recentTasks.size(); i++) { pw.println(innerPrefix + recentTasks.get(i)); @@ -584,9 +587,9 @@ public class RecentTasksController implements TaskStackListenerCallback, private class RecentTasksImpl implements RecentTasks { @Override public void getRecentTasks(int maxNum, int flags, int userId, Executor executor, - Consumer<List<GroupedRecentTaskInfo>> callback) { + Consumer<List<GroupedTaskInfo>> callback) { mMainExecutor.execute(() -> { - List<GroupedRecentTaskInfo> tasks = + List<GroupedTaskInfo> tasks = RecentTasksController.this.getRecentTasks(maxNum, flags, userId); executor.execute(() -> callback.accept(tasks)); }); @@ -650,7 +653,7 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) { mListener.call(l -> l.onTaskMovedToFront(taskInfo)); } @@ -692,17 +695,20 @@ public class RecentTasksController implements TaskStackListenerCallback, } @Override - public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) + public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) throws RemoteException { if (mController == null) { // The controller is already invalidated -- just return an empty task list for now - return new GroupedRecentTaskInfo[0]; + return new GroupedTaskInfo[0]; } - final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null}; + final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null}; executeRemoteCallWithTaskPermission(mController, "getRecentTasks", - (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId) - .toArray(new GroupedRecentTaskInfo[0]), + (controller) -> { + List<GroupedTaskInfo> tasks = controller.getRecentTasks( + maxNum, flags, userId); + out[0] = tasks.toArray(new GroupedTaskInfo[0]); + }, true /* blocking */); return out[0]; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt index e43c3a613157..61963cde2d06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt @@ -30,6 +30,7 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayChangeController import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -48,6 +49,7 @@ class DesktopTilingDecorViewModel( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, ) : DisplayChangeController.OnDisplayChangingListener { @VisibleForTesting var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>() @@ -80,6 +82,7 @@ class DesktopTilingDecorViewModel( toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, taskRepository, + desktopModeEventLogger, ) tilingTransitionHandlerByDisplayId.put(displayId, newHandler) newHandler diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt index 209eb5e501b2..6cdc517c9cb7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt @@ -23,6 +23,7 @@ import android.graphics.Rect import android.graphics.Region import android.os.Binder import android.view.LayoutInflater +import android.view.MotionEvent import android.view.RoundedCorner import android.view.SurfaceControl import android.view.SurfaceControlViewHost @@ -39,6 +40,7 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER import android.view.WindowlessWindowManager import com.android.wm.shell.R import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import java.util.function.Supplier /** @@ -141,8 +143,9 @@ class DesktopTilingDividerWindowManager( t.setRelativeLayer(leash, relativeLeash, 1) } - override fun onDividerMoveStart(pos: Int) { + override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) { setSlippery(false) + transitionHandler.onDividerHandleDragStart(motionEvent) } /** @@ -161,13 +164,13 @@ class DesktopTilingDividerWindowManager( * Notifies the transition handler of tiling operations ending, which might result in resizing * WindowContainerTransactions if the sizes of the tiled tasks changed. */ - override fun onDividerMovedEnd(pos: Int) { + override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) { setSlippery(true) val t = transactionSupplier.get() t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat()) val dividerWidth = dividerBounds.width() dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom) - transitionHandler.onDividerHandleDragEnd(dividerBounds, t) + transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent) } private fun getWindowManagerParams(): WindowManager.LayoutParams { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt index c46767c3a51d..1c593c0362ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt @@ -25,6 +25,7 @@ import android.graphics.Rect import android.os.IBinder import android.os.UserHandle import android.util.Slog +import android.view.MotionEvent import android.view.SurfaceControl import android.view.SurfaceControl.Transaction import android.view.WindowManager.TRANSIT_CHANGE @@ -44,6 +45,8 @@ import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -70,6 +73,7 @@ class DesktopTilingWindowDecoration( private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler, private val returnToDragStartAnimator: ReturnToDragStartAnimator, private val taskRepository: DesktopRepository, + private val desktopModeEventLogger: DesktopModeEventLogger, private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }, ) : Transitions.TransitionHandler, @@ -218,6 +222,25 @@ class DesktopTilingWindowDecoration( return tilingManager } + fun onDividerHandleDragStart(motionEvent: MotionEvent) { + val leftTiledTask = leftTaskResizingHelper ?: return + val rightTiledTask = rightTaskResizingHelper ?: return + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + displayController + ) + + desktopModeEventLogger.logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + displayController + ) + } + fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean { val leftTiledTask = leftTaskResizingHelper ?: return false val rightTiledTask = rightTaskResizingHelper ?: return false @@ -266,10 +289,32 @@ class DesktopTilingWindowDecoration( return true } - fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) { + fun onDividerHandleDragEnd( + dividerBounds: Rect, + t: SurfaceControl.Transaction, + motionEvent: MotionEvent, + ) { val leftTiledTask = leftTaskResizingHelper ?: return val rightTiledTask = rightTaskResizingHelper ?: return + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + leftTiledTask.taskInfo, + leftTiledTask.newBounds.height(), + leftTiledTask.newBounds.width(), + displayController + ) + + desktopModeEventLogger.logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + rightTiledTask.taskInfo, + rightTiledTask.newBounds.height(), + rightTiledTask.newBounds.width(), + displayController + ) + if (leftTiledTask.newBounds == leftTiledTask.bounds) { leftTiledTask.hideVeil() rightTiledTask.hideVeil() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt index b3b30ad4c09e..9799d01afc9f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt @@ -15,14 +15,16 @@ */ package com.android.wm.shell.windowdecor.tiling +import android.view.MotionEvent + /** Divider move callback to whichever entity that handles the moving logic. */ interface DividerMoveCallback { /** Called on the divider move start gesture. */ - fun onDividerMoveStart(pos: Int) + fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) /** Called on the divider moved by dragging it. */ fun onDividerMove(pos: Int): Boolean /** Called on divider move gesture end. */ - fun onDividerMovedEnd(pos: Int) + fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt index 89229051941c..111e28e450bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt @@ -206,7 +206,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { if (!isWithinHandleRegion(yTouchPosInDivider)) return true - callback.onDividerMoveStart(touchPos) + callback.onDividerMoveStart(touchPos, event) setTouching() canResize = true } @@ -230,7 +230,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion if (!canResize) return true if (moving && resized) { dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos - callback.onDividerMovedEnd(dividerBounds.left) + callback.onDividerMovedEnd(dividerBounds.left, event) } moving = false canResize = false diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt index df061e368071..b06c2dad4ffc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt @@ -447,6 +447,37 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() { @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) + fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() { + val wct = WindowContainerTransaction() + val launchingTask = createTask(WINDOWING_MODE_FREEFORM) + val nonLaunchTaskChange = createChange(createTask(WINDOWING_MODE_FREEFORM)) + val transition = Binder() + whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull())) + .thenReturn(transition) + mixedHandler.addPendingMixedTransition( + PendingMixedTransition.Launch( + transition = transition, + launchingTask = launchingTask.taskId, + minimizingTask = null, + exitingImmersiveTask = null, + ) + ) + + val started = mixedHandler.startAnimation( + transition, + createTransitionInfo( + TRANSIT_OPEN, + listOf(nonLaunchTaskChange) + ), + SurfaceControl.Transaction(), + SurfaceControl.Transaction(), + ) { } + + assertFalse("Should not start animation without launching desktop task", started) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS) fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() { val wct = WindowContainerTransaction() val launchingTask = createTask(WINDOWING_MODE_FREEFORM) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt index 0c100fca2036..2b30bc360d06 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt @@ -17,6 +17,7 @@ package com.android.wm.shell.recents import android.app.ActivityManager +import android.app.TaskInfo import android.graphics.Rect import android.os.Parcel import android.testing.AndroidTestingRunner @@ -24,11 +25,10 @@ import android.window.IWindowContainerToken import android.window.WindowContainerToken import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.shared.GroupedRecentTaskInfo -import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE -import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT +import com.android.wm.shell.shared.GroupedTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN +import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT import com.android.wm.shell.shared.split.SplitBounds import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Correspondence @@ -39,15 +39,15 @@ import org.junit.runner.RunWith import org.mockito.Mockito.mock /** - * Tests for [GroupedRecentTaskInfo] + * Tests for [GroupedTaskInfo] */ @SmallTest @RunWith(AndroidTestingRunner::class) -class GroupedRecentTaskInfoTest : ShellTestCase() { +class GroupedTaskInfoTest : ShellTestCase() { @Test fun testSingleTask_hasCorrectType() { - assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_SINGLE) + assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN) } @Test @@ -117,8 +117,9 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) - assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SINGLE) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) + assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNull() } @@ -130,7 +131,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT) assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1) assertThat(recentTaskInfoParcel.taskInfo2).isNotNull() @@ -146,11 +148,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { recentTaskInfo.writeToParcel(parcel, 0) parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3) // Only compare task ids - val taskIdComparator = Correspondence.transforming<ActivityManager.RecentTaskInfo, Int>( + val taskIdComparator = Correspondence.transforming<TaskInfo, Int>( { it?.taskId }, "has taskId of" ) assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator) @@ -167,7 +170,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { parcel.setDataPosition(0) // Read the object back from the parcel - val recentTaskInfoParcel = CREATOR.createFromParcel(parcel) + val recentTaskInfoParcel: GroupedTaskInfo = + GroupedTaskInfo.CREATOR.createFromParcel(parcel) assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM) assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray()) } @@ -177,24 +181,24 @@ class GroupedRecentTaskInfoTest : ShellTestCase() { token = WindowContainerToken(mock(IWindowContainerToken::class.java)) } - private fun singleTaskGroupInfo(): GroupedRecentTaskInfo { + private fun singleTaskGroupInfo(): GroupedTaskInfo { val task = createTaskInfo(id = 1) - return GroupedRecentTaskInfo.forSingleTask(task) + return GroupedTaskInfo.forFullscreenTasks(task) } - private fun splitTasksGroupInfo(): GroupedRecentTaskInfo { + private fun splitTasksGroupInfo(): GroupedTaskInfo { val task1 = createTaskInfo(id = 1) val task2 = createTaskInfo(id = 2) val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50) - return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds) + return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds) } private fun freeformTasksGroupInfo( freeformTaskIds: Array<Int>, minimizedTaskIds: Array<Int> = emptyArray() - ): GroupedRecentTaskInfo { - return GroupedRecentTaskInfo.forFreeformTasks( - freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(), + ): GroupedTaskInfo { + return GroupedTaskInfo.forFreeformTasks( + freeformTaskIds.map { createTaskInfo(it) }.toList(), minimizedTaskIds.toSet()) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 9b73d53e0639..dede583ca970 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -46,6 +46,7 @@ import static org.mockito.Mockito.when; import static java.lang.Integer.MAX_VALUE; import android.app.ActivityManager; +import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityTaskManager; import android.app.KeyguardManager; import android.content.ComponentName; @@ -71,7 +72,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopRepository; -import com.android.wm.shell.shared.GroupedRecentTaskInfo; +import com.android.wm.shell.shared.GroupedTaskInfo; import com.android.wm.shell.shared.ShellSharedConstants; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.split.SplitBounds; @@ -193,8 +194,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddRemoveSplitNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class)); @@ -207,8 +208,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testAddSameSplitBoundsInfoSkipNotifyChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); setRawList(t1, t2); // Verify only one update if the split info is the same @@ -223,13 +224,13 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, -1, @@ -238,12 +239,12 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_withPairs() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -255,8 +256,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds); mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertGroupedTasksListEquals(recentTasks, t1.taskId, -1, t2.taskId, t4.taskId, @@ -267,14 +268,14 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() { @SuppressWarnings("unchecked") - final List<GroupedRecentTaskInfo>[] recentTasks = new List[1]; - Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument; - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); - ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6); + final List<GroupedTaskInfo>[] recentTasks = new List[1]; + Consumer<List<GroupedTaskInfo>> consumer = argument -> recentTasks[0] = argument; + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t6 = makeTaskInfo(6); setRawList(t1, t2, t3, t4, t5, t6); // Mark a couple pairs [t2, t4], [t3, t5] @@ -287,7 +288,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds); mRecentTasksController.asRecentTasks() - .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer); + .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, + consumer); mMainExecutor.flushAll(); assertGroupedTasksListEquals(recentTasks[0], @@ -299,28 +301,28 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 freeform tasks should be grouped into one, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(t1, freeformGroup.getTaskInfoList().get(0)); @@ -333,11 +335,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); SplitBounds pair1Bounds = @@ -347,19 +349,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(3)).thenReturn(true); when(mDesktopRepository.isActiveTask(5)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo splitGroup = recentTasks.get(0); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup = recentTasks.get(2); + GroupedTaskInfo splitGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(1); + GroupedTaskInfo singleGroup = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType()); // Check freeform group entries assertEquals(t3, freeformGroup.getTaskInfoList().get(0)); @@ -378,24 +380,24 @@ public class RecentTasksControllerTest extends ShellTestCase { ExtendedMockito.doReturn(false) .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); setRawList(t1, t2, t3, t4); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // Expect no grouping of tasks assertEquals(4, recentTasks.size()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType()); assertEquals(t1, recentTasks.get(0).getTaskInfo1()); assertEquals(t2, recentTasks.get(1).getTaskInfo1()); @@ -405,11 +407,11 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); - ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4); - ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t4 = makeTaskInfo(4); + RecentTaskInfo t5 = makeTaskInfo(5); setRawList(t1, t2, t3, t4, t5); when(mDesktopRepository.isActiveTask(1)).thenReturn(true); @@ -417,19 +419,19 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(5)).thenReturn(true); when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); // 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries assertEquals(3, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); - GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1); - GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2); + GroupedTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo singleGroup1 = recentTasks.get(1); + GroupedTaskInfo singleGroup2 = recentTasks.get(2); // Check that groups have expected types - assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType()); - assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType()); + assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType()); + assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType()); // Check freeform group entries assertEquals(3, freeformGroup.getTaskInfoList().size()); @@ -445,8 +447,8 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test @EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE) public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400); t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450); @@ -455,11 +457,11 @@ public class RecentTasksControllerTest extends ShellTestCase { when(mDesktopRepository.isActiveTask(1)).thenReturn(true); when(mDesktopRepository.isActiveTask(2)).thenReturn(true); - ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks( - MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); + ArrayList<GroupedTaskInfo> recentTasks = + mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0); assertEquals(1, recentTasks.size()); - GroupedRecentTaskInfo freeformGroup = recentTasks.get(0); + GroupedTaskInfo freeformGroup = recentTasks.get(0); // Check bounds assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get( @@ -478,9 +480,9 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testRemovedTaskRemovesSplit() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); - ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); - ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); + RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t2 = makeTaskInfo(2); + RecentTaskInfo t3 = makeTaskInfo(3); setRawList(t1, t2, t3); // Add a pair @@ -500,7 +502,7 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testTaskWindowingModeChangedNotifiesChange() { - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); + RecentTaskInfo t1 = makeTaskInfo(1); setRawList(t1); // Remove one of the tasks and ensure the pair is removed @@ -607,7 +609,8 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo); - verify(mRecentTasksListener).onTaskMovedToFront(taskInfo); + GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo); + verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask })); } @Test @@ -656,8 +659,8 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to create a task with a given task id. */ - private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) { - ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo(); + private RecentTaskInfo makeTaskInfo(int taskId) { + RecentTaskInfo info = new RecentTaskInfo(); info.taskId = taskId; info.lastNonFullscreenBounds = new Rect(); return info; @@ -676,10 +679,10 @@ public class RecentTasksControllerTest extends ShellTestCase { /** * Helper to set the raw task list on the controller. */ - private ArrayList<ActivityManager.RecentTaskInfo> setRawList( - ActivityManager.RecentTaskInfo... tasks) { - ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>(); - for (ActivityManager.RecentTaskInfo task : tasks) { + private ArrayList<RecentTaskInfo> setRawList( + RecentTaskInfo... tasks) { + ArrayList<RecentTaskInfo> rawList = new ArrayList<>(); + for (RecentTaskInfo task : tasks) { rawList.add(task); } doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(), @@ -693,11 +696,11 @@ public class RecentTasksControllerTest extends ShellTestCase { * @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in * the grouped task list */ - private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks, + private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks, int... expectedTaskIds) { int[] flattenedTaskIds = new int[recentTasks.size() * 2]; for (int i = 0; i < recentTasks.size(); i++) { - GroupedRecentTaskInfo pair = recentTasks.get(i); + GroupedTaskInfo pair = recentTasks.get(i); int taskId1 = pair.getTaskInfo1().taskId; flattenedTaskIds[2 * i] = taskId1; flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index ef3af8e7bdac..966651f19711 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -217,7 +217,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, @@ -238,7 +237,6 @@ public class SplitScreenControllerTests extends ShellTestCase { // Put the same component to the top running task ActivityManager.RunningTaskInfo topRunningTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent); - doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(); doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any()); // Put the same component into a task in the background ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt index 80ad1df44a1b..d44c01592ff7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt @@ -25,6 +25,7 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.desktopmode.DesktopRepository +import com.android.wm.shell.desktopmode.DesktopModeEventLogger import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator @@ -52,6 +53,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { private val transitionsMock: Transitions = mock() private val shellTaskOrganizerMock: ShellTaskOrganizer = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val toggleResizeDesktopTaskTransitionHandlerMock: ToggleResizeDesktopTaskTransitionHandler = mock() @@ -74,6 +76,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandlerMock, returnToDragStartAnimatorMock, desktopRepository, + desktopModeEventLogger, ) whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt index f371f5223419..057d8fa3adb0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt @@ -21,6 +21,7 @@ import android.content.res.Resources import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner +import android.view.MotionEvent import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_CHANGE import android.view.WindowManager.TRANSIT_TO_FRONT @@ -33,6 +34,8 @@ import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeEventLogger +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger import com.android.wm.shell.desktopmode.DesktopRepository import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask @@ -91,7 +94,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { private val info: TransitionInfo = mock() private val finishCallback: Transitions.TransitionFinishCallback = mock() private val desktopRepository: DesktopRepository = mock() + private val desktopModeEventLogger: DesktopModeEventLogger = mock() private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock() + private val motionEvent: MotionEvent = mock() private lateinit var tilingDecoration: DesktopTilingWindowDecoration private val split_divider_width = 10 @@ -112,6 +117,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { toggleResizeDesktopTaskTransitionHandler, returnToDragStartAnimator, desktopRepository, + desktopModeEventLogger, ) whenever(context.createContextAsUser(any(), any())).thenReturn(context) } @@ -371,13 +377,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { // End moving, no startTransition because bounds did not change. tiledTaskHelper.newBounds.set(BOUNDS) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(tiledTaskHelper, times(2)).hideVeil() verify(transitions, never()).startTransition(any(), any(), any()) // Move then end again with bounds changing to ensure startTransition is called. tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) - tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) verify(transitions, times(1)) .startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration)) // No hide veil until start animation is called. @@ -389,6 +395,64 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() { } @Test + fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() { + // Setup + val task1 = createFreeformTask() + val task2 = createFreeformTask() + val stableBounds = STABLE_BOUNDS_MOCK + whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout) + whenever(displayLayout.getStableBounds(any())).thenAnswer { i -> + (i.arguments.first() as Rect).set(stableBounds) + } + whenever(context.resources).thenReturn(resources) + whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width) + desktopWindowDecoration.mTaskInfo = task1 + task1.minWidth = 0 + task1.minHeight = 0 + initTiledTaskHelperMock(task1) + desktopWindowDecoration.mDecorWindowContext = context + whenever(resources.getBoolean(any())).thenReturn(true) + + // Act + tilingDecoration.onAppTiled( + task1, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.RIGHT, + BOUNDS, + ) + tilingDecoration.onAppTiled( + task2, + desktopWindowDecoration, + DesktopTasksController.SnapPosition.LEFT, + BOUNDS, + ) + tilingDecoration.leftTaskResizingHelper = tiledTaskHelper + tilingDecoration.rightTaskResizingHelper = tiledTaskHelper + tilingDecoration.onDividerHandleDragStart(motionEvent) + // Log start event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingStarted( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + displayController, + ) + + tilingDecoration.onDividerHandleMoved(BOUNDS, transaction) + tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent) + // Log end event for task1 and task2, but the tasks are the same in + // this test, so we verify the same log twice. + verify(desktopModeEventLogger, times(2)).logTaskResizingEnded( + ResizeTrigger.TILING_DIVIDER, + motionEvent, + task1, + BOUNDS.height(), + BOUNDS.width(), + displayController, + ) + } + + @Test fun taskTiled_shouldBeRemoved_whenTileBroken() { val task1 = createFreeformTask() val stableBounds = STABLE_BOUNDS_MOCK diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt index c96ce955f217..734815cdd915 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt @@ -68,7 +68,7 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true) val motionEvent = @@ -79,7 +79,7 @@ class TilingDividerViewTest : ShellTestCase() { val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any(), any()) } @Test @@ -92,12 +92,12 @@ class TilingDividerViewTest : ShellTestCase() { val downMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, downMotionEvent) - verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any()) + verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any()) val upMotionEvent = getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat()) tilingDividerView.handleMotionEvent(viewMock, upMotionEvent) - verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any()) + verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any(), any()) } private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent { diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt index c3b1a7cb16e3..aeea8cb6df1b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt @@ -24,6 +24,7 @@ import android.media.AudioManager import android.util.Log import com.android.settingslib.volume.shared.model.AudioManagerEvent import com.android.settingslib.volume.shared.model.AudioStream +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharedFlow @@ -31,6 +32,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.launch @@ -44,6 +46,7 @@ interface AudioManagerEventsReceiver { class AudioManagerEventsReceiverImpl( private val context: Context, coroutineScope: CoroutineScope, + backgroundCoroutineContext: CoroutineContext, ) : AudioManagerEventsReceiver { private val allActions: Collection<String> @@ -79,6 +82,7 @@ class AudioManagerEventsReceiverImpl( .filterNotNull() .filter { intent -> allActions.contains(intent.action) } .mapNotNull { it.toAudioManagerEvent() } + .flowOn(backgroundCoroutineContext) .shareIn(coroutineScope, SharingStarted.WhileSubscribed()) private fun Intent.toAudioManagerEvent(): AudioManagerEvent? { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt index 35ee8287d52f..58a09fbacc59 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt @@ -60,7 +60,12 @@ class AudioManagerEventsReceiverTest { fun setup() { MockitoAnnotations.initMocks(this) - underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope) + underTest = + AudioManagerEventsReceiverImpl( + context, + testScope.backgroundScope, + testScope.testScheduler, + ) } @Test diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 0938167c5495..3bf3e24a2ba6 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -26,6 +26,16 @@ flag { } flag { + name: "user_encrypted_source" + namespace: "systemui" + description: "Get rid of the local cache and rely on UserManager.isUserUnlocked directly to determine whether user CE storage is encrypted." + bug: "333656491" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "modes_ui_dialog_paging" namespace: "systemui" description: "Add pagination to the Modes dialog in quick settings." @@ -1578,6 +1588,13 @@ flag { } flag { + name: "show_clipboard_indication" + namespace: "systemui" + description: "Show indication text under the clipboard overlay when copied something" + bug: "361199935" +} + +flag { name: "media_projection_dialog_behind_lockscreen" namespace: "systemui" description: "Ensure MediaProjection Dialog appears behind the lockscreen" @@ -1726,10 +1743,3 @@ flag { description: "An implementation of shortcut customizations through shortcut helper." bug: "365064144" } - -flag { - name: "touchpad_three_finger_tap_customization" - namespace: "systemui" - description: "Customize touchpad three finger tap" - bug: "365063048" -} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index 4cf264253bf8..fdb4871423c3 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -20,6 +20,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context +import android.graphics.PointF import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.drawable.GradientDrawable @@ -33,13 +34,13 @@ import android.view.ViewOverlay import android.view.animation.Interpolator import android.window.WindowAnimationState import com.android.app.animation.Interpolators.LINEAR -import com.android.app.animation.MathUtils.max import com.android.internal.annotations.VisibleForTesting import com.android.internal.dynamicanimation.animation.SpringAnimation import com.android.internal.dynamicanimation.animation.SpringForce import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import java.util.concurrent.Executor import kotlin.math.abs +import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt @@ -91,6 +92,14 @@ class TransitionAnimator( ) } + /** + * Similar to [getProgress] above, bug the delay and duration are expressed as percentages + * of the animation duration (between 0f and 1f). + */ + internal fun getProgress(linearProgress: Float, delay: Float, duration: Float): Float { + return getProgressInternal(totalDuration = 1f, linearProgress, delay, duration) + } + private fun getProgressInternal( totalDuration: Float, linearProgress: Float, @@ -262,10 +271,10 @@ class TransitionAnimator( var centerY: Float, var scale: Float = 0f, - // Cached values. - var previousCenterX: Float = -1f, - var previousCenterY: Float = -1f, - var previousScale: Float = -1f, + // Update flags (used to decide whether it's time to update the transition state). + var isCenterXUpdated: Boolean = false, + var isCenterYUpdated: Boolean = false, + var isScaleUpdated: Boolean = false, // Completion flags. var isCenterXDone: Boolean = false, @@ -286,6 +295,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.centerX = value + state.isCenterXUpdated = true } }, CENTER_Y { @@ -295,6 +305,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.centerY = value + state.isCenterYUpdated = true } }, SCALE { @@ -304,6 +315,7 @@ class TransitionAnimator( override fun setValue(state: SpringState, value: Float) { state.scale = value + state.isScaleUpdated = true } }; @@ -444,8 +456,8 @@ class TransitionAnimator( * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole] * is true. * - * If [useSpring] is true, a multi-spring animation will be used instead of the default - * interpolators. + * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation + * using it for the initial momentum will be used instead of the default interpolators. */ fun startAnimation( controller: Controller, @@ -453,9 +465,9 @@ class TransitionAnimator( windowBackgroundColor: Int, fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, - useSpring: Boolean = false, + startVelocity: PointF? = null, ): Animation { - if (!controller.isLaunching || useSpring) checkReturnAnimationFrameworkFlag() + if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag() // We add an extra layer with the same color as the dialog/app splash screen background // color, which is usually the same color of the app background. We first fade in this layer @@ -474,7 +486,7 @@ class TransitionAnimator( windowBackgroundLayer, fadeWindowBackgroundLayer, drawHole, - useSpring, + startVelocity, ) .apply { start() } } @@ -487,7 +499,7 @@ class TransitionAnimator( windowBackgroundLayer: GradientDrawable, fadeWindowBackgroundLayer: Boolean = true, drawHole: Boolean = false, - useSpring: Boolean = false, + startVelocity: PointF? = null, ): Animation { val transitionContainer = controller.transitionContainer val transitionContainerOverlay = transitionContainer.overlay @@ -504,11 +516,12 @@ class TransitionAnimator( openingWindowSyncView != null && openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl - return if (useSpring && springTimings != null && springInterpolators != null) { + return if (startVelocity != null && springTimings != null && springInterpolators != null) { createSpringAnimation( controller, startState, endState, + startVelocity, windowBackgroundLayer, transitionContainer, transitionContainerOverlay, @@ -693,6 +706,7 @@ class TransitionAnimator( controller: Controller, startState: State, endState: State, + startVelocity: PointF, windowBackgroundLayer: GradientDrawable, transitionContainer: View, transitionContainerOverlay: ViewGroupOverlay, @@ -721,19 +735,20 @@ class TransitionAnimator( fun updateProgress(state: SpringState) { if ( - (!state.isCenterXDone && state.centerX == state.previousCenterX) || - (!state.isCenterYDone && state.centerY == state.previousCenterY) || - (!state.isScaleDone && state.scale == state.previousScale) + !(state.isCenterXUpdated || state.isCenterXDone) || + !(state.isCenterYUpdated || state.isCenterYDone) || + !(state.isScaleUpdated || state.isScaleDone) ) { // Because all three springs use the same update method, we only actually update - // when all values have changed, avoiding two redundant calls per frame. + // when all properties have received their new value (which could be unchanged from + // the previous one), avoiding two redundant calls per frame. return } - // Update the latest values for the check above. - state.previousCenterX = state.centerX - state.previousCenterY = state.centerY - state.previousScale = state.scale + // Reset the update flags. + state.isCenterXUpdated = false + state.isCenterYUpdated = false + state.isScaleUpdated = false // Current scale-based values, that will be used to find the new animation bounds. val width = @@ -829,6 +844,7 @@ class TransitionAnimator( } setStartValue(startState.centerX) + setStartVelocity(startVelocity.x) setMinValue(min(startState.centerX, endState.centerX)) setMaxValue(max(startState.centerX, endState.centerX)) @@ -850,6 +866,7 @@ class TransitionAnimator( } setStartValue(startState.centerY) + setStartVelocity(startVelocity.y) setMinValue(min(startState.centerY, endState.centerY)) setMaxValue(max(startState.centerY, endState.centerY)) @@ -1057,15 +1074,13 @@ class TransitionAnimator( interpolators = springInterpolators!! val timings = springTimings!! fadeInProgress = - getProgressInternal( - totalDuration = 1f, + getProgress( linearProgress, timings.contentBeforeFadeOutDelay, timings.contentBeforeFadeOutDuration, ) fadeOutProgress = - getProgressInternal( - totalDuration = 1f, + getProgress( linearProgress, timings.contentAfterFadeInDelay, timings.contentAfterFadeInDuration, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 7872ffad6cec..041cd15bdeea 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -36,12 +36,11 @@ internal typealias SuspendedValue<T> = suspend () -> T internal interface DraggableHandler { /** - * Start a drag in the given [startedPosition], with the given [overSlop] and number of - * [pointersDown]. + * Start a drag with the given [pointersInfo] and [overSlop]. * * The returned [DragController] should be used to continue or stop the drag. */ - fun onDragStarted(startedPosition: Offset?, overSlop: Float, pointersDown: Int): DragController + fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController } /** @@ -96,7 +95,7 @@ internal class DraggableHandlerImpl( * Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f, * indicating that the transition should be intercepted. */ - internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean { + internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean { // We don't intercept the touch if we are not currently driving the transition. val dragController = dragController if (dragController?.isDrivingTransition != true) { @@ -107,7 +106,7 @@ internal class DraggableHandlerImpl( // Only intercept the current transition if one of the 2 swipes results is also a transition // between the same pair of contents. - val swipes = computeSwipes(startedPosition, pointersDown = 1) + val swipes = computeSwipes(pointersInfo) val fromContent = layoutImpl.content(swipeAnimation.currentContent) val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent) val currentScene = layoutImpl.state.currentScene @@ -124,11 +123,7 @@ internal class DraggableHandlerImpl( )) } - override fun onDragStarted( - startedPosition: Offset?, - overSlop: Float, - pointersDown: Int, - ): DragController { + override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController { if (overSlop == 0f) { val oldDragController = dragController check(oldDragController != null && oldDragController.isDrivingTransition) { @@ -153,7 +148,7 @@ internal class DraggableHandlerImpl( return updateDragController(swipes, swipeAnimation) } - val swipes = computeSwipes(startedPosition, pointersDown) + val swipes = computeSwipes(pointersInfo) val fromContent = layoutImpl.contentForUserActions() swipes.updateSwipesResults(fromContent) @@ -190,8 +185,7 @@ internal class DraggableHandlerImpl( return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation) } - internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? { - if (startedPosition == null) return null + internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? { return layoutImpl.swipeSourceDetector.source( layoutSize = layoutImpl.lastSize, position = startedPosition.round(), @@ -200,57 +194,44 @@ internal class DraggableHandlerImpl( ) } - internal fun resolveSwipe( - pointersDown: Int, - fromSource: SwipeSource.Resolved?, - isUpOrLeft: Boolean, - ): Swipe.Resolved { - return Swipe.Resolved( - direction = - when (orientation) { - Orientation.Horizontal -> - if (isUpOrLeft) { - SwipeDirection.Resolved.Left - } else { - SwipeDirection.Resolved.Right - } - - Orientation.Vertical -> - if (isUpOrLeft) { - SwipeDirection.Resolved.Up - } else { - SwipeDirection.Resolved.Down - } - }, - pointerCount = pointersDown, - fromSource = fromSource, + private fun computeSwipes(pointersInfo: PointersInfo?): Swipes { + val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) } + return Swipes( + upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource), + downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource), ) } +} - private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes { - val fromSource = resolveSwipeSource(startedPosition) - val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true) - val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false) - return if (fromSource == null) { - Swipes( - upOrLeft = null, - downOrRight = null, - upOrLeftNoSource = upOrLeft, - downOrRightNoSource = downOrRight, - ) - } else { - Swipes( - upOrLeft = upOrLeft, - downOrRight = downOrRight, - upOrLeftNoSource = upOrLeft.copy(fromSource = null), - downOrRightNoSource = downOrRight.copy(fromSource = null), - ) - } - } +private fun resolveSwipe( + orientation: Orientation, + isUpOrLeft: Boolean, + pointersInfo: PointersInfo?, + fromSource: SwipeSource.Resolved?, +): Swipe.Resolved { + return Swipe.Resolved( + direction = + when (orientation) { + Orientation.Horizontal -> + if (isUpOrLeft) { + SwipeDirection.Resolved.Left + } else { + SwipeDirection.Resolved.Right + } - companion object { - private const val TAG = "DraggableHandlerImpl" - } + Orientation.Vertical -> + if (isUpOrLeft) { + SwipeDirection.Resolved.Up + } else { + SwipeDirection.Resolved.Down + } + }, + // If the number of pointers is not specified, 1 is assumed. + pointerCount = pointersInfo?.pointersDown ?: 1, + // Resolves the pointer type only if all pointers are of the same type. + pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(), + fromSource = fromSource, + ) } /** @param swipes The [Swipes] associated to the current gesture. */ @@ -498,24 +479,14 @@ private class DragControllerImpl( } /** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */ -internal class Swipes( - val upOrLeft: Swipe.Resolved?, - val downOrRight: Swipe.Resolved?, - val upOrLeftNoSource: Swipe.Resolved?, - val downOrRightNoSource: Swipe.Resolved?, -) { +internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resolved) { /** The [UserActionResult] associated to up and down swipes. */ var upOrLeftResult: UserActionResult? = null var downOrRightResult: UserActionResult? = null fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> { - val userActions = fromContent.userActions - fun result(swipe: Swipe.Resolved?): UserActionResult? { - return userActions[swipe ?: return null] - } - - val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource) - val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource) + val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft) + val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight) return upOrLeftResult to downOrRightResult } @@ -569,11 +540,13 @@ internal class NestedScrollHandlerImpl( val connection: PriorityNestedScrollConnection = nestedScrollConnection() - private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved { - return draggableHandler.resolveSwipe( - pointersDown = pointersDown, - fromSource = draggableHandler.resolveSwipeSource(startedPosition), + private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved { + return resolveSwipe( + orientation = draggableHandler.orientation, isUpOrLeft = isUpOrLeft, + pointersInfo = pointersInfo, + fromSource = + pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) }, ) } @@ -582,12 +555,7 @@ internal class NestedScrollHandlerImpl( // moving on to the next scene. var canChangeScene = false - var _lastPointersInfo: PointersInfo? = null - fun pointersInfo(): PointersInfo { - return checkNotNull(_lastPointersInfo) { - "PointersInfo should be initialized before the transition begins." - } - } + var lastPointersInfo: PointersInfo? = null fun hasNextScene(amount: Float): Boolean { val transitionState = layoutState.transitionState @@ -595,17 +563,11 @@ internal class NestedScrollHandlerImpl( val fromScene = layoutImpl.scene(scene) val resolvedSwipe = when { - amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true) - amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false) + amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo) + amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo) else -> null } - val nextScene = - resolvedSwipe?.let { - fromScene.userActions[it] - ?: if (it.fromSource != null) { - fromScene.userActions[it.copy(fromSource = null)] - } else null - } + val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) } if (nextScene != null) return true if (transitionState !is TransitionState.Idle) return false @@ -619,13 +581,14 @@ internal class NestedScrollHandlerImpl( return PriorityNestedScrollConnection( orientation = orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> + val pointersInfo = pointersInfoOwner.pointersInfo() canChangeScene = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f val canInterceptSwipeTransition = canChangeScene && offsetAvailable != 0f && - draggableHandler.shouldImmediatelyIntercept(startedPosition = null) + draggableHandler.shouldImmediatelyIntercept(pointersInfo) if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false val threshold = layoutImpl.transitionInterceptionThreshold @@ -636,13 +599,11 @@ internal class NestedScrollHandlerImpl( return@PriorityNestedScrollConnection false } - val pointersInfo = pointersInfoOwner.pointersInfo() - - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfo + lastPointersInfo = pointersInfo // If the current swipe transition is *not* closed to 0f or 1f, then we want the // scroll events to intercept the current transition to continue the scene @@ -662,11 +623,11 @@ internal class NestedScrollHandlerImpl( if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfo + lastPointersInfo = pointersInfo val canStart = when (behavior) { @@ -704,11 +665,11 @@ internal class NestedScrollHandlerImpl( canChangeScene = false val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfo + lastPointersInfo = pointersInfo val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable) if (canStart) { @@ -718,12 +679,11 @@ internal class NestedScrollHandlerImpl( canStart }, onStart = { firstScroll -> - val pointersInfo = pointersInfo() + val pointersInfo = lastPointersInfo scrollController( dragController = draggableHandler.onDragStarted( - pointersDown = pointersInfo.pointersDown, - startedPosition = pointersInfo.startedPosition, + pointersInfo = pointersInfo, overSlop = if (isIntercepting) 0f else firstScroll, ), canChangeScene = canChangeScene, @@ -742,7 +702,7 @@ private fun scrollController( return object : ScrollController { override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float { val pointersInfo = pointersInfoOwner.pointersInfo() - if (pointersInfo.isMouseWheel) { + if (pointersInfo?.isMouseWheel == true) { // Do not support mouse wheel interactions return 0f } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index 63c5d7aed3e1..e7b66c5f0d2f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -52,6 +52,7 @@ import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.drawInContainer import com.android.compose.ui.util.lerp @@ -187,6 +188,7 @@ private fun Modifier.maybeElevateInContent( state.transformationSpec .transformations(key, content.key) .shared + ?.transformation ?.elevateInContent == content.key && isSharedElement(stateByContent, state) && isSharedElementEnabled(key, state) && @@ -901,7 +903,7 @@ private fun shouldPlaceElement( } val sharedTransformation = sharedElementTransformation(element.key, transition) - if (sharedTransformation?.enabled == false) { + if (sharedTransformation?.transformation?.enabled == false) { return true } @@ -954,13 +956,13 @@ private fun isSharedElementEnabled( element: ElementKey, transition: TransitionState.Transition, ): Boolean { - return sharedElementTransformation(element, transition)?.enabled ?: true + return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true } internal fun sharedElementTransformation( element: ElementKey, transition: TransitionState.Transition, -): SharedElementTransformation? { +): TransformationWithRange<SharedElementTransformation>? { val transformationSpec = transition.transformationSpec val sharedInFromContent = transformationSpec.transformations(element, transition.fromContent).shared @@ -1244,7 +1246,7 @@ private inline fun <T> computeValue( element: Element, transition: TransitionState.Transition?, contentValue: (Element.State) -> T, - transformation: (ElementTransformations) -> PropertyTransformation<T>?, + transformation: (ElementTransformations) -> TransformationWithRange<PropertyTransformation<T>>?, currentValue: () -> T, isSpecified: (T) -> Boolean, lerp: (T, T, Float) -> T, @@ -1280,7 +1282,7 @@ private inline fun <T> computeValue( checkNotNull(if (currentContent == toContent) toState else fromState) val idleValue = contentValue(overscrollState) val targetValue = - with(propertySpec) { + with(propertySpec.transformation) { layoutImpl.propertyTransformationScope.transform( currentContent, element.key, @@ -1375,7 +1377,7 @@ private inline fun <T> computeValue( val idleValue = contentValue(contentState) val isEntering = content == toContent val previewTargetValue = - with(previewTransformation) { + with(previewTransformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1386,7 +1388,7 @@ private inline fun <T> computeValue( val targetValueOrNull = transformation?.let { transformation -> - with(transformation) { + with(transformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, @@ -1461,7 +1463,7 @@ private inline fun <T> computeValue( val idleValue = contentValue(contentState) val targetValue = - with(transformation) { + with(transformation.transformation) { layoutImpl.propertyTransformationScope.transform( content, element.key, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 8613f6da0f62..ab2324a87d81 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -33,6 +33,7 @@ import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode import androidx.compose.ui.input.pointer.changedToDown import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed @@ -52,6 +53,7 @@ import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastFirstOrNull +import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastSumBy import com.android.compose.ui.util.SpaceVectorConverter import kotlin.coroutines.cancellation.CancellationException @@ -78,8 +80,8 @@ import kotlinx.coroutines.launch @Stable internal fun Modifier.multiPointerDraggable( orientation: Orientation, - startDragImmediately: (startedPosition: Offset) -> Boolean, - onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, onFirstPointerDown: () -> Unit = {}, swipeDetector: SwipeDetector = DefaultSwipeDetector, dispatcher: NestedScrollDispatcher, @@ -97,9 +99,8 @@ internal fun Modifier.multiPointerDraggable( private data class MultiPointerDraggableElement( private val orientation: Orientation, - private val startDragImmediately: (startedPosition: Offset) -> Boolean, - private val onDragStarted: - (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, private val onFirstPointerDown: () -> Unit, private val swipeDetector: SwipeDetector, private val dispatcher: NestedScrollDispatcher, @@ -125,9 +126,8 @@ private data class MultiPointerDraggableElement( internal class MultiPointerDraggableNode( orientation: Orientation, - var startDragImmediately: (startedPosition: Offset) -> Boolean, - var onDragStarted: - (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, var onFirstPointerDown: () -> Unit, swipeDetector: SwipeDetector = DefaultSwipeDetector, private val dispatcher: NestedScrollDispatcher, @@ -183,17 +183,22 @@ internal class MultiPointerDraggableNode( pointerInput.onPointerEvent(pointerEvent, pass, bounds) } + private var lastPointerEvent: PointerEvent? = null private var startedPosition: Offset? = null private var pointersDown: Int = 0 - private var isMouseWheel: Boolean = false - internal fun pointersInfo(): PointersInfo { - return PointersInfo( + internal fun pointersInfo(): PointersInfo? { + val startedPosition = startedPosition + val lastPointerEvent = lastPointerEvent + if (startedPosition == null || lastPointerEvent == null) { // This may be null, i.e. when the user uses TalkBack + return null + } + + return PointersInfo( startedPosition = startedPosition, - // We could have 0 pointers during fling or for other reasons. - pointersDown = pointersDown.coerceAtLeast(1), - isMouseWheel = isMouseWheel, + pointersDown = pointersDown, + lastPointerEvent = lastPointerEvent, ) } @@ -212,8 +217,8 @@ internal class MultiPointerDraggableNode( if (pointerEvent.type == PointerEventType.Enter) continue val changes = pointerEvent.changes + lastPointerEvent = pointerEvent pointersDown = changes.countDown() - isMouseWheel = pointerEvent.type == PointerEventType.Scroll when { // There are no more pointers down. @@ -285,8 +290,8 @@ internal class MultiPointerDraggableNode( detectDragGestures( orientation = orientation, startDragImmediately = startDragImmediately, - onDragStart = { startedPosition, overSlop, pointersDown -> - onDragStarted(startedPosition, overSlop, pointersDown) + onDragStart = { pointersInfo, overSlop -> + onDragStarted(pointersInfo, overSlop) }, onDrag = { controller, amount -> dispatchScrollEvents( @@ -435,9 +440,8 @@ internal class MultiPointerDraggableNode( */ private suspend fun AwaitPointerEventScope.detectDragGestures( orientation: Orientation, - startDragImmediately: (startedPosition: Offset) -> Boolean, - onDragStart: - (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + startDragImmediately: (pointersInfo: PointersInfo) -> Boolean, + onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController, onDrag: (controller: DragController, dragAmount: Float) -> Unit, onDragEnd: (controller: DragController) -> Unit, onDragCancel: (controller: DragController) -> Unit, @@ -462,8 +466,13 @@ internal class MultiPointerDraggableNode( .first() var overSlop = 0f + var lastPointersInfo = + checkNotNull(pointersInfo()) { + "We should have pointers down, last event: $currentEvent" + } + val drag = - if (startDragImmediately(consumablePointer.position)) { + if (startDragImmediately(lastPointersInfo)) { consumablePointer.consume() consumablePointer } else { @@ -488,14 +497,18 @@ internal class MultiPointerDraggableNode( consumablePointer.id, onSlopReached, ) - } + } ?: return + lastPointersInfo = + checkNotNull(pointersInfo()) { + "We should have pointers down, last event: $currentEvent" + } // Make sure that overSlop is not 0f. This can happen when the user drags by exactly // the touch slop. However, the overSlop we pass to onDragStarted() is used to // compute the direction we are dragging in, so overSlop should never be 0f unless // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned // true). - if (drag != null && overSlop == 0f) { + if (overSlop == 0f) { val delta = (drag.position - consumablePointer.position).toFloat() check(delta != 0f) { "delta is equal to 0" } overSlop = delta.sign @@ -503,49 +516,38 @@ internal class MultiPointerDraggableNode( drag } - if (drag != null) { - val controller = - onDragStart( - // The startedPosition is the starting position when a gesture begins (when the - // first pointer touches the screen), not the point where we begin dragging. - // For example, this could be different if one of our children intercepts the - // gesture first and then we do. - requireNotNull(startedPosition), - overSlop, - pointersDown, + val controller = onDragStart(lastPointersInfo, overSlop) + + val successful: Boolean + try { + onDrag(controller, overSlop) + + successful = + drag( + initialPointerId = drag.id, + hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f }, + onDrag = { + onDrag(controller, it.positionChange().toFloat()) + it.consume() + }, + onIgnoredEvent = { + // We are still dragging an object, but this event is not of interest to the + // caller. + // This event will not trigger the onDrag event, but we will consume the + // event to prevent another pointerInput from interrupting the current + // gesture just because the event was ignored. + it.consume() + }, ) + } catch (t: Throwable) { + onDragCancel(controller) + throw t + } - val successful: Boolean - try { - onDrag(controller, overSlop) - - successful = - drag( - initialPointerId = drag.id, - hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f }, - onDrag = { - onDrag(controller, it.positionChange().toFloat()) - it.consume() - }, - onIgnoredEvent = { - // We are still dragging an object, but this event is not of interest to - // the caller. - // This event will not trigger the onDrag event, but we will consume the - // event to prevent another pointerInput from interrupting the current - // gesture just because the event was ignored. - it.consume() - }, - ) - } catch (t: Throwable) { - onDragCancel(controller) - throw t - } - - if (successful) { - onDragEnd(controller) - } else { - onDragCancel(controller) - } + if (successful) { + onDragEnd(controller) + } else { + onDragCancel(controller) } } @@ -655,11 +657,57 @@ internal class MultiPointerDraggableNode( } internal fun interface PointersInfoOwner { - fun pointersInfo(): PointersInfo + /** + * Provides information about the pointers interacting with this composable. + * + * @return A [PointersInfo] object containing details about the pointers, including the starting + * position and the number of pointers down, or `null` if there are no pointers down. + */ + fun pointersInfo(): PointersInfo? } +/** + * Holds information about pointer interactions within a composable. + * + * This class stores details such as the starting position of a gesture, the number of pointers + * down, and whether the last pointer event was a mouse wheel scroll. + * + * @param startedPosition The starting position of the gesture. This is the position where the first + * pointer touched the screen, not necessarily the point where dragging begins. This may be + * different from the initial touch position if a child composable intercepts the gesture before + * this one. + * @param pointersDown The number of pointers currently down. + * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll. + * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type + * currently down/pressed. + */ internal data class PointersInfo( - val startedPosition: Offset?, + val startedPosition: Offset, val pointersDown: Int, val isMouseWheel: Boolean, -) + val pointersDownByType: Map<PointerType, Int>, +) { + init { + check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" } + } +} + +private fun PointersInfo( + startedPosition: Offset, + pointersDown: Int, + lastPointerEvent: PointerEvent, +): PointersInfo { + return PointersInfo( + startedPosition = startedPosition, + pointersDown = pointersDown, + isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll, + pointersDownByType = + buildMap { + lastPointerEvent.changes.fastForEach { change -> + if (!change.pressed) return@fastForEach + val newValue = (get(change.type) ?: 0) + 1 + put(change.type, newValue) + } + }, + ) +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 504240390674..21d87e173728 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Density @@ -407,6 +408,7 @@ data object Back : UserAction() { data class Swipe( val direction: SwipeDirection, val pointerCount: Int = 1, + val pointersType: PointerType? = null, val fromSource: SwipeSource? = null, ) : UserAction() { companion object { @@ -422,6 +424,7 @@ data class Swipe( return Resolved( direction = direction.resolve(layoutDirection), pointerCount = pointerCount, + pointersType = pointersType, fromSource = fromSource?.resolve(layoutDirection), ) } @@ -431,6 +434,7 @@ data class Swipe( val direction: SwipeDirection.Resolved, val pointerCount: Int, val fromSource: SwipeSource.Resolved?, + val pointersType: PointerType?, ) : UserAction.Resolved() } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index e1e2411da080..61332b61ed1b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -764,7 +764,8 @@ internal class MutableSceneTransitionLayoutStateImpl( return@fastForEach } - state.transformationSpec.transformations.fastForEach { transformation -> + state.transformationSpec.transformations.fastForEach { transformationWithRange -> + val transformation = transformationWithRange.transformation if ( transformation is SharedElementTransformation && transformation.elevateInContent != null diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index 8866fbfbf194..b083f79aebf5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -33,10 +33,10 @@ import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.OverscrollTranslate import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.animation.scene.transformation.Translate /** The transitions configuration of a [SceneTransitionLayout]. */ @@ -233,7 +233,7 @@ interface TransformationSpec { val distance: UserActionDistance? /** The list of [Transformation] applied to elements during this transition. */ - val transformations: List<Transformation> + val transformations: List<TransformationWithRange<*>> companion object { internal val Empty = @@ -325,7 +325,7 @@ internal class TransformationSpecImpl( override val progressSpec: AnimationSpec<Float>, override val swipeSpec: SpringSpec<Float>?, override val distance: UserActionDistance?, - override val transformations: List<Transformation>, + override val transformations: List<TransformationWithRange<*>>, ) : TransformationSpec { private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>() @@ -340,59 +340,65 @@ internal class TransformationSpecImpl( element: ElementKey, content: ContentKey, ): ElementTransformations { - var shared: SharedElementTransformation? = null - var offset: PropertyTransformation<Offset>? = null - var size: PropertyTransformation<IntSize>? = null - var drawScale: PropertyTransformation<Scale>? = null - var alpha: PropertyTransformation<Float>? = null - - fun <T> onPropertyTransformation( - root: PropertyTransformation<T>, - current: PropertyTransformation<T> = root, - ) { - when (current) { + var shared: TransformationWithRange<SharedElementTransformation>? = null + var offset: TransformationWithRange<PropertyTransformation<Offset>>? = null + var size: TransformationWithRange<PropertyTransformation<IntSize>>? = null + var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null + var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null + + transformations.fastForEach { transformationWithRange -> + val transformation = transformationWithRange.transformation + if (!transformation.matcher.matches(element, content)) { + return@fastForEach + } + + when (transformation) { + is SharedElementTransformation -> { + throwIfNotNull(shared, element, name = "shared") + shared = + transformationWithRange + as TransformationWithRange<SharedElementTransformation> + } is Translate, is OverscrollTranslate, is EdgeTranslate, is AnchoredTranslate -> { throwIfNotNull(offset, element, name = "offset") - offset = root as PropertyTransformation<Offset> + offset = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Offset>> } is ScaleSize, is AnchoredSize -> { throwIfNotNull(size, element, name = "size") - size = root as PropertyTransformation<IntSize> + size = + transformationWithRange + as TransformationWithRange<PropertyTransformation<IntSize>> } is DrawScale -> { throwIfNotNull(drawScale, element, name = "drawScale") - drawScale = root as PropertyTransformation<Scale> + drawScale = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Scale>> } is Fade -> { throwIfNotNull(alpha, element, name = "alpha") - alpha = root as PropertyTransformation<Float> - } - is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate) - } - } - - transformations.fastForEach { transformation -> - if (!transformation.matcher.matches(element, content)) { - return@fastForEach - } - - when (transformation) { - is SharedElementTransformation -> { - throwIfNotNull(shared, element, name = "shared") - shared = transformation + alpha = + transformationWithRange + as TransformationWithRange<PropertyTransformation<Float>> } - is PropertyTransformation<*> -> onPropertyTransformation(transformation) + else -> error("Unknown transformation: $transformation") } } return ElementTransformations(shared, offset, size, drawScale, alpha) } - private fun throwIfNotNull(previous: Transformation?, element: ElementKey, name: String) { + private fun throwIfNotNull( + previous: TransformationWithRange<*>?, + element: ElementKey, + name: String, + ) { if (previous != null) { error("$element has multiple $name transformations") } @@ -401,9 +407,9 @@ internal class TransformationSpecImpl( /** The transformations of an element during a transition. */ internal class ElementTransformations( - val shared: SharedElementTransformation?, - val offset: PropertyTransformation<Offset>?, - val size: PropertyTransformation<IntSize>?, - val drawScale: PropertyTransformation<Scale>?, - val alpha: PropertyTransformation<Float>?, + val shared: TransformationWithRange<SharedElementTransformation>?, + val offset: TransformationWithRange<PropertyTransformation<Offset>>?, + val size: TransformationWithRange<PropertyTransformation<IntSize>>?, + val drawScale: TransformationWithRange<PropertyTransformation<Scale>>?, + val alpha: TransformationWithRange<PropertyTransformation<Float>>?, ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index fdf01cce396b..ba5f4144aff9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -19,7 +19,6 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode import androidx.compose.ui.input.pointer.PointerEvent @@ -65,6 +64,52 @@ private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation } } +/** + * Finds the best matching [UserActionResult] for the given [swipe] within this [Content]. + * Prioritizes actions with matching [Swipe.Resolved.fromSource]. + * + * @param swipe The swipe to match against. + * @return The best matching [UserActionResult], or `null` if no match is found. + */ +internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? { + var bestPoints = Int.MIN_VALUE + var bestMatch: UserActionResult? = null + userActions.forEach { (actionSwipe, actionResult) -> + if ( + actionSwipe !is Swipe.Resolved || + // The direction must match. + actionSwipe.direction != swipe.direction || + // The number of pointers down must match. + actionSwipe.pointerCount != swipe.pointerCount || + // The action requires a specific fromSource. + (actionSwipe.fromSource != null && actionSwipe.fromSource != swipe.fromSource) || + // The action requires a specific pointerType. + (actionSwipe.pointersType != null && actionSwipe.pointersType != swipe.pointersType) + ) { + // This action is not eligible. + return@forEach + } + + val sameFromSource = actionSwipe.fromSource == swipe.fromSource + val samePointerType = actionSwipe.pointersType == swipe.pointersType + // Prioritize actions with a perfect match. + if (sameFromSource && samePointerType) { + return actionResult + } + + var points = 0 + if (sameFromSource) points++ + if (samePointerType) points++ + + // Otherwise, keep track of the best eligible action. + if (points > bestPoints) { + bestPoints = points + bestMatch = actionResult + } + } + return bestMatch +} + private data class SwipeToSceneElement( val draggableHandler: DraggableHandlerImpl, val swipeDetector: SwipeDetector, @@ -155,10 +200,10 @@ private class SwipeToSceneNode( override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput() - private fun startDragImmediately(startedPosition: Offset): Boolean { + private fun startDragImmediately(pointersInfo: PointersInfo): Boolean { // Immediately start the drag if the user can't swipe in the other direction and the gesture // handler can intercept it. - return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(startedPosition) + return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo) } private fun canOppositeSwipe(): Boolean { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 269d91b02e7d..e461f9ccc295 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -34,12 +34,11 @@ import com.android.compose.animation.scene.transformation.DrawScale import com.android.compose.animation.scene.transformation.EdgeTranslate import com.android.compose.animation.scene.transformation.Fade import com.android.compose.animation.scene.transformation.OverscrollTranslate -import com.android.compose.animation.scene.transformation.PropertyTransformation -import com.android.compose.animation.scene.transformation.RangedPropertyTransformation import com.android.compose.animation.scene.transformation.ScaleSize import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.animation.scene.transformation.Translate internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions { @@ -158,7 +157,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { } internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { - val transformations = mutableListOf<Transformation>() + val transformations = mutableListOf<TransformationWithRange<*>>() private var range: TransformationRange? = null protected var reversed = false override var distance: UserActionDistance? = null @@ -174,19 +173,13 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder { range = null } - protected fun transformation(transformation: PropertyTransformation<*>) { - val transformation = - if (range != null) { - RangedPropertyTransformation(transformation, range!!) - } else { - transformation - } - + protected fun transformation(transformation: Transformation) { + val transformationWithRange = TransformationWithRange(transformation, range) transformations.add( if (reversed) { - transformation.reversed() + transformationWithRange.reversed() } else { - transformation + transformationWithRange } ) } @@ -264,7 +257,7 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr "(${transition.toContent.debugName})" } - transformations.add(SharedElementTransformation(matcher, enabled, elevateInContent)) + transformation(SharedElementTransformation(matcher, enabled, elevateInContent)) } override fun timestampRange( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt index 5936d2595465..0ddeb7c7445f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt @@ -33,7 +33,7 @@ internal class AnchoredSize( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: IntSize, + idleValue: IntSize, ): IntSize { fun anchorSizeIn(content: ContentKey): IntSize { val size = @@ -45,8 +45,8 @@ internal class AnchoredSize( ) return IntSize( - width = if (anchorWidth) size.width else value.width, - height = if (anchorHeight) size.height else value.height, + width = if (anchorWidth) size.width else idleValue.width, + height = if (anchorHeight) size.height else idleValue.height, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt index 0a59dfe515fc..47508b41633c 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt @@ -31,7 +31,7 @@ internal class AnchoredTranslate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { fun throwException(content: ContentKey?): Nothing { throwMissingAnchorException( @@ -51,9 +51,9 @@ internal class AnchoredTranslate( val offset = anchorToOffset - anchorFromOffset return if (content == transition.toContent) { - Offset(value.x - offset.x, value.y - offset.y) + Offset(idleValue.x - offset.x, idleValue.y - offset.y) } else { - Offset(value.x + offset.x, value.y + offset.y) + Offset(idleValue.x + offset.x, idleValue.y + offset.y) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt index 7223dad43a2e..8488ae5178b0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt @@ -33,12 +33,11 @@ internal class DrawScale( private val scaleY: Float, private val pivot: Offset = Offset.Unspecified, ) : PropertyTransformation<Scale> { - override fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Scale, + idleValue: Scale, ): Scale { return Scale(scaleX, scaleY, pivot) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt index 4ae07c541011..884aae4b8b1a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt @@ -33,37 +33,37 @@ internal class EdgeTranslate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { val sceneSize = content.targetSize() ?: error("Content ${content.debugName} does not have a target size") - val elementSize = element.targetSize(content) ?: return value + val elementSize = element.targetSize(content) ?: return idleValue return when (edge.resolve(layoutDirection)) { Edge.Resolved.Top -> if (startsOutsideLayoutBounds) { - Offset(value.x, -elementSize.height.toFloat()) + Offset(idleValue.x, -elementSize.height.toFloat()) } else { - Offset(value.x, 0f) + Offset(idleValue.x, 0f) } Edge.Resolved.Left -> if (startsOutsideLayoutBounds) { - Offset(-elementSize.width.toFloat(), value.y) + Offset(-elementSize.width.toFloat(), idleValue.y) } else { - Offset(0f, value.y) + Offset(0f, idleValue.y) } Edge.Resolved.Bottom -> if (startsOutsideLayoutBounds) { - Offset(value.x, sceneSize.height.toFloat()) + Offset(idleValue.x, sceneSize.height.toFloat()) } else { - Offset(value.x, (sceneSize.height - elementSize.height).toFloat()) + Offset(idleValue.x, (sceneSize.height - elementSize.height).toFloat()) } Edge.Resolved.Right -> if (startsOutsideLayoutBounds) { - Offset(sceneSize.width.toFloat(), value.y) + Offset(sceneSize.width.toFloat(), idleValue.y) } else { - Offset((sceneSize.width - elementSize.width).toFloat(), value.y) + Offset((sceneSize.width - elementSize.width).toFloat(), idleValue.y) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt index c11ec977fe2b..ef769e7d0c19 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt @@ -27,7 +27,7 @@ internal class Fade(override val matcher: ElementMatcher) : PropertyTransformati content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Float, + idleValue: Float, ): Float { // Return the alpha value of [element] either when it starts fading in or when it finished // fading out, which is `0` in both cases. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt index a159a5b5b2bd..ef3654b65b0a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt @@ -36,11 +36,11 @@ internal class ScaleSize( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: IntSize, + idleValue: IntSize, ): IntSize { return IntSize( - width = (value.width * width).roundToInt(), - height = (value.height * height).roundToInt(), + width = (idleValue.width * width).roundToInt(), + height = (idleValue.height * height).roundToInt(), ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt index d38067d9af38..74a3ead3fbd7 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt @@ -36,14 +36,6 @@ sealed interface Transformation { */ val matcher: ElementMatcher - /** - * The range during which the transformation is applied. If it is `null`, then the - * transformation will be applied throughout the whole scene transition. - */ - // TODO(b/240432457): Move this back to PropertyTransformation. - val range: TransformationRange? - get() = null - /* * Reverse this transformation. This is called when we use Transition(from = A, to = B) when * animating from B to A and there is no Transition(from = B, to = A) defined. @@ -66,14 +58,14 @@ interface PropertyTransformation<T> : Transformation { * - the value at progress = 100% for elements that are leaving the layout (i.e. elements in the * content we are transitioning from). * - * The returned value will be interpolated using the [transition] progress and [value], the + * The returned value will be interpolated using the [transition] progress and [idleValue], the * value of the property when we are idle. */ fun PropertyTransformationScope.transform( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: T, + idleValue: T, ): T } @@ -82,20 +74,15 @@ interface PropertyTransformationScope : Density, ElementStateScope { val layoutDirection: LayoutDirection } -/** - * A [PropertyTransformation] associated to a range. This is a helper class so that normal - * implementations of [PropertyTransformation] don't have to take care of reversing their range when - * they are reversed. - */ -internal class RangedPropertyTransformation<T>( - val delegate: PropertyTransformation<T>, - override val range: TransformationRange, -) : PropertyTransformation<T> by delegate { - override fun reversed(): Transformation { - return RangedPropertyTransformation( - delegate.reversed() as PropertyTransformation<T>, - range.reversed(), - ) +/** A pair consisting of a [transformation] and optional [range]. */ +class TransformationWithRange<out T : Transformation>( + val transformation: T, + val range: TransformationRange?, +) { + fun reversed(): TransformationWithRange<T> { + if (range == null) return this + + return TransformationWithRange(transformation = transformation, range = range.reversed()) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt index af0a6edfa2fb..356ed9969458 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt @@ -35,9 +35,9 @@ internal class Translate( content: ContentKey, element: ElementKey, transition: TransitionState.Transition, - value: Offset, + idleValue: Offset, ): Offset { - return Offset(value.x + x.toPx(), value.y + y.toPx()) + return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx()) } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index f24d93f0d79d..5dad0d75cfc5 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.Text import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection @@ -51,6 +52,20 @@ import org.junit.runner.RunWith private const val SCREEN_SIZE = 100f private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) +private fun pointersInfo( + startedPosition: Offset = Offset.Zero, + pointersDown: Int = 1, + isMouseWheel: Boolean = false, + pointersDownByType: Map<PointerType, Int> = mapOf(PointerType.Touch to pointersDown), +): PointersInfo { + return PointersInfo( + startedPosition = startedPosition, + pointersDown = pointersDown, + isMouseWheel = isMouseWheel, + pointersDownByType = pointersDownByType, + ) +} + @RunWith(AndroidJUnit4::class) class DraggableHandlerTest { private class TestGestureScope(val testScope: MonotonicClockTestScope) { @@ -126,9 +141,7 @@ class DraggableHandlerTest { val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical) val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) - var pointerInfoOwner: () -> PointersInfo = { - PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false) - } + var pointerInfoOwner: () -> PointersInfo = { pointersInfo() } fun nestedScrollConnection( nestedScrollBehavior: NestedScrollBehavior, @@ -211,42 +224,32 @@ class DraggableHandlerTest { } fun onDragStarted( - startedPosition: Offset = Offset.Zero, + pointersInfo: PointersInfo = pointersInfo(), overSlop: Float, - pointersDown: Int = 1, expectedConsumedOverSlop: Float = overSlop, ): DragController { // overSlop should be 0f only if the drag gesture starts with startDragImmediately if (overSlop == 0f) error("Consider using onDragStartedImmediately()") return onDragStarted( draggableHandler = draggableHandler, - startedPosition = startedPosition, + pointersInfo = pointersInfo, overSlop = overSlop, - pointersDown = pointersDown, expectedConsumedOverSlop = expectedConsumedOverSlop, ) } - fun onDragStartedImmediately( - startedPosition: Offset = Offset.Zero, - pointersDown: Int = 1, - ): DragController { - return onDragStarted(draggableHandler, startedPosition, overSlop = 0f, pointersDown) + fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController { + return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f) } fun onDragStarted( draggableHandler: DraggableHandler, - startedPosition: Offset = Offset.Zero, + pointersInfo: PointersInfo = pointersInfo(), overSlop: Float = 0f, - pointersDown: Int = 1, expectedConsumedOverSlop: Float = overSlop, ): DragController { val dragController = - draggableHandler.onDragStarted( - startedPosition = startedPosition, - overSlop = overSlop, - pointersDown = pointersDown, - ) + draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop) // MultiPointerDraggable will always call onDelta with the initial overSlop right after dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop) @@ -528,7 +531,8 @@ class DraggableHandlerTest { mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC) val dragController = onDragStarted( - startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f), + pointersInfo = + pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)), overSlop = up(fractionOfScreen = 0.2f), ) assertTransition( @@ -554,7 +558,7 @@ class DraggableHandlerTest { // Start dragging from the bottom onDragStarted( - startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE), + pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)), overSlop = up(fractionOfScreen = 0.1f), ) assertTransition( @@ -1051,8 +1055,8 @@ class DraggableHandlerTest { navigateToSceneC() // Swipe up from the middle to transition to scene B. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition( currentScene = SceneC, fromScene = SceneC, @@ -1067,7 +1071,7 @@ class DraggableHandlerTest { // should intercept it. Because it is intercepted, the overSlop passed to onDragStarted() // should be 0f. assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue() - onDragStartedImmediately(startedPosition = middle) + onDragStartedImmediately(pointersInfo = middle) // We should have intercepted the transition, so the transition should be the same object. assertTransition( @@ -1083,9 +1087,9 @@ class DraggableHandlerTest { // Start a new gesture from the bottom of the screen. Because swiping up from the bottom of // C leads to scene A (and not B), the previous transitions is *not* intercepted and we // instead animate from C to A. - val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE) + val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)) assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse() - onDragStarted(startedPosition = bottom, overSlop = up(0.1f)) + onDragStarted(pointersInfo = bottom, overSlop = up(0.1f)) assertTransition( currentScene = SceneC, @@ -1102,8 +1106,8 @@ class DraggableHandlerTest { navigateToSceneC() // Swipe up from the middle to transition to scene B. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true) // The current transition can be intercepted. @@ -1119,15 +1123,15 @@ class DraggableHandlerTest { @Test fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest { - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse() onDragStarted(overSlop = up(0.1f)) - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue() layoutState.startTransitionImmediately( animationScope = testScope.backgroundScope, transition(SceneA, SceneB), ) - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse() } @Test @@ -1159,7 +1163,7 @@ class DraggableHandlerTest { assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB) // Intercept the transition and swipe down back to scene A. - assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue() + assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue() val dragController2 = onDragStartedImmediately() // Block the transition when the user release their finger. @@ -1203,9 +1207,7 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen - pointerInfoOwner = { - PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false) - } + pointerInfoOwner = { pointersInfo() } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1222,13 +1224,7 @@ class DraggableHandlerTest { advanceUntilIdle() // Drag from the **bottom** of the screen - pointerInfoOwner = { - PointersInfo( - startedPosition = Offset(0f, SCREEN_SIZE), - pointersDown = 1, - isMouseWheel = false, - ) - } + pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1248,9 +1244,7 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Use mouse wheel - pointerInfoOwner = { - PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true) - } + pointerInfoOwner = { pointersInfo(isMouseWheel = true) } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1260,8 +1254,8 @@ class DraggableHandlerTest { @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f)) assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true) dragController.onDragStoppedAnimateLater(velocity = 0f) @@ -1274,10 +1268,10 @@ class DraggableHandlerTest { layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) } // Swipe up to scene B at progress = 200%. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) val dragController = onDragStarted( - startedPosition = middle, + pointersInfo = middle, overSlop = up(2f), // Overscroll is disabled, it will scroll up to 100% expectedConsumedOverSlop = up(1f), @@ -1305,8 +1299,8 @@ class DraggableHandlerTest { layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) } // Swipe up to scene B at progress = 200%. - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.99f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f)) assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f) // Release the finger. @@ -1351,9 +1345,9 @@ class DraggableHandlerTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) @@ -1383,9 +1377,9 @@ class DraggableHandlerTest { overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = down(0.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) @@ -1414,9 +1408,9 @@ class DraggableHandlerTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(1.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) @@ -1446,9 +1440,9 @@ class DraggableHandlerTest { overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) } } - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) - val dragController = onDragStarted(startedPosition = middle, overSlop = down(1.5f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) @@ -1480,8 +1474,8 @@ class DraggableHandlerTest { mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB)) - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneB) @@ -1513,8 +1507,8 @@ class DraggableHandlerTest { mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC)) - val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) - val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f)) + val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)) + val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f)) val transition = assertThat(transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) assertThat(transition).hasToScene(SceneC) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index 3df608717414..5ec74f8d2260 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -98,7 +98,7 @@ class MultiPointerDraggableTest { Modifier.multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -167,7 +167,7 @@ class MultiPointerDraggableTest { orientation = Orientation.Vertical, // We want to start a drag gesture immediately startDragImmediately = { true }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -239,7 +239,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -358,7 +358,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { dragged = true }, @@ -463,7 +463,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> verticalStarted = true SimpleDragController( onDrag = { verticalDragged = true }, @@ -475,7 +475,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Horizontal, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> horizontalStarted = true SimpleDragController( onDrag = { horizontalDragged = true }, @@ -574,7 +574,7 @@ class MultiPointerDraggableTest { return swipeConsume } }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> started = true SimpleDragController( onDrag = { /* do nothing */ }, @@ -668,7 +668,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> SimpleDragController( onDrag = { consumedOnDrag = it }, onStop = { consumedOnDragStop = it }, @@ -739,7 +739,7 @@ class MultiPointerDraggableTest { .multiPointerDraggable( orientation = Orientation.Vertical, startDragImmediately = { false }, - onDragStarted = { _, _, _ -> + onDragStarted = { _, _ -> SimpleDragController( onDrag = { /* do nothing */ }, onStop = { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 2bc9b3826548..aaeaba93304d 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag @@ -61,6 +62,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.TestScenes.SceneD import com.android.compose.animation.scene.subjects.assertThat import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -127,6 +129,7 @@ class SwipeToSceneTest { mapOf( Swipe.Down to SceneA, Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB, + Swipe(SwipeDirection.Down, pointersType = PointerType.Mouse) to SceneD, Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB, Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB, ) @@ -134,6 +137,12 @@ class SwipeToSceneTest { ) { Box(Modifier.fillMaxSize()) } + scene( + key = SceneD, + userActions = if (swipesEnabled()) mapOf(Swipe.Up to SceneC) else emptyMap(), + ) { + Box(Modifier.fillMaxSize()) + } } } @@ -502,6 +511,45 @@ class SwipeToSceneTest { } @Test + fun mousePointerSwipe() { + // Start at scene C. + val layoutState = layoutState(SceneC) + + // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is + // detected as a drag event. + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + TestContent(layoutState) + } + + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) + + rule.onRoot().performMouseInput { + enter(middle) + press() + moveBy(Offset(0f, touchSlop + 10.dp.toPx()), 1_000) + } + + // We are transitioning to D because we are moving the mouse while the primary button is + // pressed. + val transition = assertThat(layoutState.transitionState).isSceneTransition() + assertThat(transition).hasFromScene(SceneC) + assertThat(transition).hasToScene(SceneD) + + rule.onRoot().performMouseInput { + release() + exit(middle) + } + // Release the mouse primary button and wait for the animation to end. We are back to C + // because we only swiped 10dp. + rule.waitForIdle() + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) + } + + @Test fun mouseWheel_pointerInputApi_ignoredByStl() { val layoutState = layoutState() var touchSlop = 0f diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt index d66d6b3ab219..d31711496ff0 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt @@ -28,8 +28,8 @@ import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.transformation.OverscrollTranslate -import com.android.compose.animation.scene.transformation.Transformation import com.android.compose.animation.scene.transformation.TransformationRange +import com.android.compose.animation.scene.transformation.TransformationWithRange import com.android.compose.test.transition import com.google.common.truth.Correspondence import com.google.common.truth.Truth.assertThat @@ -310,7 +310,8 @@ class TransitionDslTest { } val overscrollSpec = transitions.overscrollSpecs.single() - val transformation = overscrollSpec.transformationSpec.transformations.single() + val transformation = + overscrollSpec.transformationSpec.transformations.single().transformation assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java) } @@ -344,7 +345,7 @@ class TransitionDslTest { companion object { private val TRANSFORMATION_RANGE = - Correspondence.transforming<Transformation, TransformationRange?>( + Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>( { it?.range }, "has range equal to", ) diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt index f39dd676fb6e..95ef2ce821e1 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt @@ -65,4 +65,6 @@ val EmptyTestTransitions = transitions { } from(TestScenes.SceneC, to = TestScenes.SceneA) { spec = snap() } + + from(TestScenes.SceneC, to = TestScenes.SceneD) { spec = snap() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index 7f0937059494..0e3b03f74c02 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -44,6 +44,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Ignore @@ -107,6 +108,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { fun translationAndScale_whenNotDozing() = testScope.runTest { val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to not dozing (on lockscreen) keyguardTransitionRepository.sendTransitionStep( @@ -180,6 +182,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -221,6 +224,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -263,6 +267,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80)) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -305,6 +310,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -423,6 +429,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { .thenReturn(if (isWeatherClock) true else false) val movement by collectLastValue(underTest.movement) + assertThat(movement?.translationX).isEqualTo(0) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -434,6 +441,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { ), validateStep = false, ) + runCurrent() // Trigger a change to the burn-in model burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt index e12c67b24893..104aa517fa4f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt @@ -16,7 +16,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.wm.shell.recents.RecentTasks -import com.android.wm.shell.shared.GroupedRecentTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo import com.android.wm.shell.shared.split.SplitBounds import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50 import com.google.common.truth.Truth.assertThat @@ -219,9 +219,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { } @Suppress("UNCHECKED_CAST") - private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) { + private fun givenRecentTasks(vararg tasks: GroupedTaskInfo) { whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer { - val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>> + val consumer = it.arguments.last() as Consumer<List<GroupedTaskInfo>> consumer.accept(tasks.toList()) } } @@ -247,7 +247,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId: Int = 0, isVisible: Boolean = false, userType: RecentTask.UserType = STANDARD, - ): GroupedRecentTaskInfo { + ): GroupedTaskInfo { val userInfo = mock<UserInfo> { whenever(isCloneProfile).thenReturn(userType == CLONED) @@ -255,7 +255,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { whenever(isPrivateProfile).thenReturn(userType == PRIVATE) } whenever(userManager.getUserInfo(userId)).thenReturn(userInfo) - return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible)) + return GroupedTaskInfo.forFullscreenTasks(createTaskInfo(taskId, userId, isVisible)) } private fun createTaskPair( @@ -263,9 +263,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() { userId1: Int = 0, taskId2: Int, userId2: Int = 0, - isVisible: Boolean = false - ): GroupedRecentTaskInfo = - GroupedRecentTaskInfo.forSplitTasks( + isVisible: Boolean = false, + ): GroupedTaskInfo = + GroupedTaskInfo.forSplitTasks( createTaskInfo(taskId1, userId1, isVisible), createTaskInfo(taskId2, userId2, isVisible), SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt index 32f4164d509f..29b6cbe47ca6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -168,8 +168,7 @@ class NotifChipsViewModelTest : SysuiTestCase() { companion object { fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) { - assertThat(latest) - .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.IconOnly::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon) .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) } diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index 65005f840598..572f063c20c4 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -32,6 +32,34 @@ android:id="@+id/min_edge_guideline" app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing" android:orientation="vertical"/> + <!-- This toast-like indication layout was forked from text_toast.xml and will have the same + appearance as system toast. --> + <FrameLayout + android:id="@+id/indication_container" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:maxWidth="@*android:dimen/toast_width" + android:background="@android:drawable/toast_frame" + android:elevation="@*android:dimen/toast_elevation" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toBottomOf="parent"> + <TextView + android:id="@+id/indication_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="2" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:textAppearance="@*android:style/TextAppearance.Toast"/> + </FrameLayout> <!-- Negative horizontal margin because this container background must render beyond the thing it's constrained by (the actions themselves). --> <FrameLayout @@ -47,7 +75,7 @@ app:layout_constraintStart_toStartOf="@id/min_edge_guideline" app:layout_constraintTop_toTopOf="@id/actions_container" app:layout_constraintEnd_toEndOf="@id/actions_container" - app:layout_constraintBottom_toBottomOf="parent"/> + app:layout_constraintBottom_toTopOf="@id/indication_container"/> <HorizontalScrollView android:id="@+id/actions_container" android:layout_width="0dp" @@ -144,7 +172,7 @@ android:visibility="gone" android:elevation="7dp" android:padding="8dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/indication_container" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml index 9b3af52e9704..7c266e60b503 100644 --- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml +++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml @@ -65,7 +65,7 @@ android:background="@drawable/volume_drawer_selection_bg" android:contentDescription="@string/volume_ringer_change" android:gravity="center" - android:padding="10dp" + android:padding="@dimen/volume_dialog_ringer_horizontal_padding" android:src="@drawable/ic_volume_media" android:tint="?androidprv:attr/materialColorOnPrimary" /> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java index 7ec977a8d6aa..9e8cabf141ed 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java @@ -243,10 +243,6 @@ public class Task { public Rect appBounds; - // Last snapshot data, only used for recent tasks - public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData = - new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData(); - @ViewDebug.ExportedProperty(category="recents") public boolean isVisible; @@ -283,7 +279,6 @@ public class Task { public Task(Task other) { this(other.key, other.colorPrimary, other.colorBackground, other.isDockable, other.isLocked, other.taskDescription, other.topActivity); - lastSnapshotData.set(other.lastSnapshotData); positionInParent = other.positionInParent; appBounds = other.appBounds; isVisible = other.isVisible; @@ -315,33 +310,10 @@ public class Task { : key.baseIntent.getComponent(); } - public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) { - lastSnapshotData.set(rawTask.lastSnapshotData); - } - public TaskKey getKey() { return key; } - /** - * Returns the visible width to height ratio. Returns 0f if snapshot data is not available. - */ - public float getVisibleThumbnailRatio(boolean clipInsets) { - if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) { - return 0f; - } - - float availableWidth = lastSnapshotData.taskSize.x; - float availableHeight = lastSnapshotData.taskSize.y; - if (clipInsets) { - availableWidth -= - (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right); - availableHeight -= - (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom); - } - return availableWidth / availableHeight; - } - @Override public boolean equals(Object o) { if (o == this) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8b593701540b..eda07cfe8d91 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -121,6 +121,7 @@ import com.android.settingslib.WirelessUtils; import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; +import com.android.systemui.Flags; import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -473,6 +474,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + @Deprecated private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray(); private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray(); private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray(); @@ -2688,7 +2690,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @see Intent#ACTION_USER_UNLOCKED */ public boolean isUserUnlocked(int userId) { - return mUserIsUnlocked.get(userId); + if (Flags.userEncryptedSource()) { + boolean userStorageUnlocked = mUserManager.isUserUnlocked(userId); + mLogger.logUserStorageUnlocked(userId, userStorageUnlocked); + return userStorageUnlocked; + } else { + return mUserIsUnlocked.get(userId); + } } /** @@ -4213,7 +4221,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags)); pw.println("ActiveUnlockRunning=" + mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId())); - pw.println("userUnlockedCache[userid=" + userId + "]=" + isUserUnlocked(userId)); + pw.println("userUnlockedCache[userid=" + userId + "]=" + mUserIsUnlocked.get(userId)); pw.println("actualUserUnlocked[userid=" + userId + "]=" + mUserManager.isUserUnlocked(userId)); new DumpsysTableLogger( diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index bebfd859f9ed..cd19aaac6831 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -116,7 +116,7 @@ constructor( fun logUpdateLockScreenUserLockedMsg( userId: Int, - userUnlocked: Boolean, + userStorageUnlocked: Boolean, encryptedOrLockdown: Boolean, ) { buffer.log( @@ -124,12 +124,12 @@ constructor( LogLevel.DEBUG, { int1 = userId - bool1 = userUnlocked + bool1 = userStorageUnlocked bool2 = encryptedOrLockdown }, { "updateLockScreenUserLockedMsg userId=$int1 " + - "userUnlocked:$bool1 encryptedOrLockdown:$bool2" + "userStorageUnlocked:$bool1 encryptedOrLockdown:$bool2" } ) } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 12fc3c262367..b3ddde38509a 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -582,6 +582,18 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userUnlocked userId: $int1" }) } + fun logUserStorageUnlocked(userId: Int, result: Boolean) { + logBuffer.log( + TAG, + DEBUG, + { + int1 = userId + bool1 = result + }, + { "Invoked UserManager#isUserUnlocked $int1, result: $bool1" }, + ) + } + fun logUserStopped(userId: Int, isUnlocked: Boolean) { logBuffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt index 5414b623ff97..39fd44a83c58 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt @@ -63,6 +63,7 @@ constructor( private val captioningManager: StateFlow<CaptioningManager?> = userRepository.selectedUser .map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) } + .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null) override val captioningModel: StateFlow<CaptioningModel?> = diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt new file mode 100644 index 000000000000..ddd6bc9ef16b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */ +interface ClipboardIndicationCallback { + + /** Notifies the indication text changed. */ + fun onIndicationTextChanged(text: CharSequence) +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt new file mode 100644 index 000000000000..be3272369d46 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +/** Interface to provide the clipboard indication to be shown under the overlay. */ +interface ClipboardIndicationProvider { + + /** + * Gets the indication text async. + * + * @param callback callback for getting the indication text. + */ + fun getIndicationText(callback: ClipboardIndicationCallback) +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt new file mode 100644 index 000000000000..da94d5b518b7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.clipboardoverlay + +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +open class ClipboardIndicationProviderImpl @Inject constructor() : ClipboardIndicationProvider { + + override fun getIndicationText(callback: ClipboardIndicationCallback) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 65c01ed9eecd..ac747845267c 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS; import static com.android.systemui.Flags.clipboardImageTimeout; import static com.android.systemui.Flags.clipboardSharedTransitions; +import static com.android.systemui.Flags.showClipboardIndication; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER; @@ -99,6 +100,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv private final ClipboardTransitionExecutor mTransitionExecutor; private final ClipboardOverlayView mView; + private final ClipboardIndicationProvider mClipboardIndicationProvider; private Runnable mOnSessionCompleteListener; private Runnable mOnRemoteCopyTapped; @@ -173,6 +175,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv } }; + private ClipboardIndicationCallback mIndicationCallback = new ClipboardIndicationCallback() { + @Override + public void onIndicationTextChanged(@NonNull CharSequence text) { + mView.setIndicationText(text); + } + }; + @Inject public ClipboardOverlayController(@OverlayWindowContext Context context, ClipboardOverlayView clipboardOverlayView, @@ -185,11 +194,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv @Background Executor bgExecutor, ClipboardImageLoader clipboardImageLoader, ClipboardTransitionExecutor transitionExecutor, + ClipboardIndicationProvider clipboardIndicationProvider, UiEventLogger uiEventLogger) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; mClipboardImageLoader = clipboardImageLoader; mTransitionExecutor = transitionExecutor; + mClipboardIndicationProvider = clipboardIndicationProvider; mClipboardLogger = new ClipboardLogger(uiEventLogger); @@ -288,6 +299,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting; mClipboardModel = model; mClipboardLogger.setClipSource(mClipboardModel.getSource()); + if (showClipboardIndication()) { + mClipboardIndicationProvider.getIndicationText(mIndicationCallback); + } if (clipboardImageTimeout()) { if (shouldAnimate) { reset(); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java index 1762d82b3237..7e4d76280909 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -18,6 +18,8 @@ package com.android.systemui.clipboardoverlay; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static com.android.systemui.Flags.showClipboardIndication; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -53,6 +55,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.view.ViewCompat; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; @@ -103,6 +106,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private View mShareChip; private View mRemoteCopyChip; private View mActionContainerBackground; + private View mIndicationContainer; + private TextView mIndicationText; private View mDismissButton; private LinearLayout mActionContainer; private ClipboardOverlayCallbacks mClipboardCallbacks; @@ -136,6 +141,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mShareChip = requireViewById(R.id.share_chip); mRemoteCopyChip = requireViewById(R.id.remote_copy_chip); mDismissButton = requireViewById(R.id.dismiss_button); + mIndicationContainer = requireViewById(R.id.indication_container); + mIndicationText = mIndicationContainer.findViewById(R.id.indication_text); bindDefaultActionChips(); @@ -208,6 +215,14 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { } } + void setIndicationText(CharSequence text) { + mIndicationText.setText(text); + + // Set the visibility of clipboard indication based on the text is empty or not. + int visibility = text.isEmpty() ? View.GONE : View.VISIBLE; + mIndicationContainer.setVisibility(visibility); + } + void setMinimized(boolean minimized) { if (minimized) { mMinimizedPreview.setVisibility(View.VISIBLE); @@ -221,6 +236,18 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mPreviewBorder.setVisibility(View.VISIBLE); mActionContainer.setVisibility(View.VISIBLE); } + + if (showClipboardIndication()) { + // Adjust the margin of clipboard indication based on the minimized state. + int marginStart = minimized ? getResources().getDimensionPixelSize( + R.dimen.overlay_action_container_margin_horizontal) + : getResources().getDimensionPixelSize( + R.dimen.overlay_action_container_minimum_edge_spacing); + ConstraintLayout.LayoutParams params = + (ConstraintLayout.LayoutParams) mIndicationContainer.getLayoutParams(); + params.setMarginStart(marginStart); + mIndicationContainer.setLayoutParams(params); + } } void setInsets(WindowInsets insets, int orientation) { @@ -313,6 +340,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { setTranslationX(0); setAlpha(0); mActionContainerBackground.setVisibility(View.GONE); + mIndicationContainer.setVisibility(View.GONE); mDismissButton.setVisibility(View.GONE); mShareChip.setVisibility(View.GONE); mRemoteCopyChip.setVisibility(View.GONE); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt index 527819c73e2f..c81f0d98f3ad 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt @@ -15,18 +15,26 @@ */ package com.android.systemui.clipboardoverlay.dagger +import com.android.systemui.clipboardoverlay.ClipboardIndicationProvider +import com.android.systemui.clipboardoverlay.ClipboardIndicationProviderImpl import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl import dagger.Binds import dagger.Module -/** Dagger Module for code in the clipboard overlay package. */ +/** Dagger Module to provide default implementations which could be overridden. */ @Module -interface ClipboardOverlaySuppressionModule { +interface ClipboardOverlayOverrideModule { /** Provides implementation for [ClipboardOverlaySuppressionController]. */ @Binds fun provideClipboardOverlaySuppressionController( clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl ): ClipboardOverlaySuppressionController + + /** Provides implementation for [ClipboardIndicationProvider]. */ + @Binds + fun provideClipboardIndicationProvider( + clipboardIndicationProviderImpl: ClipboardIndicationProviderImpl + ): ClipboardIndicationProvider } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 609b7330b600..2e323d40edcd 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -29,7 +29,7 @@ import com.android.systemui.accessibility.AccessibilityModule; import com.android.systemui.accessibility.SystemActionsModule; import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule; import com.android.systemui.battery.BatterySaverModule; -import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlaySuppressionModule; +import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayOverrideModule; import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; @@ -125,7 +125,7 @@ import javax.inject.Named; AospPolicyModule.class, BatterySaverModule.class, CentralSurfacesModule.class, - ClipboardOverlaySuppressionModule.class, + ClipboardOverlayOverrideModule.class, CollapsedStatusBarFragmentStartableModule.class, ConnectingDisplayViewModel.StartableModule.class, DefaultBlueprintModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 6ac0a3f8443f..021cce6d1e23 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -20,6 +20,7 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor @@ -41,7 +42,6 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.debounce -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class FromDozingTransitionInteractor @@ -135,11 +135,22 @@ constructor( if (!deviceEntryInteractor.isLockscreenEnabled()) { if (!SceneContainerFlag.isEnabled) { - startTransitionTo(KeyguardState.GONE) + startTransitionTo( + KeyguardState.GONE, + ownerReason = "lockscreen not enabled", + ) } } else if (canDismissLockscreen() || isKeyguardGoingAway) { if (!SceneContainerFlag.isEnabled) { - startTransitionTo(KeyguardState.GONE) + startTransitionTo( + KeyguardState.GONE, + ownerReason = + if (canDismissLockscreen()) { + "canDismissLockscreen()" + } else { + "isKeyguardGoingAway" + }, + ) } } else if (primaryBouncerShowing) { if (!SceneContainerFlag.isEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 0dae17c594c8..cd62d5f3b6e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -16,9 +16,11 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.log.core.LogLevel.VERBOSE @@ -29,7 +31,6 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNoti import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.debounce -import com.android.app.tracing.coroutines.launchTraced as launch private val TAG = KeyguardTransitionAuditLogger::class.simpleName!! @@ -48,6 +49,7 @@ constructor( private val aodBurnInViewModel: AodBurnInViewModel, private val shadeInteractor: ShadeInteractor, private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + private val deviceEntryInteractor: DeviceEntryInteractor, ) { fun start() { @@ -84,6 +86,18 @@ constructor( } scope.launch { + deviceEntryInteractor.isUnlocked.collect { + logger.log(TAG, VERBOSE, "DeviceEntry isUnlocked", it) + } + } + + scope.launch { + deviceEntryInteractor.isLockscreenEnabled.collect { + logger.log(TAG, VERBOSE, "DeviceEntry isLockscreenEnabled", it) + } + } + + scope.launch { keyguardInteractor.primaryBouncerShowing.collect { logger.log(TAG, VERBOSE, "Primary bouncer showing", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index abd7f90bbf22..7d4d377c768a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.Log +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -33,7 +34,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import com.android.app.tracing.coroutines.launchTraced as launch /** * Each TransitionInteractor is responsible for determining under which conditions to notify @@ -201,9 +201,18 @@ sealed class TransitionInteractor( scope.launch { keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect { if (!maybeHandleInsecurePowerGesture()) { + val lastStep = transitionInteractor.transitionState.value + val modeOnCanceled = + if (lastStep.to == KeyguardState.AOD) { + // Enabled smooth transition when double-tap camera cancels + // transition to AOD + TransitionModeOnCanceled.REVERSE + } else { + TransitionModeOnCanceled.RESET + } startTransitionTo( toState = KeyguardState.OCCLUDED, - modeOnCanceled = TransitionModeOnCanceled.RESET, + modeOnCanceled = modeOnCanceled, ownerReason = "keyguardInteractor.onCameraLaunchDetected", ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index c78e0c9f5266..478372d4dc0c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.ClockSize +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R @@ -42,8 +43,10 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn @@ -164,9 +167,17 @@ constructor( private fun burnIn(params: BurnInParameters): Flow<BurnInModel> { return combine( - keyguardTransitionInteractor.transitionValue(KeyguardState.AOD).map { - Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it) - }, + merge( + keyguardTransitionInteractor.transition(Edge.create(to = KeyguardState.AOD)), + keyguardTransitionInteractor + .transition(Edge.create(from = KeyguardState.AOD)) + .map { it.copy(value = 1f - it.value) }, + keyguardTransitionInteractor + .transition(Edge.create(to = KeyguardState.LOCKSCREEN)) + .filter { it.from != KeyguardState.AOD } + .map { it.copy(value = 0f) }, + ) + .map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) }, burnInInteractor.burnIn( xDimenResourceId = R.dimen.burn_in_prevention_offset_x, yDimenResourceId = R.dimen.burn_in_prevention_offset_y, diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt index bf2aa7efc0c4..56885c3eea9f 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt @@ -18,7 +18,7 @@ package com.android.systemui.mediaprojection.appselector.data import android.annotation.ColorInt import android.annotation.UserIdInt -import android.app.ActivityManager.RecentTaskInfo +import android.app.TaskInfo import android.content.ComponentName import com.android.wm.shell.shared.split.SplitBounds @@ -34,7 +34,7 @@ data class RecentTask( val splitBounds: SplitBounds?, ) { constructor( - taskInfo: RecentTaskInfo, + taskInfo: TaskInfo, isForegroundTask: Boolean, userType: UserType, splitBounds: SplitBounds? = null diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt index 82e58cc7f1d9..d94424c59376 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker import com.android.systemui.util.kotlin.getOrNull import com.android.wm.shell.recents.RecentTasks -import com.android.wm.shell.shared.GroupedRecentTaskInfo +import com.android.wm.shell.shared.GroupedTaskInfo import java.util.Optional import java.util.concurrent.Executor import javax.inject.Inject @@ -51,7 +51,7 @@ constructor( override suspend fun loadRecentTasks(): List<RecentTask> = withContext(coroutineDispatcher) { - val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList() + val groupedTasks: List<GroupedTaskInfo> = recents?.getTasks() ?: emptyList() // Note: the returned task list is from the most-recent to least-recent order. // When opening the app selector in full screen, index 0 will be just the app selector // activity and a null second task, so the foreground task will be index 1, but when @@ -86,7 +86,7 @@ constructor( } } - private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> = + private suspend fun RecentTasks.getTasks(): List<GroupedTaskInfo> = suspendCoroutine { continuation -> getRecentTasks( Integer.MAX_VALUE, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 520cbf9d80d9..8c5a711d6a75 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -619,10 +619,11 @@ public class KeyguardIndicationController { } private void updateLockScreenUserLockedMsg(int userId) { - boolean userUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId); + boolean userStorageUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId); boolean encryptedOrLockdown = mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId); - mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userUnlocked, encryptedOrLockdown); - if (!userUnlocked || encryptedOrLockdown) { + mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userStorageUnlocked, + encryptedOrLockdown); + if (!userStorageUnlocked || encryptedOrLockdown) { mRotateTextViewController.updateIndication( INDICATION_TYPE_USER_LOCKED, new KeyguardIndication.Builder() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt index c8d3f339b3e9..752674854e2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -68,13 +68,8 @@ constructor( notifChipsInteractor.onPromotedNotificationChipTapped(this@toChipModel.key) } } - return OngoingActivityChipModel.Shown.ShortTimeDelta( - icon, - colors, - time = this.whenTime, - onClickListener, - ) - // TODO(b/364653005): If Notification.showWhen = false, don't show the time delta. + return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener) + // TODO(b/364653005): Use Notification.showWhen to determine if we should show the time. // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text. // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`. // TODO(b/364653005): If the app that posted the notification is in the foreground, don't diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt index 617aaa71d2d3..d5b8597e36ed 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt @@ -53,7 +53,9 @@ interface AudioModule { fun provideAudioManagerIntentsReceiver( @Application context: Context, @Application coroutineScope: CoroutineScope, - ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope) + @Background coroutineContext: CoroutineContext, + ): AudioManagerEventsReceiver = + AudioManagerEventsReceiverImpl(context, coroutineScope, coroutineContext) @Provides @SysUISingleton @@ -82,7 +84,7 @@ interface AudioModule { localBluetoothManager: LocalBluetoothManager?, @Application coroutineScope: CoroutineScope, @Background coroutineContext: CoroutineContext, - volumeLogger: VolumeLogger + volumeLogger: VolumeLogger, ): AudioSharingRepository = if (Flags.enableLeAudioSharing() && localBluetoothManager != null) { AudioSharingRepositoryImpl( @@ -90,7 +92,7 @@ interface AudioModule { localBluetoothManager, coroutineScope, coroutineContext, - volumeLogger + volumeLogger, ) } else { AudioSharingRepositoryEmptyImpl() @@ -111,8 +113,7 @@ interface AudioModule { @Provides @SysUISingleton - fun provideAudioSystemRepository( - @Application context: Context, - ): AudioSystemRepository = AudioSystemRepositoryImpl(context) + fun provideAudioSystemRepository(@Application context: Context): AudioSystemRepository = + AudioSystemRepositoryImpl(context) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt index dacd6c78b034..b9f47d7ad110 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt @@ -22,15 +22,17 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.Bundle import android.os.Handler +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import com.android.app.tracing.coroutines.launchTraced as launch +import kotlinx.coroutines.flow.flowOn interface MediaControllerInteractor { @@ -43,14 +45,16 @@ class MediaControllerInteractorImpl @Inject constructor( @Background private val backgroundHandler: Handler, + @Background private val backgroundCoroutineContext: CoroutineContext, ) : MediaControllerInteractor { override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> { return conflatedCallbackFlow { - val callback = MediaControllerCallbackProducer(this) - mediaController.registerCallback(callback, backgroundHandler) - awaitClose { mediaController.unregisterCallback(callback) } - } + val callback = MediaControllerCallbackProducer(this) + mediaController.registerCallback(callback, backgroundHandler) + awaitClose { mediaController.unregisterCallback(callback) } + } + .flowOn(backgroundCoroutineContext) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index aa07cfd26bdb..b3848a6d7817 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -20,10 +20,12 @@ import android.content.pm.PackageManager import android.media.VolumeProvider import android.media.session.MediaController import android.util.Log +import androidx.annotation.WorkerThread import com.android.settingslib.media.MediaDevice import com.android.settingslib.volume.data.repository.LocalMediaRepository import com.android.settingslib.volume.data.repository.MediaControllerRepository import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.concurrency.Execution import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession @@ -62,6 +64,7 @@ constructor( @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, private val mediaControllerInteractor: MediaControllerInteractor, + private val execution: Execution, ) { private val activeMediaControllers: Flow<MediaControllers> = @@ -82,9 +85,10 @@ constructor( .map { MediaDeviceSessions( local = it.local?.mediaDeviceSession(), - remote = it.remote?.mediaDeviceSession() + remote = it.remote?.mediaDeviceSession(), ) } + .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null)) /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */ @@ -115,55 +119,43 @@ constructor( val currentConnectedDevice: Flow<MediaDevice?> = localMediaRepository.flatMapLatest { it.currentConnectedDevice }.distinctUntilChanged() - private suspend fun getApplicationLabel(packageName: String): CharSequence? { - return try { - withContext(backgroundCoroutineContext) { - val appInfo = - packageManager.getApplicationInfo( - packageName, - PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER - ) - appInfo.loadLabel(packageManager) - } - } catch (e: PackageManager.NameNotFoundException) { - Log.e(TAG, "Unable to find info for package: $packageName") - null - } - } - /** Finds local and remote media controllers. */ - private fun getMediaControllers( - controllers: Collection<MediaController>, - ): MediaControllers { - var localController: MediaController? = null - var remoteController: MediaController? = null - val remoteMediaSessions: MutableSet<String> = mutableSetOf() - for (controller in controllers) { - val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue - when (playbackInfo.playbackType) { - MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> { - // MediaController can't be local if there is a remote one for the same package - if (localController?.packageName.equals(controller.packageName)) { - localController = null + private suspend fun getMediaControllers( + controllers: Collection<MediaController> + ): MediaControllers = + withContext(backgroundCoroutineContext) { + var localController: MediaController? = null + var remoteController: MediaController? = null + val remoteMediaSessions: MutableSet<String> = mutableSetOf() + for (controller in controllers) { + val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue + when (playbackInfo.playbackType) { + MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> { + // MediaController can't be local if there is a remote one for the same + // package + if (localController?.packageName.equals(controller.packageName)) { + localController = null + } + if (!remoteMediaSessions.contains(controller.packageName)) { + remoteMediaSessions.add(controller.packageName) + remoteController = chooseController(remoteController, controller) + } } - if (!remoteMediaSessions.contains(controller.packageName)) { - remoteMediaSessions.add(controller.packageName) - remoteController = chooseController(remoteController, controller) + MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> { + if (controller.packageName in remoteMediaSessions) continue + localController = chooseController(localController, controller) } } - MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> { - if (controller.packageName in remoteMediaSessions) continue - localController = chooseController(localController, controller) - } } + MediaControllers(local = localController, remote = remoteController) } - return MediaControllers(local = localController, remote = remoteController) - } + @WorkerThread private fun chooseController( currentController: MediaController?, newController: MediaController, ): MediaController { + require(!execution.isMainThread()) if (currentController == null) { return newController } @@ -175,12 +167,26 @@ constructor( return currentController } - private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? { + @WorkerThread + private fun MediaController.mediaDeviceSession(): MediaDeviceSession? { + require(!execution.isMainThread()) + val applicationLabel = + try { + packageManager + .getApplicationInfo( + packageName, + PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER, + ) + .loadLabel(packageManager) + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Unable to find info for package: $packageName") + null + } ?: return null return MediaDeviceSession( packageName = packageName, sessionToken = sessionToken, canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED, - appLabel = getApplicationLabel(packageName) ?: return null + appLabel = applicationLabel, ) } @@ -195,10 +201,7 @@ constructor( .onStart { emit(this@stateChanges) } } - private data class MediaControllers( - val local: MediaController?, - val remote: MediaController?, - ) + private data class MediaControllers(val local: MediaController?, val remote: MediaController?) private companion object { const val TAG = "MediaOutputInteractor" diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json index aa8044515ea2..aa8044515ea2 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json index a840d3cb1225..7abff2c74531 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenLaunching_withSpring.json", + "goldenIdentifier": "backgroundAnimation_whenLaunching_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimation_whenLaunching[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenLaunching_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json index aa8044515ea2..aa8044515ea2 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json index a840d3cb1225..561961145ca1 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenReturning_withSpring.json", + "goldenIdentifier": "backgroundAnimation_whenReturning_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimation_whenReturning[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenReturning_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json index 7f623575fef4..7f623575fef4 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json index 18eedd450751..825190ba7a32 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenLaunching_withSpring.json", + "goldenIdentifier": "backgroundAnimationWithoutFade_whenLaunching_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimationWithoutFade_whenLaunching[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenLaunching_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json index 98005c53f6e0..98005c53f6e0 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json index 18eedd450751..63c263175122 100644 --- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json +++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json @@ -33,118 +33,118 @@ "bottom": 0 }, { - "left": 94, - "top": 284, - "right": 206, + "left": 104, + "top": 285, + "right": 215, "bottom": 414 }, { - "left": 83, - "top": 251, - "right": 219, + "left": 92, + "top": 252, + "right": 227, "bottom": 447 }, { - "left": 70, - "top": 212, - "right": 234, - "bottom": 485 + "left": 77, + "top": 213, + "right": 242, + "bottom": 486 }, { - "left": 57, - "top": 173, - "right": 250, - "bottom": 522 + "left": 63, + "top": 175, + "right": 256, + "bottom": 524 }, { - "left": 46, - "top": 139, - "right": 264, - "bottom": 555 + "left": 50, + "top": 141, + "right": 269, + "bottom": 558 }, { - "left": 36, - "top": 109, - "right": 276, - "bottom": 584 + "left": 40, + "top": 112, + "right": 279, + "bottom": 587 }, { - "left": 28, - "top": 84, - "right": 285, - "bottom": 608 + "left": 31, + "top": 88, + "right": 288, + "bottom": 611 }, { - "left": 21, - "top": 65, - "right": 293, - "bottom": 627 + "left": 23, + "top": 68, + "right": 296, + "bottom": 631 }, { - "left": 16, - "top": 49, - "right": 300, - "bottom": 642 + "left": 18, + "top": 53, + "right": 301, + "bottom": 646 }, { - "left": 12, - "top": 36, - "right": 305, - "bottom": 653 + "left": 13, + "top": 41, + "right": 306, + "bottom": 658 }, { - "left": 9, - "top": 27, - "right": 308, - "bottom": 662 + "left": 10, + "top": 31, + "right": 309, + "bottom": 667 }, { "left": 7, - "top": 20, + "top": 24, "right": 312, - "bottom": 669 + "bottom": 673 }, { "left": 5, - "top": 14, + "top": 18, "right": 314, - "bottom": 675 + "bottom": 678 }, { "left": 4, - "top": 11, + "top": 13, "right": 315, - "bottom": 678 + "bottom": 681 }, { "left": 3, - "top": 8, + "top": 10, "right": 316, - "bottom": 681 + "bottom": 684 }, { "left": 2, - "top": 5, + "top": 7, "right": 317, - "bottom": 684 + "bottom": 685 }, { "left": 1, - "top": 4, + "top": 5, "right": 318, - "bottom": 685 + "bottom": 687 }, { "left": 1, - "top": 3, + "top": 4, "right": 318, - "bottom": 686 + "bottom": 688 }, { "left": 0, - "top": 2, + "top": 3, "right": 319, - "bottom": 687 + "bottom": 688 } ] }, @@ -371,5 +371,14 @@ 0 ] } - ] + ], + "\/\/metadata": { + "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenReturning_withSpring.json", + "goldenIdentifier": "backgroundAnimationWithoutFade_whenReturning_withSpring", + "testClassName": "TransitionAnimatorTest", + "testMethodName": "backgroundAnimationWithoutFade_whenReturning[true]", + "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots", + "result": "FAILED", + "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenReturning_withSpring.actual.mp4" + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt index 288ed4dc9d4f..a1f59c2cc2b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt @@ -20,18 +20,20 @@ import android.animation.AnimatorRuleRecordingSpec import android.animation.AnimatorTestRuleToolkit import android.animation.MotionControl import android.animation.recordMotion +import android.graphics.Color +import android.graphics.PointF import android.graphics.drawable.GradientDrawable import android.platform.test.annotations.MotionTest import android.view.ViewGroup import android.widget.FrameLayout import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.runOnMainThreadAndWaitForIdleSync import kotlin.test.assertTrue import org.junit.Rule import org.junit.Test @@ -47,13 +49,25 @@ import platform.test.screenshot.PathConfig @SmallTest @MotionTest @RunWith(ParameterizedAndroidJunit4::class) -class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { +class TransitionAnimatorTest( + private val fadeWindowBackgroundLayer: Boolean, + private val isLaunching: Boolean, + private val useSpring: Boolean, +) : SysuiTestCase() { companion object { private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens" - @get:Parameters(name = "{0}") + @get:Parameters(name = "fadeBackground={0}, isLaunching={1}, useSpring={2}") @JvmStatic - val useSpringValues = booleanArrayOf(false, true).toList() + val parameterValues = buildList { + booleanArrayOf(true, false).forEach { fadeBackground -> + booleanArrayOf(true, false).forEach { isLaunching -> + booleanArrayOf(true, false).forEach { useSpring -> + add(arrayOf(fadeBackground, isLaunching, useSpring)) + } + } + } + } } private val kosmos = Kosmos() @@ -66,11 +80,23 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { ActivityTransitionAnimator.SPRING_TIMINGS, ActivityTransitionAnimator.SPRING_INTERPOLATORS, ) - private val withSpring = + private val fade = + if (fadeWindowBackgroundLayer) { + "withFade" + } else { + "withoutFade" + } + private val direction = + if (isLaunching) { + "whenLaunching" + } else { + "whenReturning" + } + private val mode = if (useSpring) { - "_withSpring" + "withSpring" } else { - "" + "withAnimator" } @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java) @@ -83,113 +109,75 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { ) @Test - fun backgroundAnimation_whenLaunching() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = true).apply { - getInstrumentation().runOnMainSync { start() } - } + fun backgroundAnimationTimeSeries() { + val transitionContainer = createScene() + val backgroundLayer = createBackgroundLayer() + val animation = createAnimation(transitionContainer, backgroundLayer) - val recordedMotion = recordMotion(backgroundLayer, animator) + val recordedMotion = record(backgroundLayer, animation) motionRule .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimation_whenLaunching$withSpring") + .timeSeriesMatchesGolden("backgroundAnimationTimeSeries_${fade}_${direction}_$mode") } - @Test - fun backgroundAnimation_whenReturning() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = false).apply { - getInstrumentation().runOnMainSync { start() } - } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimation_whenReturning$withSpring") - } - - @Test - fun backgroundAnimationWithoutFade_whenLaunching() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false) - .apply { getInstrumentation().runOnMainSync { start() } } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenLaunching$withSpring") - } - - @Test - fun backgroundAnimationWithoutFade_whenReturning() { - val backgroundLayer = GradientDrawable().apply { alpha = 0 } - val animator = - setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false) - .apply { getInstrumentation().runOnMainSync { start() } } - - val recordedMotion = recordMotion(backgroundLayer, animator) - - motionRule - .assertThat(recordedMotion) - .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenReturning$withSpring") - } - - private fun setUpTest( - backgroundLayer: GradientDrawable, - isLaunching: Boolean, - fadeWindowBackgroundLayer: Boolean = true, - ): TransitionAnimator.Animation { + private fun createScene(): ViewGroup { lateinit var transitionContainer: ViewGroup activityRule.scenario.onActivity { activity -> - transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) } + transitionContainer = FrameLayout(activity) activity.setContentView(transitionContainer) } waitForIdleSync() + return transitionContainer + } + private fun createBackgroundLayer() = + GradientDrawable().apply { + setColor(Color.BLACK) + alpha = 0 + } + + private fun createAnimation( + transitionContainer: ViewGroup, + backgroundLayer: GradientDrawable, + ): TransitionAnimator.Animation { val controller = TestController(transitionContainer, isLaunching) - return transitionAnimator.createAnimation( - controller, - controller.createAnimatorState(), - createEndState(transitionContainer), - backgroundLayer, - fadeWindowBackgroundLayer, - useSpring = useSpring, - ) - } - private fun createEndState(container: ViewGroup): TransitionAnimator.State { val containerLocation = IntArray(2) - container.getLocationOnScreen(containerLocation) - return TransitionAnimator.State( - left = containerLocation[0], - top = containerLocation[1], - right = containerLocation[0] + 320, - bottom = containerLocation[1] + 690, - topCornerRadius = 0f, - bottomCornerRadius = 0f, - ) + transitionContainer.getLocationOnScreen(containerLocation) + val endState = + TransitionAnimator.State( + left = containerLocation[0], + top = containerLocation[1], + right = containerLocation[0] + 320, + bottom = containerLocation[1] + 690, + topCornerRadius = 0f, + bottomCornerRadius = 0f, + ) + + val startVelocity = + if (useSpring) { + PointF(2500f, 30000f) + } else { + null + } + + return transitionAnimator + .createAnimation( + controller, + controller.createAnimatorState(), + endState, + backgroundLayer, + fadeWindowBackgroundLayer, + startVelocity = startVelocity, + ) + .apply { runOnMainThreadAndWaitForIdleSync { start() } } } - private fun recordMotion( + private fun record( backgroundLayer: GradientDrawable, animation: TransitionAnimator.Animation, ): RecordedMotion { - fun record(motionControl: MotionControl, sampleIntervalMs: Long): RecordedMotion { - return motionRule.recordMotion( - AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) { - feature(DrawableFeatureCaptures.bounds, "bounds") - feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") - feature(DrawableFeatureCaptures.alpha, "alpha") - } - ) - } - val motionControl: MotionControl val sampleIntervalMs: Long if (useSpring) { @@ -204,9 +192,13 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { sampleIntervalMs = 20L } - var recording: RecordedMotion? = null - getInstrumentation().runOnMainSync { recording = record(motionControl, sampleIntervalMs) } - return recording!! + return motionRule.recordMotion( + AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) { + feature(DrawableFeatureCaptures.bounds, "bounds") + feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") + feature(DrawableFeatureCaptures.alpha, "alpha") + } + ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java index 6061063db903..562481567536 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java @@ -20,6 +20,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS; import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE; +import static com.android.systemui.Flags.FLAG_SHOW_CLIPBOARD_INDICATION; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED; import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED; @@ -121,6 +122,24 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + private class FakeClipboardIndicationProvider implements ClipboardIndicationProvider { + private ClipboardIndicationCallback mIndicationCallback; + + public void notifyIndicationTextChanged(CharSequence indicationText) { + if (mIndicationCallback != null) { + mIndicationCallback.onIndicationTextChanged(indicationText); + } + } + + @Override + public void getIndicationText(ClipboardIndicationCallback callback) { + mIndicationCallback = callback; + } + } + + private FakeClipboardIndicationProvider mClipboardIndicationProvider = + new FakeClipboardIndicationProvider(); + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -156,6 +175,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { mExecutor, mClipboardImageLoader, mClipboardTransitionExecutor, + mClipboardIndicationProvider, mUiEventLogger); verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture()); mCallbacks = mOverlayCallbacksCaptor.getValue(); @@ -305,6 +325,17 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_SHOW_CLIPBOARD_INDICATION) + public void test_onIndicationTextChanged_setIndicationTextCorrectly() { + initController(); + mOverlayController.setClipData(mSampleClipData, ""); + + mClipboardIndicationProvider.notifyIndicationTextChanged("copied"); + + verify(mClipboardOverlayView).setIndicationText("copied"); + } + + @Test @DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS) public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() { initController(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt new file mode 100644 index 000000000000..bf66cb6e8ecc --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeExecution: FakeExecution by + Kosmos.Fixture { FakeExecution().apply { simulateMainThread = false } } +var Kosmos.execution: Execution by Kosmos.Fixture { fakeExecution } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt index 1b58582a806f..ed5322ed098e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt @@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.media.mediaOutputDialogManager +import com.android.systemui.util.concurrency.execution import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -53,6 +54,7 @@ val Kosmos.mediaOutputInteractor by testScope.testScheduler, mediaControllerRepository, mediaControllerInteractor, + execution, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt index 652b3ea984e7..fdeb8cef02b9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.os.Handler import android.os.looper import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope var Kosmos.mediaControllerInteractor: MediaControllerInteractor by - Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) } + Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper), testScope.testScheduler) } diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java index ae93991d3945..0855815b67a9 100644 --- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java +++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java @@ -65,27 +65,31 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { @Override public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) { + boolean changed; if (restricted) { if (!mGlobalRestrictions.containsKey(clientToken)) { mGlobalRestrictions.put(clientToken, new SparseBooleanArray()); } SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken); Objects.requireNonNull(restrictedCodes); - boolean changed = !restrictedCodes.get(code); + changed = !restrictedCodes.get(code); restrictedCodes.put(code, true); - return changed; } else { SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken); if (restrictedCodes == null) { return false; } - boolean changed = restrictedCodes.get(code); + changed = restrictedCodes.get(code); restrictedCodes.delete(code); if (restrictedCodes.size() == 0) { mGlobalRestrictions.remove(clientToken); } - return changed; } + + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } + return changed; } @Override @@ -104,7 +108,11 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { @Override public boolean clearGlobalRestrictions(Object clientToken) { - return mGlobalRestrictions.remove(clientToken) != null; + boolean changed = mGlobalRestrictions.remove(clientToken) != null; + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } + return changed; } @RequiresPermission(anyOf = { @@ -122,6 +130,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { changed |= putUserRestrictionExclusions(clientToken, userIds[i], excludedPackageTags); } + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } @@ -191,6 +202,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { changed |= mUserRestrictions.remove(clientToken) != null; changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null; notifyAllUserRestrictions(allUserRestrictedCodes); + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } @@ -244,6 +258,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions { } } + if (changed) { + AppOpsManager.invalidateAppOpModeCache(); + } return changed; } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 702ad9541af8..5e74d67905a6 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -998,6 +998,7 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onUidModeChanged(int uid, int code, int mode, String persistentDeviceId) { + AppOpsManager.invalidateAppOpModeCache(); mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this, code, uid, false, persistentDeviceId)); @@ -1006,6 +1007,7 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public void onPackageModeChanged(String packageName, int userId, int code, int mode) { + AppOpsManager.invalidateAppOpModeCache(); mHandler.sendMessage(PooledLambda.obtainMessage( AppOpsService::notifyOpChangedForPkg, AppOpsService.this, packageName, code, mode, userId)); @@ -1032,6 +1034,11 @@ public class AppOpsService extends IAppOpsService.Stub { // To migrate storageFile to recentAccessesFile, these reads must be called in this order. readRecentAccesses(); mAppOpsCheckingService.readState(); + // The system property used by the cache is created the first time it is written, that only + // happens inside invalidateCache(). Until the service calls invalidateCache() the property + // will not exist and the nonce will be UNSET. + AppOpsManager.invalidateAppOpModeCache(); + AppOpsManager.disableAppOpModeCache(); } public void publish() { @@ -2830,6 +2837,13 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkOperationRaw(int code, int uid, String packageName, @Nullable String attributionTag) { + if (Binder.getCallingPid() != Process.myPid() + && Flags.appopAccessTrackingLoggingEnabled()) { + FrameworkStatsLog.write( + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code, + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION, + false); + } return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, Context.DEVICE_ID_DEFAULT, true /*raw*/); } @@ -2837,6 +2851,13 @@ public class AppOpsService extends IAppOpsService.Stub { @Override public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName, @Nullable String attributionTag, int virtualDeviceId) { + if (Binder.getCallingPid() != Process.myPid() + && Flags.appopAccessTrackingLoggingEnabled()) { + FrameworkStatsLog.write( + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code, + APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION, + false); + } return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag, virtualDeviceId, true /*raw*/); } @@ -2894,8 +2915,14 @@ public class AppOpsService extends IAppOpsService.Stub { return AppOpsManager.MODE_IGNORED; } } - return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, - virtualDeviceId, raw); + + if (Flags.appopModeCachingEnabled()) { + return getAppOpMode(code, uid, resolvedPackageName, attributionTag, virtualDeviceId, + raw, true); + } else { + return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, + virtualDeviceId, raw); + } } /** @@ -2961,6 +2988,54 @@ public class AppOpsService extends IAppOpsService.Stub { } } + /** + * This method unifies mode checking logic between checkOperationUnchecked and + * noteOperationUnchecked. It can replace those two methods once the flag is fully rolled out. + * + * @param isCheckOp This param is only used in user's op restriction. When checking if a package + * can bypass user's restriction we should account for attributionTag as well. + * But existing checkOp APIs don't accept attributionTag so we added a hack to + * skip attributionTag check for checkOp. After we add an overload of checkOp + * that accepts attributionTag we should remove this param. + */ + private @Mode int getAppOpMode(int code, int uid, @NonNull String packageName, + @Nullable String attributionTag, int virtualDeviceId, boolean raw, boolean isCheckOp) { + PackageVerificationResult pvr; + try { + pvr = verifyAndGetBypass(uid, packageName, attributionTag); + } catch (SecurityException e) { + logVerifyAndGetBypassFailure(uid, e, "getAppOpMode"); + return MODE_IGNORED; + } + + if (isOpRestrictedDueToSuspend(code, packageName, uid)) { + return MODE_IGNORED; + } + + synchronized (this) { + if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId, + pvr.bypass, isCheckOp)) { + return MODE_IGNORED; + } + if (isOpAllowedForUid(uid)) { + return MODE_ALLOWED; + } + + int switchCode = AppOpsManager.opToSwitch(code); + int rawUidMode = mAppOpsCheckingService.getUidMode(uid, + getPersistentId(virtualDeviceId), switchCode); + + if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) { + return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode); + } + + int rawPackageMode = mAppOpsCheckingService.getPackageMode(packageName, switchCode, + UserHandle.getUserId(uid)); + return raw ? rawPackageMode : evaluateForegroundMode(uid, switchCode, rawPackageMode); + } + } + + @Override public int checkAudioOperation(int code, int usage, int uid, String packageName) { return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName); @@ -3213,7 +3288,6 @@ public class AppOpsService extends IAppOpsService.Stub { PackageVerificationResult pvr; try { pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName); - boolean wasNull = attributionTag == null; if (!pvr.isAttributionTagValid) { attributionTag = null; } diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java index 146ce1732070..46be1ca0eec2 100644 --- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java +++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java @@ -27,6 +27,7 @@ import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; +import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -59,8 +60,14 @@ final class AdditionalSubtypeUtils { private static final String NODE_SUBTYPE = "subtype"; private static final String NODE_IMI = "imi"; private static final String ATTR_ID = "id"; + /** The resource ID of the subtype name. */ private static final String ATTR_LABEL = "label"; + /** The untranslatable name of the subtype. */ private static final String ATTR_NAME_OVERRIDE = "nameOverride"; + /** The layout label string resource identifier. */ + private static final String ATTR_LAYOUT_LABEL = "layoutLabel"; + /** The non-localized layout label. */ + private static final String ATTR_LAYOUT_LABEL_NON_LOCALIZED = "layoutLabelNonLocalized"; private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag"; private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType"; private static final String ATTR_ICON = "icon"; @@ -173,6 +180,11 @@ final class AdditionalSubtypeUtils { out.attributeInt(null, ATTR_ICON, subtype.getIconResId()); out.attributeInt(null, ATTR_LABEL, subtype.getNameResId()); out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString()); + if (Flags.imeSwitcherRevampApi()) { + out.attributeInt(null, ATTR_LAYOUT_LABEL, subtype.getLayoutLabelResource()); + out.attribute(null, ATTR_LAYOUT_LABEL_NON_LOCALIZED, + subtype.getLayoutLabelNonLocalized().toString()); + } ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag(); if (pkLanguageTag != null) { out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG, @@ -264,6 +276,16 @@ final class AdditionalSubtypeUtils { final int label = parser.getAttributeInt(null, ATTR_LABEL); final String untranslatableName = parser.getAttributeValue(null, ATTR_NAME_OVERRIDE); + final int layoutLabelResource; + final String layoutLabelNonLocalized; + if (Flags.imeSwitcherRevampApi()) { + layoutLabelResource = parser.getAttributeInt(null, ATTR_LAYOUT_LABEL); + layoutLabelNonLocalized = parser.getAttributeValue(null, + ATTR_LAYOUT_LABEL_NON_LOCALIZED); + } else { + layoutLabelResource = 0; + layoutLabelNonLocalized = null; + } final String pkLanguageTag = parser.getAttributeValue(null, ATTR_NAME_PK_LANGUAGE_TAG); final String pkLayoutType = parser.getAttributeValue(null, @@ -283,6 +305,7 @@ final class AdditionalSubtypeUtils { final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() .setSubtypeNameResId(label) + .setLayoutLabelResource(layoutLabelResource) .setPhysicalKeyboardHint( pkLanguageTag == null ? null : new ULocale(pkLanguageTag), pkLayoutType == null ? "" : pkLayoutType) @@ -301,6 +324,9 @@ final class AdditionalSubtypeUtils { if (untranslatableName != null) { builder.setSubtypeNameOverride(untranslatableName); } + if (layoutLabelNonLocalized != null) { + builder.setLayoutLabelNonLocalized(layoutLabelNonLocalized); + } tempSubtypesArray.add(builder.build()); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java index 6abd5aabfabf..9f94905686fe 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java @@ -238,8 +238,8 @@ final class InputMethodMenuControllerNew { prevImeId = imeId; } - menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mImi, - item.mSubtypeIndex)); + menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mLayoutName, + item.mImi, item.mSubtypeIndex)); } return menuItems; @@ -348,6 +348,13 @@ final class InputMethodMenuControllerNew { @Nullable final CharSequence mSubtypeName; + /** + * The name of the subtype's layout, or {@code null} if this item doesn't have a subtype, + * or doesn't specify a layout. + */ + @Nullable + private final CharSequence mLayoutName; + /** The info of the input method. */ @NonNull final InputMethodInfo mImi; @@ -360,10 +367,11 @@ final class InputMethodMenuControllerNew { final int mSubtypeIndex; SubtypeItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, - @NonNull InputMethodInfo imi, + @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, @IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex) { mImeName = imeName; mSubtypeName = subtypeName; + mLayoutName = layoutName; mImi = imi; mSubtypeIndex = subtypeIndex; } @@ -521,6 +529,9 @@ final class InputMethodMenuControllerNew { /** The name of the item. */ @NonNull private final TextView mName; + /** The layout name. */ + @NonNull + private final TextView mLayout; /** Indicator for the selected status of the item. */ @NonNull private final ImageView mCheckmark; @@ -536,6 +547,7 @@ final class InputMethodMenuControllerNew { mContainer = itemView; mName = itemView.requireViewById(com.android.internal.R.id.text); + mLayout = itemView.requireViewById(com.android.internal.R.id.text2); mCheckmark = itemView.requireViewById(com.android.internal.R.id.image); mContainer.setOnClickListener((v) -> { @@ -563,6 +575,9 @@ final class InputMethodMenuControllerNew { // Trigger the ellipsize marquee behaviour by selecting the name. mName.setSelected(isSelected); mName.setText(name); + mLayout.setText(item.mLayoutName); + mLayout.setVisibility( + !TextUtils.isEmpty(item.mLayoutName) ? View.VISIBLE : View.GONE); mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 96b3e084d102..51b85e90c447 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -85,6 +85,12 @@ final class InputMethodSubtypeSwitchingController { public final CharSequence mImeName; @Nullable public final CharSequence mSubtypeName; + /** + * The subtype's layout name, or {@code null} if this item doesn't have a subtype, + * or doesn't specify a layout. + */ + @Nullable + public final CharSequence mLayoutName; @NonNull public final InputMethodInfo mImi; /** @@ -96,10 +102,11 @@ final class InputMethodSubtypeSwitchingController { public final boolean mIsSystemLanguage; ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName, - @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale, - @NonNull String systemLocale) { + @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, int subtypeIndex, + @Nullable String subtypeLocale, @NonNull String systemLocale) { mImeName = imeName; mSubtypeName = subtypeName; + mLayoutName = layoutName; mImi = imi; mSubtypeIndex = subtypeIndex; if (TextUtils.isEmpty(subtypeLocale)) { @@ -252,8 +259,11 @@ final class InputMethodSubtypeSwitchingController { subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(userAwareContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo); - imList.add(new ImeSubtypeListItem(imeLabel, - subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null + : subtype.getLayoutDisplayName(userAwareContext, + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName, + imi, j, subtype.getLocale(), mSystemLocaleStr)); // Removing this subtype from enabledSubtypeSet because we no // longer need to add an entry of this subtype to imList to avoid @@ -262,8 +272,8 @@ final class InputMethodSubtypeSwitchingController { } } } else { - imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null, - mSystemLocaleStr)); + imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */, + null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr)); } } Collections.sort(imList); @@ -311,13 +321,16 @@ final class InputMethodSubtypeSwitchingController { subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(userAwareContext, imi.getPackageName(), imi.getServiceInfo().applicationInfo); - imList.add(new ImeSubtypeListItem(imeLabel, - subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr)); + final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null + : subtype.getLayoutDisplayName(userAwareContext, + imi.getServiceInfo().applicationInfo); + imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName, + imi, j, subtype.getLocale(), mSystemLocaleStr)); } } } else { - imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null, - mSystemLocaleStr)); + imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */, + null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr)); } } return imList; diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java index 89555a9f1de4..c8a87994ee16 100644 --- a/services/core/java/com/android/server/media/MediaSession2Record.java +++ b/services/core/java/com/android/server/media/MediaSession2Record.java @@ -161,6 +161,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl { } @Override + public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) { + // NA as MediaSession2 doesn't support UserEngagementStates for FGS. + } + + @Override public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService, KeyEvent ke, int sequenceId, ResultReceiver cb) { // TODO(jaewan): Implement. diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index d752429e64f7..668ee2adbd9f 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -230,51 +230,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde private final Runnable mUserEngagementTimeoutExpirationRunnable = () -> { synchronized (mLock) { - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ true, + /* isGlobalPrioritySessionActive= */ false); } }; @GuardedBy("mLock") private @UserEngagementState int mUserEngagementState = USER_DISENGAGED; - @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED}) + @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARILY_ENGAGED, USER_DISENGAGED}) @Retention(RetentionPolicy.SOURCE) private @interface UserEngagementState {} /** - * Indicates that the session is active and in one of the user engaged states. + * Indicates that the session is {@linkplain MediaSession#isActive() active} and in one of the + * {@linkplain PlaybackState#isActive() active states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ private static final int USER_PERMANENTLY_ENGAGED = 0; /** - * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state. + * Indicates that the session is {@linkplain MediaSession#isActive() active} and has recently + * switched to one of the {@linkplain PlaybackState#isActive() inactive states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ - private static final int USER_TEMPORARY_ENGAGED = 1; + private static final int USER_TEMPORARILY_ENGAGED = 1; /** - * Indicates that the session is either not active or in one of the user disengaged states + * Indicates that the session is either not {@linkplain MediaSession#isActive() active} or in + * one of the {@linkplain PlaybackState#isActive() inactive states}. * * @see #updateUserEngagedStateIfNeededLocked(boolean) */ private static final int USER_DISENGAGED = 2; /** - * Indicates the duration of the temporary engaged states, in milliseconds. + * Indicates the duration of the temporary engaged state, in milliseconds. * - * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily - * engaged, meaning the corresponding session is only considered in an engaged state for the - * duration of this timeout, and only if coming from an engaged state. - * - * <p>For example, if a session is transitioning from a user-engaged state {@link - * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link - * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for - * the duration of this timeout, starting at the transition instant. However, a temporary - * user-engaged state is not considered user-engaged when transitioning from a non-user engaged - * state {@link PlaybackState#STATE_STOPPED}. + * <p>When switching to an {@linkplain PlaybackState#isActive() inactive state}, the user is + * treated as temporarily engaged, meaning the corresponding session is only considered in an + * engaged state for the duration of this timeout, and only if coming from an engaged state. */ private static final int TEMP_USER_ENGAGED_TIMEOUT_MS = 600000; @@ -598,7 +596,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde mSessionCb.mCb.asBinder().unlinkToDeath(this, 0); mDestroyed = true; mPlaybackState = null; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ true, /* isGlobalPrioritySessionActive= */ false); mHandler.post(MessageHandler.MSG_DESTROYED); } } @@ -615,6 +614,24 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde mHandler.post(mUserEngagementTimeoutExpirationRunnable); } + @Override + public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) { + mHandler.post( + () -> { + synchronized (mLock) { + if (isGlobalPrioritySessionActive) { + mHandler.removeCallbacks(mUserEngagementTimeoutExpirationRunnable); + } else { + if (mUserEngagementState == USER_TEMPORARILY_ENGAGED) { + mHandler.postDelayed( + mUserEngagementTimeoutExpirationRunnable, + TEMP_USER_ENGAGED_TIMEOUT_MS); + } + } + } + }); + } + /** * Sends media button. * @@ -1063,21 +1080,20 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } @GuardedBy("mLock") - private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) { + private void updateUserEngagedStateIfNeededLocked( + boolean isTimeoutExpired, boolean isGlobalPrioritySessionActive) { if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { return; } int oldUserEngagedState = mUserEngagementState; int newUserEngagedState; - if (!isActive() || mPlaybackState == null || mDestroyed) { + if (!isActive() || mPlaybackState == null) { newUserEngagedState = USER_DISENGAGED; - } else if (isActive() && mPlaybackState.isActive()) { + } else if (mPlaybackState.isActive()) { newUserEngagedState = USER_PERMANENTLY_ENGAGED; - } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) { - newUserEngagedState = - oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired - ? USER_TEMPORARY_ENGAGED - : USER_DISENGAGED; + } else if (oldUserEngagedState == USER_PERMANENTLY_ENGAGED + || (oldUserEngagedState == USER_TEMPORARILY_ENGAGED && !isTimeoutExpired)) { + newUserEngagedState = USER_TEMPORARILY_ENGAGED; } else { newUserEngagedState = USER_DISENGAGED; } @@ -1086,7 +1102,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde } mUserEngagementState = newUserEngagedState; - if (newUserEngagedState == USER_TEMPORARY_ENGAGED) { + if (newUserEngagedState == USER_TEMPORARILY_ENGAGED && !isGlobalPrioritySessionActive) { mHandler.postDelayed( mUserEngagementTimeoutExpirationRunnable, TEMP_USER_ENGAGED_TIMEOUT_MS); } else { @@ -1141,9 +1157,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK, callingUid, callingPid); } + boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive(); synchronized (mLock) { mIsActive = active; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive); } long token = Binder.clearCallingIdentity(); try { @@ -1300,9 +1318,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState) || (!TRANSITION_PRIORITY_STATES.contains(oldState) && TRANSITION_PRIORITY_STATES.contains(newState)); + boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive(); synchronized (mLock) { mPlaybackState = state; - updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false); + updateUserEngagedStateIfNeededLocked( + /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive); } final long token = Binder.clearCallingIdentity(); try { diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java index 15f90d4fdd0e..6c3b1234935a 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java +++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java @@ -206,6 +206,10 @@ public abstract class MediaSessionRecordImpl { */ public abstract void expireTempEngaged(); + /** Notifies record that the global priority session active state changed. */ + public abstract void onGlobalPrioritySessionActiveChanged( + boolean isGlobalPrioritySessionActive); + @Override public final boolean equals(Object o) { if (this == o) return true; diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 1ebc856af2d8..2b29fbd9c5b5 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -362,6 +362,7 @@ public class MediaSessionService extends SystemService implements Monitor { + record.isActive()); } user.pushAddressedPlayerChangedLocked(); + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); } else { if (!user.mPriorityStack.contains(record)) { Log.w(TAG, "Unknown session updated. Ignoring."); @@ -394,11 +395,16 @@ public class MediaSessionService extends SystemService implements Monitor { // Currently only media1 can become global priority session. void setGlobalPrioritySession(MediaSessionRecord record) { + boolean globalPrioritySessionActiveChanged = false; synchronized (mLock) { FullUserRecord user = getFullUserRecordLocked(record.getUserId()); if (mGlobalPrioritySession != record) { Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession + " to " + record); + globalPrioritySessionActiveChanged = + (mGlobalPrioritySession == null && record.isActive()) + || (mGlobalPrioritySession != null + && mGlobalPrioritySession.isActive() != record.isActive()); mGlobalPrioritySession = record; if (user != null && user.mPriorityStack.contains(record)) { // Handle the global priority session separately. @@ -409,6 +415,30 @@ public class MediaSessionService extends SystemService implements Monitor { } } } + if (globalPrioritySessionActiveChanged) { + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); + } + } + + /** Returns whether the global priority session is active. */ + boolean isGlobalPrioritySessionActive() { + synchronized (mLock) { + return isGlobalPriorityActiveLocked(); + } + } + + private void notifyGlobalPrioritySessionActiveChanged() { + if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) { + return; + } + synchronized (mLock) { + boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked(); + for (Set<MediaSessionRecordImpl> records : mUserEngagedSessionsForFgs.values()) { + for (MediaSessionRecordImpl record : records) { + record.onGlobalPrioritySessionActiveChanged(isGlobalPriorityActive); + } + } + } } private List<MediaSessionRecord> getActiveSessionsLocked(int userId) { @@ -646,8 +676,11 @@ public class MediaSessionService extends SystemService implements Monitor { if (mGlobalPrioritySession == session) { mGlobalPrioritySession = null; - if (session.isActive() && user != null) { - user.pushAddressedPlayerChangedLocked(); + if (session.isActive()) { + if (user != null) { + user.pushAddressedPlayerChangedLocked(); + } + mHandler.post(this::notifyGlobalPrioritySessionActiveChanged); } } else { if (user != null) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7ecfe7f64ffe..9ca206a9e1ca 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1395,33 +1395,46 @@ public class UserManagerService extends IUserManager.Stub { .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) { return UserHandle.USER_SYSTEM; } - // Return the previous foreground user, if there is one. - final int previousUser = getPreviousFullUserToEnterForeground(); - if (previousUser != UserHandle.USER_NULL) { - Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser); - return previousUser; - } - // No previous user. Return the first switchable user if there is one. - synchronized (mUsersLock) { - final int userSize = mUsers.size(); - for (int i = 0; i < userSize; i++) { - final UserData userData = mUsers.valueAt(i); - if (userData.info.supportsSwitchToByUser()) { - int firstSwitchable = userData.info.id; - Slogf.i(LOG_TAG, - "Boot user is first switchable user %d", firstSwitchable); - return firstSwitchable; - } - } - } - // No switchable users found. Uh oh! - throw new UserManager.CheckedUserOperationException( - "No switchable users found", USER_OPERATION_ERROR_UNKNOWN); + return getPreviousOrFirstSwitchableUser(); } // Not HSUM, return system user. return UserHandle.USER_SYSTEM; } + private @UserIdInt int getPreviousOrFirstSwitchableUser() + throws UserManager.CheckedUserOperationException { + // Return the previous foreground user, if there is one. + final int previousUser = getPreviousFullUserToEnterForeground(); + if (previousUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser); + return previousUser; + } + // No previous user. Return the first switchable user if there is one. + final int firstSwitchableUser = getFirstSwitchableUser(); + if (firstSwitchableUser != UserHandle.USER_NULL) { + Slogf.i(LOG_TAG, + "Boot user is first switchable user %d", firstSwitchableUser); + return firstSwitchableUser; + } + // No switchable users found. Uh oh! + throw new UserManager.CheckedUserOperationException( + "No switchable users found", USER_OPERATION_ERROR_UNKNOWN); + } + + private @UserIdInt int getFirstSwitchableUser() { + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + final UserData userData = mUsers.valueAt(i); + if (userData.info.supportsSwitchToByUser()) { + int firstSwitchable = userData.info.id; + return firstSwitchable; + } + } + } + return UserHandle.USER_NULL; + } + @Override public int getPreviousFullUserToEnterForeground() { diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index db76eb9ac5d9..ebb50db54693 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -164,41 +164,46 @@ final class AppCompatUtils { appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap()); - final Rect bounds = top.getBounds(); - final Rect appBounds = getAppBounds(top); - appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); - appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); - appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); - appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); + final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed(); + appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed); + if (isTopActivityLetterboxed) { + final Rect bounds = top.getBounds(); + final Rect appBounds = getAppBounds(top); + appCompatTaskInfo.topActivityLetterboxWidth = bounds.width(); + appCompatTaskInfo.topActivityLetterboxHeight = bounds.height(); + appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width(); + appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height(); - // We need to consider if letterboxed or pillarboxed. - // TODO(b/336807329) Encapsulate reachability logic - appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides - .isLetterboxDoubleTapEducationEnabled()); - if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) { - if (appCompatTaskInfo.isTopActivityPillarboxed()) { - if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) { - // Pillarboxed. - appCompatTaskInfo.topActivityLetterboxHorizontalPosition = - reachabilityOverrides.getLetterboxPositionForHorizontalReachability(); - } else { - appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); - } - } else { - if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) { - // Letterboxed. - appCompatTaskInfo.topActivityLetterboxVerticalPosition = - reachabilityOverrides.getLetterboxPositionForVerticalReachability(); + // We need to consider if letterboxed or pillarboxed. + // TODO(b/336807329) Encapsulate reachability logic + appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides + .isLetterboxDoubleTapEducationEnabled()); + if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) { + if (appCompatTaskInfo.isTopActivityPillarboxShaped()) { + if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) { + // Pillarboxed. + appCompatTaskInfo.topActivityLetterboxHorizontalPosition = + reachabilityOverrides + .getLetterboxPositionForHorizontalReachability(); + } else { + appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + } } else { - appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) { + // Letterboxed. + appCompatTaskInfo.topActivityLetterboxVerticalPosition = + reachabilityOverrides.getLetterboxPositionForVerticalReachability(); + } else { + appCompatTaskInfo.setLetterboxDoubleTapEnabled(false); + } } } } + final boolean eligibleForAspectRatioButton = !info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat() && aspectRatioOverrides.shouldEnableUserAspectRatioSettings(); appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton); - appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed()); appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = AppCompatCameraPolicy.getCameraCompatFreeformMode(top); appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index ee07d2e58389..76e8a70768c1 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1736,7 +1736,7 @@ public class DisplayPolicy { // Show IME over the keyguard if the target allows it. final boolean showImeOverKeyguard = - imeTarget != null && imeTarget.isOnScreen() && win.mIsImWindow && ( + imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && ( imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard()); if (showImeOverKeyguard) { return false; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index aca6f7235714..6cdab3f51f85 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -255,7 +255,6 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; -import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -462,7 +461,6 @@ import android.permission.PermissionControllerManager; import android.provider.CalendarContract; import android.provider.ContactsContract.QuickContact; import android.provider.ContactsInternal; -import android.provider.DeviceConfig; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Telephony; @@ -908,10 +906,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + "management app's authentication policy"; private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s"; - private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG = - "enable_permission_based_access"; - private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false; - private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3; /** @@ -4646,22 +4640,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @GuardedBy("getLockObject()") private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) { if (isSeparateProfileChallengeEnabled(userHandle)) { - - if (isPermissionCheckFlagEnabled()) { - return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle); - } // If this user has a separate challenge, only return its restrictions. return getUserDataUnchecked(userHandle).mAdminList; } // If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile // we need to query the parent user who owns the credential. - if (isPermissionCheckFlagEnabled()) { - return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle), - (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); - } else { - return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), - (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); - } + return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle), + (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)); } @@ -4684,33 +4669,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { (user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id)); } - /** - * Get the list of active admins for an affected user: - * <ul> - * <li>The active admins associated with the userHandle itself</li> - * <li>The parent active admins for each managed profile associated with the userHandle</li> - * <li>The permission based admin associated with the userHandle itself</li> - * </ul> - * - * @param userHandle the affected user for whom to get the active admins - * @return the list of active admins for the affected user - */ - @GuardedBy("getLockObject()") - private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked( - int userHandle) { - List<ActiveAdmin> list; - - if (isManagedProfile(userHandle)) { - list = getUserDataUnchecked(userHandle).mAdminList; - } - list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle, - /* shouldIncludeProfileAdmins */ (user) -> false); - - if (getUserData(userHandle).mPermissionBasedAdmin != null) { - list.add(getUserData(userHandle).mPermissionBasedAdmin); - } - return list; - } /** * Returns the list of admins on the given user, as well as parent admins for each managed @@ -4763,44 +4721,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users); } - /** - * Returns the list of admins on the given user, as well as parent admins for each managed - * profile associated with the given user. Optionally also include the admin of each managed - * profile. - * <p> Should not be called on a profile user. - */ - @GuardedBy("getLockObject()") - private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle, - Predicate<UserInfo> shouldIncludeProfileAdmins) { - ArrayList<ActiveAdmin> admins = new ArrayList<>(); - mInjector.binderWithCleanCallingIdentity(() -> { - for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) { - DevicePolicyData policy = getUserDataUnchecked(userInfo.id); - if (userInfo.id == userHandle) { - admins.addAll(policy.mAdminList); - if (policy.mPermissionBasedAdmin != null) { - admins.add(policy.mPermissionBasedAdmin); - } - } else if (userInfo.isManagedProfile()) { - for (int i = 0; i < policy.mAdminList.size(); i++) { - ActiveAdmin admin = policy.mAdminList.get(i); - if (admin.hasParentActiveAdmin()) { - admins.add(admin.getParentActiveAdmin()); - } - if (shouldIncludeProfileAdmins.test(userInfo)) { - admins.add(admin); - } - } - if (policy.mPermissionBasedAdmin != null - && shouldIncludeProfileAdmins.test(userInfo)) { - admins.add(policy.mPermissionBasedAdmin); - } - } - } - }); - return admins; - } - private boolean isSeparateProfileChallengeEnabled(int userHandle) { return mInjector.binderWithCleanCallingIdentity(() -> mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle)); @@ -4893,25 +4813,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } + Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms"); int userHandle = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; synchronized (getLockObject()) { ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUserId) - .getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent); - } + ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent); // Calling this API automatically bumps the expiration date final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; ap.passwordExpirationDate = expiration; @@ -4972,28 +4882,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean addCrossProfileWidgetProvider(ComponentName admin, String callerPackageName, String packageName) { - CallerIdentity caller; + CallerIdentity caller = getCallerIdentity(admin); - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } - ActiveAdmin activeAdmin; + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } List<String> changedProviders = null; @@ -5026,28 +4922,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean removeCrossProfileWidgetProvider(ComponentName admin, String callerPackageName, String packageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } + CallerIdentity caller = getCallerIdentity(admin); - ActiveAdmin activeAdmin; + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } List<String> changedProviders = null; @@ -5080,27 +4962,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public List<String> getCrossProfileWidgetProviders(ComponentName admin, String callerPackageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } - ActiveAdmin activeAdmin; + CallerIdentity caller = getCallerIdentity(admin); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( - admin, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - caller.getUserId()); - activeAdmin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isProfileOwner(caller)); - synchronized (getLockObject()) { - activeAdmin = getProfileOwnerLocked(caller.getUserId()); - } + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isProfileOwner(caller)); + + ActiveAdmin activeAdmin; + synchronized (getLockObject()) { + activeAdmin = getProfileOwnerLocked(caller.getUserId()); } synchronized (getLockObject()) { @@ -5449,24 +5318,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforceUserUnlocked(userHandle, parent); synchronized (getLockObject()) { - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent ? getProfileParentId(userHandle) : userHandle; - enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - callerPackageName, affectedUser); - } else { - // This API can only be called by an active device admin, - // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked( - null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); - } + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked( + null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent); int credentialOwner = getCredentialOwner(userHandle, parent); DevicePolicyData policy = getUserDataUnchecked(credentialOwner); PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner); final int userToCheck = getProfileParentUserIfRequested(userHandle, parent); - boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked( + return isActivePasswordSufficientForUserLocked( policy.mPasswordValidAtLastCheckpoint, metrics, userToCheck); - return activePasswordSufficientForUserLocked; } } @@ -5622,21 +5484,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller), "Only profile owner, device owner and system may call this method on parent."); } else { - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) - || hasCallingOrSelfPermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS) - || isDefaultDeviceOwner(caller) || isProfileOwner(caller), - "Must have " + REQUEST_PASSWORD_COMPLEXITY + " or " + - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS - + " permissions, or be a profile owner or device owner."); - } else { - Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) - || isDefaultDeviceOwner(caller) || isProfileOwner(caller), - "Must have " + REQUEST_PASSWORD_COMPLEXITY - + " permission, or be a profile owner or device owner."); - } + Preconditions.checkCallAuthorization( + hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY) + || isDefaultDeviceOwner(caller) || isProfileOwner(caller), + "Must have " + REQUEST_PASSWORD_COMPLEXITY + + " permission, or be a profile owner or device owner."); } synchronized (getLockObject()) { @@ -5728,26 +5580,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private void setRequiredPasswordComplexityPreCoexistence( String callerPackageName, int passwordComplexity, boolean calledOnParent) { CallerIdentity caller = getCallerIdentity(callerPackageName); - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); - Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); - } + + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller)); synchronized (getLockObject()) { ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - // TODO: Make sure this returns the parent of the fake admin - // TODO: Deal with null componentname - int affectedUser = calledOnParent - ? getProfileParentId(caller.getUserId()) : caller.getUserId(); - admin = enforcePermissionAndGetEnforcingAdmin( - null, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUser).getActiveAdmin(); - } else { - admin = getParentOfAdminIfRequired( - getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent); - } + admin = getParentOfAdminIfRequired( + getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent); if (admin.mPasswordComplexity != passwordComplexity) { // We require the caller to explicitly clear any password quality requirements set @@ -5907,14 +5748,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!isSystemUid(caller)) { // This API can be called by an active device admin or by keyguard code. if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) { - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent ? getProfileParentId(userHandle) : userHandle; - enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - callerPackageName, affectedUser); - } else { - getActiveAdminForCallerLocked( - null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); - } + getActiveAdminForCallerLocked( + null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); } } @@ -5931,31 +5766,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } - + Objects.requireNonNull(who, "ComponentName is null"); int userId = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userId) : userId; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, - /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA, - /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA, - caller.getPackageName(), affectedUserId).getActiveAdmin(); - } else { - // This API can only be called by an active device admin, - // so try to retrieve it to check that the caller is one. - getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent); - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); - } + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent); + ActiveAdmin ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent); if (ap.maximumFailedPasswordsForWipe != num) { ap.maximumFailedPasswordsForWipe = num; @@ -6210,25 +6032,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(who, "ComponentName is null"); - } + + Objects.requireNonNull(who, "ComponentName is null"); + int userHandle = mInjector.userHandleGetCallingUserId(); int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(who, callerPackageName); - ap = enforcePermissionAndGetEnforcingAdmin( - who, - /*permission=*/ MANAGE_DEVICE_POLICY_LOCK, - /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK, - caller.getPackageName(), - affectedUserId).getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked( - who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent); - } + ActiveAdmin ap = getActiveAdminForCallerLocked( + who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent); if (ap.maximumTimeToUnlock != timeMs) { ap.maximumTimeToUnlock = timeMs; @@ -6334,16 +6145,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) { return; } + Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number."); - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwner(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwner(caller)); + // timeoutMs with value 0 means that the admin doesn't participate // timeoutMs is clamped to the interval in case the internal constants change in the future final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs(); @@ -6357,17 +6165,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userHandle = caller.getUserId(); boolean changed = false; synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - int affectedUser = parent - ? getProfileParentId(caller.getUserId()) : caller.getUserId(); - ap = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - caller.getPackageName(), affectedUser).getActiveAdmin(); - } else { - ap = getParentOfAdminIfRequired( - getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); - } + ActiveAdmin ap = getParentOfAdminIfRequired( + getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent); if (ap.strongAuthUnlockTimeout != timeoutMs) { ap.strongAuthUnlockTimeout = timeoutMs; saveSettingsLocked(userHandle); @@ -6664,16 +6463,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization(!isUserSelectable, "The credential " + "management app is not allowed to install a user selectable key pair"); @@ -6733,16 +6525,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -6802,13 +6587,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean canInstallCertificates(CallerIdentity caller) { - if (isPermissionCheckFlagEnabled()) { - return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()); - } else { - return isProfileOwner(caller) || isDefaultDeviceOwner(caller) - || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); - } + return isProfileOwner(caller) || isDefaultDeviceOwner(caller) + || isCallerDelegate(caller, DELEGATION_CERT_INSTALL); } private boolean canChooseCertificates(CallerIdentity caller) { @@ -7001,16 +6781,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getPackageName(), caller.getUid())); enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags); } else { - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner( - caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && ( - isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner( + caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && ( + isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -7143,16 +6916,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL); final boolean isCredentialManagementApp = isCredentialManagementApp(caller); - if (isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, - caller.getPackageName(), caller.getUserId()) - || isCredentialManagementApp); - } else { - Preconditions.checkCallAuthorization((caller.hasAdminComponent() - && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) - || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); - } + Preconditions.checkCallAuthorization((caller.hasAdminComponent() + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) + || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp))); if (isCredentialManagementApp) { Preconditions.checkCallAuthorization( isAliasInCredentialManagementAppPolicy(caller, alias), @@ -8285,29 +8051,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkNotNull(who, "ComponentName is null"); - } + + Preconditions.checkNotNull(who, "ComponentName is null"); + CallerIdentity caller = getCallerIdentity(who, callerPackageName); - if (!isPermissionCheckFlagEnabled()) { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager .OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY); final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow(); synchronized (getLockObject()) { ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( - who, MANAGE_DEVICE_POLICY_FACTORY_RESET, caller.getPackageName(), - UserHandle.USER_ALL) - .getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); admin.mFactoryResetProtectionPolicy = policy; saveSettingsLocked(caller.getUserId()); } @@ -8347,7 +8105,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || hasCallingPermission(permission.MASTER_CLEAR) || hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET), "Must be called by the FRP management agent on device"); - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); } else { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) @@ -10247,15 +10005,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return admin; } - ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() { - ensureLocked(); - ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - if (isPermissionCheckFlagEnabled() && doOrPo == null) { - return getUserData(0).mPermissionBasedAdmin; - } - return doOrPo; - } - @Override public void clearDeviceOwner(String packageName) { Objects.requireNonNull(packageName, "packageName is null"); @@ -10998,8 +10747,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * (2.1.1) The caller is the profile owner. * (2.1.2) The caller is from another app in the same user as the profile owner, AND * the caller is the delegated cert installer. - * (3) The caller holds the - * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission. * * For the device owner case, simply check that the caller is the device owner or the * delegated certificate installer. @@ -11013,24 +10760,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @VisibleForTesting boolean hasDeviceIdAccessUnchecked(String packageName, int uid) { final int userId = UserHandle.getUserId(uid); - // TODO(b/280048070): Introduce a permission to handle device ID access - if (isPermissionCheckFlagEnabled() - && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) { - return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId); - } else { - ComponentName deviceOwner = getDeviceOwnerComponent(true); - if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) - || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { - return true; - } - ComponentName profileOwner = getProfileOwnerAsUser(userId); - final boolean isCallerProfileOwnerOrDelegate = profileOwner != null - && (profileOwner.getPackageName().equals(packageName) - || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); - if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId) - || isUserAffiliatedWithDevice(userId))) { - return true; - } + ComponentName deviceOwner = getDeviceOwnerComponent(true); + if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) { + return true; + } + ComponentName profileOwner = getProfileOwnerAsUser(userId); + final boolean isCallerProfileOwnerOrDelegate = profileOwner != null + && (profileOwner.getPackageName().equals(packageName) + || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL)); + if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId) + || isUserAffiliatedWithDevice(userId))) { + return true; } return false; } @@ -11731,25 +11472,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setDefaultSmsApplication(ComponentName admin, String callerPackageName, String packageName, boolean parent) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - } else { - caller = getCallerIdentity(admin); - } + CallerIdentity caller = getCallerIdentity(admin); - final int userId; - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_DEFAULT_SMS, - caller.getPackageName(), - getAffectedUser(parent)); - } else { - Objects.requireNonNull(admin, "ComponentName is null"); - Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Objects.requireNonNull(admin, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); if (!parent && isManagedProfile(caller.getUserId()) && getManagedSubscriptionsPolicy().getPolicyType() @@ -11759,6 +11487,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + "ManagedSubscriptions policy is set"); } + final int userId; if (parent) { userId = getProfileParentId(mInjector.userHandleGetCallingUserId()); mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage( @@ -11957,10 +11686,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "admin is null"); - } - + Objects.requireNonNull(admin, "admin is null"); Objects.requireNonNull(agent, "agent is null"); PolicySizeVerifier.enforceMaxPackageNameLength(agent.getPackageName()); @@ -11972,19 +11698,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int userHandle = mInjector.userHandleGetCallingUserId(); synchronized (getLockObject()) { - ActiveAdmin ap; - if (isPermissionCheckFlagEnabled()) { - CallerIdentity caller = getCallerIdentity(admin, callerPackageName); - int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle; - ap = enforcePermissionAndGetEnforcingAdmin( - admin, - /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD, - /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, - caller.getPackageName(), affectedUserId).getActiveAdmin(); - } else { - ap = getActiveAdminForCallerLocked(admin, - DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent); - } + ActiveAdmin ap = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent); checkCanExecuteOrThrowUnsafe( DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION); @@ -12080,27 +11795,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void addCrossProfileIntentFilter(ComponentName who, String callerPackageName, IntentFilter filter, int flags) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - int callingUserId = caller.getUserId(); + CallerIdentity caller = getCallerIdentity(who); + + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - callingUserId); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - } synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { + int callingUserId = caller.getUserId(); UserInfo parent = mUserManager.getProfileParent(callingUserId); if (parent == null) { Slogf.e(LOG_TAG, "Cannot call addCrossProfileIntentFilter if there is no " @@ -12144,28 +11848,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void clearCrossProfileIntentFilters(ComponentName who, String callerPackageName) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - int callingUserId = caller.getUserId(); + CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - enforcePermission( - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - caller.getPackageName(), - callingUserId); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isProfileOwner(caller) || isDefaultDeviceOwner(caller)); - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isProfileOwner(caller) || isDefaultDeviceOwner(caller)); synchronized (getLockObject()) { long id = mInjector.binderClearCallingIdentity(); try { + int callingUserId = caller.getUserId(); UserInfo parent = mUserManager.getProfileParent(callingUserId); if (parent == null) { Slogf.e(LOG_TAG, "Cannot call clearCrossProfileIntentFilter if there is no " @@ -15166,19 +14858,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Preconditions.checkNotNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, @@ -15197,16 +14882,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, who.getPackageName(), - UserHandle.USER_ALL); - } else { - Preconditions.checkNotNull(who, "ComponentName is null"); - - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Preconditions.checkNotNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0); @@ -15294,18 +14973,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean setTime(@Nullable ComponentName who, String callerPackageName, long millis) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - // This is a global action. - enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set time when auto time is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) { @@ -15322,18 +14994,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean setTimeZone(@Nullable ComponentName who, String callerPackageName, String timeZone) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - // This is a global action. - enforcePermission(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); // Don't allow set timezone when auto timezone is on. if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) { @@ -16537,22 +16202,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.validateAgainstPreviousFreezePeriod(record.first, record.second, LocalDate.now()); } - CallerIdentity caller; - synchronized (getLockObject()) { - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(who); - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) + CallerIdentity caller = getCallerIdentity(who); + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller)); - } - checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY); + checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY); + synchronized (getLockObject()) { if (policy == null) { mOwners.clearSystemUpdatePolicy(); } else { @@ -16699,7 +16357,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) { Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast " + "update information."); - return; } }); @@ -16723,7 +16380,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } // Get running users. - final int runningUserIds[]; + final int[] runningUserIds; try { runningUserIds = mInjector.getIActivityManager().getRunningUserIds(); } catch (RemoteException e) { @@ -16966,10 +16623,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - if (!isRuntimePermission(permission)) { - return false; - } - return true; + return isRuntimePermission(permission); } private void enforcePermissionGrantStateOnFinancedDevice( @@ -17384,18 +17038,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public String getWifiMacAddress(ComponentName admin, String callerPackageName) { -// if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "ComponentName is null"); -// } + Objects.requireNonNull(admin, "ComponentName is null"); final CallerIdentity caller = getCallerIdentity(admin, callerPackageName); -// if (isPermissionCheckFlagEnabled()) { -// enforcePermission(MANAGE_DEVICE_POLICY_WIFI, UserHandle.USER_ALL); -// } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); -// } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); return mInjector.binderWithCleanCallingIdentity(() -> { String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses(); @@ -17462,25 +17110,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return; } - CallerIdentity caller; - ActiveAdmin admin; message = PolicySizeVerifier.truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH); - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - synchronized (getLockObject()) { - admin = getActiveAdminForUidLocked(who, caller.getUid()); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getActiveAdminForUidLocked(who, caller.getUid()); } synchronized (getLockObject()) { @@ -17501,23 +17139,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (!mHasFeature) { return null; } - CallerIdentity caller; - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - caller = getCallerIdentity(who); - Objects.requireNonNull(who, "ComponentName is null"); - synchronized (getLockObject()) { - admin = getActiveAdminForUidLocked(who, caller.getUid()); - } + CallerIdentity caller = getCallerIdentity(who); + Objects.requireNonNull(who, "ComponentName is null"); + + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getActiveAdminForUidLocked(who, caller.getUid()); } return admin.shortSupportMessage; } @@ -17680,26 +17308,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } CallerIdentity caller = getCallerIdentity(who); - ActiveAdmin admin = null; - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); text = PolicySizeVerifier.truncateIfLonger(text, MAX_ORG_NAME_LENGTH); synchronized (getLockObject()) { - if (!isPermissionCheckFlagEnabled()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (!TextUtils.equals(admin.organizationName, text)) { admin.organizationName = (text == null || text.length() == 0) ? null : text.toString(); @@ -17714,23 +17330,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } CallerIdentity caller = getCallerIdentity(who); - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); - Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); - synchronized (getLockObject()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin; + synchronized (getLockObject()) { + admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); } return admin.organizationName; @@ -18214,28 +17821,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(admin, packageName); - if (isPermissionCheckFlagEnabled()) { - synchronized (getLockObject()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(), - UserHandle.USER_ALL); - } + if (admin != null) { + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller) + || isDefaultDeviceOwner(caller)); } else { - if (admin != null) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller) - || isDefaultDeviceOwner(caller)); - } else { - // A delegate app passes a null admin component, which is expected - Preconditions.checkCallAuthorization( - isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); - } + // A delegate app passes a null admin component, which is expected + Preconditions.checkCallAuthorization( + isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING)); + } - synchronized (getLockObject()) { - Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() - || areAllUsersAffiliatedWithDeviceLocked()); - } + synchronized (getLockObject()) { + Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile() + || areAllUsersAffiliatedWithDeviceLocked()); } DevicePolicyEventLogger @@ -18259,7 +17857,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return new ParceledListSlice<SecurityEvent>(output); } catch (IOException e) { Slogf.w(LOG_TAG, "Fail to read previous events" , e); - return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList()); + return new ParceledListSlice<SecurityEvent>(Collections.emptyList()); } } @@ -18752,8 +18350,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean hasIncompatibleAccounts(int userId) { - return mHasIncompatibleAccounts == null ? true - : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false); + return mHasIncompatibleAccounts == null || mHasIncompatibleAccounts.getOrDefault( + userId, /* default= */ false); } /** @@ -18870,7 +18468,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } } - }; + } private boolean isAdb(CallerIdentity caller) { return isShellUid(caller) || isRootUid(caller); @@ -20168,21 +19766,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void installUpdateFromFile(ComponentName admin, String callerPackageName, ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) { - if (!isPermissionCheckFlagEnabled()) { - Objects.requireNonNull(admin, "ComponentName is null"); - } + Objects.requireNonNull(admin, "ComponentName is null"); - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(admin, callerPackageName); - enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - caller = getCallerIdentity(admin); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + CallerIdentity caller = getCallerIdentity(admin); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE); DevicePolicyEventLogger @@ -20752,32 +20341,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setCommonCriteriaModeEnabled(ComponentName who, String callerPackageName, boolean enabled) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(who, callerPackageName); - } else { - caller = getCallerIdentity(who); - } - final ActiveAdmin admin; + CallerIdentity caller = getCallerIdentity(who); - if (isPermissionCheckFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( - who, - MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, - caller.getPackageName(), - caller.getUserId()); - admin = enforcingAdmin.getActiveAdmin(); - } else { - Objects.requireNonNull(who, "ComponentName is null"); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "Common Criteria mode can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - synchronized (getLockObject()) { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } - } + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Common Criteria mode can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); admin.mCommonCriteriaMode = enabled; saveSettingsLocked(caller.getUserId()); } @@ -20809,7 +20381,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // their ActiveAdmin, instead of iterating through all admins. ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); - return admin != null ? admin.mCommonCriteriaMode : false; + return admin != null && admin.mCommonCriteriaMode; } } @@ -22209,7 +21781,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { owner = getDeviceOrProfileOwnerAdminLocked(userId); } - boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false; + boolean canGrant = owner != null && owner.mAdminCanGrantSensorsPermissions; mPolicyCache.setAdminCanGrantSensorsPermissions(canGrant); } } @@ -22408,27 +21980,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level) { - CallerIdentity caller; - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(callerPackageName); - } else { - caller = getCallerIdentity(); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "Wi-Fi minimum security level can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - } + CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Wi-Fi minimum security level can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); boolean valueChanged = false; synchronized (getLockObject()) { - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin(/* admin= */ null, - MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), caller.getUserId()) - .getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (admin.mWifiMinimumSecurityLevel != level) { admin.mWifiMinimumSecurityLevel = level; saveSettingsLocked(caller.getUserId()); @@ -22450,21 +22010,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public WifiSsidPolicy getWifiSsidPolicy(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_WIFI, callerPackageName, - caller.getUserId()); - } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) - || canQueryAdminPolicy(caller), - "SSID policy can only be retrieved by a device owner or " - + "a profile owner on an organization-owned device or " - + "an app with the QUERY_ADMIN_POLICY permission."); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller) + || canQueryAdminPolicy(caller), + "SSID policy can only be retrieved by a device owner or " + + "a profile owner on an organization-owned device or " + + "an app with the QUERY_ADMIN_POLICY permission."); synchronized (getLockObject()) { ActiveAdmin admin; - admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked(); + admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); return admin != null ? admin.mWifiSsidPolicy : null; } } @@ -22485,29 +22040,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) { - CallerIdentity caller; - - if (isPermissionCheckFlagEnabled()) { - caller = getCallerIdentity(callerPackageName); - } else { - caller = getCallerIdentity(); - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), - "SSID denylist can only be controlled by a device owner or " - + "a profile owner on an organization-owned device."); - } + CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "SSID denylist can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); boolean changed = false; synchronized (getLockObject()) { - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( - /* admin= */ null, MANAGE_DEVICE_POLICY_WIFI, - caller.getPackageName(), - caller.getUserId()).getActiveAdmin(); - } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); if (!Objects.equals(policy, admin.mWifiSsidPolicy)) { admin.mWifiSsidPolicy = policy; changed = true; @@ -22715,7 +22256,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener { - private RoleManager mRm; + private final RoleManager mRm; private final Executor mExecutor; private final Context mContext; @@ -22732,13 +22273,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) { mDevicePolicyEngine.handleRoleChanged(roleName, user.getIdentifier()); - if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) { - handleDevicePolicyManagementRoleChange(user); - return; - } - if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) { - handleFinancedDeviceKioskRoleChange(); - return; + switch (roleName) { + case RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT -> + handleDevicePolicyManagementRoleChange(user); + case RoleManager.ROLE_FINANCED_DEVICE_KIOSK -> + handleFinancedDeviceKioskRoleChange(); } } @@ -23390,26 +22929,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Checks if the calling process has been granted permission to apply a device policy on a - * specific user. - * The given permission will be checked along with its associated cross-user permission if it - * exists and the target user is different to the calling user. - * Returns an {@link EnforcingAdmin} for the caller. - * - * @param admin the component name of the admin. - * @param callerPackageName The package name of the calling application. - * @param permission The name of the permission being checked. - * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on. - * @throws SecurityException if the caller has not been granted the given permission, - * the associated cross-user permission if the caller's user is different to the target user. - */ - private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin, - String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) { - enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId); - return getEnforcingAdminForCaller(admin, callerPackageName); - } - - /** - * Checks if the calling process has been granted permission to apply a device policy on a * specific user. Only one permission provided in the list needs to be granted to pass this * check. * The given permissions will be checked along with their associated cross-user permissions if @@ -23431,23 +22950,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } /** - * Checks whether the calling process has been granted permission to query a device policy on - * a specific user. - * The given permission will be checked along with its associated cross-user permission if it - * exists and the target user is different to the calling user. - * - * @param permission The name of the permission being checked. - * @param targetUserId The userId of the user which the caller needs permission to act on. - * @throws SecurityException if the caller has not been granted the given permission, - * the associated cross-user permission if the caller's user is different to the target user. - */ - private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin, - String permission, String callerPackageName, int targetUserId) { - enforceCanQuery(permission, callerPackageName, targetUserId); - return getEnforcingAdminForCaller(admin, callerPackageName); - } - - /** * Checks if the calling process has been granted permission to apply a device policy. * * @param callerPackageName The package name of the calling application. @@ -23754,13 +23256,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return NOT_A_DPC; } - private boolean isPermissionCheckFlagEnabled() { - return DeviceConfig.getBoolean( - NAMESPACE_DEVICE_POLICY_MANAGER, - PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG, - DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG); - } - private static boolean isSetStatusBarDisabledCoexistenceEnabled() { return false; } @@ -23837,14 +23332,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)); } - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller)); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller)); synchronized (getLockObject()) { ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); @@ -23874,15 +23364,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public int getMtePolicy(String callerPackageName) { final CallerIdentity caller = getCallerIdentity(callerPackageName); - if (isPermissionCheckFlagEnabled()) { - enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(), - UserHandle.USER_ALL); - } else { - Preconditions.checkCallAuthorization( - isDefaultDeviceOwner(caller) - || isProfileOwnerOfOrganizationOwnedDevice(caller) - || isSystemUid(caller)); - } + Preconditions.checkCallAuthorization( + isDefaultDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isSystemUid(caller)); + synchronized (getLockObject()) { ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(); @@ -24666,7 +24152,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { || isCallerDevicePolicyManagementRoleHolder(caller) || isCallerSystemSupervisionRoleHolder(caller)); return getFinancedDeviceKioskRoleHolderOnAnyUser() != null; - }; + } @Override public String getFinancedDeviceKioskRoleHolder(String callerPackageName) { diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java index a804f24acc8f..c30ab738b098 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java @@ -101,13 +101,13 @@ public final class InputMethodSubtypeSwitchingControllerTest { TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME); if (subtypes == null) { - items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi, - NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE)); + items.add(new ImeSubtypeListItem(imeName, null /* subtypeName */, null /* layoutName */, + imi, NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE)); } else { for (int i = 0; i < subtypes.size(); ++i) { final String subtypeLocale = subtypeLocales.get(i); - items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale, - SYSTEM_LOCALE)); + items.add(new ImeSubtypeListItem(imeName, subtypeLocale, null /* layoutName */, + imi, i, subtypeLocale, SYSTEM_LOCALE)); } } } @@ -138,8 +138,8 @@ public final class InputMethodSubtypeSwitchingControllerTest { final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME, TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID, TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME); - return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale, - SYSTEM_LOCALE); + return new ImeSubtypeListItem(imeName, subtypeName, null /* layoutName */, imi, + subtypeIndex, subtypeLocale, SYSTEM_LOCALE); } @NonNull diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java index 197342874b2a..f7c2e8b72d6b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java @@ -21,6 +21,7 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION; import static org.junit.Assert.assertEquals; +import android.app.PropertyInvalidatedCache; import android.content.Context; import android.os.Handler; @@ -63,6 +64,7 @@ public class AppOpsLegacyRestrictionsTest { @Before public void setUp() { + PropertyInvalidatedCache.disableForTestMode(); mSession = ExtendedMockito.mockitoSession() .initMocks(this) .strictness(Strictness.LENIENT) diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java index c4b3c149bd8d..5d7ffe91e67d 100644 --- a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java @@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; import android.app.AppOpsManager; +import android.app.PropertyInvalidatedCache; import android.companion.virtual.VirtualDeviceManager; import android.content.Context; import android.os.FileUtils; @@ -69,6 +70,7 @@ public class AppOpsRecentAccessPersistenceTest { @Before public void setUp() { + PropertyInvalidatedCache.disableForTestMode(); when(mAppOpCheckingService.addAppOpsModeChangedListener(any())).thenReturn(true); LocalServices.addService(AppOpsCheckingServiceInterface.class, mAppOpCheckingService); |