diff options
185 files changed, 5390 insertions, 3377 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index b1c091bfa946..a60ced5835ea 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -65,13 +65,13 @@ aconfig_declarations_group { "android.sdk.flags-aconfig-java", "android.security.flags-aconfig-java", "android.server.app.flags-aconfig-java", + "android.service.appprediction.flags-aconfig-java", "android.service.autofill.flags-aconfig-java", "android.service.chooser.flags-aconfig-java", "android.service.compat.flags-aconfig-java", "android.service.controls.flags-aconfig-java", "android.service.dreams.flags-aconfig-java", "android.service.notification.flags-aconfig-java", - "android.service.appprediction.flags-aconfig-java", "android.service.quickaccesswallet.flags-aconfig-java", "android.service.voice.flags-aconfig-java", "android.speech.flags-aconfig-java", @@ -523,7 +523,10 @@ aconfig_declarations { package: "android.companion.virtualdevice.flags", container: "system", exportable: true, - srcs: ["core/java/android/companion/virtual/flags/*.aconfig"], + srcs: [ + "core/java/android/companion/virtual/flags/flags.aconfig", + "core/java/android/companion/virtual/flags/launched_flags.aconfig", + ], } java_aconfig_library { @@ -548,7 +551,7 @@ aconfig_declarations { name: "android.companion.virtual.flags-aconfig", package: "android.companion.virtual.flags", container: "system", - srcs: ["core/java/android/companion/virtual/*.aconfig"], + srcs: ["core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig"], } // InputMethod @@ -828,8 +831,8 @@ java_aconfig_library { min_sdk_version: "30", apex_available: [ "//apex_available:platform", - "com.android.permission", "com.android.nfcservices", + "com.android.permission", ], } diff --git a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java index df6e3c836256..e790874ebc61 100644 --- a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java +++ b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java @@ -43,7 +43,7 @@ public class AconfigPackagePerfTest { @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); - @Parameterized.Parameters(name = "isPlatform={0}") + @Parameterized.Parameters(name = "isPlatform_{0}") public static Collection<Object[]> data() { return Arrays.asList(new Object[][] {{false}, {true}}); } @@ -60,10 +60,9 @@ public class AconfigPackagePerfTest { } } - @Parameterized.Parameter(0) - // if this variable is true, then the test query flags from system/product/vendor // if this variable is false, then the test query flags from updatable partitions + @Parameterized.Parameter(0) public boolean mIsPlatform; @Test diff --git a/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java index a12121fd13f7..5d39ccc882a8 100644 --- a/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java +++ b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java @@ -20,7 +20,6 @@ import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.om.OverlayManager; -import android.os.UserHandle; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.perftests.utils.TestPackageInstaller; @@ -127,7 +126,7 @@ public class OverlayManagerPerfTest { private void assertSetEnabled(boolean enabled, Context context, Stream<String> packagesStream) { final var overlayPackages = packagesStream.toList(); overlayPackages.forEach( - name -> sOverlayManager.setEnabled(name, enabled, UserHandle.SYSTEM)); + name -> sOverlayManager.setEnabled(name, enabled, context.getUser())); // Wait for the overlay changes to propagate final var endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(20); @@ -174,7 +173,7 @@ public class OverlayManagerPerfTest { // Disable the overlay and remove the idmap for the next iteration of the test state.pauseTiming(); assertSetEnabled(false, sContext, packageName); - sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM); + sOverlayManager.invalidateCachesForOverlay(packageName, sContext.getUser()); state.resumeTiming(); } } @@ -189,7 +188,7 @@ public class OverlayManagerPerfTest { // Disable the overlay and remove the idmap for the next iteration of the test state.pauseTiming(); assertSetEnabled(false, sContext, packageName); - sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM); + sOverlayManager.invalidateCachesForOverlay(packageName, sContext.getUser()); state.resumeTiming(); } } diff --git a/boot/preloaded-classes b/boot/preloaded-classes index b83bd4e4d401..9926aef91ee1 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -6470,6 +6470,7 @@ android.os.connectivity.WifiActivityEnergyInfo android.os.connectivity.WifiBatteryStats$1 android.os.connectivity.WifiBatteryStats android.os.flagging.AconfigPackage +android.os.flagging.PlatformAconfigPackage android.os.health.HealthKeys$Constant android.os.health.HealthKeys$Constants android.os.health.HealthKeys$SortedIntArray diff --git a/config/preloaded-classes b/config/preloaded-classes index e53c78f65877..bdd95f8e67ae 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -6474,6 +6474,7 @@ android.os.connectivity.WifiActivityEnergyInfo android.os.connectivity.WifiBatteryStats$1 android.os.connectivity.WifiBatteryStats android.os.flagging.AconfigPackage +android.os.flagging.PlatformAconfigPackage android.os.health.HealthKeys$Constant android.os.health.HealthKeys$Constants android.os.health.HealthKeys$SortedIntArray diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f82aecbd6d44..a775c2bc1891 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1290,7 +1290,6 @@ package android.app { public class WallpaperManager { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int); - method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int); method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point); method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount(); method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean); @@ -8095,16 +8094,16 @@ package android.media.soundtrigger { method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID); method public int getDetectionServiceOperationsTimeout(); method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getModelState(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int getModelState(@NonNull java.util.UUID); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties(); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean isRecognitionActive(@NonNull java.util.UUID); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public boolean isRecognitionActive(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int stopRecognition(@NonNull java.util.UUID); - method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int unloadSoundModel(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int stopRecognition(@NonNull java.util.UUID); + method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int unloadSoundModel(@NonNull java.util.UUID); method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index cd2cc07b8cc3..a988acf1f4a9 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -536,6 +536,7 @@ package android.app { method @Nullable public android.graphics.Bitmap getBitmap(); method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int); method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull java.util.List<android.graphics.Point>, int, boolean); + method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int); method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List<android.graphics.Point>, @Nullable java.util.Map<android.graphics.Point,android.graphics.Rect>); method public boolean isLockscreenLiveWallpaperEnabled(); method @Nullable public android.graphics.Rect peekBitmapDimensions(); @@ -4537,7 +4538,6 @@ package android.window { field public final int displayId; field public final boolean isDuplicateTouchToWallpaper; field public final boolean isFocusable; - field public final boolean isPreventSplitting; field public final boolean isTouchable; field public final boolean isTrustedOverlay; field public final boolean isVisible; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 717a2acb4b4a..1f3e6559a695 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -105,6 +105,7 @@ import android.content.pm.ServiceInfo; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.ResourceTimer; import android.content.res.Resources; import android.content.res.ResourcesImpl; import android.content.res.loader.ResourcesLoader; @@ -5253,6 +5254,7 @@ public final class ActivityThread extends ClientTransactionHandler Resources.dumpHistory(pw, ""); pw.flush(); + ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh"); if (info.finishCallback != null) { info.finishCallback.sendResult(null); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 24594ab41100..614e2aaf42e8 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -774,8 +774,9 @@ public class Notification implements Parcelable /** * Bit to be bitwise-ored into the {@link #flags} field that should be - * set by the system if this notification is a promoted ongoing notification, either via a - * user setting or allowlist. + * set by the system if this notification is a promoted ongoing notification, both because it + * {@link #hasPromotableCharacteristics()} and the user has not disabled the feature for this + * app. * * Applications cannot set this flag directly, but the posting app and * {@link android.service.notification.NotificationListenerService} can read it. diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index 360376da618c..73ecc7199686 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -1690,7 +1690,7 @@ public class WallpaperManager { * @hide */ @FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS) - @SystemApi + @TestApi @RequiresPermission(READ_WALLPAPER_INTERNAL) @NonNull public SparseArray<Rect> getBitmapCrops(@SetWallpaperFlags int which) { diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig index f31e7d4c61cd..eae50624539e 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig @@ -1,11 +1,13 @@ -# Do not add new flags to this file. +# Do not modify this file. # -# Due to "virtual" keyword in the package name flags -# added to this file cannot be accessed from C++ -# code. +# Due to "virtual" keyword in the package name flags added to this file cannot +# be accessed from C++ code. # # Use frameworks/base/core/java/android/companion/virtual/flags/flags.aconfig -# instead. +# instead for new flags. +# +# All of the remaining flags here have been used for API flagging and are +# therefore exported and should not be deleted. package: "android.companion.virtual.flags" container: "system" diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index de01280f293f..84af84072f1b 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -1,17 +1,11 @@ +# VirtualDeviceManager flags # -# Copyright (C) 2023 The Android Open Source Project +# This file contains flags guarding features that are in development. # -# 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. +# Once a flag is launched or abandoned and there are no more references to it in +# the codebase, it should be either: +# - deleted, or +# - moved to launched_flags.aconfig if it was launched and used for API flagging. package: "android.companion.virtualdevice.flags" container: "system" diff --git a/core/java/android/companion/virtual/flags/launched_flags.aconfig b/core/java/android/companion/virtual/flags/launched_flags.aconfig new file mode 100644 index 000000000000..ee896319bb72 --- /dev/null +++ b/core/java/android/companion/virtual/flags/launched_flags.aconfig @@ -0,0 +1,6 @@ +# This file contains the launched VirtualDeviceManager flags from the +# "android.companion.virtualdevice.flags" package that cannot be deleted because +# they have been used for API flagging. + +package: "android.companion.virtualdevice.flags" +container: "system" diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 908999b64961..b938aac811fd 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -25,6 +25,7 @@ import android.content.res.loader.ResourcesProvider; import android.ravenwood.annotation.RavenwoodClassLoadHook; import android.ravenwood.annotation.RavenwoodKeepWholeClass; import android.text.TextUtils; +import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -50,6 +51,7 @@ import java.util.Objects; @RavenwoodKeepWholeClass @RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK) public final class ApkAssets { + private static final boolean DEBUG = false; /** * The apk assets contains framework resource values specified by the system. @@ -134,6 +136,17 @@ public final class ApkAssets { @Nullable private final AssetsProvider mAssets; + @NonNull + private String mName; + + private static final int UPTODATE_FALSE = 0; + private static final int UPTODATE_TRUE = 1; + private static final int UPTODATE_ALWAYS_TRUE = 2; + + // Start with the only value that may change later and would force a native call to + // double check it. + private int mPreviousUpToDateResult = UPTODATE_TRUE; + /** * Creates a new ApkAssets instance from the given path on disk. * @@ -304,7 +317,7 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { - this(format, flags, assets); + this(format, flags, assets, path); Objects.requireNonNull(path, "path"); mNativePtr = nativeLoad(format, path, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); @@ -313,7 +326,7 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { - this(format, flags, assets); + this(format, flags, assets, friendlyName); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); @@ -323,7 +336,7 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { - this(format, flags, assets); + this(format, flags, assets, friendlyName); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); @@ -331,16 +344,17 @@ public final class ApkAssets { } private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { - this(FORMAT_APK, flags, assets); + this(FORMAT_APK, flags, assets, "empty"); mNativePtr = nativeLoadEmpty(flags, assets); mStringBlock = null; } private ApkAssets(@FormatType int format, @PropertyFlags int flags, - @Nullable AssetsProvider assets) { + @Nullable AssetsProvider assets, @NonNull String name) { mFlags = flags; mAssets = assets; mIsOverlay = format == FORMAT_IDMAP; + if (DEBUG) mName = name; } @UnsupportedAppUsage @@ -421,13 +435,41 @@ public final class ApkAssets { } } + private static double intervalMs(long beginNs, long endNs) { + return (endNs - beginNs) / 1000000.0; + } + /** * Returns false if the underlying APK was changed since this ApkAssets was loaded. */ public boolean isUpToDate() { + // This function is performance-critical - it's called multiple times on every Resources + // object creation, and on few other cache accesses - so it's important to avoid the native + // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE + // and FALSE). + if (mPreviousUpToDateResult != UPTODATE_TRUE) { + return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE; + } + final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs; + if (DEBUG) beforeTs = System.nanoTime(); + final int res; synchronized (this) { - return nativeIsUpToDate(mNativePtr); + if (DEBUG) afterLockTs = System.nanoTime(); + res = nativeIsUpToDate(mNativePtr); + if (DEBUG) afterNativeTs = System.nanoTime(); + } + if (DEBUG) { + afterUnlockTs = System.nanoTime(); + if (afterUnlockTs - beforeTs >= 10L * 1000000) { + Log.d("ApkAssets", "isUpToDate(" + mName + ") took " + + intervalMs(beforeTs, afterUnlockTs) + + " ms: " + intervalMs(beforeTs, afterLockTs) + + " / " + intervalMs(afterLockTs, afterNativeTs) + + " / " + intervalMs(afterNativeTs, afterUnlockTs)); + } } + mPreviousUpToDateResult = res; + return res != UPTODATE_FALSE; } public boolean isSystem() { @@ -487,7 +529,7 @@ public final class ApkAssets { private static native @NonNull String nativeGetAssetPath(long ptr); private static native @NonNull String nativeGetDebugName(long ptr); private static native long nativeGetStringBlock(long ptr); - @CriticalNative private static native boolean nativeIsUpToDate(long ptr); + @CriticalNative private static native int nativeIsUpToDate(long ptr); private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException; private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr, String overlayableName) throws IOException; diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java index d51f64ce8106..2d1bf4d9d296 100644 --- a/core/java/android/content/res/ResourceTimer.java +++ b/core/java/android/content/res/ResourceTimer.java @@ -17,13 +17,10 @@ package android.content.res; import android.annotation.NonNull; -import android.annotation.Nullable; - import android.app.AppProtoEnums; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.SystemClock; import android.text.TextUtils; @@ -33,6 +30,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FrameworkStatsLog; +import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.Arrays; @@ -277,38 +275,40 @@ public final class ResourceTimer { * Update the metrics information and dump it. * @hide */ - public static void dumpTimers(@NonNull ParcelFileDescriptor pfd, @Nullable String[] args) { - FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); - PrintWriter pw = new FastPrintWriter(fout); - synchronized (sLock) { - if (!sEnabled || (sConfig == null)) { + public static void dumpTimers(@NonNull FileDescriptor fd, String... args) { + try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd))) { + pw.println("\nDumping ResourceTimers"); + + final boolean enabled; + synchronized (sLock) { + enabled = sEnabled && sConfig != null; + } + if (!enabled) { pw.println(" Timers are not enabled in this process"); - pw.flush(); return; } - } - // Look for the --refresh switch. If the switch is present, then sTimers is updated. - // Otherwise, the current value of sTimers is displayed. - boolean refresh = Arrays.asList(args).contains("-refresh"); - - synchronized (sLock) { - update(refresh); - long runtime = sLastUpdated - sProcessStart; - pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName()); - for (int i = 0; i < sTimers.length; i++) { - Timer t = sTimers[i]; - if (t.count != 0) { - String name = sConfig.timers[i]; - pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s " - + "largest=%s\n", - name, t.count, t.total / t.count, t.mintime, t.maxtime, - packedString(t.percentile), - packedString(t.largest)); + // Look for the --refresh switch. If the switch is present, then sTimers is updated. + // Otherwise, the current value of sTimers is displayed. + boolean refresh = Arrays.asList(args).contains("-refresh"); + + synchronized (sLock) { + update(refresh); + long runtime = sLastUpdated - sProcessStart; + pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName()); + for (int i = 0; i < sTimers.length; i++) { + Timer t = sTimers[i]; + if (t.count != 0) { + String name = sConfig.timers[i]; + pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s " + + "largest=%s\n", + name, t.count, t.total / t.count, t.mintime, t.maxtime, + packedString(t.percentile), + packedString(t.largest)); + } } } } - pw.flush(); } // Enable (or disabled) the runtime timers. Note that timers are disabled by default. This diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java index 0e2c05f92e7c..1d2f133ee759 100644 --- a/core/java/android/hardware/display/DisplayTopology.java +++ b/core/java/android/hardware/display/DisplayTopology.java @@ -679,8 +679,7 @@ public final class DisplayTopology implements Parcelable { } /** - * Tests whether two brightness float values are within a small enough tolerance - * of each other. + * Tests whether two float values are within a small enough tolerance of each other. * @param a first float to compare * @param b second float to compare * @return whether the two values are within a small enough tolerance value diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java index 8da630c95135..b380e259577c 100644 --- a/core/java/android/hardware/input/InputSettings.java +++ b/core/java/android/hardware/input/InputSettings.java @@ -78,6 +78,24 @@ public class InputSettings { public static final int DEFAULT_POINTER_SPEED = 0; /** + * Pointer Speed: The minimum (slowest) mouse scrolling speed (-7). + * @hide + */ + public static final int MIN_MOUSE_SCROLLING_SPEED = -7; + + /** + * Pointer Speed: The maximum (fastest) mouse scrolling speed (7). + * @hide + */ + public static final int MAX_MOUSE_SCROLLING_SPEED = 7; + + /** + * Pointer Speed: The default mouse scrolling speed (0). + * @hide + */ + public static final int DEFAULT_MOUSE_SCROLLING_SPEED = 0; + + /** * Bounce Keys Threshold: The default value of the threshold (500 ms). * * @hide @@ -650,6 +668,54 @@ public class InputSettings { } /** + * Gets the mouse scrolling speed. + * + * The returned value only applies when mouse scrolling acceleration is not enabled. + * + * @param context The application context. + * @return The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED} and + * {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value + * {@link #DEFAULT_MOUSE_SCROLLING_SPEED}. + * + * @hide + */ + public static int getMouseScrollingSpeed(@NonNull Context context) { + if (!isMouseScrollingAccelerationFeatureFlagEnabled()) { + return 0; + } + + return Settings.System.getIntForUser(context.getContentResolver(), + Settings.System.MOUSE_SCROLLING_SPEED, DEFAULT_MOUSE_SCROLLING_SPEED, + UserHandle.USER_CURRENT); + } + + /** + * Sets the mouse scrolling speed, and saves it in the settings. + * + * The new speed will only apply when mouse scrolling acceleration is not enabled. + * + * @param context The application context. + * @param speed The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED} + * and {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value + * {@link #DEFAULT_MOUSE_SCROLLING_SPEED}. + * + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SETTINGS) + public static void setMouseScrollingSpeed(@NonNull Context context, int speed) { + if (isMouseScrollingAccelerationEnabled(context)) { + return; + } + + if (speed < MIN_MOUSE_SCROLLING_SPEED || speed > MAX_MOUSE_SCROLLING_SPEED) { + throw new IllegalArgumentException("speed out of range"); + } + + Settings.System.putIntForUser(context.getContentResolver(), + Settings.System.MOUSE_SCROLLING_SPEED, speed, UserHandle.USER_CURRENT); + } + + /** * Whether mouse vertical scrolling is reversed. This applies only to connected mice. * * @param context The application context. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c94526bcdcd7..2656a7b58c8d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6372,6 +6372,19 @@ public final class Settings { "mouse_pointer_acceleration_enabled"; /** + * Mouse scrolling speed setting. + * + * This is an integer value in a range between -7 and +7, so there are 15 possible values. + * The setting only applies when mouse scrolling acceleration is not enabled. + * -7 = slowest + * 0 = default speed + * +7 = fastest + * + * @hide + */ + public static final String MOUSE_SCROLLING_SPEED = "mouse_scrolling_speed"; + + /** * Pointer fill style, specified by * {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants. * @@ -6623,6 +6636,7 @@ public final class Settings { PRIVATE_SETTINGS.add(MOUSE_POINTER_ACCELERATION_ENABLED); PRIVATE_SETTINGS.add(PREFERRED_REGION); PRIVATE_SETTINGS.add(MOUSE_SCROLLING_ACCELERATION); + PRIVATE_SETTINGS.add(MOUSE_SCROLLING_SPEED); } /** @@ -17395,13 +17409,6 @@ public final class Settings { /** - * Whether back preview animations are played when user does a back gesture or presses - * the back button. - * @hide - */ - public static final String ENABLE_BACK_ANIMATION = "enable_back_animation"; - - /** * An allow list of packages for which the user has granted the permission to communicate * across profiles. * diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java index 5c38a1597433..195896dc8edf 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -81,7 +81,7 @@ public final class InputEventConsistencyVerifier { // Bitfield of pointer ids that are currently down. // Assumes that the largest possible pointer id is 31, which is potentially subject to change. - // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + // (See MAX_POINTER_ID in frameworks/native/include/input/input.h) private int mTouchEventStreamPointers; // The device id and source of the current stream of touch events. diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index 6cd4a4033adf..3e529ccf064a 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -57,7 +57,7 @@ public final class InputWindowHandle { InputConfig.NO_INPUT_CHANNEL, InputConfig.NOT_FOCUSABLE, InputConfig.NOT_TOUCHABLE, - InputConfig.PREVENT_SPLITTING, + InputConfig.DEPRECATED_PREVENT_SPLITTING, InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER, InputConfig.IS_WALLPAPER, InputConfig.PAUSE_DISPATCHING, diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 833f2d98554e..e665c08c63e4 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -160,6 +160,10 @@ public final class SurfaceControl implements Parcelable { float l, float t, float r, float b); private static native void nativeSetCornerRadius(long transactionObj, long nativeObject, float cornerRadius); + private static native void nativeSetClientDrawnCornerRadius(long transactionObj, + long nativeObject, float clientDrawnCornerRadius); + private static native void nativeSetClientDrawnShadows(long transactionObj, + long nativeObject, float clientDrawnShadows); private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject, int blurRadius); private static native void nativeSetLayerStack(long transactionObj, long nativeObject, @@ -3654,6 +3658,66 @@ public final class SurfaceControl implements Parcelable { return this; } + + /** + * Disables corner radius of a {@link SurfaceControl}. When the radius set by + * {@link Transaction#setCornerRadius(SurfaceControl, float)} is equal to + * clientDrawnCornerRadius the corner radius drawn by SurfaceFlinger is disabled. + * + * @param sc SurfaceControl + * @param clientDrawnCornerRadius Corner radius drawn by the client + * @return Itself. + * @hide + */ + @NonNull + public Transaction setClientDrawnCornerRadius(@NonNull SurfaceControl sc, + float clientDrawnCornerRadius) { + checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setClientDrawnCornerRadius", this, sc, "clientDrawnCornerRadius=" + + clientDrawnCornerRadius); + } + if (Flags.ignoreCornerRadiusAndShadows()) { + nativeSetClientDrawnCornerRadius(mNativeObject, sc.mNativeObject, + clientDrawnCornerRadius); + } else { + Log.w(TAG, "setClientDrawnCornerRadius was called but" + + "ignore_corner_radius_and_shadows flag is disabled"); + } + + return this; + } + + /** + * Disables shadows of a {@link SurfaceControl}. When the radius set by + * {@link Transaction#setClientDrawnShadows(SurfaceControl, float)} is equal to + * clientDrawnShadowRadius the shadows drawn by SurfaceFlinger is disabled. + * + * @param sc SurfaceControl + * @param clientDrawnShadowRadius Shadow radius drawn by the client + * @return Itself. + * @hide + */ + @NonNull + public Transaction setClientDrawnShadows(@NonNull SurfaceControl sc, + float clientDrawnShadowRadius) { + checkPreconditions(sc); + if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { + SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( + "setClientDrawnShadows", this, sc, + "clientDrawnShadowRadius=" + clientDrawnShadowRadius); + } + if (Flags.ignoreCornerRadiusAndShadows()) { + nativeSetClientDrawnShadows(mNativeObject, sc.mNativeObject, + clientDrawnShadowRadius); + } else { + Log.w(TAG, "setClientDrawnShadows was called but" + + "ignore_corner_radius_and_shadows flag is disabled"); + } + return this; + } + /** * Sets the background blur radius of the {@link SurfaceControl}. * diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index f50ea9106a61..25bd713d9191 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -453,6 +453,7 @@ public final class WindowManagerGlobal { try { root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { + Log.e(TAG, "Couldn't add view: " + view, e); final int viewIndex = (index >= 0) ? index : (mViews.size() - 1); // BadTokenException or InvalidDisplayException, clean up. if (viewIndex >= 0) { diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java index 1f341caa8ed3..6d2c0d0061dd 100644 --- a/core/java/android/view/WindowManagerPolicyConstants.java +++ b/core/java/android/view/WindowManagerPolicyConstants.java @@ -30,7 +30,8 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public interface WindowManagerPolicyConstants { - // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h and + // Policy flags. These flags are also defined in + // frameworks/native/include/input/Input.h and // frameworks/native/libs/input/android/os/IInputConstants.aidl int FLAG_WAKE = 0x00000001; int FLAG_VIRTUAL = 0x00000002; diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java index ac9bec305fff..6461f2a0fcda 100644 --- a/core/java/android/window/WindowInfosListenerForTest.java +++ b/core/java/android/window/WindowInfosListenerForTest.java @@ -103,12 +103,6 @@ public class WindowInfosListenerForTest { public final boolean isFocusable; /** - * True if the window is preventing splitting - */ - @SuppressLint("UnflaggedApi") // The API is only used for tests. - public final boolean isPreventSplitting; - - /** * True if the window duplicates touches received to wallpaper. */ @SuppressLint("UnflaggedApi") // The API is only used for tests. @@ -133,8 +127,6 @@ public class WindowInfosListenerForTest { this.transform = transform; this.isTouchable = (inputConfig & InputConfig.NOT_TOUCHABLE) == 0; this.isFocusable = (inputConfig & InputConfig.NOT_FOCUSABLE) == 0; - this.isPreventSplitting = (inputConfig - & InputConfig.PREVENT_SPLITTING) != 0; this.isDuplicateTouchToWallpaper = (inputConfig & InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER) != 0; this.isWatchOutsideTouch = (inputConfig diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index bb4770768cb1..8ff2e6aebdd0 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -91,6 +91,14 @@ flag { } flag { + name: "ignore_corner_radius_and_shadows" + namespace: "window_surfaces" + description: "Ignore the corner radius and shadows of a SurfaceControl" + bug: "375624570" + is_fixed_read_only: true +} # ignore_corner_radius_and_shadows + +flag { name: "jank_api" namespace: "window_surfaces" description: "Adds the jank data listener to AttachedSurfaceControl" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index a8641326b1f2..de3e0d3faf43 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -113,13 +113,6 @@ flag { } flag { - name: "predictive_back_system_anims" - namespace: "systemui" - description: "Predictive back for system animations" - bug: "320510464" -} - -flag { name: "remove_activity_starter_dream_callback" namespace: "windowing_frontend" description: "Avoid a race with DreamManagerService callbacks for isDreaming by checking Activity state directly" diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl index d62c8f378af0..73c2265d5f6e 100644 --- a/core/java/com/android/internal/policy/IKeyguardService.aidl +++ b/core/java/com/android/internal/policy/IKeyguardService.aidl @@ -53,21 +53,21 @@ oneway interface IKeyguardService { * * @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason * we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT. - * @param cameraGestureTriggered whether the camera gesture was triggered between - * {@link #onStartedGoingToSleep} and this method; if it's been - * triggered, we shouldn't lock the device. + * @param powerButtonLaunchGestureTriggered whether the power button double tap gesture was + * triggered between {@link #onStartedGoingToSleep} and this + * method; if it's been triggered, we shouldn't lock the device. */ - void onFinishedGoingToSleep(int pmSleepReason, boolean cameraGestureTriggered); + void onFinishedGoingToSleep(int pmSleepReason, boolean powerButtonLaunchGestureTriggered); /** * Called when the device has started waking up. * @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the reason we're waking up, * such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE. - * @param cameraGestureTriggered Whether we're waking up due to a power button double tap - * gesture. + * @param powerButtonLaunchGestureTriggered Whether we're waking up due to a power button + * double tap gesture. */ - void onStartedWakingUp(int pmWakeReason, boolean cameraGestureTriggered); + void onStartedWakingUp(int pmWakeReason, boolean powerButtonLaunchGestureTriggered); /** * Called when the device has finished waking up. diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index 1e7bfe32ba79..e6364a96bd9f 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -111,9 +111,8 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass class LoaderAssetsProvider : public AssetsProvider { public: static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) { - return (!assets_provider) ? EmptyAssetsProvider::Create() - : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider( - env, assets_provider)); + return std::unique_ptr<AssetsProvider>{ + assets_provider ? new LoaderAssetsProvider(env, assets_provider) : nullptr}; } bool ForEachFile(const std::string& /* root_path */, @@ -129,8 +128,8 @@ class LoaderAssetsProvider : public AssetsProvider { return debug_name_; } - bool IsUpToDate() const override { - return true; + UpToDate IsUpToDate() const override { + return UpToDate::Always; } ~LoaderAssetsProvider() override { @@ -212,7 +211,7 @@ class LoaderAssetsProvider : public AssetsProvider { auto string_result = static_cast<jstring>(env->CallObjectMethod( assets_provider_, gAssetsProviderOffsets.toString)); ScopedUtfChars str(env, string_result); - debug_name_ = std::string(str.c_str(), str.size()); + debug_name_ = std::string(str.c_str()); } // The global reference to the AssetsProvider @@ -243,10 +242,10 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags); break; case FORMAT_ARSC: - apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()), - std::move(loader_assets), - property_flags); - break; + apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()), + MultiAssetsProvider::Create(std::move(loader_assets)), + property_flags); + break; case FORMAT_DIRECTORY: { auto assets = MultiAssetsProvider::Create(std::move(loader_assets), DirectoryAssetsProvider::Create(path.c_str())); @@ -316,10 +315,11 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t break; } case FORMAT_ARSC: - apk_assets = ApkAssets::LoadTable( - AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */), - std::move(loader_assets), property_flags); - break; + apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd), + nullptr /* path */), + MultiAssetsProvider::Create(std::move(loader_assets)), + property_flags); + break; default: const std::string error_msg = base::StringPrintf("Unsupported format type %d", format); jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); @@ -386,12 +386,15 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_ break; } case FORMAT_ARSC: - apk_assets = ApkAssets::LoadTable( - AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */, - static_cast<off64_t>(offset), - static_cast<off64_t>(length)), - std::move(loader_assets), property_flags); - break; + apk_assets = + ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd), + nullptr /* path */, + static_cast<off64_t>(offset), + static_cast<off64_t>( + length)), + MultiAssetsProvider::Create(std::move(loader_assets)), + property_flags); + break; default: const std::string error_msg = base::StringPrintf("Unsupported format type %d", format); jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str()); @@ -408,13 +411,16 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_ } static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) { - auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags); - if (apk_assets == nullptr) { - const std::string error_msg = - base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider); - jniThrowException(env, "java/io/IOException", error_msg.c_str()); - return 0; - } + auto apk_assets = ApkAssets::Load(MultiAssetsProvider::Create( + LoaderAssetsProvider::Create(env, assets_provider)), + flags); + if (apk_assets == nullptr) { + const std::string error_msg = + base::StringPrintf("Failed to load empty assets with provider %p", + (void*)assets_provider); + jniThrowException(env, "java/io/IOException", error_msg.c_str()); + return 0; + } return CreateGuardedApkAssets(std::move(apk_assets)); } @@ -443,10 +449,10 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool()); } -static jboolean NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { +static jint NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr)); auto apk_assets = scoped_apk_assets->get(); - return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE; + return (jint)apk_assets->IsUpToDate(); } static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) { @@ -558,7 +564,7 @@ static const JNINativeMethod gApkAssetsMethods[] = { {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName}, {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, // @CriticalNative - {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate}, + {"nativeIsUpToDate", "(J)I", (void*)NativeIsUpToDate}, {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml}, {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;", (void*)NativeGetOverlayableInfo}, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index 0c243d1dc185..6f69e4005b80 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -1113,6 +1113,22 @@ static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionOb transaction->setCornerRadius(ctrl, cornerRadius); } +static void nativeSetClientDrawnCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jfloat clientDrawnCornerRadius) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setClientDrawnCornerRadius(ctrl, clientDrawnCornerRadius); +} + +static void nativeSetClientDrawnShadows(JNIEnv* env, jclass clazz, jlong transactionObj, + jlong nativeObject, jfloat clientDrawnShadowRadius) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + transaction->setClientDrawnShadowRadius(ctrl, clientDrawnShadowRadius); +} + static void nativeSetBackgroundBlurRadius(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jint blurRadius) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -2547,6 +2563,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeSetCrop }, {"nativeSetCornerRadius", "(JJF)V", (void*)nativeSetCornerRadius }, + {"nativeSetClientDrawnCornerRadius", "(JJF)V", + (void*) nativeSetClientDrawnCornerRadius }, + {"nativeSetClientDrawnShadows", "(JJF)V", + (void*) nativeSetClientDrawnShadows }, {"nativeSetBackgroundBlurRadius", "(JJI)V", (void*)nativeSetBackgroundBlurRadius }, {"nativeSetLayerStack", "(JJI)V", diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto index 0d99200f4e6f..64c9f540a97b 100644 --- a/core/proto/android/providers/settings/system.proto +++ b/core/proto/android/providers/settings/system.proto @@ -229,6 +229,7 @@ message SystemSettingsProto { optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto scrolling_acceleration = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; optional SettingProto pointer_acceleration_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto scrolling_speed = 5 [ (android.privacy).dest = DEST_AUTOMATIC ]; } optional Mouse mouse = 38; diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 45a5d85a097d..c50c5e9d3341 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4244,12 +4244,19 @@ is non-interactive. --> <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool> - <!-- Allow the gesture to double tap the power button to trigger a target action. --> - <bool name="config_doubleTapPowerGestureEnabled">true</bool> - <!-- Default target action for double tap of the power button gesture. + <!-- Controls the double tap power button gesture to trigger a target action. + 0: Gesture is disabled + 1: Launch camera mode, allowing the user to disable/enable the double tap power gesture + from launching the camera application. + 2: Multi target mode, allowing the user to select one of the targets defined in + config_doubleTapPowerGestureMultiTargetDefaultAction and to disable/enable the double + tap power gesture from triggering the selected target action. + --> + <integer name="config_doubleTapPowerGestureMode">2</integer> + <!-- Default target action for double tap of the power button gesture in multi target mode. 0: Launch camera 1: Launch wallet --> - <integer name="config_defaultDoubleTapPowerGestureAction">0</integer> + <integer name="config_doubleTapPowerGestureMultiTargetDefaultAction">0</integer> <!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos experience while the device is non-interactive. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a18f923d625b..24e7057320ff 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3167,8 +3167,8 @@ <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" /> <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" /> <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" /> - <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" /> - <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" /> + <java-symbol type="integer" name="config_doubleTapPowerGestureMode" /> + <java-symbol type="integer" name="config_doubleTapPowerGestureMultiTargetDefaultAction" /> <java-symbol type="bool" name="config_emergencyGestureEnabled" /> <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" /> <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" /> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index e96bc02c1198..8dabd54a01ff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -28,7 +28,6 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME; -import static com.android.window.flags.Flags.predictiveBackSystemAnims; import static com.android.window.flags.Flags.unifyBackNavigationTransition; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; @@ -40,23 +39,17 @@ import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.TaskInfo; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Configuration; -import android.database.ContentObserver; import android.graphics.Point; import android.graphics.Rect; import android.hardware.input.InputManager; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SystemClock; -import android.os.SystemProperties; -import android.os.UserHandle; -import android.provider.Settings.Global; import android.util.Log; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; @@ -92,7 +85,6 @@ import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.shared.TransitionUtil; -import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -102,7 +94,6 @@ import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; /** @@ -111,14 +102,7 @@ import java.util.function.Predicate; public class BackAnimationController implements RemoteCallable<BackAnimationController>, ConfigurationChangeListener { private static final String TAG = "ShellBackPreview"; - private static final int SETTING_VALUE_OFF = 0; - private static final int SETTING_VALUE_ON = 1; - public static final boolean IS_ENABLED = - SystemProperties.getInt("persist.wm.debug.predictive_back", - SETTING_VALUE_ON) == SETTING_VALUE_ON; - - /** Predictive back animation developer option */ - private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false); + /** * Max duration to wait for an animation to finish before triggering the real back. */ @@ -148,11 +132,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private boolean mReceivedNullNavigationInfo = false; private final IActivityTaskManager mActivityTaskManager; private final Context mContext; - private final ContentResolver mContentResolver; private final ShellController mShellController; private final ShellCommandHandler mShellCommandHandler; private final ShellExecutor mShellExecutor; - private final Handler mBgHandler; private final WindowManager mWindowManager; private final Transitions mTransitions; @VisibleForTesting @@ -245,7 +227,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, - @NonNull @ShellBackgroundThread Handler backgroundHandler, Context context, @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, @@ -256,10 +237,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont shellInit, shellController, shellExecutor, - backgroundHandler, ActivityTaskManager.getService(), context, - context.getContentResolver(), backAnimationBackground, shellBackAnimationRegistry, shellCommandHandler, @@ -272,10 +251,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull ShellInit shellInit, @NonNull ShellController shellController, @NonNull @ShellMainThread ShellExecutor shellExecutor, - @NonNull @ShellBackgroundThread Handler bgHandler, @NonNull IActivityTaskManager activityTaskManager, Context context, - ContentResolver contentResolver, @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, @@ -285,10 +262,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; mContext = context; - mContentResolver = contentResolver; mRequirePointerPilfer = context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer); - mBgHandler = bgHandler; shellInit.addInitCallback(this::onInit, this); mAnimationBackground = backAnimationBackground; mShellBackAnimationRegistry = shellBackAnimationRegistry; @@ -305,8 +280,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private void onInit() { - setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler); - updateEnableAnimationFromFlags(); createAdapter(); mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR, this::createExternalInterface, this); @@ -314,42 +287,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mShellController.addConfigurationChangeListener(this); } - private void setupAnimationDeveloperSettingsObserver( - @NonNull ContentResolver contentResolver, - @NonNull @ShellBackgroundThread final Handler backgroundHandler) { - if (predictiveBackSystemAnims()) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore " - + "developer settings flag is ignored and no content observer registered"); - return; - } - ContentObserver settingsObserver = new ContentObserver(backgroundHandler) { - @Override - public void onChange(boolean selfChange, Uri uri) { - updateEnableAnimationFromFlags(); - } - }; - contentResolver.registerContentObserver( - Global.getUriFor(Global.ENABLE_BACK_ANIMATION), - false, settingsObserver, UserHandle.USER_SYSTEM - ); - } - - /** - * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the - * aconfig flag and the developer settings flag - */ - @ShellBackgroundThread - private void updateEnableAnimationFromFlags() { - boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled(); - mEnableAnimations.set(isEnabled); - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled); - } - - private boolean isDeveloperSettingEnabled() { - return Global.getInt(mContext.getContentResolver(), - Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON; - } - public BackAnimation getBackAnimationImpl() { return mBackAnimation; } @@ -617,14 +554,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void startBackNavigation(@NonNull BackTouchTracker touchTracker) { try { startLatencyTracking(); - final BackAnimationAdapter adapter = mEnableAnimations.get() - ? mBackAnimationAdapter : null; - if (adapter != null && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) { - adapter.updateSupportedAnimators( + if (mBackAnimationAdapter != null + && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) { + mBackAnimationAdapter.updateSupportedAnimators( mShellBackAnimationRegistry.getSupportedAnimators()); } mBackNavigationInfo = mActivityTaskManager.startBackNavigation( - mNavigationObserver, adapter); + mNavigationObserver, mBackAnimationAdapter); onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker); } catch (RemoteException remoteException) { Log.e(TAG, "Failed to initAnimation", remoteException); @@ -696,9 +632,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } private boolean shouldDispatchToAnimator() { - return mEnableAnimations.get() - && mBackNavigationInfo != null - && mBackNavigationInfo.isPrepareRemoteAnimation(); + return mBackNavigationInfo != null && mBackNavigationInfo.isPrepareRemoteAnimation(); } private void tryPilferPointers() { @@ -1093,7 +1027,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont () -> mShellExecutor.execute(this::onBackAnimationFinished)); if (mApps.length >= 1) { - mCurrentTracker.updateStartLocation(); BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]); dispatchOnBackStarted(mActiveCallback, startEvent); if (startEvent.getSwipeEdge() == EDGE_NONE) { @@ -1194,7 +1127,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ private void dump(PrintWriter pw, String prefix) { pw.println(prefix + "BackAnimationController state:"); - pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get()); pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted); pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress); pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 1408b6efc7f9..2600bcc18f72 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -108,7 +108,6 @@ import com.android.wm.shell.recents.TaskStackTransitionObserver; import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.TransactionPool; import com.android.wm.shell.shared.annotations.ShellAnimationThread; -import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.annotations.ShellSplashscreenThread; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; @@ -438,29 +437,24 @@ public abstract class WMShellBaseModule { ShellInit shellInit, ShellController shellController, @ShellMainThread ShellExecutor shellExecutor, - @ShellBackgroundThread Handler backgroundHandler, BackAnimationBackground backAnimationBackground, Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, Transitions transitions, @ShellMainThread Handler handler ) { - if (BackAnimationController.IS_ENABLED) { return shellBackAnimationRegistry.map( (animations) -> new BackAnimationController( shellInit, shellController, shellExecutor, - backgroundHandler, context, backAnimationBackground, animations, shellCommandHandler, transitions, handler)); - } - return Optional.empty(); } @BindsOptionalOf diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java index 03f388c9f1c9..9e2b9b20be16 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java @@ -117,6 +117,7 @@ public abstract class Pip2Module { PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, + PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor) { if (!PipUtils.isPip2ExperimentEnabled()) { return Optional.empty(); @@ -126,7 +127,7 @@ public abstract class Pip2Module { displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController, - mainExecutor)); + pipUiEventLogger, mainExecutor)); } } @@ -188,11 +189,11 @@ public abstract class Pip2Module { FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler, Optional<PipPerfHintController> pipPerfHintControllerOptional, - PipBoundsAlgorithm pipBoundsAlgorithm, - PipTransitionState pipTransitionState) { + PipTransitionState pipTransitionState, + PipUiEventLogger pipUiEventLogger) { return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm, floatingContentCoordinator, pipScheduler, pipPerfHintControllerOptional, - pipBoundsAlgorithm, pipTransitionState); + pipTransitionState, pipUiEventLogger); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index ee817b34b24a..7dab476f5ddb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -310,6 +310,11 @@ class DesktopTasksController( transitions.startTransition(transitionType, wct, handler).also { t -> handler?.setTransition(t) } + + // launch from recent DesktopTaskView + desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted( + FREEFORM_ANIMATION_DURATION + ) } /** Gets number of visible freeform tasks in [displayId]. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index 491b577386d7..e24b2c5f0134 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -332,7 +332,9 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll dragSession = new DragSession(ActivityTaskManager.getInstance(), mDisplayController.getDisplayLayout(displayId), event.getClipData(), event.getDragFlags()); - dragSession.initialize(); + // Only update the running task for now to determine if we should defer to desktop to + // handle the drag + dragSession.updateRunningTask(); final ActivityManager.RunningTaskInfo taskInfo = dragSession.runningTaskInfo; // Desktop tasks will have their own drag handling. final boolean isDesktopDrag = taskInfo != null && taskInfo.isFreeform() @@ -340,7 +342,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll pd.isHandlingDrag = DragUtils.canHandleDrag(event) && !isDesktopDrag; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s", - pd.isHandlingDrag, event.getClipData().getItemCount(), + pd.isHandlingDrag, + event.getClipData() != null ? event.getClipData().getItemCount() : -1, DragUtils.getMimeTypesConcatenated(description), DragUtils.dragFlagsToString(event.getDragFlags())); } @@ -355,6 +358,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll Slog.w(TAG, "Unexpected drag start during an active drag"); return false; } + // Only initialize the session after we've checked that we're handling the drag + dragSession.initialize(true /* skipUpdateRunningTask */); pd.dragSession = dragSession; pd.activeDragCount++; pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession)); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java index c4ff87d175a7..279452ee8b9b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java @@ -29,7 +29,6 @@ import android.content.ClipData; import android.content.ClipDescription; import android.content.Intent; import android.content.pm.ActivityInfo; -import android.os.PersistableBundle; import androidx.annotation.Nullable; @@ -44,6 +43,7 @@ import java.util.List; */ public class DragSession { private final ActivityTaskManager mActivityTaskManager; + @Nullable private final ClipData mInitialDragData; private final int mInitialDragFlags; @@ -66,7 +66,7 @@ public class DragSession { @WindowConfiguration.ActivityType int runningTaskActType = ACTIVITY_TYPE_STANDARD; boolean dragItemSupportsSplitscreen; - int hideDragSourceTaskId = -1; + final int hideDragSourceTaskId; DragSession(ActivityTaskManager activityTaskManager, DisplayLayout dispLayout, ClipData data, int dragFlags) { @@ -83,7 +83,6 @@ public class DragSession { /** * Returns the clip description associated with the drag. - * @return */ ClipDescription getClipDescription() { return mInitialDragData.getDescription(); @@ -125,8 +124,10 @@ public class DragSession { /** * Updates the session data based on the current state of the system at the start of the drag. */ - void initialize() { - updateRunningTask(); + void initialize(boolean skipUpdateRunningTask) { + if (!skipUpdateRunningTask) { + updateRunningTask(); + } activityInfo = mInitialDragData.getItemAt(0).getActivityInfo(); // TODO: This should technically check & respect config_supportsNonResizableMultiWindow diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java index 248a1124cd86..a62dd1c83520 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java @@ -49,7 +49,7 @@ public class DragUtils { * Returns whether we can handle this particular drag. */ public static boolean canHandleDrag(DragEvent event) { - if (event.getClipData().getItemCount() <= 0) { + if (event.getClipData() == null || event.getClipData().getItemCount() <= 0) { // No clip data, ignore this drag return false; } 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 8c6d5f5c6660..562b26014bf3 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 @@ -59,6 +59,7 @@ import com.android.wm.shell.common.pip.PipAppOpsListener; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipDisplayLayoutState; +import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.protolog.ShellProtoLogGroup; @@ -98,6 +99,7 @@ public class PipController implements ConfigurationChangeListener, private final PipTouchHandler mPipTouchHandler; private final PipAppOpsListener mPipAppOpsListener; private final PhonePipMenuController mPipMenuController; + private final PipUiEventLogger mPipUiEventLogger; private final ShellExecutor mMainExecutor; private final PipImpl mImpl; private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>(); @@ -143,6 +145,7 @@ public class PipController implements ConfigurationChangeListener, PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, + PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor) { mContext = context; mShellCommandHandler = shellCommandHandler; @@ -160,6 +163,7 @@ public class PipController implements ConfigurationChangeListener, mPipTouchHandler = pipTouchHandler; mPipAppOpsListener = pipAppOpsListener; mPipMenuController = pipMenuController; + mPipUiEventLogger = pipUiEventLogger; mMainExecutor = mainExecutor; mImpl = new PipImpl(); @@ -187,6 +191,7 @@ public class PipController implements ConfigurationChangeListener, PipTouchHandler pipTouchHandler, PipAppOpsListener pipAppOpsListener, PhonePipMenuController pipMenuController, + PipUiEventLogger pipUiEventLogger, ShellExecutor mainExecutor) { if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, @@ -197,7 +202,7 @@ public class PipController implements ConfigurationChangeListener, displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController, - mainExecutor); + pipUiEventLogger, mainExecutor); } public PipImpl getPipImpl() { @@ -238,18 +243,6 @@ public class PipController implements ConfigurationChangeListener, }); mPipAppOpsListener.setCallback(mPipTouchHandler.getMotionHelper()); - mPipTransitionState.addPipTransitionStateChangedListener( - (oldState, newState, extra) -> { - if (newState == PipTransitionState.ENTERED_PIP) { - final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo(); - if (taskInfo != null && taskInfo.topActivity != null) { - mPipAppOpsListener.onActivityPinned( - taskInfo.topActivity.getPackageName()); - } - } else if (newState == PipTransitionState.EXITED_PIP) { - mPipAppOpsListener.onActivityUnpinned(); - } - }); } private ExternalInterfaceBinder createExternalInterface() { @@ -446,14 +439,25 @@ public class PipController implements ConfigurationChangeListener, mPipTransitionState.setSwipePipToHomeState(overlay, appBounds); break; case PipTransitionState.ENTERED_PIP: + final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo(); + if (taskInfo != null && taskInfo.topActivity != null) { + mPipAppOpsListener.onActivityPinned(taskInfo.topActivity.getPackageName()); + mPipUiEventLogger.setTaskInfo(taskInfo); + } if (mPipTransitionState.isInSwipePipToHomeTransition()) { + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER); mPipTransitionState.resetSwipePipToHomeState(); + } else { + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER); } for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { listener.accept(true /* inPip */); } break; case PipTransitionState.EXITED_PIP: + mPipAppOpsListener.onActivityUnpinned(); + mPipUiEventLogger.setTaskInfo(null); for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) { listener.accept(false /* inPip */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 37296531ee34..9babe9e9e4eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -43,20 +43,20 @@ import com.android.wm.shell.R; import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.pip.PipAppOpsListener; -import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; import com.android.wm.shell.common.pip.PipPerfHintController; import com.android.wm.shell.common.pip.PipSnapAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; import com.android.wm.shell.pip2.animation.PipResizeAnimator; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.PhysicsAnimator; import com.android.wm.shell.shared.magnetictarget.MagnetizedObject; +import java.util.Optional; + import kotlin.Unit; import kotlin.jvm.functions.Function0; -import java.util.Optional; - /** * A helper to animate and manipulate the PiP. */ @@ -80,12 +80,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private static final float DISMISS_CIRCLE_PERCENT = 0.85f; private final Context mContext; - private @NonNull PipBoundsState mPipBoundsState; - private @NonNull PipBoundsAlgorithm mPipBoundsAlgorithm; - private @NonNull PipScheduler mPipScheduler; - private @NonNull PipTransitionState mPipTransitionState; - private PhonePipMenuController mMenuController; - private PipSnapAlgorithm mSnapAlgorithm; + @NonNull private final PipBoundsState mPipBoundsState; + @NonNull private final PipScheduler mPipScheduler; + @NonNull private final PipTransitionState mPipTransitionState; + @NonNull private final PipUiEventLogger mPipUiEventLogger; + private final PhonePipMenuController mMenuController; + private final PipSnapAlgorithm mSnapAlgorithm; /** The region that all of PIP must stay within. */ private final Rect mFloatingAllowedArea = new Rect(); @@ -168,10 +168,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler, Optional<PipPerfHintController> pipPerfHintControllerOptional, - PipBoundsAlgorithm pipBoundsAlgorithm, PipTransitionState pipTransitionState) { + PipTransitionState pipTransitionState, PipUiEventLogger pipUiEventLogger) { mContext = context; mPipBoundsState = pipBoundsState; - mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipScheduler = pipScheduler; mMenuController = menuController; mSnapAlgorithm = snapAlgorithm; @@ -185,6 +184,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, }; mPipTransitionState = pipTransitionState; mPipTransitionState.addPipTransitionStateChangedListener(this); + mPipUiEventLogger = pipUiEventLogger; } void init() { @@ -850,9 +850,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, if (mPipBoundsState.getBounds().left < 0 && mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) { mPipBoundsState.setStashed(STASH_TYPE_LEFT); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT); } else if (mPipBoundsState.getBounds().left >= 0 && mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) { mPipBoundsState.setStashed(STASH_TYPE_RIGHT); + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT); } mMenuController.hideMenu(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 3f65d9318692..1264c013faf5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -231,6 +231,7 @@ internal class AppHandleViewHolder( fun disposeStatusBarInputLayer() { if (!statusBarInputLayerExists) return statusBarInputLayerExists = false + statusBarInputLayer?.view?.setOnTouchListener(null) handler.post { statusBarInputLayer?.releaseView() statusBarInputLayer = null diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 47ee7bb20199..bbdb90f0a37c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -61,9 +61,7 @@ import android.os.RemoteCallback; import android.os.RemoteException; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; -import android.provider.Settings; import android.testing.AndroidTestingRunner; -import android.testing.TestableContentResolver; import android.testing.TestableLooper; import android.view.IRemoteAnimationRunner; import android.view.KeyEvent; @@ -84,7 +82,6 @@ import android.window.WindowContainerToken; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; -import com.android.internal.util.test.FakeSettingsProvider; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; @@ -109,7 +106,6 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class BackAnimationControllerTest extends ShellTestCase { - private static final String ANIMATION_ENABLED = "1"; private final TestShellExecutor mShellExecutor = new TestShellExecutor(); private ShellInit mShellInit; @@ -148,8 +144,6 @@ public class BackAnimationControllerTest extends ShellTestCase { private Transitions.TransitionHandler mTakeoverHandler; private BackAnimationController mController; - private TestableContentResolver mContentResolver; - private TestableLooper mTestableLooper; private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation; private CrossTaskBackAnimation mCrossTaskBackAnimation; @@ -166,11 +160,6 @@ public class BackAnimationControllerTest extends ShellTestCase { MockitoAnnotations.initMocks(this); mContext.addMockSystemService(InputManager.class, mInputManager); mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; - mContentResolver = new TestableContentResolver(mContext); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, - ANIMATION_ENABLED); - mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext, mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler); @@ -187,10 +176,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellInit, mShellController, mShellExecutor, - new Handler(mTestableLooper.getLooper()), mActivityTaskManager, mContext, - mContentResolver, mAnimationBackground, mShellBackAnimationRegistry, mShellCommandHandler, @@ -342,47 +329,6 @@ public class BackAnimationControllerTest extends ShellTestCase { } @Test - public void animationDisabledFromSettings() throws RemoteException { - // Toggle the setting off - Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0"); - ShellInit shellInit = new ShellInit(mShellExecutor); - mController = - new BackAnimationController( - shellInit, - mShellController, - mShellExecutor, - new Handler(mTestableLooper.getLooper()), - mActivityTaskManager, - mContext, - mContentResolver, - mAnimationBackground, - mShellBackAnimationRegistry, - mShellCommandHandler, - mTransitions, - mHandler); - shellInit.init(); - registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - - ArgumentCaptor<BackMotionEvent> backEventCaptor = - ArgumentCaptor.forClass(BackMotionEvent.class); - - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, - /* enableAnimation = */ false, - /* isAnimationCallback = */ false); - - triggerBackGesture(); - releaseBackGesture(); - - verify(mAppCallback, times(1)).onBackInvoked(); - - verify(mAnimatorCallback, never()).onBackStarted(any()); - verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture()); - verify(mAnimatorCallback, never()).onBackInvoked(); - verify(mBackAnimationRunner, never()).onAnimationStart( - anyInt(), any(), any(), any(), any()); - } - - @Test public void gestureQueued_WhenPreviousTransitionHasNotYetEnded() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index e40bbad7adda..32bb8bbdbbe3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -150,4 +150,25 @@ public class DragAndDropControllerTest extends ShellTestCase { mController.onDrag(dragLayout, event); verify(mDragAndDropListener, never()).onDragStarted(); } + + @Test + public void testOnDragStarted_withNoClipData() { + final View dragLayout = mock(View.class); + final Display display = mock(Display.class); + doReturn(display).when(dragLayout).getDisplay(); + doReturn(DEFAULT_DISPLAY).when(display).getDisplayId(); + + final ClipData clipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT); + final DragEvent event = mock(DragEvent.class); + doReturn(ACTION_DRAG_STARTED).when(event).getAction(); + doReturn(null).when(event).getClipData(); + doReturn(clipData.getDescription()).when(event).getClipDescription(); + + // Ensure there's a target so that onDrag will execute + mController.addDisplayDropTarget(0, mContext, mock(WindowManager.class), + mock(FrameLayout.class), mock(DragLayout.class)); + + // Verify the listener is called on a valid drag action. + mController.onDrag(dragLayout, event); + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java index 0cf15baf30b0..a284663d9a38 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java @@ -220,7 +220,7 @@ public class SplitDragPolicyTest extends ShellTestCase { setRunningTask(mHomeTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, data, 0 /* dragFlags */); - dragSession.initialize(); + dragSession.initialize(false /* skipUpdateRunningTask */); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_FULLSCREEN); @@ -235,7 +235,7 @@ public class SplitDragPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, data, 0 /* dragFlags */); - dragSession.initialize(); + dragSession.initialize(false /* skipUpdateRunningTask */); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT); @@ -255,7 +255,7 @@ public class SplitDragPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mPortraitDisplayLayout, data, 0 /* dragFlags */); - dragSession.initialize(); + dragSession.initialize(false /* skipUpdateRunningTask */); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = assertExactTargetTypes( mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM); @@ -276,7 +276,7 @@ public class SplitDragPolicyTest extends ShellTestCase { setRunningTask(mFullscreenAppTask); DragSession dragSession = new DragSession(mActivityTaskManager, mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */); - dragSession.initialize(); + dragSession.initialize(false /* skipUpdateRunningTask */); mPolicy.start(dragSession, mLoggerSessionId); ArrayList<Target> targets = mPolicy.getTargets(mInsets); for (Target t : targets) { diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index dbb891455ddd..e693fcfd3918 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -162,10 +162,13 @@ const std::string& ApkAssets::GetDebugName() const { return assets_provider_->GetDebugName(); } -bool ApkAssets::IsUpToDate() const { +UpToDate ApkAssets::IsUpToDate() const { // Loaders are invalidated by the app, not the system, so assume they are up to date. - return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate()) - && assets_provider_->IsUpToDate()); + if (IsLoader()) { + return UpToDate::Always; + } + const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always; + return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); }); } } // namespace android diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp index 2d3c06506a1f..11b12eb030a6 100644 --- a/libs/androidfw/AssetsProvider.cpp +++ b/libs/androidfw/AssetsProvider.cpp @@ -24,9 +24,8 @@ #include <ziparchive/zip_archive.h> namespace android { -namespace { -constexpr const char* kEmptyDebugString = "<empty>"; -} // namespace + +static constexpr std::string_view kEmptyDebugString = "<empty>"; std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode, bool* file_exists) const { @@ -86,11 +85,9 @@ void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const { } ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path, - package_property_t flags, time_t last_mod_time) - : zip_handle_(handle), - name_(std::move(path)), - flags_(flags), - last_mod_time_(last_mod_time) {} + package_property_t flags, ModDate last_mod_time) + : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) { +} std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, package_property_t flags, @@ -104,10 +101,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, return {}; } - struct stat sb{.st_mtime = -1}; + ModDate mod_date = kInvalidModDate; // Skip all up-to-date checks if the file won't ever change. - if (!isReadonlyFilesystem(path.c_str())) { - if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) { + if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) { + if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) { // Stat requires execute permissions on all directories path to the file. If the process does // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will // always have to return true. @@ -116,7 +113,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path, } return std::unique_ptr<ZipAssetsProvider>( - new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime)); + new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date)); } std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, @@ -137,10 +134,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, return {}; } - struct stat sb{.st_mtime = -1}; + ModDate mod_date = kInvalidModDate; // Skip all up-to-date checks if the file won't ever change. if (!isReadonlyFilesystem(released_fd)) { - if (fstat(released_fd, &sb) < 0) { + if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) { // Stat requires execute permissions on all directories path to the file. If the process does // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will // always have to return true. @@ -150,7 +147,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd, } return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider( - handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime)); + handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date)); } std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path, @@ -282,21 +279,16 @@ const std::string& ZipAssetsProvider::GetDebugName() const { return name_.GetDebugName(); } -bool ZipAssetsProvider::IsUpToDate() const { - if (last_mod_time_ == -1) { - return true; - } - struct stat sb{}; - if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) { - // If fstat fails on the zip archive, return true so the zip archive the resource system does - // attempt to refresh the ApkAsset. - return true; +UpToDate ZipAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; } - return last_mod_time_ == sb.st_mtime; + return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get()))); } -DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time) - : dir_(std::move(path)), last_mod_time_(last_mod_time) {} +DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time) + : dir_(std::move(path)), last_mod_time_(last_mod_time) { +} std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) { struct stat sb; @@ -317,7 +309,7 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st const bool isReadonly = isReadonlyFilesystem(path.c_str()); return std::unique_ptr<DirectoryAssetsProvider>( - new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime)); + new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb))); } std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path, @@ -346,17 +338,11 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const { return dir_; } -bool DirectoryAssetsProvider::IsUpToDate() const { - if (last_mod_time_ == -1) { - return true; - } - struct stat sb; - if (stat(dir_.c_str(), &sb) < 0) { - // If stat fails on the zip archive, return true so the zip archive the resource system does - // attempt to refresh the ApkAsset. - return true; +UpToDate DirectoryAssetsProvider::IsUpToDate() const { + if (last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; } - return last_mod_time_ == sb.st_mtime; + return fromBool(last_mod_time_ == getFileModDate(dir_.c_str())); } MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary, @@ -369,8 +355,14 @@ MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& prima std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create( std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) { - if (primary == nullptr || secondary == nullptr) { - return nullptr; + if (primary == nullptr && secondary == nullptr) { + return EmptyAssetsProvider::Create(); + } + if (!primary) { + return secondary; + } + if (!secondary) { + return primary; } return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary), std::move(secondary))); @@ -397,8 +389,8 @@ const std::string& MultiAssetsProvider::GetDebugName() const { return debug_name_; } -bool MultiAssetsProvider::IsUpToDate() const { - return primary_->IsUpToDate() && secondary_->IsUpToDate(); +UpToDate MultiAssetsProvider::IsUpToDate() const { + return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); }); } EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) : @@ -438,12 +430,12 @@ const std::string& EmptyAssetsProvider::GetDebugName() const { if (path_.has_value()) { return *path_; } - const static std::string kEmpty = kEmptyDebugString; + constexpr static std::string kEmpty{kEmptyDebugString}; return kEmpty; } -bool EmptyAssetsProvider::IsUpToDate() const { - return true; +UpToDate EmptyAssetsProvider::IsUpToDate() const { + return UpToDate::Always; } } // namespace android diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp index 3ecd82b074a1..262e7df185b7 100644 --- a/libs/androidfw/Idmap.cpp +++ b/libs/androidfw/Idmap.cpp @@ -22,9 +22,10 @@ #include "android-base/logging.h" #include "android-base/stringprintf.h" #include "android-base/utf8.h" -#include "androidfw/misc.h" +#include "androidfw/AssetManager.h" #include "androidfw/ResourceTypes.h" #include "androidfw/Util.h" +#include "androidfw/misc.h" #include "utils/ByteOrder.h" #include "utils/Trace.h" @@ -268,11 +269,16 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head configurations_(configs), overlay_entries_(overlay_entries), string_pool_(std::move(string_pool)), - idmap_fd_( - android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)), overlay_apk_path_(overlay_apk_path), target_apk_path_(target_apk_path), - idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) { + idmap_last_mod_time_(kInvalidModDate) { + if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) || + !(target_apk_path_ == AssetManager::TARGET_APK_PATH || + isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) { + idmap_fd_.reset( + android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)); + idmap_last_mod_time_ = getFileModDate(idmap_fd_); + } } std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) { @@ -381,8 +387,11 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path)); } -bool LoadedIdmap::IsUpToDate() const { - return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()); +UpToDate LoadedIdmap::IsUpToDate() const { + if (idmap_last_mod_time_ == kInvalidModDate) { + return UpToDate::Always; + } + return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get())); } } // namespace android diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index de9991a8be5e..a8eb062a2ece 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -152,12 +152,11 @@ static void fill9patchOffsets(Res_png_9patch* patch) { patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t)); } -void Res_value::copyFrom_dtoh(const Res_value& src) -{ - size = dtohs(src.size); - res0 = src.res0; - dataType = src.dataType; - data = dtohl(src.data); +void Res_value::copyFrom_dtoh_slow(const Res_value& src) { + size = dtohs(src.size); + res0 = src.res0; + dataType = src.dataType; + data = dtohl(src.data); } void Res_png_9patch::deviceToFile() @@ -2031,16 +2030,6 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const // -------------------------------------------------------------------- // -------------------------------------------------------------------- -void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) { - const size_t size = dtohl(o.size); - if (size >= sizeof(ResTable_config)) { - *this = o; - } else { - memcpy(this, &o, size); - memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size); - } -} - /* static */ size_t unpackLanguageOrRegion(const char in[2], const char base, char out[4]) { if (in[0] & 0x80) { @@ -2105,34 +2094,33 @@ size_t ResTable_config::unpackRegion(char region[4]) const { return unpackLanguageOrRegion(this->country, '0', region); } - -void ResTable_config::copyFromDtoH(const ResTable_config& o) { - copyFromDeviceNoSwap(o); - size = sizeof(ResTable_config); - mcc = dtohs(mcc); - mnc = dtohs(mnc); - density = dtohs(density); - screenWidth = dtohs(screenWidth); - screenHeight = dtohs(screenHeight); - sdkVersion = dtohs(sdkVersion); - minorVersion = dtohs(minorVersion); - smallestScreenWidthDp = dtohs(smallestScreenWidthDp); - screenWidthDp = dtohs(screenWidthDp); - screenHeightDp = dtohs(screenHeightDp); -} - -void ResTable_config::swapHtoD() { - size = htodl(size); - mcc = htods(mcc); - mnc = htods(mnc); - density = htods(density); - screenWidth = htods(screenWidth); - screenHeight = htods(screenHeight); - sdkVersion = htods(sdkVersion); - minorVersion = htods(minorVersion); - smallestScreenWidthDp = htods(smallestScreenWidthDp); - screenWidthDp = htods(screenWidthDp); - screenHeightDp = htods(screenHeightDp); +void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) { + copyFromDeviceNoSwap(o); + size = sizeof(ResTable_config); + mcc = dtohs(mcc); + mnc = dtohs(mnc); + density = dtohs(density); + screenWidth = dtohs(screenWidth); + screenHeight = dtohs(screenHeight); + sdkVersion = dtohs(sdkVersion); + minorVersion = dtohs(minorVersion); + smallestScreenWidthDp = dtohs(smallestScreenWidthDp); + screenWidthDp = dtohs(screenWidthDp); + screenHeightDp = dtohs(screenHeightDp); +} + +void ResTable_config::swapHtoD_slow() { + size = htodl(size); + mcc = htods(mcc); + mnc = htods(mnc); + density = htods(density); + screenWidth = htods(screenWidth); + screenHeight = htods(screenHeight); + sdkVersion = htods(sdkVersion); + minorVersion = htods(minorVersion); + smallestScreenWidthDp = htods(smallestScreenWidthDp); + screenWidthDp = htods(screenWidthDp); + screenHeightDp = htods(screenHeightDp); } /* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) { @@ -2145,7 +2133,7 @@ void ResTable_config::swapHtoD() { // systems should happen very infrequently (if at all.) // The comparison code relies on memcmp low-level optimizations that make it // more efficient than strncmp. - const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; + static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'}; const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript; const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript; diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp index be55fe8b4bb6..86c459fb4647 100644 --- a/libs/androidfw/Util.cpp +++ b/libs/androidfw/Util.cpp @@ -32,13 +32,18 @@ namespace android { namespace util { void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) { - char buf[5]; - while (*src && len != 0) { - char16_t c = static_cast<char16_t>(dtohs(*src)); - utf16_to_utf8(&c, 1, buf, sizeof(buf)); - out->append(buf, strlen(buf)); - ++src; - --len; + static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001; + if constexpr (kDeviceEndiannessSame) { + *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)}); + } else { + char buf[5]; + while (*src && len != 0) { + char16_t c = static_cast<char16_t>(dtohs(*src)); + utf16_to_utf8(&c, 1, buf, sizeof(buf)); + out->append(buf, strlen(buf)); + ++src; + --len; + } } } @@ -63,8 +68,10 @@ std::string Utf16ToUtf8(StringPiece16 utf16) { } std::string utf8; - utf8.resize(utf8_length); - utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1); + utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) { + utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1); + return size; + }); return utf8; } diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 231808beb718..3f6f4661f2f7 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -116,7 +116,7 @@ class ApkAssets : public RefBase { return resources_asset_ != nullptr && resources_asset_->isAllocated(); } - bool IsUpToDate() const; + UpToDate IsUpToDate() const; // DANGER! // This is a destructive method that rips the assets provider out of ApkAssets object. diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h index d33c325ff369..e3b3ae41f7f4 100644 --- a/libs/androidfw/include/androidfw/AssetsProvider.h +++ b/libs/androidfw/include/androidfw/AssetsProvider.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef ANDROIDFW_ASSETSPROVIDER_H -#define ANDROIDFW_ASSETSPROVIDER_H +#pragma once #include <memory> #include <string> @@ -58,7 +57,7 @@ struct AssetsProvider { WARN_UNUSED virtual const std::string& GetDebugName() const = 0; // Returns whether the interface provides the most recent version of its files. - WARN_UNUSED virtual bool IsUpToDate() const = 0; + WARN_UNUSED virtual UpToDate IsUpToDate() const = 0; // Creates an Asset from a file on disk. static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path); @@ -95,7 +94,7 @@ struct ZipAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const; ~ZipAssetsProvider() override = default; @@ -106,7 +105,7 @@ struct ZipAssetsProvider : public AssetsProvider { private: struct PathOrDebugName; ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags, - time_t last_mod_time); + ModDate last_mod_time); struct PathOrDebugName { static PathOrDebugName Path(std::string value) { @@ -135,7 +134,7 @@ struct ZipAssetsProvider : public AssetsProvider { std::unique_ptr<ZipArchive, ZipCloser> zip_handle_; PathOrDebugName name_; package_property_t flags_; - time_t last_mod_time_; + ModDate last_mod_time_; }; // Supplies assets from a root directory. @@ -147,7 +146,7 @@ struct DirectoryAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~DirectoryAssetsProvider() override = default; protected: @@ -156,23 +155,23 @@ struct DirectoryAssetsProvider : public AssetsProvider { bool* file_exists) const override; private: - explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time); + explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time); std::string dir_; - time_t last_mod_time_; + ModDate last_mod_time_; }; // Supplies assets from a `primary` asset provider and falls back to supplying assets from the // `secondary` asset provider if the asset cannot be found in the `primary`. struct MultiAssetsProvider : public AssetsProvider { static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary, - std::unique_ptr<AssetsProvider>&& secondary); + std::unique_ptr<AssetsProvider>&& secondary = {}); bool ForEachFile(const std::string& root_path, base::function_ref<void(StringPiece, FileType)> f) const override; WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~MultiAssetsProvider() override = default; protected: @@ -199,7 +198,7 @@ struct EmptyAssetsProvider : public AssetsProvider { WARN_UNUSED std::optional<std::string_view> GetPath() const override; WARN_UNUSED const std::string& GetDebugName() const override; - WARN_UNUSED bool IsUpToDate() const override; + WARN_UNUSED UpToDate IsUpToDate() const override; ~EmptyAssetsProvider() override = default; protected: @@ -212,5 +211,3 @@ struct EmptyAssetsProvider : public AssetsProvider { }; } // namespace android - -#endif /* ANDROIDFW_ASSETSPROVIDER_H */ diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index ac75eb3bb98c..87f3c9df9a91 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -14,8 +14,7 @@ * limitations under the License. */ -#ifndef IDMAP_H_ -#define IDMAP_H_ +#pragma once #include <memory> #include <string> @@ -32,6 +31,31 @@ namespace android { +// An enum that tracks more states than just 'up to date' or 'not' for a resources container: +// there are several cases where we know for sure that the object can't change and won't get +// out of date. Reporting those states to the managed layer allows it to stop checking here +// completely, speeding up the cache lookups by dozens of milliseconds. +enum class UpToDate : int { False, True, Always }; + +// Combines two UpToDate values, and only accesses the second one if it matters to the result. +template <class Getter> +UpToDate combine(UpToDate first, Getter secondGetter) { + switch (first) { + case UpToDate::False: + return UpToDate::False; + case UpToDate::True: { + const auto second = secondGetter(); + return second == UpToDate::False ? UpToDate::False : UpToDate::True; + } + case UpToDate::Always: + return secondGetter(); + } +} + +inline UpToDate fromBool(bool value) { + return value ? UpToDate::True : UpToDate::False; +} + class LoadedIdmap; class IdmapResMap; struct Idmap_header; @@ -196,7 +220,7 @@ class LoadedIdmap { // Returns whether the idmap file on disk has not been modified since the construction of this // LoadedIdmap. - bool IsUpToDate() const; + UpToDate IsUpToDate() const; protected: // Exposed as protected so that tests can subclass and mock this class out. @@ -231,5 +255,3 @@ class LoadedIdmap { }; } // namespace android - -#endif // IDMAP_H_ diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index e330410ed1a0..819fe4b38c87 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -47,6 +47,8 @@ namespace android { +constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001; + constexpr const uint32_t kIdmapMagic = 0x504D4449u; constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au; @@ -408,7 +410,16 @@ struct Res_value typedef uint32_t data_type; data_type data; - void copyFrom_dtoh(const Res_value& src); + void copyFrom_dtoh(const Res_value& src) { + if constexpr (kDeviceEndiannessSame) { + *this = src; + } else { + copyFrom_dtoh_slow(src); + } + } + + private: + void copyFrom_dtoh_slow(const Res_value& src); }; /** @@ -1254,11 +1265,32 @@ struct ResTable_config // Varies in length from 3 to 8 chars. Zero-filled value. char localeNumberingSystem[8]; - void copyFromDeviceNoSwap(const ResTable_config& o); - - void copyFromDtoH(const ResTable_config& o); - - void swapHtoD(); + void copyFromDeviceNoSwap(const ResTable_config& o) { + const auto o_size = dtohl(o.size); + if (o_size >= sizeof(ResTable_config)) [[likely]] { + *this = o; + } else { + memcpy(this, &o, o_size); + memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size); + } + this->size = sizeof(*this); + } + + void copyFromDtoH(const ResTable_config& o) { + if constexpr (kDeviceEndiannessSame) { + copyFromDeviceNoSwap(o); + } else { + copyFromDtoH_slow(o); + } + } + + void swapHtoD() { + if constexpr (kDeviceEndiannessSame) { + ; // noop + } else { + swapHtoD_slow(); + } + } int compare(const ResTable_config& o) const; int compareLogical(const ResTable_config& o) const; @@ -1384,6 +1416,10 @@ struct ResTable_config bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const; String8 toString() const; + + private: + void copyFromDtoH_slow(const ResTable_config& o); + void swapHtoD_slow(); }; /** diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index c9ba8a01a5e9..d8ca64a174a2 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -15,6 +15,7 @@ */ #pragma once +#include <sys/stat.h> #include <time.h> // @@ -64,10 +65,15 @@ ModDate getFileModDate(const char* fileName); /* same, but also returns -1 if the file has already been deleted */ ModDate getFileModDate(int fd); +// Extract the modification date from the stat structure. +ModDate getModDate(const struct ::stat& st); + // Check if |path| or |fd| resides on a readonly filesystem. bool isReadonlyFilesystem(const char* path); bool isReadonlyFilesystem(int fd); +bool isKnownWritablePath(const char* path); + } // namespace android // Whoever uses getFileModDate() will need this as well diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 32f3624a3aee..26eb320805c9 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -16,10 +16,10 @@ #define LOG_TAG "misc" -// -// Miscellaneous utility functions. -// -#include <androidfw/misc.h> +#include "androidfw/misc.h" + +#include <errno.h> +#include <sys/stat.h> #include "android-base/logging.h" @@ -28,9 +28,7 @@ #include <sys/vfs.h> #endif // __linux__ -#include <errno.h> -#include <sys/stat.h> - +#include <array> #include <cstdio> #include <cstring> #include <tuple> @@ -40,28 +38,26 @@ namespace android { /* * Get a file's type. */ -FileType getFileType(const char* fileName) -{ - struct stat sb; - - if (stat(fileName, &sb) < 0) { - if (errno == ENOENT || errno == ENOTDIR) - return kFileTypeNonexistent; - else { - PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; - return kFileTypeUnknown; - } - } else { - if (S_ISREG(sb.st_mode)) - return kFileTypeRegular; - else if (S_ISDIR(sb.st_mode)) - return kFileTypeDirectory; - else if (S_ISCHR(sb.st_mode)) - return kFileTypeCharDev; - else if (S_ISBLK(sb.st_mode)) - return kFileTypeBlockDev; - else if (S_ISFIFO(sb.st_mode)) - return kFileTypeFifo; +FileType getFileType(const char* fileName) { + struct stat sb; + if (stat(fileName, &sb) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + return kFileTypeNonexistent; + else { + PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed"; + return kFileTypeUnknown; + } + } else { + if (S_ISREG(sb.st_mode)) + return kFileTypeRegular; + else if (S_ISDIR(sb.st_mode)) + return kFileTypeDirectory; + else if (S_ISCHR(sb.st_mode)) + return kFileTypeCharDev; + else if (S_ISBLK(sb.st_mode)) + return kFileTypeBlockDev; + else if (S_ISFIFO(sb.st_mode)) + return kFileTypeFifo; #if defined(S_ISLNK) else if (S_ISLNK(sb.st_mode)) return kFileTypeSymlink; @@ -75,7 +71,7 @@ FileType getFileType(const char* fileName) } } -static ModDate getModDate(const struct stat& st) { +ModDate getModDate(const struct stat& st) { #ifdef _WIN32 return st.st_mtime; #elif defined(__APPLE__) @@ -113,8 +109,14 @@ bool isReadonlyFilesystem(const char*) { bool isReadonlyFilesystem(int) { return false; } +bool isKnownWritablePath(const char*) { + return false; +} #else // __linux__ bool isReadonlyFilesystem(const char* path) { + if (isKnownWritablePath(path)) { + return false; + } struct statfs sfs; if (::statfs(path, &sfs)) { PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed"; @@ -131,6 +133,13 @@ bool isReadonlyFilesystem(int fd) { } return (sfs.f_flags & ST_RDONLY) != 0; } + +bool isKnownWritablePath(const char* path) { + // We know that all paths in /data/ are writable. + static constexpr char kRwPrefix[] = "/data/"; + return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0; +} + #endif // __linux__ } // namespace android diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index cb2e56f5f5e4..22b9e69500d9 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -218,10 +218,11 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { auto apk_assets = ApkAssets::LoadOverlay(temp_file.path); ASSERT_NE(nullptr, apk_assets); - ASSERT_TRUE(apk_assets->IsUpToDate()); + ASSERT_TRUE(apk_assets->IsOverlay()); + ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate()); unlink(temp_file.path); - ASSERT_FALSE(apk_assets->IsUpToDate()); + ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate()); const auto sleep_duration = std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull)); @@ -230,7 +231,27 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { base::WriteStringToFile("hello", temp_file.path); std::this_thread::sleep_for(sleep_duration); - ASSERT_FALSE(apk_assets->IsUpToDate()); + ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate()); +} + +TEST(IdmapTestUpToDate, Combine) { + ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] { + ADD_FAILURE(); // Shouldn't get called at all. + return UpToDate::False; + })); + + ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; })); + + ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; })); + ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; })); + ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; })); + + ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; })); +} + +TEST(IdmapTestUpToDate, FromBool) { + ASSERT_EQ(UpToDate::False, fromBool(false)); + ASSERT_EQ(UpToDate::True, fromBool(true)); } } // namespace diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 3d0c4069e782..213bc0673da6 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -27,6 +27,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.WorkerThread; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -475,6 +476,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int loadSoundModel(@NonNull SoundModel soundModel) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); @@ -518,6 +520,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params, @NonNull ComponentName detectionService, @NonNull RecognitionConfig config) { Objects.requireNonNull(soundModelId); @@ -544,6 +547,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int stopRecognition(@NonNull UUID soundModelId) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); @@ -568,6 +572,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int unloadSoundModel(@NonNull UUID soundModelId) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); @@ -587,6 +592,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public boolean isRecognitionActive(@NonNull UUID soundModelId) { if (soundModelId == null || mSoundTriggerSession == null) { return false; @@ -624,6 +630,7 @@ public final class SoundTriggerManager { @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @UnsupportedAppUsage @FlaggedApi(Flags.FLAG_MANAGER_API) + @WorkerThread public int getModelState(@NonNull UUID soundModelId) { if (mSoundTriggerSession == null) { throw new IllegalStateException("No underlying SoundTriggerModule available"); diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml index 5ddf005d9468..dafcc729b8f1 100644 --- a/packages/SettingsProvider/res/values/defaults.xml +++ b/packages/SettingsProvider/res/values/defaults.xml @@ -322,9 +322,6 @@ <!-- Whether vibrate icon is shown in the status bar by default. --> <integer name="def_statusBarVibrateIconEnabled">0</integer> - <!-- Whether predictive back animation is enabled by default. --> - <bool name="def_enable_back_animation">false</bool> - <!-- Whether wifi is always requested by default. --> <bool name="def_enable_wifi_always_requested">false</bool> diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java index 5b4ee8bdb339..1f56f10cca7d 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java @@ -109,6 +109,7 @@ public class SystemSettings { Settings.System.LOCALE_PREFERENCES, Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING, Settings.System.MOUSE_SCROLLING_ACCELERATION, + Settings.System.MOUSE_SCROLLING_SPEED, Settings.System.MOUSE_SWAP_PRIMARY_BUTTON, Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED, Settings.System.TOUCHPAD_POINTER_SPEED, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 0432eeacec4d..4d98a11bdfe7 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -227,6 +227,7 @@ public class SystemSettingsValidators { VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR); VALIDATORS.put(System.MOUSE_SCROLLING_ACCELERATION, BOOLEAN_VALIDATOR); VALIDATORS.put(System.MOUSE_POINTER_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(System.MOUSE_SCROLLING_SPEED, new InclusiveIntegerRangeValidator(-7, 7)); VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7)); VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR); VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index a2cc008843a4..ef0bc3b100e0 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -193,6 +193,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { "power_button_instantly_locks"; private static final String KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY = "pin_enhanced_privacy"; + private static final int NUM_LOCK_SETTINGS = 5; // Error messages for logging metrics. private static final String ERROR_COULD_NOT_READ_FROM_CURSOR = @@ -208,6 +209,13 @@ public class SettingsBackupAgent extends BackupAgentHelper { private static final String ERROR_SKIPPED_DUE_TO_LARGE_SCREEN = "skipped_due_to_large_screen"; private static final String ERROR_DID_NOT_PASS_VALIDATION = "did_not_pass_validation"; + private static final String ERROR_IO_EXCEPTION = "io_exception"; + private static final String ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG = + "failed_to_restore_softap_config"; + private static final String ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES = + "failed_to_convert_network_policies"; + private static final String ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION = + "unknown_backup_serialization_version"; // Name of the temporary file we use during full backup/restore. This is @@ -794,29 +802,44 @@ public class SettingsBackupAgent extends BackupAgentHelper { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream out = new DataOutputStream(baos); + int backedUpSettingsCount = 0; try { out.writeUTF(KEY_LOCK_SETTINGS_OWNER_INFO_ENABLED); out.writeUTF(ownerInfoEnabled ? "1" : "0"); + backedUpSettingsCount++; if (ownerInfo != null) { out.writeUTF(KEY_LOCK_SETTINGS_OWNER_INFO); out.writeUTF(ownerInfo != null ? ownerInfo : ""); + backedUpSettingsCount++; } if (lockPatternUtils.isVisiblePatternEverChosen(userId)) { out.writeUTF(KEY_LOCK_SETTINGS_VISIBLE_PATTERN_ENABLED); out.writeUTF(visiblePatternEnabled ? "1" : "0"); + backedUpSettingsCount++; } if (lockPatternUtils.isPowerButtonInstantlyLocksEverChosen(userId)) { out.writeUTF(KEY_LOCK_SETTINGS_POWER_BUTTON_INSTANTLY_LOCKS); out.writeUTF(powerButtonInstantlyLocks ? "1" : "0"); + backedUpSettingsCount++; } if (lockPatternUtils.isPinEnhancedPrivacyEverChosen(userId)) { out.writeUTF(KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY); out.writeUTF(lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) ? "1" : "0"); + backedUpSettingsCount++; } // End marker out.writeUTF(""); out.flush(); + if (areAgentMetricsEnabled) { + numberOfSettingsPerKey.put(KEY_LOCK_SETTINGS, backedUpSettingsCount); + } } catch (IOException ioe) { + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsBackupFailed( + KEY_LOCK_SETTINGS, + NUM_LOCK_SETTINGS - backedUpSettingsCount, + ERROR_IO_EXCEPTION); + } } return baos.toByteArray(); } @@ -1162,6 +1185,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, nBytes); DataInputStream in = new DataInputStream(bais); + int restoredLockSettingsCount = 0; try { String key; // Read until empty string marker @@ -1187,9 +1211,20 @@ public class SettingsBackupAgent extends BackupAgentHelper { lockPatternUtils.setPinEnhancedPrivacyEnabled("1".equals(value), userId); break; } + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestored(KEY_LOCK_SETTINGS, /* count= */ 1); + restoredLockSettingsCount++; + } + } in.close(); } catch (IOException ioe) { + if (areAgentMetricsEnabled) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_LOCK_SETTINGS, + NUM_LOCK_SETTINGS - restoredLockSettingsCount, + ERROR_IO_EXCEPTION); + } } } @@ -1309,12 +1344,31 @@ public class SettingsBackupAgent extends BackupAgentHelper { mWifiManager.restoreSupplicantBackupData(supplicant_bytes, ipconfig_bytes); } - private byte[] getSoftAPConfiguration() { - return mWifiManager.retrieveSoftApBackupData(); + @VisibleForTesting + byte[] getSoftAPConfiguration() { + byte[] data = mWifiManager.retrieveSoftApBackupData(); + if (areAgentMetricsEnabled) { + // We're unable to determine how many settings this includes, so we'll just log 1. + numberOfSettingsPerKey.put(KEY_SOFTAP_CONFIG, 1); + } + return data; } - private void restoreSoftApConfiguration(byte[] data) { - SoftApConfiguration configInCloud = mWifiManager.restoreSoftApBackupData(data); + @VisibleForTesting + void restoreSoftApConfiguration(byte[] data) { + SoftApConfiguration configInCloud; + if (areAgentMetricsEnabled) { + try { + configInCloud = mWifiManager.restoreSoftApBackupData(data); + mBackupRestoreEventLogger.logItemsRestored(KEY_SOFTAP_CONFIG, /* count= */ 1); + } catch (Exception e) { + configInCloud = null; + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_SOFTAP_CONFIG, /* count= */ 1, ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG); + } + } else { + configInCloud = mWifiManager.restoreSoftApBackupData(data); + } if (configInCloud != null) { if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration "); // Depending on device hardware, we may need to notify the user of a setting change @@ -1384,6 +1438,7 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { out.writeInt(NETWORK_POLICIES_BACKUP_VERSION); out.writeInt(policies.length); + int numberOfPoliciesBackedUp = 0; for (NetworkPolicy policy : policies) { // We purposefully only backup policies that the user has // defined; any inferred policies might include @@ -1393,13 +1448,23 @@ public class SettingsBackupAgent extends BackupAgentHelper { out.writeByte(BackupUtils.NOT_NULL); out.writeInt(marshaledPolicy.length); out.write(marshaledPolicy); + if (areAgentMetricsEnabled) { + numberOfPoliciesBackedUp++; + } } else { out.writeByte(BackupUtils.NULL); } } + if (areAgentMetricsEnabled) { + numberOfSettingsPerKey.put(KEY_NETWORK_POLICIES, numberOfPoliciesBackedUp); + } } catch (IOException ioe) { Log.e(TAG, "Failed to convert NetworkPolicies to byte array " + ioe.getMessage()); baos.reset(); + mBackupRestoreEventLogger.logItemsBackupFailed( + KEY_NETWORK_POLICIES, + policies.length, + ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES); } } return baos.toByteArray(); @@ -1433,6 +1498,10 @@ public class SettingsBackupAgent extends BackupAgentHelper { try { int version = in.readInt(); if (version < 1 || version > NETWORK_POLICIES_BACKUP_VERSION) { + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_NETWORK_POLICIES, + /* count= */ 1, + ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION); throw new BackupUtils.BadVersionException( "Unknown Backup Serialization Version"); } @@ -1449,10 +1518,15 @@ public class SettingsBackupAgent extends BackupAgentHelper { } // Only set the policies if there was no error in the restore operation networkPolicyManager.setNetworkPolicies(policies); + mBackupRestoreEventLogger.logItemsRestored(KEY_NETWORK_POLICIES, policies.length); } catch (NullPointerException | IOException | BackupUtils.BadVersionException | DateTimeException e) { // NPE can be thrown when trying to instantiate a NetworkPolicy Log.e(TAG, "Failed to convert byte array to NetworkPolicies " + e.getMessage()); + mBackupRestoreEventLogger.logItemsRestoreFailed( + KEY_NETWORK_POLICIES, + /* count= */ 1, + ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES); } } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index ed193515b382..cb656bdd5d54 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -6122,17 +6122,7 @@ public class SettingsProvider extends ContentProvider { } if (currentVersion == 220) { - final SettingsState globalSettings = getGlobalSettingsLocked(); - final Setting enableBackAnimation = - globalSettings.getSettingLocked(Global.ENABLE_BACK_ANIMATION); - if (enableBackAnimation.isNull()) { - final boolean defEnableBackAnimation = - getContext() - .getResources() - .getBoolean(R.bool.def_enable_back_animation); - initGlobalSettingsDefaultValLocked( - Settings.Global.ENABLE_BACK_ANIMATION, defEnableBackAnimation); - } + // Version 221: Removed currentVersion = 221; } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index c88a7fd834d6..cbdb36fff98c 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -564,7 +564,6 @@ public class SettingsBackupTest { Settings.Global.WATCHDOG_TIMEOUT_MILLIS, Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER, Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, - Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, // cache per hearing device Settings.Global.HEARING_DEVICE_LOCAL_NOTIFICATION, // cache per hearing device Settings.Global.Wearable.COMBINED_LOCATION_ENABLE, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java index 18c43a704bcc..95dd0db40c0e 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java @@ -16,6 +16,8 @@ package com.android.providers.settings; +import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; @@ -28,6 +30,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.backup.BackupAnnotations.BackupDestination; import android.app.backup.BackupAnnotations.OperationType; import android.app.backup.BackupDataInput; @@ -42,6 +45,8 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; +import android.net.wifi.SoftApConfiguration; +import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; import android.os.UserHandle; @@ -126,6 +131,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { @Mock private BackupDataInput mBackupDataInput; @Mock private BackupDataOutput mBackupDataOutput; + @Mock private static WifiManager mWifiManager; private TestFriendlySettingsBackupAgent mAgentUnderTest; private Context mContext; @@ -754,6 +760,80 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest)); } + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void getSoftAPConfiguration_flagIsEnabled_numberOfSettingsInKeyAreRecorded() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + when(mWifiManager.retrieveSoftApBackupData()).thenReturn(null); + + mAgentUnderTest.getSoftAPConfiguration(); + + assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_SOFTAP_CONFIG), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void getSoftAPConfiguration_flagIsNotEnabled_numberOfSettingsInKeyAreNotRecorded() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP); + when(mWifiManager.retrieveSoftApBackupData()).thenReturn(null); + + mAgentUnderTest.getSoftAPConfiguration(); + + assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_SOFTAP_CONFIG), 0); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreSoftApConfiguration_flagIsEnabled_restoreIsSuccessful_successMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build(); + byte[] data = config.toString().getBytes(); + when(mWifiManager.restoreSoftApBackupData(any())).thenReturn(null); + + mAgentUnderTest.restoreSoftApConfiguration(data); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getSuccessCount(), 1); + } + + @Test + @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void + restoreSoftApConfiguration_flagIsEnabled_restoreIsNotSuccessful_failureMetricsAreLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build(); + byte[] data = config.toString().getBytes(); + when(mWifiManager.restoreSoftApBackupData(any())).thenThrow(new RuntimeException()); + + mAgentUnderTest.restoreSoftApConfiguration(data); + + DataTypeResult loggingResult = + getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest); + assertNotNull(loggingResult); + assertEquals(loggingResult.getFailCount(), 1); + } + + @Test + @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS) + public void restoreSoftApConfiguration_flagIsNotEnabled_metricsAreNotLogged() { + mAgentUnderTest.onCreate( + UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE); + SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build(); + byte[] data = config.toString().getBytes(); + when(mWifiManager.restoreSoftApBackupData(any())).thenReturn(null); + + mAgentUnderTest.restoreSoftApConfiguration(data); + + assertNull(getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest)); + } + private byte[] generateBackupData(Map<String, String> keyValueData) { int totalBytes = 0; for (String key : keyValueData.keySet()) { @@ -890,6 +970,13 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { this.numberOfSettingsPerKey.put(key, numberOfSettings); } } + + int getNumberOfSettingsPerKey(String key) { + if (numberOfSettingsPerKey == null || !numberOfSettingsPerKey.containsKey(key)) { + return 0; + } + return numberOfSettingsPerKey.get(key); + } } /** The TestSettingsHelper tracks which values have been backed up and/or restored. */ @@ -944,6 +1031,14 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest { public ContentResolver getContentResolver() { return mContentResolver; } + + @Override + public Object getSystemService(String name) { + if (name.equals(Context.WIFI_SERVICE)) { + return mWifiManager; + } + return super.getSystemService(name); + } } /** ContentProvider which returns a set of known test values. */ diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 795b39576391..c6cc9a975191 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -119,4 +119,5 @@ xuqiu@google.com yeinj@google.com yuandizhou@google.com yurilin@google.com +yuzhechen@google.com zakcohen@google.com diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 715d22328f2b..7d5fd903c01b 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -133,14 +133,6 @@ flag { } flag { - name: "notifications_footer_view_refactor" - namespace: "systemui" - description: "Enables the refactored version of the footer view in the notification shade " - "(containing the \"Clear all\" button). Should not bring any behavior changes" - bug: "293167744" -} - -flag { name: "notifications_icon_container_refactor" namespace: "systemui" description: "Enables the refactored version of the notification icon container in StatusBar, " diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt index e02e8b483543..5f1f588bb2b5 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.input.pointer.PointerEventPass 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.changedToDownIgnoreConsumed import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed @@ -52,7 +53,6 @@ import androidx.compose.ui.node.currentValueOf import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity -import androidx.compose.ui.util.fastSumBy import com.android.compose.modifiers.thenIf import kotlin.math.sign import kotlinx.coroutines.CompletableDeferred @@ -81,7 +81,13 @@ interface NestedDraggable { * in the direction given by [sign], with the given number of [pointersDown] when the touch slop * was detected. */ - fun onDragStarted(position: Offset, sign: Float, pointersDown: Int): Controller + fun onDragStarted( + position: Offset, + sign: Float, + pointersDown: Int, + // TODO(b/382665591): Make this non-nullable. + pointerType: PointerType?, + ): Controller /** * Whether this draggable should consume any scroll amount with the given [sign] coming from a @@ -184,8 +190,8 @@ private class NestedDraggableNode( */ private var lastFirstDown: Offset? = null - /** The number of pointers down. */ - private var pointersDownCount = 0 + /** The pointers currently down, in order of which they were done and mapping to their type. */ + private val pointersDown = linkedMapOf<PointerId, PointerType>() init { delegate(nestedScrollModifierNode(this, nestedScrollDispatcher)) @@ -256,7 +262,9 @@ private class NestedDraggableNode( check(down.position == lastFirstDown) { "Position from detectDrags() is not the same as position in trackDownPosition()" } - check(pointersDownCount == 1) { "pointersDownCount is equal to $pointersDownCount" } + check(pointersDown.size == 1 && pointersDown.keys.first() == down.id) { + "pointersDown should only contain $down but it contains $pointersDown" + } var overSlop = 0f val onTouchSlopReached = { change: PointerInputChange, over: Float -> @@ -295,8 +303,9 @@ private class NestedDraggableNode( } } - check(pointersDownCount > 0) { "pointersDownCount is equal to $pointersDownCount" } - val controller = draggable.onDragStarted(down.position, sign, pointersDownCount) + check(pointersDown.size > 0) { "pointersDown is empty" } + val controller = + draggable.onDragStarted(down.position, sign, pointersDown.size, drag.type) if (overSlop != 0f) { onDrag(controller, drag, overSlop, velocityTracker) } @@ -450,20 +459,24 @@ private class NestedDraggableNode( private suspend fun PointerInputScope.trackDownPosition() { awaitEachGesture { - val down = awaitFirstDown(requireUnconsumed = false) - lastFirstDown = down.position - pointersDownCount = 1 + try { + val down = awaitFirstDown(requireUnconsumed = false) + lastFirstDown = down.position + pointersDown[down.id] = down.type - do { - pointersDownCount += - awaitPointerEvent().changes.fastSumBy { change -> + do { + awaitPointerEvent().changes.forEach { change -> when { - change.changedToDownIgnoreConsumed() -> 1 - change.changedToUpIgnoreConsumed() -> -1 - else -> 0 + change.changedToDownIgnoreConsumed() -> { + pointersDown[change.id] = change.type + } + change.changedToUpIgnoreConsumed() -> pointersDown.remove(change.id) } } - } while (pointersDownCount > 0) + } while (pointersDown.size > 0) + } finally { + pointersDown.clear() + } } } @@ -491,12 +504,13 @@ private class NestedDraggableNode( if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) { val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" } - // TODO(b/382665591): Replace this by check(pointersDownCount > 0). - val pointersDown = pointersDownCount.coerceAtLeast(1) + // TODO(b/382665591): Ensure that there is at least one pointer down. + val pointersDownCount = pointersDown.size.coerceAtLeast(1) + val pointerType = pointersDown.entries.firstOrNull()?.value nestedScrollController = NestedScrollController( overscrollEffect, - draggable.onDragStarted(startedPosition, sign, pointersDown), + draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType), ) } diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt index 9c49090916e3..7f70e97411f4 100644 --- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt +++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt @@ -33,10 +33,12 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.PointerType import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.test.junit4.ComposeContentTestRule import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performMouseInput import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeDown import androidx.compose.ui.test.swipeLeft @@ -653,6 +655,61 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw assertThat(flingIsDone).isTrue() } + @Test + fun pointerType() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + rule.onRoot().performTouchInput { + down(center) + moveBy(touchSlop.toOffset()) + } + + assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Touch) + } + + @Test + fun pointerType_mouse() { + val draggable = TestDraggable() + val touchSlop = + rule.setContentWithTouchSlop { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation)) + } + + rule.onRoot().performMouseInput { + moveTo(center) + press() + moveBy(touchSlop.toOffset()) + release() + } + + assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Mouse) + } + + @Test + fun pointersDown_clearedWhenDisabled() { + val draggable = TestDraggable() + var enabled by mutableStateOf(true) + rule.setContent { + Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation, enabled = enabled)) + } + + rule.onRoot().performTouchInput { down(center) } + + enabled = false + rule.waitForIdle() + + rule.onRoot().performTouchInput { up() } + + enabled = true + rule.waitForIdle() + + rule.onRoot().performTouchInput { down(center) } + } + private fun ComposeContentTestRule.setContentWithTouchSlop( content: @Composable () -> Unit ): Float { @@ -688,6 +745,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw var onDragStartedPosition = Offset.Zero var onDragStartedSign = 0f var onDragStartedPointersDown = 0 + var onDragStartedPointerType: PointerType? = null var onDragDelta = 0f override fun shouldStartDrag(change: PointerInputChange): Boolean = shouldStartDrag @@ -696,11 +754,13 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw position: Offset, sign: Float, pointersDown: Int, + pointerType: PointerType?, ): NestedDraggable.Controller { onDragStartedCalled = true onDragStartedPosition = position onDragStartedSign = sign onDragStartedPointersDown = pointersDown + onDragStartedPointerType = pointerType onDragDelta = 0f onDragStarted.invoke(position, sign) 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 c704a3e96467..de428a7d3548 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 @@ -35,6 +35,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection +import com.android.compose.gesture.NestedScrollableBound import com.android.compose.gesture.effect.ContentOverscrollEffect /** @@ -238,6 +239,18 @@ interface BaseContentScope : ElementStateScope { fun Modifier.noResizeDuringTransitions(): Modifier /** + * Temporarily disable this content swipe actions when any scrollable below this modifier has + * consumed any amount of scroll delta, until the scroll gesture is finished. + * + * This can for instance be used to ensure that a scrollable list is overscrolled once it + * reached its bounds instead of directly starting a scene transition from the same scroll + * gesture. + */ + fun Modifier.disableSwipesWhenScrolling( + bounds: NestedScrollableBound = NestedScrollableBound.Any + ): Modifier + + /** * A [NestedSceneTransitionLayout] will share its elements with its ancestor STLs therefore * enabling sharedElement transitions between them. */ diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt index 607e4fadc256..ba92f9bea07d 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt @@ -315,16 +315,10 @@ internal class SwipeAnimation<T : ContentKey>( val skipAnimation = hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress) - val targetOffset = - if (targetContent == fromContent) { - 0f - } else { - val distance = distance() - check(distance != DistanceUnspecified) { - "distance is equal to $DistanceUnspecified" - } - distance - } + val distance = distance() + check(distance != DistanceUnspecified) { "distance is equal to $DistanceUnspecified" } + + val targetOffset = if (targetContent == fromContent) 0f else distance // If the effective current content changed, it should be reflected right now in the // current state, even before the settle animation is ongoing. That way all the @@ -343,7 +337,16 @@ internal class SwipeAnimation<T : ContentKey>( } val animatable = - Animatable(initialOffset, OffsetVisibilityThreshold).also { offsetAnimation = it } + Animatable(initialOffset, OffsetVisibilityThreshold).also { + offsetAnimation = it + + // We should animate when the progress value is between [0, 1]. + if (distance > 0) { + it.updateBounds(0f, distance) + } else { + it.updateBounds(distance, 0f) + } + } check(isAnimatingOffset()) @@ -370,42 +373,26 @@ internal class SwipeAnimation<T : ContentKey>( val velocityConsumed = CompletableDeferred<Float>() offsetAnimationRunnable.complete { - try { + val result = animatable.animateTo( targetValue = targetOffset, animationSpec = swipeSpec, initialVelocity = initialVelocity, - ) { - // Immediately stop this transition if we are bouncing on a content that - // does not bounce. - if (!contentTransition.isWithinProgressRange(progress)) { - // We are no longer able to consume the velocity, the rest can be - // consumed by another component in the hierarchy. - velocityConsumed.complete(initialVelocity - velocity) - throw SnapException() - } - } - } catch (_: SnapException) { - /* Ignore. */ - } finally { - if (!velocityConsumed.isCompleted) { - // The animation consumed the whole available velocity - velocityConsumed.complete(initialVelocity) - } + ) - // Wait for overscroll to finish so that the transition is removed from the STLState - // only after the overscroll is done, to avoid dropping frame right when the user - // lifts their finger and overscroll is animated to 0. - overscrollCompletable?.await() - } + // We are no longer able to consume the velocity, the rest can be consumed by another + // component in the hierarchy. + velocityConsumed.complete(initialVelocity - result.endState.velocity) + + // Wait for overscroll to finish so that the transition is removed from the STLState + // only after the overscroll is done, to avoid dropping frame right when the user + // lifts their finger and overscroll is animated to 0. + overscrollCompletable?.await() } return velocityConsumed.await() } - /** An exception thrown during the animation to stop it immediately. */ - private class SnapException : Exception() - private fun canChangeContent(targetContent: ContentKey): Boolean { return when (val transition = contentTransition) { is TransitionState.Transition.ChangeScene -> 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 c5b3df222855..3f6bce724b1b 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 @@ -54,7 +54,7 @@ private fun DraggableHandlerImpl.contentForSwipes(): Content { /** Whether swipe should be enabled in the given [orientation]. */ internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { - if (userActions.isEmpty()) { + if (userActions.isEmpty() || !areSwipesAllowed()) { return false } @@ -69,6 +69,10 @@ internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean { * @return The best matching [UserActionResult], or `null` if no match is found. */ internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? { + if (!areSwipesAllowed()) { + return null + } + var bestPoints = Int.MIN_VALUE var bestMatch: UserActionResult? = null userActions.forEach { (actionSwipe, actionResult) -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt index 4c15f7a4534f..59b4a09385f5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt @@ -56,7 +56,10 @@ import com.android.compose.animation.scene.effect.GestureEffect import com.android.compose.animation.scene.effect.VisualEffect import com.android.compose.animation.scene.element import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions +import com.android.compose.gesture.NestedScrollControlState +import com.android.compose.gesture.NestedScrollableBound import com.android.compose.gesture.effect.OffsetOverscrollEffect +import com.android.compose.gesture.nestedScrollController import com.android.compose.modifiers.thenIf import com.android.compose.ui.graphics.ContainerState import com.android.compose.ui.graphics.container @@ -70,7 +73,8 @@ internal sealed class Content( actions: Map<UserAction.Resolved, UserActionResult>, zIndex: Float, ) { - internal val scope = ContentScopeImpl(layoutImpl, content = this) + private val nestedScrollControlState = NestedScrollControlState() + internal val scope = ContentScopeImpl(layoutImpl, content = this, nestedScrollControlState) val containerState = ContainerState() var content by mutableStateOf(content) @@ -101,11 +105,14 @@ internal sealed class Content( scope.content() } } + + fun areSwipesAllowed(): Boolean = nestedScrollControlState.isOuterScrollAllowed } internal class ContentScopeImpl( private val layoutImpl: SceneTransitionLayoutImpl, private val content: Content, + private val nestedScrollControlState: NestedScrollControlState, ) : ContentScope, ElementStateScope by layoutImpl.elementStateScope { override val contentKey: ContentKey get() = content.key @@ -176,6 +183,10 @@ internal class ContentScopeImpl( return noResizeDuringTransitions(layoutState = layoutImpl.state) } + override fun Modifier.disableSwipesWhenScrolling(bounds: NestedScrollableBound): Modifier { + return nestedScrollController(nestedScrollControlState, bounds) + } + @Composable override fun NestedSceneTransitionLayout( state: SceneTransitionLayoutState, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt new file mode 100644 index 000000000000..06a9735d97e2 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 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.compose.animation.scene + +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ContentTest { + @get:Rule val rule = createComposeRule() + + @Test + fun disableSwipesWhenScrolling() { + lateinit var layoutImpl: SceneTransitionLayoutImpl + rule.setContent { + SceneTransitionLayoutForTesting( + remember { MutableSceneTransitionLayoutState(SceneA) }, + onLayoutImpl = { layoutImpl = it }, + ) { + scene(SceneA) { + Box( + Modifier.fillMaxSize() + .disableSwipesWhenScrolling() + .scrollable(rememberScrollableState { it }, Orientation.Vertical) + ) + } + } + } + + val content = layoutImpl.content(SceneA) + assertThat(content.areSwipesAllowed()).isTrue() + rule.onRoot().performTouchInput { + down(topLeft) + moveBy(bottomLeft) + } + + assertThat(content.areSwipesAllowed()).isFalse() + rule.onRoot().performTouchInput { up() } + assertThat(content.areSwipesAllowed()).isTrue() + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 7c8c6e5f6c12..e580e3c40690 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -21,6 +21,7 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size @@ -33,6 +34,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.test.SemanticsNodeInteraction import androidx.compose.ui.test.assertHeightIsEqualTo @@ -43,6 +45,9 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onChild import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performTouchInput +import androidx.compose.ui.test.swipeDown import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.IntOffset @@ -469,4 +474,41 @@ class SceneTransitionLayoutTest { assertThat(layoutImpl.overlaysOrNullForTest()).isNull() } + + @Test + fun transitionProgressBoundedBetween0And1() { + val layoutWidth = 200.dp + val layoutHeight = 400.dp + + // 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 + val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) } + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Spacer(Modifier.fillMaxSize()) + } + scene(SceneB) { Spacer(Modifier.fillMaxSize()) } + } + } + assertThat(state.transitionState).isIdle() + + rule.mainClock.autoAdvance = false + + // Swipe the verticalSwipeDistance. + rule.onRoot().performTouchInput { + swipeDown(endY = bottom + touchSlop, durationMillis = 50) + } + + rule.mainClock.advanceTimeBy(16) + val transition = assertThat(state.transitionState).isSceneTransition() + assertThat(transition).isNotNull() + assertThat(transition).hasProgress(1f, tolerance = 0.01f) + + rule.mainClock.advanceTimeBy(16) + // Fling animation, we are overscrolling now. Progress should always be between [0, 1]. + assertThat(transition).hasProgress(1f) + } } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 0f8ca947479b..2b0825f39243 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -30,7 +30,6 @@ import android.util.AttributeSet import android.util.Log import android.util.MathUtils import android.util.TypedValue -import android.view.View.MeasureSpec.AT_MOST import android.view.View.MeasureSpec.EXACTLY import android.view.animation.Interpolator import android.widget.TextView @@ -77,7 +76,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe var maxSingleDigitWidth = -1 var digitTranslateAnimator: DigitTranslateAnimator? = null var aodFontSizePx: Float = -1F - var isVertical: Boolean = false // Store the font size when there's no height constraint as a reference when adjusting font size private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE @@ -148,16 +146,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { logger.d("onMeasure()") - if (isVertical) { - // use at_most to avoid apply measuredWidth from last measuring to measuredHeight - // cause we use max to setMeasuredDimension - super.onMeasure( - MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), AT_MOST), - MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), AT_MOST), - ) - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - } + super.onMeasure(widthMeasureSpec, heightMeasureSpec) val layout = this.layout if (layout != null) { @@ -213,18 +202,10 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe ) } - if (isVertical) { - expectedWidth = expectedHeight.also { expectedHeight = expectedWidth } - } setMeasuredDimension(expectedWidth, expectedHeight) } override fun onDraw(canvas: Canvas) { - if (isVertical) { - canvas.save() - canvas.translate(0F, measuredHeight.toFloat()) - canvas.rotate(-90F) - } logger.d({ "onDraw(); ls: $str1" }) { str1 = textAnimator.textInterpolator.shapedText } val translation = getLocalTranslation() canvas.translate(translation.x.toFloat(), translation.y.toFloat()) @@ -238,9 +219,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe canvas.translate(-it.updatedTranslate.x.toFloat(), -it.updatedTranslate.y.toFloat()) } canvas.translate(-translation.x.toFloat(), -translation.y.toFloat()) - if (isVertical) { - canvas.restore() - } } override fun invalidate() { @@ -353,18 +331,20 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe } private fun updateXtranslation(inPoint: Point, interpolatedTextBounds: Rect): Point { - val viewWidth = if (isVertical) measuredHeight else measuredWidth when (horizontalAlignment) { HorizontalAlignment.LEFT -> { inPoint.x = lockScreenPaint.strokeWidth.toInt() - interpolatedTextBounds.left } HorizontalAlignment.RIGHT -> { inPoint.x = - viewWidth - interpolatedTextBounds.right - lockScreenPaint.strokeWidth.toInt() + measuredWidth - + interpolatedTextBounds.right - + lockScreenPaint.strokeWidth.toInt() } HorizontalAlignment.CENTER -> { inPoint.x = - (viewWidth - interpolatedTextBounds.width()) / 2 - interpolatedTextBounds.left + (measuredWidth - interpolatedTextBounds.width()) / 2 - + interpolatedTextBounds.left } } return inPoint @@ -373,7 +353,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe // translation of reference point of text // used for translation when calling textInterpolator private fun getLocalTranslation(): Point { - val viewHeight = if (isVertical) measuredWidth else measuredHeight val interpolatedTextBounds = updateInterpolatedTextBounds() val localTranslation = Point(0, 0) val correctedBaseline = if (baseline != -1) baseline else baselineFromMeasure @@ -381,7 +360,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe when (verticalAlignment) { VerticalAlignment.CENTER -> { localTranslation.y = - ((viewHeight - interpolatedTextBounds.height()) / 2 - + ((measuredHeight - interpolatedTextBounds.height()) / 2 - interpolatedTextBounds.top - correctedBaseline) } @@ -392,7 +371,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe } VerticalAlignment.BOTTOM -> { localTranslation.y = - viewHeight - + measuredHeight - interpolatedTextBounds.bottom - lockScreenPaint.strokeWidth.toInt() - correctedBaseline diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java index 2e9d6e85d0aa..49cbb5a924f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java @@ -53,7 +53,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.flags.QSComposeFragment; import com.android.systemui.res.R; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import org.junit.Test; import org.junit.runner.RunWith; @@ -365,7 +364,6 @@ public class QuickSettingsControllerImplTest extends QuickSettingsControllerImpl } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void updateExpansion_partiallyExpanded_fullscreenFalse() { // WHEN QS are only partially expanded mQsController.setExpanded(true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt index 83fb14aaf792..6b2c4b260806 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt @@ -9,9 +9,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -25,7 +24,7 @@ class ConditionExtensionsTest : SysuiTestCase() { @Before fun setUp() { - testScope = TestScope(StandardTestDispatcher()) + testScope = TestScope(UnconfinedTestDispatcher()) } @Test @@ -34,11 +33,9 @@ class ConditionExtensionsTest : SysuiTestCase() { val flow = flowOf(true) val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) - runCurrent() assertThat(condition.isConditionSet).isFalse() condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isTrue() } @@ -49,11 +46,9 @@ class ConditionExtensionsTest : SysuiTestCase() { val flow = flowOf(false) val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) - runCurrent() assertThat(condition.isConditionSet).isFalse() condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() } @@ -65,7 +60,6 @@ class ConditionExtensionsTest : SysuiTestCase() { val condition = flow.toCondition(scope = this, Condition.START_EAGERLY) condition.start() - runCurrent() assertThat(condition.isConditionSet).isFalse() assertThat(condition.isConditionMet).isFalse() } @@ -78,11 +72,10 @@ class ConditionExtensionsTest : SysuiTestCase() { flow.toCondition( scope = this, strategy = Condition.START_EAGERLY, - initialValue = true + initialValue = true, ) condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isTrue() } @@ -95,11 +88,10 @@ class ConditionExtensionsTest : SysuiTestCase() { flow.toCondition( scope = this, strategy = Condition.START_EAGERLY, - initialValue = false + initialValue = false, ) condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() } @@ -111,16 +103,13 @@ class ConditionExtensionsTest : SysuiTestCase() { val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) condition.start() - runCurrent() assertThat(condition.isConditionSet).isTrue() assertThat(condition.isConditionMet).isFalse() flow.value = true - runCurrent() assertThat(condition.isConditionMet).isTrue() flow.value = false - runCurrent() assertThat(condition.isConditionMet).isFalse() condition.stop() @@ -131,15 +120,12 @@ class ConditionExtensionsTest : SysuiTestCase() { testScope.runTest { val flow = MutableSharedFlow<Boolean>() val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY) - runCurrent() assertThat(flow.subscriptionCount.value).isEqualTo(0) condition.start() - runCurrent() assertThat(flow.subscriptionCount.value).isEqualTo(1) condition.stop() - runCurrent() assertThat(flow.subscriptionCount.value).isEqualTo(0) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt index a70d24efada7..912633c874ed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt @@ -28,11 +28,11 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener +import com.android.systemui.util.mockito.withArgCaptor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any -import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.inOrder import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -59,10 +59,9 @@ class RenderStageManagerTest : SysuiTestCase() { fun setUp() { renderStageManager = RenderStageManager() renderStageManager.attach(shadeListBuilder) - - val captor = argumentCaptor<ShadeListBuilder.OnRenderListListener>() - verify(shadeListBuilder).setOnRenderListListener(captor.capture()) - onRenderListListener = captor.lastValue + onRenderListListener = withArgCaptor { + verify(shadeListBuilder).setOnRenderListListener(capture()) + } } private fun setUpRenderer() { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt index 34f46088ad79..3d5d1eddf581 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.policy.data.repository.zenModeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -48,7 +47,6 @@ import platform.test.runner.parameterized.Parameters @OptIn(ExperimentalCoroutinesApi::class) @RunWith(ParameterizedAndroidJunit4::class) @SmallTest -@EnableFlags(FooterViewRefactor.FLAG_NAME) class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index 615f4b01df9b..daa1db2d49fa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.footer.ui.view; -import static com.android.systemui.log.LogAssertKt.assertLogsWtf; - import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertFalse; @@ -34,7 +32,6 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.platform.test.annotations.DisableFlags; -import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.view.LayoutInflater; import android.view.View; @@ -44,7 +41,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter; import org.junit.Before; @@ -62,8 +58,7 @@ public class FooterViewTest extends SysuiTestCase { @Parameters(name = "{0}") public static List<FlagsParameterization> getFlags() { - return FlagsParameterization.progressionOf(FooterViewRefactor.FLAG_NAME, - NotifRedesignFooter.FLAG_NAME); + return FlagsParameterization.allCombinationsOf(NotifRedesignFooter.FLAG_NAME); } public FooterViewTest(FlagsParameterization flags) { @@ -106,24 +101,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void setHistoryShown() { - mView.showHistory(true); - assertTrue(mView.isHistoryShown()); - assertTrue(((TextView) mView.findViewById(R.id.manage_text)) - .getText().toString().contains("History")); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void setHistoryNotShown() { - mView.showHistory(false); - assertFalse(mView.isHistoryShown()); - assertTrue(((TextView) mView.findViewById(R.id.manage_text)) - .getText().toString().contains("Manage")); - } - - @Test public void testPerformVisibilityAnimation() { mView.setVisible(false /* visible */, false /* animate */); assertFalse(mView.isVisible()); @@ -140,7 +117,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) @DisableFlags(NotifRedesignFooter.FLAG_NAME) public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() { int resId = R.string.manage_notifications_history_text; @@ -160,16 +136,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetManageOrHistoryButtonText_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.manage_notifications_history_text; - assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) @DisableFlags(NotifRedesignFooter.FLAG_NAME) public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() { int resId = R.string.manage_notifications_history_text; @@ -189,16 +155,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.accessibility_clear_all; - assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetClearAllButtonText_resourceOnlyFetchedOnce() { int resId = R.string.clear_all_notifications_text; mView.setClearAllButtonText(resId); @@ -217,16 +173,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetClearAllButtonText_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.clear_all_notifications_text; - assertLogsWtf(() -> mView.setClearAllButtonText(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() { int resId = R.string.accessibility_clear_all; mView.setClearAllButtonDescription(resId); @@ -245,16 +191,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetClearAllButtonDescription_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.accessibility_clear_all; - assertLogsWtf(() -> mView.setClearAllButtonDescription(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetMessageString_resourceOnlyFetchedOnce() { int resId = R.string.unlock_to_see_notif_text; mView.setMessageString(resId); @@ -273,16 +209,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetMessageString_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.string.unlock_to_see_notif_text; - assertLogsWtf(() -> mView.setMessageString(resId)); - verify(mSpyContext, never()).getString(anyInt()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetMessageIcon_resourceOnlyFetchedOnce() { int resId = R.drawable.ic_friction_lock_closed; mView.setMessageIcon(resId); @@ -298,15 +224,6 @@ public class FooterViewTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testSetMessageIcon_expectsFlagEnabled() { - clearInvocations(mSpyContext); - int resId = R.drawable.ic_friction_lock_closed; - assertLogsWtf(() -> mView.setMessageIcon(resId)); - verify(mSpyContext, never()).getDrawable(anyInt()); - } - - @Test public void testSetFooterLabelVisible() { mView.setFooterLabelVisible(true); assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 1adfc2b72214..06b1c432955a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -40,7 +40,6 @@ import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRe import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter import com.android.systemui.testKosmos import com.android.systemui.util.ui.isAnimating @@ -57,7 +56,6 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) @SmallTest -@EnableFlags(FooterViewRefactor.FLAG_NAME) class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index c6cffa9da13b..20cd6c7517e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -25,14 +25,10 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static kotlinx.coroutines.flow.FlowKt.emptyFlow; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -45,7 +41,6 @@ import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper; import android.view.MotionEvent; -import android.view.View; import android.view.ViewTreeObserver; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -57,15 +52,12 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.ExpandHelper; import com.android.systemui.SysuiTestCase; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.DisableSceneContainer; import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.media.controls.ui.controller.KeyguardMediaController; import com.android.systemui.plugins.ActivityStarter; @@ -78,23 +70,18 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.NotifCollection; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; -import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; @@ -106,11 +93,8 @@ import com.android.systemui.statusbar.notification.stack.ui.viewbinder.Notificat import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController; import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; -import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor; @@ -145,16 +129,13 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private Provider<IStatusBarService> mStatusBarService; @Mock private NotificationRoundnessManager mNotificationRoundnessManager; @Mock private TunerService mTunerService; - @Mock private DeviceProvisionedController mDeviceProvisionedController; @Mock private DynamicPrivacyController mDynamicPrivacyController; @Mock private ConfigurationController mConfigurationController; @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout; - @Mock private ZenModeController mZenModeController; @Mock private KeyguardMediaController mKeyguardMediaController; @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController; @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private PowerInteractor mPowerInteractor; - @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private WallpaperInteractor mWallpaperInteractor; @Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager; @Mock private MetricsLogger mMetricsLogger; @@ -164,12 +145,10 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder; @Mock private NotificationSwipeHelper mNotificationSwipeHelper; @Mock private GroupExpansionManager mGroupExpansionManager; - @Mock private SectionHeaderController mSilentHeaderController; @Mock private NotifPipeline mNotifPipeline; @Mock private NotifCollection mNotifCollection; @Mock private UiEventLogger mUiEventLogger; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; - @Mock private NotificationRemoteInputManager mRemoteInputManager; @Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; @Mock private ShadeController mShadeController; @Mock private Provider<WindowRootView> mWindowRootView; @@ -193,9 +172,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor; - private final SeenNotificationsInteractor mSeenNotificationsInteractor = - mKosmos.getSeenNotificationsInteractor(); - private NotificationStackScrollLayoutController mController; private NotificationTestHelper mNotificationTestHelper; @@ -279,114 +255,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true); - initController(/* viewIsAttached= */ true); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ true); - - setupShowEmptyShadeViewState(false); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ false, - /* notifVisibleInShade= */ true); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ false); - - setupShowEmptyShadeViewState(false); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ false, - /* notifVisibleInShade= */ false); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - verify(mSysuiStatusBarStateController).addCallback( - mStateListenerArgumentCaptor.capture(), anyInt()); - StatusBarStateController.StateListener stateListener = - mStateListenerArgumentCaptor.getValue(); - stateListener.onStateChanged(SHADE); - mController.getView().removeAllViews(); - - mController.setQsFullScreen(false); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ false); - - mController.setQsFullScreen(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* notifVisibleInShade= */ false); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - - // THEN the PrimaryBouncerInteractor value is used. Since the bouncer is showing, we - // hide the empty view. - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ false, - /* areNotificationsHiddenInShade= */ false); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() { - when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false); - initController(/* viewIsAttached= */ true); - - when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false); - - setupShowEmptyShadeViewState(true); - reset(mNotificationStackScrollLayout); - mController.updateShowEmptyShadeView(); - - // THEN the PrimaryBouncerInteractor value is used. Since the bouncer isn't showing, we - // can show the empty view. - verify(mNotificationStackScrollLayout).updateEmptyShadeView( - /* visible= */ true, - /* areNotificationsHiddenInShade= */ false); - } - - @Test public void testOnUserChange_verifyNotSensitive() { when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false); initController(/* viewIsAttached= */ true); @@ -788,31 +656,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testUpdateFooter_remoteInput() { - ArgumentCaptor<RemoteInputController.Callback> callbackCaptor = - ArgumentCaptor.forClass(RemoteInputController.Callback.class); - doNothing().when(mRemoteInputManager).addControllerCallback(callbackCaptor.capture()); - when(mRemoteInputManager.isRemoteInputActive()).thenReturn(false); - initController(/* viewIsAttached= */ true); - verify(mNotificationStackScrollLayout).setIsRemoteInputActive(false); - RemoteInputController.Callback callback = callbackCaptor.getValue(); - callback.onRemoteInputActive(true); - verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() { - initController(/* viewIsAttached= */ true); - mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true); - mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); - verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true); - verify(mNotificationStackScrollLayout).updateFooter(); - verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean()); - } - - @Test public void testAttach_updatesViewStatusBarState() { // GIVEN: Controller is attached initController(/* viewIsAttached= */ true); @@ -844,98 +687,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() { - // GIVEN: Controller is attached, active notifications is empty, - // and mNotificationStackScrollLayout.onKeyguard() is true - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true); - mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); - - // THEN: mNotificationStackScrollLayout should not be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() { - // GIVEN: Controller is attached, active notifications is not empty, - // and mNotificationStackScrollLayout.onKeyguard() is true - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true); - mController.getNotifStackController().setNotifStats( - new NotifStats( - /* numActiveNotifs = */ 1, - /* hasNonClearableAlertingNotifs = */ false, - /* hasClearableAlertingNotifs = */ false, - /* hasNonClearableSilentNotifs = */ false, - /* hasClearableSilentNotifs = */ false) - ); - - // THEN: mNotificationStackScrollLayout should be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() { - // GIVEN: Controller is attached, active notifications is not empty, - // and mNotificationStackScrollLayout.onKeyguard() is false - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false); - mController.getNotifStackController().setNotifStats( - new NotifStats( - /* numActiveNotifs = */ 1, - /* hasNonClearableAlertingNotifs = */ false, - /* hasClearableAlertingNotifs = */ false, - /* hasNonClearableSilentNotifs = */ false, - /* hasClearableSilentNotifs = */ false) - ); - - // THEN: mNotificationStackScrollLayout should be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() { - // GIVEN: Controller is attached, active notifications is empty, - // and mNotificationStackScrollLayout.onKeyguard() is false - initController(/* viewIsAttached= */ true); - when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false); - mController.getNotifStackController().setNotifStats(NotifStats.getEmpty()); - - // THEN: mNotificationStackScrollLayout should be important for A11y - verify(mNotificationStackScrollLayout) - .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() { - initController(/* viewIsAttached= */ true); - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.GONE, - /* to= */ KeyguardState.AOD)); - verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean()); - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() { - initController(/* viewIsAttached= */ true); - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.OCCLUDED, - /* to= */ KeyguardState.AOD)); - verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean()); - } - - @Test @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) public void sensitiveNotificationProtectionControllerListenerNotRegistered() { initController(/* viewIsAttached= */ true); @@ -996,24 +747,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { return argThat(new LogMatcher(category, type)); } - private void setupShowEmptyShadeViewState(boolean toShow) { - if (toShow) { - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.LOCKSCREEN, - /* to= */ KeyguardState.GONE)); - mController.setQsFullScreen(false); - mController.getView().removeAllViews(); - } else { - mController.onKeyguardTransitionChanged( - new TransitionStep( - /* from= */ KeyguardState.GONE, - /* to= */ KeyguardState.AOD)); - mController.setQsFullScreen(true); - mController.getView().addContainerView(mock(ExpandableNotificationRow.class)); - } - } - private void initController(boolean viewIsAttached) { when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(viewIsAttached); ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class); @@ -1033,16 +766,12 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { mStatusBarService, mNotificationRoundnessManager, mTunerService, - mDeviceProvisionedController, mDynamicPrivacyController, mConfigurationController, mSysuiStatusBarStateController, mKeyguardMediaController, mKeyguardBypassController, mPowerInteractor, - mPrimaryBouncerInteractor, - mKeyguardTransitionRepo, - mZenModeController, mNotificationLockscreenUserManager, mMetricsLogger, mColorUpdateLogger, @@ -1051,14 +780,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { new FalsingManagerFake(), mNotificationSwipeHelperBuilder, mGroupExpansionManager, - mSilentHeaderController, mNotifPipeline, mNotifCollection, mLockscreenShadeTransitionController, mUiEventLogger, - mRemoteInputManager, mVisibilityLocationProviderDelegator, - mSeenNotificationsInteractor, mViewBinder, mShadeController, mWindowRootView, @@ -1076,7 +802,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } static class LogMatcher implements ArgumentMatcher<LogMaker> { - private int mCategory, mType; + private final int mCategory, mType; LogMatcher(int category, int type) { mCategory = category; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index dcac2941b48b..39cff63f363e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -2,12 +2,10 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.DimenRes import android.content.pm.PackageManager -import android.platform.test.annotations.DisableFlags import android.platform.test.flag.junit.FlagsParameterization import android.widget.FrameLayout import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation.getContentAlpha import com.android.systemui.dump.DumpManager @@ -740,20 +738,6 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() { assertThat((footerView.viewState as FooterViewState).hideContent).isTrue() } - @DisableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) - @Test - fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() { - ambientState.isClearAllInProgress = true - ambientState.isShadeExpanded = true - ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack - hostView.removeAllViews() // remove all rows - hostView.addView(footerView) - - stackScrollAlgorithm.resetViewStates(ambientState, 0) - - assertThat((footerView.viewState as FooterViewState).hideContent).isTrue() - } - @Test fun getGapForLocation_onLockscreen_returnsSmallGap() { val gap = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index e592e4b319e3..1b4f9a79557d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -41,7 +40,6 @@ import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRo import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.headsup.PinnedStatus import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository @@ -63,7 +61,6 @@ import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) -@EnableFlags(FooterViewRefactor.FLAG_NAME) class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt deleted file mode 100644 index 2ad1124d72d4..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.touchpad.tutorial.ui - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError -import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toTutorialActionState -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidJUnit4::class) -class StateTransitionsTest : SysuiTestCase() { - - companion object { - private const val START_MARKER = "startMarker" - private const val END_MARKER = "endMarker" - private const val SUCCESS_ANIMATION = 0 - } - - // needed to simulate caching last state as it's used to create new state - private var lastState: TutorialActionState = NotStarted - - private fun GestureState.toTutorialActionState(): TutorialActionState { - val newState = - this.toGestureUiState( - progressStartMarker = START_MARKER, - progressEndMarker = END_MARKER, - successAnimation = SUCCESS_ANIMATION, - ) - .toTutorialActionState(lastState) - lastState = newState - return lastState - } - - @Test - fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() { - val happyPath = - listOf( - GestureState.NotStarted, - GestureState.InProgress(0f), - GestureState.InProgress(0.5f), - GestureState.InProgress(1f), - GestureState.Finished, - ) - - val resultingStates = mutableListOf<TutorialActionState>() - happyPath.forEach { resultingStates.add(it.toTutorialActionState()) } - - assertThat(resultingStates) - .containsExactly( - NotStarted, - InProgress(0f, START_MARKER, END_MARKER), - InProgress(0.5f, START_MARKER, END_MARKER), - InProgress(1f, START_MARKER, END_MARKER), - Finished(SUCCESS_ANIMATION), - ) - .inOrder() - } - - @Test - fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() { - val errorPath = - listOf( - GestureState.NotStarted, - GestureState.InProgress(0f), - GestureState.Error, - GestureState.InProgress(0.5f), - GestureState.InProgress(1f), - GestureState.Finished, - ) - - val resultingStates = mutableListOf<TutorialActionState>() - errorPath.forEach { resultingStates.add(it.toTutorialActionState()) } - - assertThat(resultingStates) - .containsExactly( - NotStarted, - InProgress(0f, START_MARKER, END_MARKER), - Error, - InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)), - InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)), - Finished(SUCCESS_ANIMATION), - ) - .inOrder() - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt index 4aec88e8497b..d752046f4791 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt @@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources @@ -71,8 +71,8 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "gesture to L", - progressEndMarker = "end progress L", + startMarker = "gesture to L", + endMarker = "end progress L", ), ) } @@ -85,8 +85,8 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "gesture to R", - progressEndMarker = "end progress R", + startMarker = "gesture to R", + endMarker = "end progress R", ), ) } @@ -114,7 +114,7 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { kosmos.runTest { fun performBackGesture() = ThreeFingerGesture.swipeLeft().forEach { viewModel.handleEvent(it) } - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performBackGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -134,15 +134,21 @@ class BackGestureScreenViewModelTest : SysuiTestCase() { fakeConfigRepository.onAnyConfigurationChange() } - private fun Kosmos.assertProgressWhileMovingFingers(deltaX: Float, expected: GestureUiState) { + private fun Kosmos.assertProgressWhileMovingFingers( + deltaX: Float, + expected: TutorialActionState, + ) { assertStateAfterEvents( events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, expected = expected, ) } - private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) { - val state by collectLastValue(viewModel.gestureUiState) + private fun Kosmos.assertStateAfterEvents( + events: List<MotionEvent>, + expected: TutorialActionState, + ) { + val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt index 65a995dcd043..7862fd32ca04 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt @@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity @@ -86,8 +86,8 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "drag with gesture", - progressEndMarker = "release playback realtime", + startMarker = "drag with gesture", + endMarker = "release playback realtime", ), ) } @@ -108,7 +108,7 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performHomeGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -121,7 +121,7 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performHomeGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -147,8 +147,11 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() { fakeConfigRepository.onAnyConfigurationChange() } - private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) { - val state by collectLastValue(viewModel.gestureUiState) + private fun Kosmos.assertStateAfterEvents( + events: List<MotionEvent>, + expected: TutorialActionState, + ) { + val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt index 1bc60b67095e..6180fa98b1cd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt @@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.testKosmos -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity @@ -89,8 +89,8 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { expected = InProgress( progress = 1f, - progressStartMarker = "drag with gesture", - progressEndMarker = "onPause", + startMarker = "drag with gesture", + endMarker = "onPause", ), ) } @@ -111,7 +111,7 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performRecentAppsGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -124,7 +124,7 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { @Test fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() = kosmos.runTest { - val state by collectLastValue(viewModel.gestureUiState) + val state by collectLastValue(viewModel.tutorialState) performRecentAppsGesture() assertThat(state).isInstanceOf(Finished::class.java) @@ -150,8 +150,11 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() { fakeConfigRepository.onAnyConfigurationChange() } - private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) { - val state by collectLastValue(viewModel.gestureUiState) + private fun Kosmos.assertStateAfterEvents( + events: List<MotionEvent>, + expected: TutorialActionState, + ) { + val state by collectLastValue(viewModel.tutorialState) events.forEach { viewModel.handleEvent(it) } assertThat(state).isEqualTo(expected) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt new file mode 100644 index 000000000000..c113dd9e1eff --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt @@ -0,0 +1,117 @@ +/* + * 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.touchpad.tutorial.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted +import com.android.systemui.kosmos.collectValues +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher +import com.android.systemui.testKosmos +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TouchpadTutorialScreenViewModelTest : SysuiTestCase() { + + companion object { + private const val START_MARKER = "startMarker" + private const val END_MARKER = "endMarker" + private const val SUCCESS_ANIMATION = 0 + } + + private val kosmos = testKosmos() + private val animationProperties = + TutorialAnimationProperties( + progressStartMarker = START_MARKER, + progressEndMarker = END_MARKER, + successAnimation = SUCCESS_ANIMATION, + ) + + @Before + fun before() { + kosmos.useUnconfinedTestDispatcher() + } + + @Test + fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() = + kosmos.runTest { + val happyPath: Flow<Pair<GestureState, TutorialAnimationProperties>> = + listOf( + GestureState.NotStarted, + GestureState.InProgress(0f), + GestureState.InProgress(0.5f), + GestureState.InProgress(1f), + GestureState.Finished, + ) + .map { it to animationProperties } + .asFlow() + + val resultingStates by collectValues(happyPath.mapToTutorialState()) + + assertThat(resultingStates) + .containsExactly( + NotStarted, + InProgress(0f, START_MARKER, END_MARKER), + InProgress(0.5f, START_MARKER, END_MARKER), + InProgress(1f, START_MARKER, END_MARKER), + Finished(SUCCESS_ANIMATION), + ) + .inOrder() + } + + @Test + fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() = + kosmos.runTest { + val errorPath: Flow<Pair<GestureState, TutorialAnimationProperties>> = + listOf( + GestureState.NotStarted, + GestureState.InProgress(0f), + GestureState.Error, + GestureState.InProgress(0.5f), + GestureState.InProgress(1f), + GestureState.Finished, + ) + .map { it to animationProperties } + .asFlow() + + val resultingStates by collectValues(errorPath.mapToTutorialState()) + + assertThat(resultingStates) + .containsExactly( + NotStarted, + InProgress(0f, START_MARKER, END_MARKER), + Error, + InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)), + InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)), + Finished(SUCCESS_ANIMATION), + ) + .inOrder() + } +} diff --git a/packages/SystemUI/res/color/active_track_color.xml b/packages/SystemUI/res/color/active_track_color.xml new file mode 100644 index 000000000000..232555553d12 --- /dev/null +++ b/packages/SystemUI/res/color/active_track_color.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2025 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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" /> + <item android:alpha="0.38" android:color="@androidprv:color/materialColorOnSurface" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/inactive_track_color.xml b/packages/SystemUI/res/color/inactive_track_color.xml new file mode 100644 index 000000000000..2ba5ebd8818a --- /dev/null +++ b/packages/SystemUI/res/color/inactive_track_color.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="true" /> + <item android:alpha="0.12" android:color="@androidprv:color/materialColorOnSurface" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/color/on_active_track_color.xml b/packages/SystemUI/res/color/on_active_track_color.xml new file mode 100644 index 000000000000..7ca79a9e95af --- /dev/null +++ b/packages/SystemUI/res/color/on_active_track_color.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="@androidprv:color/materialColorOnPrimary" android:state_enabled="true" /> + <item android:color="@androidprv:color/materialColorOnSurfaceVariant" /> +</selector> diff --git a/packages/SystemUI/res/color/on_inactive_track_color.xml b/packages/SystemUI/res/color/on_inactive_track_color.xml new file mode 100644 index 000000000000..0eb4bfa106fb --- /dev/null +++ b/packages/SystemUI/res/color/on_inactive_track_color.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" /> + <item android:color="@androidprv:color/materialColorOnSurfaceVariant" /> +</selector> diff --git a/packages/SystemUI/res/color/thumb_color.xml b/packages/SystemUI/res/color/thumb_color.xml new file mode 100644 index 000000000000..2b0e3a9a072b --- /dev/null +++ b/packages/SystemUI/res/color/thumb_color.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2025 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. + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" /> + <item android:alpha="0.38" android:color="@androidprv:color/materialColorOnSurface" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 691fb50a15b8..08891aa65417 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -576,12 +576,12 @@ <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider"> <item name="labelStyle">@style/Widget.Material3.Slider.Label</item> - <item name="thumbColor">@androidprv:color/materialColorPrimary</item> - <item name="tickColorActive">@androidprv:color/materialColorSurfaceContainerHighest</item> - <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item> - <item name="trackColorActive">@androidprv:color/materialColorPrimary</item> - <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item> - <item name="trackIconActiveColor">@androidprv:color/materialColorSurfaceContainerHighest</item> + <item name="thumbColor">@color/thumb_color</item> + <item name="tickColorActive">@color/on_active_track_color</item> + <item name="tickColorInactive">@color/on_inactive_track_color</item> + <item name="trackColorActive">@color/active_track_color</item> + <item name="trackColorInactive">@color/inactive_track_color</item> + <item name="trackIconActiveColor">@color/on_active_track_color</item> </style> <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/> diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index e02e3fbc339b..10f060c13a59 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -22,10 +22,10 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAK import android.annotation.MainThread; import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; -import android.os.Trace; import android.util.Log; import android.view.Display; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.internal.util.Preconditions; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.dagger.DozeScope; @@ -314,7 +314,7 @@ public class DozeMachine { mState = newState; mDozeLog.traceState(newState); - Trace.traceCounter(Trace.TRACE_TAG_APP, "doze_machine_state", newState.ordinal()); + TrackTracer.instantForGroup("keyguard", "doze_machine_state", newState.ordinal()); updatePulseReason(newState, oldState, pulseReason); performTransitionOnComponents(oldState, newState); diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 63ac783ad42b..129a6bb72996 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -35,7 +35,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression import com.android.systemui.statusbar.notification.shared.NotificationMinimalism @@ -57,7 +56,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token NotificationMinimalism.token dependsOn NotificationThrottleHun.token - ModesEmptyShadeFix.token dependsOn FooterViewRefactor.token ModesEmptyShadeFix.token dependsOn modesUi // SceneContainer dependencies diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index d40fe468b0a5..591383999182 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -538,27 +538,30 @@ public class KeyguardService extends Service { @Override // Binder interface public void onFinishedGoingToSleep( - @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { + @PowerManager.GoToSleepReason int pmSleepReason, boolean + powerButtonLaunchGestureTriggered) { trace("onFinishedGoingToSleep pmSleepReason=" + pmSleepReason - + " cameraGestureTriggered=" + cameraGestureTriggered); + + " powerButtonLaunchTriggered=" + powerButtonLaunchGestureTriggered); checkPermission(); mKeyguardViewMediator.onFinishedGoingToSleep( WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason), - cameraGestureTriggered); - mPowerInteractor.onFinishedGoingToSleep(cameraGestureTriggered); + powerButtonLaunchGestureTriggered); + mPowerInteractor.onFinishedGoingToSleep(powerButtonLaunchGestureTriggered); mKeyguardLifecyclesDispatcher.dispatch( KeyguardLifecyclesDispatcher.FINISHED_GOING_TO_SLEEP); } @Override // Binder interface public void onStartedWakingUp( - @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { + @PowerManager.WakeReason int pmWakeReason, + boolean powerButtonLaunchGestureTriggered) { trace("onStartedWakingUp pmWakeReason=" + pmWakeReason - + " cameraGestureTriggered=" + cameraGestureTriggered); + + " powerButtonLaunchGestureTriggered=" + powerButtonLaunchGestureTriggered); Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp"); checkPermission(); - mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); - mPowerInteractor.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); + mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, + powerButtonLaunchGestureTriggered); + mPowerInteractor.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered); mKeyguardLifecyclesDispatcher.dispatch( KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason); Trace.endSection(); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 4bc6742b5040..647362873015 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -109,6 +109,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.app.animation.Interpolators; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.internal.foldables.FoldGracePeriodProvider; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; @@ -3981,7 +3982,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void setPendingLock(boolean hasPendingLock) { mPendingLock = hasPendingLock; - Trace.traceCounter(Trace.TRACE_TAG_APP, "pendingLock", mPendingLock ? 1 : 0); + TrackTracer.instantForGroup("keyguard", "pendingLock", mPendingLock ? 1 : 0); } private boolean isViewRootReady() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java index 633628f1167e..c3182003227f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java @@ -16,8 +16,7 @@ package com.android.systemui.keyguard; -import android.os.Trace; - +import com.android.app.tracing.coroutines.TrackTracer; import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; import com.android.systemui.power.domain.interactor.PowerInteractor; @@ -80,7 +79,7 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme private void setScreenState(int screenState) { mScreenState = screenState; - Trace.traceCounter(Trace.TRACE_TAG_APP, "screenState", screenState); + TrackTracer.instantForGroup("screen", "screenState", screenState); } public interface Observer { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java index c0ffda6640b2..c261cfefb2b8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java @@ -24,11 +24,11 @@ import android.graphics.Point; import android.os.Bundle; import android.os.PowerManager; import android.os.RemoteException; -import android.os.Trace; import android.util.DisplayMetrics; import androidx.annotation.Nullable; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; @@ -197,7 +197,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe private void setWakefulness(@Wakefulness int wakefulness) { mWakefulness = wakefulness; - Trace.traceCounter(Trace.TRACE_TAG_APP, "wakefulness", wakefulness); + TrackTracer.instantForGroup("screen", "wakefulness", wakefulness); } private void updateLastWakeOriginLocation() { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index e168025b2bf8..c9eb4962ab00 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -162,7 +162,6 @@ import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.ViewGroupFadeHelper; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener; @@ -1214,14 +1213,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private boolean hasVisibleNotifications() { - if (FooterViewRefactor.isEnabled()) { - return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() - || mMediaDataManager.hasActiveMediaOrRecommendation(); - } else { - return mNotificationStackScrollLayoutController - .getVisibleNotificationCount() != 0 - || mMediaDataManager.hasActiveMediaOrRecommendation(); - } + return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() + || mMediaDataManager.hasActiveMediaOrRecommendation(); } @Override @@ -2218,9 +2211,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void setBouncerShowing(boolean bouncerShowing) { mBouncerShowing = bouncerShowing; - if (!FooterViewRefactor.isEnabled()) { - mNotificationStackScrollLayoutController.updateShowEmptyShadeView(); - } updateVisibility(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java index c88e7b827881..14087a0efcfc 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java @@ -86,7 +86,6 @@ import com.android.systemui.statusbar.PulseExpansionHandler; import com.android.systemui.statusbar.QsFrameTranslateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; @@ -96,8 +95,8 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager; +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.SplitShadeStateController; @@ -1022,12 +1021,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum } void updateQsState() { - if (!FooterViewRefactor.isEnabled()) { - // Update full screen state; note that this will be true if the QS panel is only - // partially expanded, and that is fixed with the footer view refactor. - setQsFullScreen(/* qsFullScreen = */ getExpanded() && !mSplitShadeEnabled); - } - if (mQsStateUpdateListener != null) { mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling); } @@ -1094,10 +1087,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum // Update the light bar mLightBarController.setQsExpanded(mFullyExpanded); - if (FooterViewRefactor.isEnabled()) { - // Update full screen state - setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled); - } + // Update full screen state + setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled); } float getLockscreenShadeDragProgress() { @@ -2268,10 +2259,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum setExpansionHeight(qsHeight); } - boolean hasNotifications = FooterViewRefactor.isEnabled() - ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue() - : mNotificationStackScrollLayoutController.getVisibleNotificationCount() - != 0; + boolean hasNotifications = + mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue(); if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) { // No notifications are visible, let's animate to the height of qs instead if (isQsFragmentCreated()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt index a747abbc6a6e..1c14d3349027 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt @@ -28,17 +28,11 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.Measurable -import androidx.compose.ui.layout.MeasureResult -import androidx.compose.ui.layout.MeasureScope -import androidx.compose.ui.node.LayoutModifierNode -import androidx.compose.ui.node.ModifierNodeElement import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.Constraints -import androidx.compose.ui.unit.constrain import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth import kotlinx.coroutines.delay /** Platform-optimized interface for getting current time */ @@ -97,35 +91,3 @@ fun ChronometerText( modifier = modifier.neverDecreaseWidth(), ) } - -/** A modifier that ensures the width of the content only increases and never decreases. */ -private fun Modifier.neverDecreaseWidth(): Modifier { - return this.then(neverDecreaseWidthElement) -} - -private data object neverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() { - override fun create(): NeverDecreaseWidthNode { - return NeverDecreaseWidthNode() - } - - override fun update(node: NeverDecreaseWidthNode) { - error("This should never be called") - } -} - -private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode { - private var minWidth = 0 - - override fun MeasureScope.measure( - measurable: Measurable, - constraints: Constraints, - ): MeasureResult { - val placeable = measurable.measure(Constraints(minWidth = minWidth).constrain(constraints)) - val width = placeable.width - val height = placeable.height - - minWidth = maxOf(minWidth, width) - - return layout(width, height) { placeable.place(0, 0) } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt new file mode 100644 index 000000000000..505a5fcb18b4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2025 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.statusbar.chips.ui.compose.modifiers + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.constrain + +/** A modifier that ensures the width of the content only increases and never decreases. */ +fun Modifier.neverDecreaseWidth(): Modifier { + return this.then(neverDecreaseWidthElement) +} + +private data object neverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() { + override fun create(): NeverDecreaseWidthNode { + return NeverDecreaseWidthNode() + } + + override fun update(node: NeverDecreaseWidthNode) { + error("This should never be called") + } +} + +private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode { + private var minWidth = 0 + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + val placeable = measurable.measure(Constraints(minWidth = minWidth).constrain(constraints)) + val width = placeable.width + val height = placeable.height + + minWidth = maxOf(minWidth, width) + + return layout(width, height) { placeable.place(0, 0) } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt index 32de65be5b5b..d4d3cdf42fb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt @@ -27,7 +27,6 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStackC import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController import javax.inject.Inject @@ -43,7 +42,8 @@ internal constructor( private val groupExpansionManagerImpl: GroupExpansionManagerImpl, private val renderListInteractor: RenderNotificationListInteractor, private val activeNotificationsInteractor: ActiveNotificationsInteractor, - private val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController, + private val sensitiveNotificationProtectionController: + SensitiveNotificationProtectionController, ) : Coordinator { override fun attach(pipeline: NotifPipeline) { @@ -51,14 +51,11 @@ internal constructor( groupExpansionManagerImpl.attach(pipeline) } + // TODO: b/293167744 - Remove controller param. private fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) = traceSection("StackCoordinator.onAfterRenderList") { val notifStats = calculateNotifStats(entries) - if (FooterViewRefactor.isEnabled) { - activeNotificationsInteractor.setNotifStats(notifStats) - } else { - controller.setNotifStats(notifStats) - } + activeNotificationsInteractor.setNotifStats(notifStats) renderListInteractor.setRenderedList(entries) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt index fbec6406e9d4..7e2361f24da9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt @@ -26,7 +26,6 @@ import com.android.systemui.shared.notifications.domain.interactor.NotificationS import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterMessageViewModel import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.util.kotlin.FlowDumperImpl @@ -35,7 +34,6 @@ import dagger.assisted.AssistedInject import java.util.Locale import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf @@ -57,9 +55,7 @@ constructor( dumpManager: DumpManager, ) : FlowDumperImpl(dumpManager) { val areNotificationsHiddenInShade: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else if (ModesEmptyShadeFix.isEnabled) { + if (ModesEmptyShadeFix.isEnabled) { zenModeInteractor.areNotificationsHiddenInShade .dumpWhileCollecting("areNotificationsHiddenInShade") .flowOn(bgDispatcher) @@ -70,15 +66,10 @@ constructor( } } - val hasFilteredOutSeenNotifications: StateFlow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - MutableStateFlow(false) - } else { - seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue( - "hasFilteredOutSeenNotifications" - ) - } - } + val hasFilteredOutSeenNotifications: StateFlow<Boolean> = + seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue( + "hasFilteredOutSeenNotifications" + ) val text: Flow<String> by lazy { if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt deleted file mode 100644 index 7e6044eb6869..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.notification.footer.shared - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the FooterView refactor flag state. */ -@Suppress("NOTHING_TO_INLINE") -object FooterViewRefactor { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.notificationsFooterViewRefactor() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index d25889820629..a670f69df601 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -41,7 +41,6 @@ import androidx.annotation.NonNull; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.ColorUpdateLogger; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter; import com.android.systemui.statusbar.notification.row.FooterViewButton; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; @@ -63,16 +62,9 @@ public class FooterView extends StackScrollerDecorView { private FooterViewButton mSettingsButton; private FooterViewButton mHistoryButton; private boolean mShouldBeHidden; - private boolean mShowHistory; - // String cache, for performance reasons. - // Reading them from a Resources object can be quite slow sometimes. - private String mManageNotificationText; - private String mManageNotificationHistoryText; // Footer label private TextView mSeenNotifsFooterTextView; - private String mSeenNotifsFilteredText; - private Drawable mSeenNotifsFilteredIcon; private @StringRes int mClearAllButtonTextId; private @StringRes int mClearAllButtonDescriptionId; @@ -159,8 +151,8 @@ public class FooterView extends StackScrollerDecorView { IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal); super.dump(pw, args); DumpUtilsKt.withIncreasedIndent(pw, () -> { + // TODO: b/375010573 - update dumps for redesign pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility())); - pw.println("manageButton showHistory: " + mShowHistory); pw.println("manageButton visibility: " + DumpUtilsKt.visibilityString(mClearAllButton.getVisibility())); pw.println("dismissButton visibility: " @@ -170,7 +162,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the text label for the "Clear all" button. */ public void setClearAllButtonText(@StringRes int textId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mClearAllButtonTextId == textId) { return; // nothing changed } @@ -187,9 +178,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the accessibility content description for the "Clear all" button. */ public void setClearAllButtonDescription(@StringRes int contentDescriptionId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - return; - } if (mClearAllButtonDescriptionId == contentDescriptionId) { return; // nothing changed } @@ -207,7 +195,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the text label for the "Manage"/"History" button. */ public void setManageOrHistoryButtonText(@StringRes int textId) { NotifRedesignFooter.assertInLegacyMode(); - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mManageOrHistoryButtonTextId == textId) { return; // nothing changed } @@ -226,9 +213,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the accessibility content description for the "Clear all" button. */ public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) { NotifRedesignFooter.assertInLegacyMode(); - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - return; - } if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) { return; // nothing changed } @@ -247,7 +231,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the string for a message to be shown instead of the buttons. */ public void setMessageString(@StringRes int messageId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mMessageStringId == messageId) { return; // nothing changed } @@ -265,7 +248,6 @@ public class FooterView extends StackScrollerDecorView { /** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */ public void setMessageIcon(@DrawableRes int iconId) { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return; if (mMessageIconId == iconId) { return; // nothing changed } @@ -303,32 +285,17 @@ public class FooterView extends StackScrollerDecorView { mManageOrHistoryButton = findViewById(R.id.manage_text); } mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer); - if (!FooterViewRefactor.isEnabled()) { - updateResources(); - } updateContent(); updateColors(); } /** Show a message instead of the footer buttons. */ public void setFooterLabelVisible(boolean isVisible) { - // In the refactored code, hiding the buttons is handled in the FooterViewModel - if (FooterViewRefactor.isEnabled()) { - if (isVisible) { - mSeenNotifsFooterTextView.setVisibility(View.VISIBLE); - } else { - mSeenNotifsFooterTextView.setVisibility(View.GONE); - } + // Note: hiding the buttons is handled in the FooterViewModel + if (isVisible) { + mSeenNotifsFooterTextView.setVisibility(View.VISIBLE); } else { - if (isVisible) { - mManageOrHistoryButton.setVisibility(View.GONE); - mClearAllButton.setVisibility(View.GONE); - mSeenNotifsFooterTextView.setVisibility(View.VISIBLE); - } else { - mManageOrHistoryButton.setVisibility(View.VISIBLE); - mClearAllButton.setVisibility(View.VISIBLE); - mSeenNotifsFooterTextView.setVisibility(View.GONE); - } + mSeenNotifsFooterTextView.setVisibility(View.GONE); } } @@ -359,10 +326,8 @@ public class FooterView extends StackScrollerDecorView { /** Set onClickListener for the clear all (end) button. */ public void setClearAllButtonClickListener(OnClickListener listener) { - if (FooterViewRefactor.isEnabled()) { - if (mClearAllButtonClickListener == listener) return; - mClearAllButtonClickListener = listener; - } + if (mClearAllButtonClickListener == listener) return; + mClearAllButtonClickListener = listener; mClearAllButton.setOnClickListener(listener); } @@ -379,62 +344,17 @@ public class FooterView extends StackScrollerDecorView { || touchY > mContent.getY() + mContent.getHeight(); } - /** Show "History" instead of "Manage" on the start button. */ - public void showHistory(boolean showHistory) { - FooterViewRefactor.assertInLegacyMode(); - if (mShowHistory == showHistory) { - return; - } - mShowHistory = showHistory; - updateContent(); - } - private void updateContent() { - if (FooterViewRefactor.isEnabled()) { - updateClearAllButtonText(); - updateClearAllButtonDescription(); - - if (!NotifRedesignFooter.isEnabled()) { - updateManageOrHistoryButtonText(); - updateManageOrHistoryButtonDescription(); - } - - updateMessageString(); - updateMessageIcon(); - } else { - // NOTE: Prior to the refactor, `updateResources` set the class properties to the right - // string values. It was always being called together with `updateContent`, which - // deals with actually associating those string values with the correct views - // (buttons or text). - // In the new code, the resource IDs are being set in the view binder (through - // setMessageString and similar setters). The setters themselves now deal with - // updating both the resource IDs and the views where appropriate (as in, calling - // `updateMessageString` when the resource ID changes). This eliminates the need for - // `updateResources`, which will eventually be removed. There are, however, still - // situations in which we want to update the views even if the resource IDs didn't - // change, such as configuration changes. - if (mShowHistory) { - mManageOrHistoryButton.setText(mManageNotificationHistoryText); - mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText); - } else { - mManageOrHistoryButton.setText(mManageNotificationText); - mManageOrHistoryButton.setContentDescription(mManageNotificationText); - } - - mClearAllButton.setText(R.string.clear_all_notifications_text); - mClearAllButton.setContentDescription( - mContext.getString(R.string.accessibility_clear_all)); + updateClearAllButtonText(); + updateClearAllButtonDescription(); - mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText); - mSeenNotifsFooterTextView - .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null); + if (!NotifRedesignFooter.isEnabled()) { + updateManageOrHistoryButtonText(); + updateManageOrHistoryButtonDescription(); } - } - /** Whether the start button shows "History" (true) or "Manage" (false). */ - public boolean isHistoryShown() { - FooterViewRefactor.assertInLegacyMode(); - return mShowHistory; + updateMessageString(); + updateMessageIcon(); } @Override @@ -445,9 +365,6 @@ public class FooterView extends StackScrollerDecorView { } super.onConfigurationChanged(newConfig); updateColors(); - if (!FooterViewRefactor.isEnabled()) { - updateResources(); - } updateContent(); } @@ -502,18 +419,6 @@ public class FooterView extends StackScrollerDecorView { } } - private void updateResources() { - FooterViewRefactor.assertInLegacyMode(); - mManageNotificationText = getContext().getString(R.string.manage_notifications_text); - mManageNotificationHistoryText = getContext() - .getString(R.string.manage_notifications_history_text); - int unlockIconSize = getResources() - .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size); - mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text); - mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed); - mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize); - } - @Override @NonNull public ExpandableViewState createExpandableViewState() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt index e724935e3ef4..5696e9f0c5a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt @@ -27,7 +27,6 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter.S import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent @@ -144,6 +143,7 @@ class FooterViewModel( ) } +// TODO: b/293167744 - remove this, use new viewmodel style @Module object FooterViewModelModule { @Provides @@ -153,18 +153,13 @@ object FooterViewModelModule { notificationSettingsInteractor: Provider<NotificationSettingsInteractor>, seenNotificationsInteractor: Provider<SeenNotificationsInteractor>, shadeInteractor: Provider<ShadeInteractor>, - ): Optional<FooterViewModel> { - return if (FooterViewRefactor.isEnabled) { - Optional.of( - FooterViewModel( - activeNotificationsInteractor.get(), - notificationSettingsInteractor.get(), - seenNotificationsInteractor.get(), - shadeInteractor.get(), - ) + ): Optional<FooterViewModel> = + Optional.of( + FooterViewModel( + activeNotificationsInteractor.get(), + notificationSettingsInteractor.get(), + seenNotificationsInteractor.get(), + shadeInteractor.get(), ) - } else { - Optional.empty() - } - } + ) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 071d23283c43..76591ac4e453 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -108,7 +108,6 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix; import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil; @@ -703,9 +702,6 @@ public class NotificationStackScrollLayout if (!ModesEmptyShadeFix.isEnabled()) { inflateEmptyShadeView(); } - if (!FooterViewRefactor.isEnabled()) { - inflateFooterView(); - } } /** @@ -741,22 +737,12 @@ public class NotificationStackScrollLayout } void reinflateViews() { - if (!FooterViewRefactor.isEnabled()) { - inflateFooterView(); - updateFooter(); - } if (!ModesEmptyShadeFix.isEnabled()) { inflateEmptyShadeView(); } mSectionsManager.reinflateViews(); } - public void setIsRemoteInputActive(boolean isActive) { - FooterViewRefactor.assertInLegacyMode(); - mIsRemoteInputActive = isActive; - updateFooter(); - } - void sendRemoteInputRowBottomBound(Float bottom) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; if (bottom != null) { @@ -766,43 +752,6 @@ public class NotificationStackScrollLayout mScrollViewFields.sendRemoteInputRowBottomBound(bottom); } - /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */ - public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) { - FooterViewRefactor.assertInLegacyMode(); - mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications; - } - - @VisibleForTesting - public void updateFooter() { - FooterViewRefactor.assertInLegacyMode(); - if (mFooterView == null || mController == null) { - return; - } - final boolean showHistory = mController.isHistoryEnabled(); - final boolean showDismissView = shouldShowDismissView(); - - updateFooterView(shouldShowFooterView(showDismissView)/* visible */, - showDismissView /* showDismissView */, - showHistory/* showHistory */); - } - - private boolean shouldShowDismissView() { - FooterViewRefactor.assertInLegacyMode(); - return mController.hasActiveClearableNotifications(ROWS_ALL); - } - - private boolean shouldShowFooterView(boolean showDismissView) { - FooterViewRefactor.assertInLegacyMode(); - return (showDismissView || mController.getVisibleNotificationCount() > 0) - && mIsCurrentUserSetup // see: b/193149550 - && !onKeyguard() - && mUpcomingStatusBarState != StatusBarState.KEYGUARD - // quick settings don't affect notifications when not in full screen - && (getQsExpansionFraction() != 1 || !mQsFullScreen) - && !mScreenOffAnimationController.shouldHideNotificationsFooter() - && !mIsRemoteInputActive; - } - void updateBgColor() { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); @@ -1861,9 +1810,6 @@ public class NotificationStackScrollLayout */ private float getAppearEndPosition() { SceneContainerFlag.assertInLegacyMode(); - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - return getAppearEndPositionLegacy(); - } int appearPosition = mAmbientState.getStackTopMargin(); if (mEmptyShadeView.getVisibility() == GONE) { @@ -1883,32 +1829,6 @@ public class NotificationStackScrollLayout return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding()); } - /** - * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't - * need to know about that, so we want to phase this out with the footer view refactor. - */ - private float getAppearEndPositionLegacy() { - FooterViewRefactor.assertInLegacyMode(); - - int appearPosition = mAmbientState.getStackTopMargin(); - int visibleNotifCount = mController.getVisibleNotificationCount(); - if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) { - if (isHeadsUpTransition() - || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) { - if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) { - appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements; - } - appearPosition += getTopHeadsUpPinnedHeight() - + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow()); - } else if (mShelf.getVisibility() != GONE) { - appearPosition += mShelf.getIntrinsicHeight(); - } - } else { - appearPosition = mEmptyShadeView.getHeight(); - } - return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding()); - } - private boolean isHeadsUpTransition() { return mAmbientState.getTrackedHeadsUpRow() != null; } @@ -1928,8 +1848,7 @@ public class NotificationStackScrollLayout // This can't use expansion fraction as that goes only from 0 to 1. Also when // appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3 // and that makes translation jump immediately. - float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition() - : getAppearEndPositionLegacy(); + float appearEndPosition = getAppearEndPosition(); float appearStartPosition = getAppearStartPosition(); float hunAppearFraction = (height - appearStartPosition) / (appearEndPosition - appearStartPosition); @@ -4848,15 +4767,6 @@ public class NotificationStackScrollLayout } } - /** - * Returns whether or not a History button is shown in the footer. If there is no footer, then - * this will return false. - **/ - public boolean isHistoryShown() { - FooterViewRefactor.assertInLegacyMode(); - return mFooterView != null && mFooterView.isHistoryShown(); - } - /** Bind the {@link FooterView} to the NSSL. */ public void setFooterView(@NonNull FooterView footerView) { int index = -1; @@ -4866,18 +4776,6 @@ public class NotificationStackScrollLayout } mFooterView = footerView; addView(mFooterView, index); - if (!FooterViewRefactor.isEnabled()) { - if (mManageButtonClickListener != null) { - mFooterView.setManageButtonClickListener(mManageButtonClickListener); - } - mFooterView.setClearAllButtonClickListener(v -> { - if (mFooterClearAllListener != null) { - mFooterClearAllListener.onClearAll(); - } - clearNotifications(ROWS_ALL, true /* closeShade */); - footerView.setClearAllButtonVisible(false /* visible */, true /* animate */); - }); - } } public void setEmptyShadeView(EmptyShadeView emptyShadeView) { @@ -4890,13 +4788,6 @@ public class NotificationStackScrollLayout addView(mEmptyShadeView, index); } - /** Legacy version, should be removed with the footer refactor flag. */ - public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) { - FooterViewRefactor.assertInLegacyMode(); - updateEmptyShadeView(visible, areNotificationsHiddenInShade, - mHasFilteredOutSeenNotifications); - } - /** Trigger an update for the empty shade resources and visibility. */ public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade, boolean hasFilteredOutSeenNotifications) { @@ -4949,18 +4840,6 @@ public class NotificationStackScrollLayout return mEmptyShadeView.isVisible(); } - public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) { - FooterViewRefactor.assertInLegacyMode(); - if (mFooterView == null || mNotificationStackSizeCalculator == null) { - return; - } - boolean animate = mIsExpanded && mAnimationsEnabled; - mFooterView.setVisible(visible, animate); - mFooterView.showHistory(showHistory); - mFooterView.setClearAllButtonVisible(showDismissView, animate); - mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications); - } - @VisibleForTesting public void setClearAllInProgress(boolean clearAllInProgress) { mClearAllInProgress = clearAllInProgress; @@ -5244,10 +5123,8 @@ public class NotificationStackScrollLayout public void setQsFullScreen(boolean qsFullScreen) { SceneContainerFlag.assertInLegacyMode(); - if (FooterViewRefactor.isEnabled()) { - if (qsFullScreen == mQsFullScreen) { - return; // no change - } + if (qsFullScreen == mQsFullScreen) { + return; // no change } mQsFullScreen = qsFullScreen; updateAlgorithmLayoutMinHeight(); @@ -5266,8 +5143,6 @@ public class NotificationStackScrollLayout public void setQsExpansionFraction(float qsExpansionFraction) { SceneContainerFlag.assertInLegacyMode(); - boolean footerAffected = getQsExpansionFraction() != qsExpansionFraction - && (getQsExpansionFraction() == 1 || qsExpansionFraction == 1); mQsExpansionFraction = qsExpansionFraction; updateUseRoundedRectClipping(); @@ -5276,9 +5151,6 @@ public class NotificationStackScrollLayout if (getOwnScrollY() > 0) { setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, getQsExpansionFraction())); } - if (!FooterViewRefactor.isEnabled() && footerAffected) { - updateFooter(); - } } @VisibleForTesting @@ -5456,14 +5328,6 @@ public class NotificationStackScrollLayout requestChildrenUpdate(); } - void setUpcomingStatusBarState(int upcomingStatusBarState) { - FooterViewRefactor.assertInLegacyMode(); - mUpcomingStatusBarState = upcomingStatusBarState; - if (mUpcomingStatusBarState != mStatusBarState) { - updateFooter(); - } - } - void onStatePostChange(boolean fromShadeLocked) { boolean onKeyguard = onKeyguard(); @@ -5472,9 +5336,6 @@ public class NotificationStackScrollLayout } setExpandingEnabled(!onKeyguard); - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } requestChildrenUpdate(); onUpdateRowStates(); updateVisibility(); @@ -5490,8 +5351,7 @@ public class NotificationStackScrollLayout if (mEmptyShadeView == null || mEmptyShadeView.getVisibility() == GONE) { return getMinExpansionHeight(); } else { - return FooterViewRefactor.isEnabled() ? getAppearEndPosition() - : getAppearEndPositionLegacy(); + return getAppearEndPosition(); } } @@ -5583,12 +5443,6 @@ public class NotificationStackScrollLayout for (int i = 0; i < childCount; i++) { ExpandableView child = getChildAtIndex(i); child.dump(pw, args); - if (!FooterViewRefactor.isEnabled()) { - if (child instanceof FooterView) { - DumpUtilsKt.withIncreasedIndent(pw, - () -> dumpFooterViewVisibility(pw)); - } - } pw.println(); } int transientViewCount = getTransientViewCount(); @@ -5615,45 +5469,6 @@ public class NotificationStackScrollLayout pw.append(" bottomRadius=").println(mBgCornerRadii[4]); } - private void dumpFooterViewVisibility(IndentingPrintWriter pw) { - FooterViewRefactor.assertInLegacyMode(); - final boolean showDismissView = shouldShowDismissView(); - - pw.println("showFooterView: " + shouldShowFooterView(showDismissView)); - DumpUtilsKt.withIncreasedIndent( - pw, - () -> { - pw.println("showDismissView: " + showDismissView); - DumpUtilsKt.withIncreasedIndent( - pw, - () -> { - pw.println( - "hasActiveClearableNotifications: " - + mController.hasActiveClearableNotifications( - ROWS_ALL)); - }); - pw.println(); - pw.println("showHistory: " + mController.isHistoryEnabled()); - pw.println(); - pw.println( - "visibleNotificationCount: " - + mController.getVisibleNotificationCount()); - pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup); - pw.println("onKeyguard: " + onKeyguard()); - pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState); - if (!SceneContainerFlag.isEnabled()) { - pw.println("QsExpansionFraction: " + getQsExpansionFraction()); - } - pw.println("mQsFullScreen: " + mQsFullScreen); - pw.println( - "mScreenOffAnimationController" - + ".shouldHideNotificationsFooter: " - + mScreenOffAnimationController - .shouldHideNotificationsFooter()); - pw.println("mIsRemoteInputActive: " + mIsRemoteInputActive); - }); - } - public boolean isFullyHidden() { return mAmbientState.isFullyHidden(); } @@ -5764,14 +5579,6 @@ public class NotificationStackScrollLayout clearNotifications(ROWS_GENTLE, closeShade, hideSilentSection); } - /** Legacy version of clearNotifications below. Uses the old data source for notif stats. */ - void clearNotifications(@SelectedRows int selection, boolean closeShade) { - FooterViewRefactor.assertInLegacyMode(); - final boolean hideSilentSection = !mController.hasNotifications( - ROWS_GENTLE, false /* clearable */); - clearNotifications(selection, closeShade, hideSilentSection); - } - /** * Collects a list of visible rows, and animates them away in a staggered fashion as if they * were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd. @@ -5826,25 +5633,6 @@ public class NotificationStackScrollLayout return canChildBeCleared(row) && matchesSelection(row, selection); } - /** - * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. - */ - public void setManageButtonClickListener(@Nullable OnClickListener listener) { - FooterViewRefactor.assertInLegacyMode(); - mManageButtonClickListener = listener; - if (mFooterView != null) { - mFooterView.setManageButtonClickListener(mManageButtonClickListener); - } - } - - @VisibleForTesting - protected void inflateFooterView() { - FooterViewRefactor.assertInLegacyMode(); - FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_notification_footer, this, false); - setFooterView(footerView); - } - private void inflateEmptyShadeView() { ModesEmptyShadeFix.assertInLegacyMode(); @@ -6091,11 +5879,6 @@ public class NotificationStackScrollLayout mHighPriorityBeforeSpeedBump = highPriorityBeforeSpeedBump; } - void setFooterClearAllListener(FooterClearAllListener listener) { - FooterViewRefactor.assertInLegacyMode(); - mFooterClearAllListener = listener; - } - void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) { mClearAllFinishedWhilePanelExpandedRunnable = runnable; } @@ -6394,17 +6177,6 @@ public class NotificationStackScrollLayout } /** - * Sets whether the current user is set up, which is required to show the footer (b/193149550) - */ - public void setCurrentUserSetup(boolean isCurrentUserSetup) { - FooterViewRefactor.assertInLegacyMode(); - if (mIsCurrentUserSetup != isCurrentUserSetup) { - mIsCurrentUserSetup = isCurrentUserSetup; - updateFooter(); - } - } - - /** * Sets a {@link StackStateLogger} which is notified as the {@link StackStateAnimator} updates * the views. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index a33a9ed2df75..b892bebb3120 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -29,11 +29,8 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL; -import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE; -import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.animation.ObjectAnimator; import android.content.res.Configuration; @@ -64,14 +61,10 @@ import com.android.internal.view.OneShotPreDrawListener; import com.android.systemui.Dumpable; import com.android.systemui.ExpandHelper; import com.android.systemui.Gefingerpoken; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; -import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.media.controls.ui.controller.KeyguardMediaController; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -92,18 +85,13 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; -import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.ColorUpdateLogger; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl; -import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; -import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController; import com.android.systemui.statusbar.notification.LaunchAnimationParameters; -import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.EntryWithDismissStats; import com.android.systemui.statusbar.notification.collection.NotifCollection; @@ -115,14 +103,15 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener; import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider; import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator; +import com.android.systemui.statusbar.notification.collection.render.DefaultNotifStackController; import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager; import com.android.systemui.statusbar.notification.collection.render.NotifStackController; -import com.android.systemui.statusbar.notification.collection.render.NotifStats; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; -import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController; -import com.android.systemui.statusbar.notification.dagger.SilentHeader; -import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; +import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; +import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl; +import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper; +import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController; +import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; @@ -137,13 +126,8 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; -import com.android.systemui.statusbar.notification.headsup.HeadsUpManager; -import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController; import com.android.systemui.statusbar.policy.SplitShadeStateController; -import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.Compile; import com.android.systemui.util.settings.SecureSettings; @@ -179,10 +163,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { private HeadsUpTouchHelper mHeadsUpTouchHelper; private final NotificationRoundnessManager mNotificationRoundnessManager; private final TunerService mTunerService; - private final DeviceProvisionedController mDeviceProvisionedController; private final DynamicPrivacyController mDynamicPrivacyController; private final ConfigurationController mConfigurationController; - private final ZenModeController mZenModeController; private final MetricsLogger mMetricsLogger; private final ColorUpdateLogger mColorUpdateLogger; @@ -193,7 +175,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotifPipeline mNotifPipeline; private final NotifCollection mNotifCollection; private final UiEventLogger mUiEventLogger; - private final NotificationRemoteInputManager mRemoteInputManager; private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator; private final ShadeController mShadeController; private final Provider<WindowRootView> mWindowRootView; @@ -201,9 +182,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final SysuiStatusBarStateController mStatusBarStateController; private final KeyguardBypassController mKeyguardBypassController; private final PowerInteractor mPowerInteractor; - private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; private final NotificationLockscreenUserManager mLockscreenUserManager; - private final SectionHeaderController mSilentHeaderController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; private final InteractionJankMonitor mJankMonitor; private final NotificationStackSizeCalculator mNotificationStackSizeCalculator; @@ -211,8 +190,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotificationStackScrollLogger mLogger; private final GroupExpansionManager mGroupExpansionManager; - private final SeenNotificationsInteractor mSeenNotificationsInteractor; - private final KeyguardTransitionRepository mKeyguardTransitionRepo; private NotificationStackScrollLayout mView; private TouchHandler mTouchHandler; private NotificationSwipeHelper mSwipeHelper; @@ -220,7 +197,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { private Boolean mHistoryEnabled; private int mBarState; private HeadsUpAppearanceController mHeadsUpAppearanceController; - private boolean mIsInTransitionToAod = false; private final NotificationTargetsHelper mNotificationTargetsHelper; private final SecureSettings mSecureSettings; @@ -235,11 +211,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final NotificationListContainerImpl mNotificationListContainer = new NotificationListContainerImpl(); + // TODO: b/293167744 - Remove this. private final NotifStackController mNotifStackController = - new NotifStackControllerImpl(); - - @Nullable - private NotificationActivityStarter mNotificationActivityStarter; + new DefaultNotifStackController(); @VisibleForTesting final View.OnAttachStateChangeListener mOnAttachStateChangeListener = @@ -248,9 +222,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onViewAttachedToWindow(View v) { mColorUpdateLogger.logTriggerEvent("NSSLC.onViewAttachedToWindow()"); mConfigurationController.addCallback(mConfigurationListener); - if (!FooterViewRefactor.isEnabled()) { - mZenModeController.addCallback(mZenModeControllerCallback); - } final int newBarState = mStatusBarStateController.getState(); if (newBarState != mBarState) { mStateListener.onStateChanged(newBarState); @@ -264,9 +235,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onViewDetachedFromWindow(View v) { mColorUpdateLogger.logTriggerEvent("NSSLC.onViewDetachedFromWindow()"); mConfigurationController.removeCallback(mConfigurationListener); - if (!FooterViewRefactor.isEnabled()) { - mZenModeController.removeCallback(mZenModeControllerCallback); - } mStatusBarStateController.removeCallback(mStateListener); } }; @@ -287,28 +255,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { @Nullable private ObjectAnimator mHideAlphaAnimator = null; - private final DeviceProvisionedListener mDeviceProvisionedListener = - new DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - updateCurrentUserIsSetup(); - } - - @Override - public void onUserSwitched() { - updateCurrentUserIsSetup(); - } - - @Override - public void onUserSetupChanged() { - updateCurrentUserIsSetup(); - } - - private void updateCurrentUserIsSetup() { - mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup()); - } - }; - private final Runnable mSensitiveStateChangedListener = new Runnable() { @Override public void run() { @@ -318,20 +264,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; - private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> { - if (!FooterViewRefactor.isEnabled()) { - // Let's update the footer once the notifications have been updated (in the next frame) - mView.post(this::updateFooter); - } - }; - @VisibleForTesting final ConfigurationListener mConfigurationListener = new ConfigurationListener() { @Override public void onDensityOrFontScaleChanged() { - if (!FooterViewRefactor.isEnabled()) { - updateShowEmptyShadeView(); - } mView.reinflateViews(); } @@ -351,10 +287,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.updateBgColor(); mView.updateDecorViews(); mView.reinflateViews(); - if (!FooterViewRefactor.isEnabled()) { - updateShowEmptyShadeView(); - updateFooter(); - } } @Override @@ -363,7 +295,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; - private NotifStats mNotifStats = NotifStats.getEmpty(); private float mMaxAlphaForKeyguard = 1.0f; private String mMaxAlphaForKeyguardSource = "constructor"; private float mMaxAlphaForUnhide = 1.0f; @@ -401,19 +332,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { } @Override - public void onUpcomingStateChanged(int newState) { - if (!FooterViewRefactor.isEnabled()) { - mView.setUpcomingStatusBarState(newState); - } - } - - @Override public void onStatePostChange() { updateSensitivenessWithAnimation(mStatusBarStateController.goingToFullShade()); mView.onStatePostChange(mStatusBarStateController.fromShadeLocked()); - if (!FooterViewRefactor.isEnabled()) { - updateImportantForAccessibility(); - } } }; @@ -422,9 +343,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void onUserChanged(int userId) { updateSensitivenessWithAnimation(false); mHistoryEnabled = null; - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } } }; @@ -656,7 +574,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { == null) { mHeadsUpManager.removeNotification( row.getEntry().getSbn().getKey(), - /* removeImmediately= */ true , + /* removeImmediately= */ true, /* reason= */ "onChildSnappedBack" ); } @@ -714,14 +632,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } }; - private final ZenModeController.Callback mZenModeControllerCallback = - new ZenModeController.Callback() { - @Override - public void onZenChanged(int zen) { - updateShowEmptyShadeView(); - } - }; - @Inject public NotificationStackScrollLayoutController( NotificationStackScrollLayout view, @@ -734,16 +644,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { Provider<IStatusBarService> statusBarService, NotificationRoundnessManager notificationRoundnessManager, TunerService tunerService, - DeviceProvisionedController deviceProvisionedController, DynamicPrivacyController dynamicPrivacyController, @ShadeDisplayAware ConfigurationController configurationController, SysuiStatusBarStateController statusBarStateController, KeyguardMediaController keyguardMediaController, KeyguardBypassController keyguardBypassController, PowerInteractor powerInteractor, - PrimaryBouncerInteractor primaryBouncerInteractor, - KeyguardTransitionRepository keyguardTransitionRepo, - ZenModeController zenModeController, NotificationLockscreenUserManager lockscreenUserManager, MetricsLogger metricsLogger, ColorUpdateLogger colorUpdateLogger, @@ -752,14 +658,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { FalsingManager falsingManager, NotificationSwipeHelper.Builder notificationSwipeHelperBuilder, GroupExpansionManager groupManager, - @SilentHeader SectionHeaderController silentHeaderController, NotifPipeline notifPipeline, NotifCollection notifCollection, LockscreenShadeTransitionController lockscreenShadeTransitionController, UiEventLogger uiEventLogger, - NotificationRemoteInputManager remoteInputManager, VisibilityLocationProviderDelegator visibilityLocationProviderDelegator, - SeenNotificationsInteractor seenNotificationsInteractor, NotificationListViewBinder viewBinder, ShadeController shadeController, Provider<WindowRootView> windowRootView, @@ -775,7 +678,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { SensitiveNotificationProtectionController sensitiveNotificationProtectionController, WallpaperInteractor wallpaperInteractor) { mView = view; - mKeyguardTransitionRepo = keyguardTransitionRepo; mViewBinder = viewBinder; mStackStateLogger = stackLogger; mLogger = logger; @@ -795,15 +697,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { } mNotificationRoundnessManager = notificationRoundnessManager; mTunerService = tunerService; - mDeviceProvisionedController = deviceProvisionedController; mDynamicPrivacyController = dynamicPrivacyController; mConfigurationController = configurationController; mStatusBarStateController = statusBarStateController; mKeyguardMediaController = keyguardMediaController; mKeyguardBypassController = keyguardBypassController; mPowerInteractor = powerInteractor; - mPrimaryBouncerInteractor = primaryBouncerInteractor; - mZenModeController = zenModeController; mLockscreenUserManager = lockscreenUserManager; mMetricsLogger = metricsLogger; mColorUpdateLogger = colorUpdateLogger; @@ -815,13 +714,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { mJankMonitor = jankMonitor; mNotificationStackSizeCalculator = notificationStackSizeCalculator; mGroupExpansionManager = groupManager; - mSilentHeaderController = silentHeaderController; mNotifPipeline = notifPipeline; mNotifCollection = notifCollection; mUiEventLogger = uiEventLogger; - mRemoteInputManager = remoteInputManager; mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator; - mSeenNotificationsInteractor = seenNotificationsInteractor; mShadeController = shadeController; mWindowRootView = windowRootView; mNotificationTargetsHelper = notificationTargetsHelper; @@ -850,18 +746,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setClearAllAnimationListener(this::onAnimationEnd); mView.setClearAllListener((selection) -> mUiEventLogger.log( NotificationPanelEvent.fromSelection(selection))); - if (!FooterViewRefactor.isEnabled()) { - mView.setFooterClearAllListener(() -> - mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES)); - mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive()); - mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() { - @Override - public void onRemoteInputActive(boolean active) { - mView.setIsRemoteInputActive(active); - } - }); - } - mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> { + mView.setClearAllFinishedWhilePanelExpandedRunnable(() -> { final Runnable doCollapseRunnable = () -> mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE); mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE); @@ -889,19 +774,11 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled()); mKeyguardBypassController .registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled); - if (!FooterViewRefactor.isEnabled()) { - mView.setManageButtonClickListener(v -> { - if (mNotificationActivityStarter != null) { - mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown()); - } - }); - } if (!SceneContainerFlag.isEnabled()) { mHeadsUpManager.addListener(mOnHeadsUpChangedListener); } mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed); - mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener); mLockscreenShadeTransitionController.setStackScroller(this); @@ -914,9 +791,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { switch (key) { case Settings.Secure.NOTIFICATION_HISTORY_ENABLED: mHistoryEnabled = null; // invalidate - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } break; case HIGH_PRIORITY: mView.setHighPriorityBeforeSpeedBump("1".equals(newValue)); @@ -938,12 +812,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { return kotlin.Unit.INSTANCE; }); - if (!FooterViewRefactor.isEnabled()) { - // attach callback, and then call it to update mView immediately - mDeviceProvisionedController.addCallback(mDeviceProvisionedListener); - mDeviceProvisionedListener.onDeviceProvisionedChanged(); - } - if (screenshareNotificationHiding()) { mSensitiveNotificationProtectionController .registerSensitiveStateListener(mSensitiveStateChangedListener); @@ -953,20 +821,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { mOnAttachStateChangeListener.onViewAttachedToWindow(mView); } mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener); - if (!FooterViewRefactor.isEnabled()) { - mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications()); - } mGroupExpansionManager.registerGroupExpansionChangeListener( (changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded)); mViewBinder.bindWhileAttached(mView, this); - if (!FooterViewRefactor.isEnabled()) { - collectFlow(mView, mKeyguardTransitionRepo.getTransitions(), - this::onKeyguardTransitionChanged); - } - mView.setWallpaperInteractor(mWallpaperInteractor); } @@ -1168,11 +1028,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView != null && mView.isAddOrRemoveAnimationPending(); } - public int getVisibleNotificationCount() { - FooterViewRefactor.assertInLegacyMode(); - return mNotifStats.getNumActiveNotifs(); - } - public boolean isHistoryEnabled() { Boolean historyEnabled = mHistoryEnabled; if (historyEnabled == null) { @@ -1284,9 +1139,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { public void setQsFullScreen(boolean fullScreen) { mView.setQsFullScreen(fullScreen); - if (!FooterViewRefactor.isEnabled()) { - updateShowEmptyShadeView(); - } } public void setScrollingEnabled(boolean enabled) { @@ -1464,64 +1316,12 @@ public class NotificationStackScrollLayoutController implements Dumpable { } /** - * Set the visibility of the view, and propagate it to specific children. + * Set the visibility of the view. * * @param visible either the view is visible or not. */ public void updateVisibility(boolean visible) { mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); - - // Refactor note: the empty shade's visibility doesn't seem to actually depend on the - // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not - // modeled in the refactored code. - if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) { - // Synchronize EmptyShadeView visibility with the parent container. - updateShowEmptyShadeView(); - updateImportantForAccessibility(); - } - } - - /** - * Update whether we should show the empty shade view ("no notifications" in the shade). - * <p> - * When in split mode, notifications are always visible regardless of the state of the - * QuickSettings panel. That being the case, empty view is always shown if the other conditions - * are true. - */ - public void updateShowEmptyShadeView() { - FooterViewRefactor.assertInLegacyMode(); - - Trace.beginSection("NSSLC.updateShowEmptyShadeView"); - - final boolean shouldShow = getVisibleNotificationCount() == 0 - && !mView.isQsFullScreen() - // Hide empty shade view when in transition to AOD. - // That avoids "No Notifications" to blink when transitioning to AOD. - // For more details, see: b/228790482 - && !mIsInTransitionToAod - // Don't show any notification content if the bouncer is showing. See b/267060171. - && !mPrimaryBouncerInteractor.isBouncerShowing(); - - mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade()); - - Trace.endSection(); - } - - /** - * Update the importantForAccessibility of NotificationStackScrollLayout. - * <p> - * We want the NSSL to be unimportant for accessibility when there's no - * notifications in it while the device is on lock screen, to avoid unlablel NSSL view. - * Otherwise, we want it to be important for accessibility to enable accessibility - * auto-scrolling in NSSL. - */ - public void updateImportantForAccessibility() { - FooterViewRefactor.assertInLegacyMode(); - if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) { - mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } else { - mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } } public boolean isShowingEmptyShadeView() { @@ -1577,34 +1377,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setPulsing(pulsing, animatePulse); } - /** - * Return whether there are any clearable notifications - */ - public boolean hasActiveClearableNotifications(@SelectedRows int selection) { - FooterViewRefactor.assertInLegacyMode(); - return hasNotifications(selection, true /* clearable */); - } - - public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) { - FooterViewRefactor.assertInLegacyMode(); - boolean hasAlertingMatchingClearable = isClearable - ? mNotifStats.getHasClearableAlertingNotifs() - : mNotifStats.getHasNonClearableAlertingNotifs(); - boolean hasSilentMatchingClearable = isClearable - ? mNotifStats.getHasClearableSilentNotifs() - : mNotifStats.getHasNonClearableSilentNotifs(); - switch (selection) { - case ROWS_GENTLE: - return hasSilentMatchingClearable; - case ROWS_HIGH_PRIORITY: - return hasAlertingMatchingClearable; - case ROWS_ALL: - return hasSilentMatchingClearable || hasAlertingMatchingClearable; - default: - throw new IllegalStateException("Bad selection: " + selection); - } - } - /** Sets whether the NSSL is displayed over the unoccluded Lockscreen. */ public void setOnLockscreen(boolean isOnLockscreen) { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return; @@ -1637,9 +1409,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive); entry.notifyHeightChanged(true /* needsAnimation */); - if (!FooterViewRefactor.isEnabled()) { - updateFooter(); - } } public void lockScrollTo(NotificationEntry entry) { @@ -1662,13 +1431,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { }; } - public void updateFooter() { - FooterViewRefactor.assertInLegacyMode(); - Trace.beginSection("NSSLC.updateFooter"); - mView.updateFooter(); - Trace.endSection(); - } - public void onUpdateRowStates() { mView.onUpdateRowStates(); } @@ -1695,18 +1457,10 @@ public class NotificationStackScrollLayoutController implements Dumpable { return mView.getTransientViewCount(); } - public View getTransientView(int i) { - return mView.getTransientView(i); - } - public NotificationStackScrollLayout getView() { return mView; } - public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) { - return mView.calculateGapHeight(previousView, child, count); - } - NotificationRoundnessManager getNotificationRoundnessManager() { return mNotificationRoundnessManager; } @@ -1772,13 +1526,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { return NotificationSwipeHelper.isTouchInView(event, view); } - public void clearSilentNotifications() { - FooterViewRefactor.assertInLegacyMode(); - // Leave the shade open if there will be other notifs left over to clear - final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY); - mView.clearNotifications(ROWS_GENTLE, closeShade); - } - private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows) { if (selectedRows == ROWS_ALL) { @@ -1880,10 +1627,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.animateNextTopPaddingChange(); } - public void setNotificationActivityStarter(NotificationActivityStarter activityStarter) { - mNotificationActivityStarter = activityStarter; - } - public NotificationTargetsHelper getNotificationTargetsHelper() { return mNotificationTargetsHelper; } @@ -1898,18 +1641,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } @VisibleForTesting - void onKeyguardTransitionChanged(TransitionStep transitionStep) { - FooterViewRefactor.assertInLegacyMode(); - boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD) - && (transitionStep.getFrom().equals(KeyguardState.GONE) - || transitionStep.getFrom().equals(KeyguardState.OCCLUDED)); - if (mIsInTransitionToAod != isTransitionToAod) { - mIsInTransitionToAod = isTransitionToAod; - updateShowEmptyShadeView(); - } - } - - @VisibleForTesting TouchHandler getTouchHandler() { return mTouchHandler; } @@ -2288,22 +2019,4 @@ public class NotificationStackScrollLayoutController implements Dumpable { && !mSwipeHelper.isSwiping(); } } - - private class NotifStackControllerImpl implements NotifStackController { - @Override - public void setNotifStats(@NonNull NotifStats notifStats) { - FooterViewRefactor.assertInLegacyMode(); - mNotifStats = notifStats; - - if (!FooterViewRefactor.isEnabled()) { - mView.setHasFilteredOutSeenNotifications( - mSeenNotificationsInteractor - .getHasFilteredOutSeenNotifications().getValue()); - - updateFooter(); - updateShowEmptyShadeView(); - updateImportantForAccessibility(); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index 1653029dc994..06b989aaab57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -35,7 +35,6 @@ import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.notification.SourceType; import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -463,26 +462,23 @@ public class StackScrollAlgorithm { if (v == ambientState.getShelf()) { continue; } - if (FooterViewRefactor.isEnabled()) { - if (v instanceof EmptyShadeView) { - emptyShadeVisible = true; - } - if (v instanceof FooterView footerView) { - if (emptyShadeVisible || notGoneIndex == 0) { - // if the empty shade is visible or the footer is the first visible - // view, we're in a transitory state so let's leave the footer alone. - if (Flags.notificationsFooterVisibilityFix() - && !SceneContainerFlag.isEnabled()) { - // ...except for the hidden state, to prevent it from flashing on - // the screen (this piece is copied from updateChild, and is not - // necessary in flexiglass). - if (footerView.shouldBeHidden() - || !ambientState.isShadeExpanded()) { - footerView.getViewState().hidden = true; - } + if (v instanceof EmptyShadeView) { + emptyShadeVisible = true; + } + if (v instanceof FooterView footerView) { + if (emptyShadeVisible || notGoneIndex == 0) { + // if the empty shade is visible or the footer is the first visible + // view, we're in a transitory state so let's leave the footer alone. + if (Flags.notificationsFooterVisibilityFix() + && !SceneContainerFlag.isEnabled()) { + // ...except for the hidden state, to prevent it from flashing on + // the screen (this piece is copied from updateChild, and is not + // necessary in flexiglass). + if (footerView.shouldBeHidden() || !ambientState.isShadeExpanded()) { + footerView.getViewState().hidden = true; } - continue; } + continue; } } @@ -699,44 +695,28 @@ public class StackScrollAlgorithm { viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation() ); if (view instanceof FooterView) { - if (FooterViewRefactor.isEnabled()) { - if (SceneContainerFlag.isEnabled()) { - final float footerEnd = - stackTop + viewState.getYTranslation() + view.getIntrinsicHeight(); - final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff(); - ((FooterView.FooterViewState) viewState).hideContent = - noSpaceForFooter || (ambientState.isClearAllInProgress() - && !hasNonClearableNotifs(algorithmState)); - } else { - // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed - // already, so we shouldn't need to use ambientState here. However, - // currently it doesn't get updated quickly enough and can cause the footer to - // flash when closing the shade. As such, we temporarily also check the - // ambientState directly. - if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) { - viewState.hidden = true; - } else { - final float footerEnd = algorithmState.mCurrentExpandedYPosition - + view.getIntrinsicHeight(); - final boolean noSpaceForFooter = - footerEnd > ambientState.getStackEndHeight(); - ((FooterView.FooterViewState) viewState).hideContent = - noSpaceForFooter || (ambientState.isClearAllInProgress() - && !hasNonClearableNotifs(algorithmState)); - } - } + if (SceneContainerFlag.isEnabled()) { + final float footerEnd = + stackTop + viewState.getYTranslation() + view.getIntrinsicHeight(); + final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff(); + ((FooterView.FooterViewState) viewState).hideContent = + noSpaceForFooter || (ambientState.isClearAllInProgress() + && !hasNonClearableNotifs(algorithmState)); } else { - final boolean shadeClosed = !ambientState.isShadeExpanded(); - final boolean isShelfShowing = algorithmState.firstViewInShelf != null; - if (shadeClosed) { + // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed + // already, so we shouldn't need to use ambientState here. However, + // currently it doesn't get updated quickly enough and can cause the footer to + // flash when closing the shade. As such, we temporarily also check the + // ambientState directly. + if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) { viewState.hidden = true; } else { final float footerEnd = algorithmState.mCurrentExpandedYPosition + view.getIntrinsicHeight(); - final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight(); + final boolean noSpaceForFooter = + footerEnd > ambientState.getStackEndHeight(); ((FooterView.FooterViewState) viewState).hideContent = - isShelfShowing || noSpaceForFooter - || (ambientState.isClearAllInProgress() + noSpaceForFooter || (ambientState.isClearAllInProgress() && !hasNonClearableNotifs(algorithmState)); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index b4561686b7b2..1d7e658932ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -40,7 +40,6 @@ import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyS import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView import com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder.EmptyShadeViewBinder import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter import com.android.systemui.statusbar.notification.footer.ui.view.FooterView import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder @@ -108,25 +107,20 @@ constructor( launch { bindShelf(shelf) } bindHideList(viewController, viewModel, hiderTracker) - if (FooterViewRefactor.isEnabled) { - val hasNonClearableSilentNotifications: StateFlow<Boolean> = - viewModel.hasNonClearableSilentNotifications.stateIn(this) - launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) } - launch { - if (ModesEmptyShadeFix.isEnabled) { - reinflateAndBindEmptyShade(view) - } else { - bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view) - } + val hasNonClearableSilentNotifications: StateFlow<Boolean> = + viewModel.hasNonClearableSilentNotifications.stateIn(this) + launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) } + launch { + if (ModesEmptyShadeFix.isEnabled) { + reinflateAndBindEmptyShade(view) + } else { + bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view) } - launch { - bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) - } - launch { - viewModel.isImportantForAccessibility.collect { isImportantForAccessibility - -> - view.setImportantForAccessibilityYesNo(isImportantForAccessibility) - } + } + launch { bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) } + launch { + viewModel.isImportantForAccessibility.collect { isImportantForAccessibility -> + view.setImportantForAccessibilityYesNo(isImportantForAccessibility) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt index ea714608ea66..0b2b84e60f4b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt @@ -28,7 +28,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer @@ -81,9 +80,6 @@ constructor( controller.setOverExpansion(0f) controller.setOverScrollAmount(0) - if (!FooterViewRefactor.isEnabled) { - controller.updateFooter() - } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 38390e7bdb39..fcc671a5bae6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -25,7 +25,6 @@ import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotif import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel @@ -75,46 +74,37 @@ constructor( * we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL. * See b/242235264 for more details. */ - val isImportantForAccessibility: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(true) - } else { - combine( - activeNotificationsInteractor.areAnyNotificationsPresent, - notificationStackInteractor.isShowingOnLockscreen, - ) { hasNotifications, isShowingOnLockscreen -> - hasNotifications || !isShowingOnLockscreen - } - .distinctUntilChanged() - .dumpWhileCollecting("isImportantForAccessibility") - .flowOn(bgDispatcher) - } - } + val isImportantForAccessibility: Flow<Boolean> = + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + notificationStackInteractor.isShowingOnLockscreen, + ) { hasNotifications, isShowingOnLockscreen -> + hasNotifications || !isShowingOnLockscreen + } + .distinctUntilChanged() + .dumpWhileCollecting("isImportantForAccessibility") + .flowOn(bgDispatcher) val shouldShowEmptyShadeView: Flow<Boolean> by lazy { ModesEmptyShadeFix.assertInLegacyMode() - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - combine( - activeNotificationsInteractor.areAnyNotificationsPresent, - shadeInteractor.isQsFullscreen, - notificationStackInteractor.isShowingOnLockscreen, - ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen -> - when { - hasNotifications -> false - isQsFullScreen -> false - // Do not show the empty shade if the lockscreen is visible (including AOD - // b/228790482 and bouncer b/267060171), except if the shade is opened on - // top. - isShowingOnLockscreen -> false - else -> true - } + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + shadeInteractor.isQsFullscreen, + notificationStackInteractor.isShowingOnLockscreen, + ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen -> + when { + hasNotifications -> false + isQsFullScreen -> false + // Do not show the empty shade if the lockscreen is visible (including AOD + // b/228790482 and bouncer b/267060171), except if the shade is opened on + // top. + isShowingOnLockscreen -> false + else -> true } - .distinctUntilChanged() - .dumpWhileCollecting("shouldShowEmptyShadeView") - .flowOn(bgDispatcher) - } + } + .distinctUntilChanged() + .dumpWhileCollecting("shouldShowEmptyShadeView") + .flowOn(bgDispatcher) } val shouldShowEmptyShadeViewAnimated: Flow<AnimatedValue<Boolean>> by lazy { @@ -164,18 +154,14 @@ constructor( */ val shouldHideFooterView: Flow<Boolean> by lazy { SceneContainerFlag.assertInLegacyMode() - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - // When the shade is closed, the footer is still present in the list, but not visible. - // This prevents the footer from being shown when a HUN is present, while still allowing - // the footer to be counted as part of the shade for measurements. - shadeInteractor.shadeExpansion - .map { it == 0f } - .distinctUntilChanged() - .dumpWhileCollecting("shouldHideFooterView") - .flowOn(bgDispatcher) - } + // When the shade is closed, the footer is still present in the list, but not visible. + // This prevents the footer from being shown when a HUN is present, while still allowing + // the footer to be counted as part of the shade for measurements. + shadeInteractor.shadeExpansion + .map { it == 0f } + .distinctUntilChanged() + .dumpWhileCollecting("shouldHideFooterView") + .flowOn(bgDispatcher) } /** @@ -188,68 +174,64 @@ constructor( */ val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy { SceneContainerFlag.assertInLegacyMode() - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(AnimatedValue.NotAnimating(false)) - } else { - combine( - activeNotificationsInteractor.areAnyNotificationsPresent, - userSetupInteractor.isUserSetUp, - notificationStackInteractor.isShowingOnLockscreen, - shadeInteractor.isQsFullscreen, - remoteInputInteractor.isRemoteInputActive, - ) { - hasNotifications, - isUserSetUp, - isShowingOnLockscreen, - qsFullScreen, - isRemoteInputActive -> - when { - !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - // Hide the footer until the user setup is complete, to prevent access - // to settings (b/193149550). - !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - // Do not show the footer if the lockscreen is visible (incl. AOD), - // except if the shade is opened on top. See also b/219680200. - // Do not animate, as that makes the footer appear briefly when - // transitioning between the shade and keyguard. - isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION - // Do not show the footer if quick settings are fully expanded (except - // for the foldable split shade view). See b/201427195 && b/222699879. - qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - // Hide the footer if remote input is active (i.e. user is replying to a - // notification). See b/75984847. - isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION - else -> VisibilityChange.APPEAR_WITH_ANIMATION - } + combine( + activeNotificationsInteractor.areAnyNotificationsPresent, + userSetupInteractor.isUserSetUp, + notificationStackInteractor.isShowingOnLockscreen, + shadeInteractor.isQsFullscreen, + remoteInputInteractor.isRemoteInputActive, + ) { + hasNotifications, + isUserSetUp, + isShowingOnLockscreen, + qsFullScreen, + isRemoteInputActive -> + when { + !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + // Hide the footer until the user setup is complete, to prevent access + // to settings (b/193149550). + !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + // Do not show the footer if the lockscreen is visible (incl. AOD), + // except if the shade is opened on top. See also b/219680200. + // Do not animate, as that makes the footer appear briefly when + // transitioning between the shade and keyguard. + isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION + // Do not show the footer if quick settings are fully expanded (except + // for the foldable split shade view). See b/201427195 && b/222699879. + qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + // Hide the footer if remote input is active (i.e. user is replying to a + // notification). See b/75984847. + isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION + else -> VisibilityChange.APPEAR_WITH_ANIMATION } - .distinctUntilChanged( - // Equivalent unless visibility changes - areEquivalent = { a: VisibilityChange, b: VisibilityChange -> - a.visible == b.visible - } - ) - // Should we animate the visibility change? - .sample( - // TODO(b/322167853): This check is currently duplicated in FooterViewModel, - // but instead it should be a field in ShadeAnimationInteractor. - combine( - shadeInteractor.isShadeFullyExpanded, - shadeInteractor.isShadeTouchable, - ::Pair, - ) - .onStart { emit(Pair(false, false)) } - ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) -> - // Animate if the shade is interactive, but NOT on the lockscreen. Having - // animations enabled while on the lockscreen makes the footer appear briefly - // when transitioning between the shade and keyguard. - val shouldAnimate = - isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate - AnimatableEvent(visibilityChange.visible, shouldAnimate) + } + .distinctUntilChanged( + // Equivalent unless visibility changes + areEquivalent = { a: VisibilityChange, b: VisibilityChange -> + a.visible == b.visible } - .toAnimatedValueFlow() - .dumpWhileCollecting("shouldIncludeFooterView") - .flowOn(bgDispatcher) - } + ) + // Should we animate the visibility change? + .sample( + // TODO(b/322167853): This check is currently duplicated in FooterViewModel, + // but instead it should be a field in ShadeAnimationInteractor. + combine( + shadeInteractor.isShadeFullyExpanded, + shadeInteractor.isShadeTouchable, + ::Pair, + ) + .onStart { emit(Pair(false, false)) } + ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) -> + // Animate if the shade is interactive, but NOT on the lockscreen. Having + // animations enabled while on the lockscreen makes the footer appear briefly + // when transitioning between the shade and keyguard. + val shouldAnimate = + isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate + AnimatableEvent(visibilityChange.visible, shouldAnimate) + } + .toAnimatedValueFlow() + .dumpWhileCollecting("shouldIncludeFooterView") + .flowOn(bgDispatcher) } // This flow replaces shouldHideFooterView+shouldIncludeFooterView in flexiglass. @@ -328,25 +310,15 @@ constructor( APPEAR_WITH_ANIMATION(visible = true, canAnimate = true), } - val hasClearableAlertingNotifications: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting( - "hasClearableAlertingNotifications" - ) - } - } + val hasClearableAlertingNotifications: Flow<Boolean> = + activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting( + "hasClearableAlertingNotifications" + ) - val hasNonClearableSilentNotifications: Flow<Boolean> by lazy { - if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) { - flowOf(false) - } else { - activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting( - "hasNonClearableSilentNotifications" - ) - } - } + val hasNonClearableSilentNotifications: Flow<Boolean> = + activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting( + "hasNonClearableSilentNotifications" + ) val topHeadsUpRow: Flow<HeadsUpRowKey?> by lazy { if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 1474789ea0e3..3d6cd7e49dfe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1487,8 +1487,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mActivityTransitionAnimator.setCallback(mActivityTransitionAnimatorCallback); mActivityTransitionAnimator.addListener(mActivityTransitionAnimatorListener); mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController); - mStackScrollerController.setNotificationActivityStarter( - mNotificationActivityStarterLazy.get()); mGutsManager.setNotificationActivityStarter(mNotificationActivityStarterLazy.get()); mShadeController.setNotificationPresenter(mPresenterLazy.get()); mNotificationsController.initialize( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 31cae79c6b94..81d06a8db0b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -32,6 +32,7 @@ import android.os.Trace; import androidx.annotation.VisibleForTesting; +import com.android.app.tracing.coroutines.TrackTracer; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; @@ -241,7 +242,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController { private void setKeyguardFadingAway(boolean keyguardFadingAway) { if (mKeyguardFadingAway != keyguardFadingAway) { - Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway", + TrackTracer.instantForGroup("keyguard", "FadingAway", keyguardFadingAway ? 1 : 0); mKeyguardFadingAway = keyguardFadingAway; invokeForEachCallback(Callback::onKeyguardFadingAwayChanged); @@ -356,7 +357,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController { @Override public void notifyKeyguardGoingAway(boolean keyguardGoingAway) { if (mKeyguardGoingAway != keyguardGoingAway) { - Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway", + Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguard##GoingAway", keyguardGoingAway ? 1 : 0); mKeyguardGoingAway = keyguardGoingAway; mKeyguardInteractorLazy.get().setIsKeyguardGoingAway(keyguardGoingAway); diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt index ae32b7a6175c..bce55cbdcc4a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt @@ -50,7 +50,7 @@ fun BackGestureTutorialScreen( ) GestureTutorialScreen( screenConfig = screenConfig, - gestureUiStateFlow = viewModel.gestureUiState, + tutorialStateFlow = viewModel.tutorialState, motionEventConsumer = { easterEggGestureViewModel.accept(it) viewModel.handleEvent(it) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt index 73c54af595d9..284e23e5a288 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt @@ -18,7 +18,6 @@ package com.android.systemui.touchpad.tutorial.ui.composable import android.view.MotionEvent import androidx.activity.compose.BackHandler -import androidx.annotation.RawRes import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box @@ -27,77 +26,21 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInteropFilter import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.NotStarted -import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import kotlinx.coroutines.flow.Flow -sealed interface GestureUiState { - data object NotStarted : GestureUiState - - data class Finished(@RawRes val successAnimation: Int) : GestureUiState - - data class InProgress( - val progress: Float = 0f, - val progressStartMarker: String, - val progressEndMarker: String, - ) : GestureUiState - - data object Error : GestureUiState -} - -fun GestureState.toGestureUiState( - progressStartMarker: String, - progressEndMarker: String, - successAnimation: Int, -): GestureUiState { - return when (this) { - GestureState.NotStarted -> NotStarted - is GestureState.InProgress -> - GestureUiState.InProgress(this.progress, progressStartMarker, progressEndMarker) - is GestureState.Finished -> GestureUiState.Finished(successAnimation) - GestureState.Error -> GestureUiState.Error - } -} - -fun GestureUiState.toTutorialActionState(previousState: TutorialActionState): TutorialActionState { - return when (this) { - NotStarted -> TutorialActionState.NotStarted - is GestureUiState.InProgress -> { - val inProgress = - TutorialActionState.InProgress( - progress = progress, - startMarker = progressStartMarker, - endMarker = progressEndMarker, - ) - if ( - previousState is TutorialActionState.InProgressAfterError || - previousState is TutorialActionState.Error - ) { - return TutorialActionState.InProgressAfterError(inProgress) - } else { - return inProgress - } - } - is Finished -> TutorialActionState.Finished(successAnimation) - GestureUiState.Error -> TutorialActionState.Error - } -} - @Composable fun GestureTutorialScreen( screenConfig: TutorialScreenConfig, - gestureUiStateFlow: Flow<GestureUiState>, + tutorialStateFlow: Flow<TutorialActionState>, motionEventConsumer: (MotionEvent) -> Boolean, easterEggTriggeredFlow: Flow<Boolean>, onEasterEggFinished: () -> Unit, @@ -106,25 +49,21 @@ fun GestureTutorialScreen( ) { BackHandler(onBack = onBack) val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false) - val gestureState by gestureUiStateFlow.collectAsStateWithLifecycle(NotStarted) + val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted) TouchpadGesturesHandlingBox( motionEventConsumer, - gestureState, + tutorialState, easterEggTriggered, onEasterEggFinished, ) { - var lastState: TutorialActionState by remember { - mutableStateOf(TutorialActionState.NotStarted) - } - lastState = gestureState.toTutorialActionState(lastState) - ActionTutorialContent(lastState, onDoneButtonClicked, screenConfig) + ActionTutorialContent(tutorialState, onDoneButtonClicked, screenConfig) } } @Composable private fun TouchpadGesturesHandlingBox( motionEventConsumer: (MotionEvent) -> Boolean, - gestureState: GestureUiState, + tutorialState: TutorialActionState, easterEggTriggered: Boolean, onEasterEggFinished: () -> Unit, modifier: Modifier = Modifier, @@ -150,7 +89,7 @@ private fun TouchpadGesturesHandlingBox( .pointerInteropFilter( onTouchEvent = { event -> // FINISHED is the final state so we don't need to process touches anymore - if (gestureState is Finished) { + if (tutorialState is TutorialActionState.Finished) { false } else { motionEventConsumer(event) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt index 4f1f40dc4c05..4acdb6070200 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt @@ -49,7 +49,7 @@ fun HomeGestureTutorialScreen( ) GestureTutorialScreen( screenConfig = screenConfig, - gestureUiStateFlow = viewModel.gestureUiState, + tutorialStateFlow = viewModel.tutorialState, motionEventConsumer = { easterEggGestureViewModel.accept(it) viewModel.handleEvent(it) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt index 6c9e26c4b7ea..8dd53a7fb815 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt @@ -50,7 +50,7 @@ fun RecentAppsGestureTutorialScreen( ) GestureTutorialScreen( screenConfig = screenConfig, - gestureUiStateFlow = viewModel.gestureUiState, + tutorialStateFlow = viewModel.tutorialState, motionEventConsumer = { easterEggGestureViewModel.accept(it) viewModel.handleEvent(it) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt index 8e53669a7841..7a3d4d1ba88a 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt @@ -17,12 +17,12 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.res.R -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import com.android.systemui.util.kotlin.pairwiseBy import kotlinx.coroutines.flow.Flow @@ -30,21 +30,26 @@ import kotlinx.coroutines.flow.Flow class BackGestureScreenViewModel(val gestureRecognizer: GestureRecognizerAdapter) : TouchpadTutorialScreenViewModel { - override val gestureUiState: Flow<GestureUiState> = - gestureRecognizer.gestureState.pairwiseBy(GestureState.NotStarted) { previous, current -> - toGestureUiState(current, previous) - } + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .pairwiseBy(NotStarted) { previous, current -> + current to toAnimationProperties(current, previous) + } + .mapToTutorialState() override fun handleEvent(event: MotionEvent): Boolean { return gestureRecognizer.handleTouchpadMotionEvent(event) } - private fun toGestureUiState(current: GestureState, previous: GestureState): GestureUiState { + private fun toAnimationProperties( + current: GestureState, + previous: GestureState, + ): TutorialAnimationProperties { val (startMarker, endMarker) = if (current is InProgress && current.direction == GestureDirection.LEFT) { "gesture to L" to "end progress L" } else "gesture to R" to "end progress R" - return current.toGestureUiState( + return TutorialAnimationProperties( progressStartMarker = startMarker, progressEndMarker = endMarker, successAnimation = successAnimation(previous), diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt index 9d6f568fa1b1..c75d44f01e8c 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt @@ -17,9 +17,8 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.res.R -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -27,14 +26,17 @@ import kotlinx.coroutines.flow.map class HomeGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : TouchpadTutorialScreenViewModel { - override val gestureUiState: Flow<GestureUiState> = - gestureRecognizer.gestureState.map { - it.toGestureUiState( - progressStartMarker = "drag with gesture", - progressEndMarker = "release playback realtime", - successAnimation = R.raw.trackpad_home_success, - ) - } + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .map { + it to + TutorialAnimationProperties( + progressStartMarker = "drag with gesture", + progressEndMarker = "release playback realtime", + successAnimation = R.raw.trackpad_home_success, + ) + } + .mapToTutorialState() override fun handleEvent(event: MotionEvent): Boolean { return gestureRecognizer.handleTouchpadMotionEvent(event) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt index 97528583277f..9fab5f3641a4 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt @@ -17,9 +17,8 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState import com.android.systemui.res.R -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState -import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -27,14 +26,17 @@ import kotlinx.coroutines.flow.map class RecentAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) : TouchpadTutorialScreenViewModel { - override val gestureUiState: Flow<GestureUiState> = - gestureRecognizer.gestureState.map { - it.toGestureUiState( - progressStartMarker = "drag with gesture", - progressEndMarker = "onPause", - successAnimation = R.raw.trackpad_recent_apps_success, - ) - } + override val tutorialState: Flow<TutorialActionState> = + gestureRecognizer.gestureState + .map { + it to + TutorialAnimationProperties( + progressStartMarker = "drag with gesture", + progressEndMarker = "onPause", + successAnimation = R.raw.trackpad_recent_apps_success, + ) + } + .mapToTutorialState() override fun handleEvent(event: MotionEvent): Boolean { return gestureRecognizer.handleTouchpadMotionEvent(event) diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt index 31e953d6643c..3b6e3c76cdeb 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt @@ -17,11 +17,62 @@ package com.android.systemui.touchpad.tutorial.ui.viewmodel import android.view.MotionEvent -import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState +import androidx.annotation.RawRes +import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress +import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow interface TouchpadTutorialScreenViewModel { - val gestureUiState: Flow<GestureUiState> + val tutorialState: Flow<TutorialActionState> fun handleEvent(event: MotionEvent): Boolean } + +data class TutorialAnimationProperties( + val progressStartMarker: String, + val progressEndMarker: String, + @RawRes val successAnimation: Int, +) + +fun Flow<Pair<GestureState, TutorialAnimationProperties>>.mapToTutorialState(): + Flow<TutorialActionState> { + return flow<TutorialActionState> { + var lastState: TutorialActionState = TutorialActionState.NotStarted + collect { (gestureState, animationProperties) -> + val newState = gestureState.toTutorialActionState(animationProperties, lastState) + lastState = newState + emit(newState) + } + } +} + +fun GestureState.toTutorialActionState( + properties: TutorialAnimationProperties, + previousState: TutorialActionState, +): TutorialActionState { + return when (this) { + NotStarted -> TutorialActionState.NotStarted + is InProgress -> { + val inProgress = + TutorialActionState.InProgress( + progress = progress, + startMarker = properties.progressStartMarker, + endMarker = properties.progressEndMarker, + ) + if ( + previousState is TutorialActionState.InProgressAfterError || + previousState is TutorialActionState.Error + ) { + TutorialActionState.InProgressAfterError(inProgress) + } else { + inProgress + } + } + is Finished -> TutorialActionState.Finished(properties.successAnimation) + GestureState.Error -> TutorialActionState.Error + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt index 04dc80c45a18..3988acbea7c2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt @@ -16,7 +16,9 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor +import com.android.settingslib.volume.shared.model.AudioStream import com.android.systemui.plugins.VolumeDialogController +import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel @@ -27,6 +29,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.stateIn @@ -39,8 +43,17 @@ constructor( @VolumeDialog private val coroutineScope: CoroutineScope, volumeDialogStateInteractor: VolumeDialogStateInteractor, private val volumeDialogController: VolumeDialogController, + zenModeInteractor: ZenModeInteractor, ) { + val isDisabledByZenMode: Flow<Boolean> = + if (sliderType is VolumeDialogSliderType.Stream) { + zenModeInteractor.activeModesBlockingStream(AudioStream(sliderType.audioStream)).map { + it.mainMode != null + } + } else { + flowOf(false) + } val slider: Flow<VolumeDialogStreamModel> = volumeDialogStateInteractor.volumeDialogState .mapNotNull { diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt index ccd16ac5a331..3b964fdec1b8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt @@ -69,6 +69,7 @@ constructor( viewModel.setStreamVolume(value.roundToInt(), fromUser) } + viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this) viewModel.state .onEach { sliderView.setModel(it, animation, isInitialUpdate) diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt index 06d9426fbe04..d999910675b0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt @@ -73,6 +73,7 @@ constructor( .stateIn(coroutineScope, SharingStarted.Eagerly, null) .filterNotNull() + val isDisabledByZenMode: Flow<Boolean> = interactor.isDisabledByZenMode val state: Flow<VolumeDialogSliderStateModel> = model .flatMapLatest { streamModel -> @@ -82,7 +83,7 @@ constructor( level = level, levelMin = levelMin, levelMax = levelMax, - isMuted = muted, + isMuted = muteSupported && muted, isRoutedToBluetooth = routedToBluetooth, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index 2c37f510a45c..77bac59b9dcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -33,15 +32,14 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStackC import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController +import com.android.systemui.util.mockito.withArgCaptor import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -82,9 +80,9 @@ class StackCoordinatorTest : SysuiTestCase() { sensitiveNotificationProtectionController, ) coordinator.attach(pipeline) - val captor = argumentCaptor<OnAfterRenderListListener>() - verify(pipeline).addOnAfterRenderListListener(captor.capture()) - afterRenderListListener = captor.lastValue + afterRenderListListener = withArgCaptor { + verify(pipeline).addOnAfterRenderListListener(capture()) + } } @Test @@ -94,93 +92,9 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetRenderedListOnInteractor_footerFlagOn() { - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(renderListInteractor).setRenderedList(eq(listOf(entry))) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) fun testSetNotificationStats_clearableAlerting() { whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = true, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = false, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) - fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() { - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - whenever(section.bucket).thenReturn(BUCKET_ALERTING) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = true, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = false, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_clearableSilent() { - whenever(section.bucket).thenReturn(BUCKET_SILENT) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = false, - hasClearableSilentNotifs = true, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @DisableFlags(FooterViewRefactor.FLAG_NAME) - @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) - fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() { - whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) - whenever(section.bucket).thenReturn(BUCKET_SILENT) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) - verify(stackController) - .setNotifStats( - NotifStats( - 1, - hasNonClearableAlertingNotifs = false, - hasClearableAlertingNotifs = false, - hasNonClearableSilentNotifs = true, - hasClearableSilentNotifs = false, - ) - ) - verifyNoMoreInteractions(activeNotificationsInteractor) - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_footerFlagOn_clearableAlerting() { - whenever(section.bucket).thenReturn(BUCKET_ALERTING) - afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(activeNotificationsInteractor) .setNotifStats( NotifStats( @@ -195,12 +109,8 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags( - FooterViewRefactor.FLAG_NAME, - FLAG_SCREENSHARE_NOTIFICATION_HIDING, - FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - ) - fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableAlerting() { + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) + fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() { whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) @@ -218,8 +128,7 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_footerFlagOn_clearableSilent() { + fun testSetNotificationStats_clearableSilent() { whenever(section.bucket).thenReturn(BUCKET_SILENT) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(activeNotificationsInteractor) @@ -236,12 +145,8 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags( - FooterViewRefactor.FLAG_NAME, - FLAG_SCREENSHARE_NOTIFICATION_HIDING, - FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX, - ) - fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableSilent() { + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) + fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() { whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true) whenever(section.bucket).thenReturn(BUCKET_SILENT) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) @@ -259,8 +164,7 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) - fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() { + fun testSetNotificationStats_nonClearableRedacted() { entry.setSensitive(true, true) whenever(section.bucket).thenReturn(BUCKET_ALERTING) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index e1a891662889..3763282cdebc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack; -import static android.view.View.GONE; import static android.view.WindowInsets.Type.ime; import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag; @@ -28,17 +27,14 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; import static org.junit.Assert.assertFalse; -import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; @@ -64,7 +60,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowInsetsAnimation; -import android.widget.TextView; import androidx.test.filters.SmallTest; @@ -92,8 +87,6 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix; import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView; -import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; -import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter; import com.android.systemui.statusbar.notification.footer.ui.view.FooterView; import com.android.systemui.statusbar.notification.headsup.AvalancheController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -603,158 +596,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void manageNotifications_visible() { - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - when(view.willBeGone()).thenReturn(true); - - mStackScroller.updateFooterView(true, false, true); - - verify(view).setVisible(eq(true), anyBoolean()); - verify(view).setClearAllButtonVisible(eq(false), anyBoolean()); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void clearAll_visible() { - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - when(view.willBeGone()).thenReturn(true); - - mStackScroller.updateFooterView(true, true, true); - - verify(view).setVisible(eq(true), anyBoolean()); - verify(view).setClearAllButtonVisible(eq(true), anyBoolean()); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testInflateFooterView() { - mStackScroller.inflateFooterView(); - ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class); - verify(mStackScroller).setFooterView(captor.capture()); - - assertNotNull(captor.getValue().findViewById(R.id.manage_text)); - assertNotNull(captor.getValue().findViewById(R.id.dismiss_text)); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testUpdateFooter_noNotifications() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_remoteInput() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - mStackScroller.setIsRemoteInputActive(true); - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testUpdateFooter_withoutNotifications() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(false); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_oneClearableNotification() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_withoutHistory() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(false); - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void testUpdateFooter_oneClearableNotification_beforeUserSetup() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(false); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(true); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - @DisableSceneContainer - public void testUpdateFooter_oneNonClearableNotification() { - setBarStateForTest(StatusBarState.SHADE); - mStackScroller.setCurrentUserSetup(true); - - when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1); - when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL))) - .thenReturn(false); - when(mEmptyShadeView.getVisibility()).thenReturn(GONE); - - FooterView view = mock(FooterView.class); - mStackScroller.setFooterView(view); - mStackScroller.updateFooter(); - verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true); - } - - @Test public void testFooterPosition_atEnd() { // add footer FooterView view = mock(FooterView.class); @@ -772,19 +613,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, - ModesEmptyShadeFix.FLAG_NAME, - NotifRedesignFooter.FLAG_NAME}) - public void testReInflatesFooterViews() { - when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text); - clearInvocations(mStackScroller); - mStackScroller.reinflateViews(); - verify(mStackScroller).setFooterView(any()); - verify(mStackScroller).setEmptyShadeView(any()); - } - - @Test - @EnableFlags(FooterViewRefactor.FLAG_NAME) @DisableFlags(ModesEmptyShadeFix.FLAG_NAME) public void testReInflatesEmptyShadeView() { when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text); @@ -1231,31 +1059,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME}) - public void hasFilteredOutSeenNotifs_updateFooter() { - mStackScroller.setCurrentUserSetup(true); - - // add footer - mStackScroller.inflateFooterView(); - TextView footerLabel = - mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer); - - mStackScroller.setHasFilteredOutSeenNotifications(true); - mStackScroller.updateFooter(); - - assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE); - } - - @Test - @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME}) - public void hasFilteredOutSeenNotifs_updateEmptyShadeView() { - mStackScroller.setHasFilteredOutSeenNotifications(true); - mStackScroller.updateEmptyShadeView(true, false); - - verify(mEmptyShadeView).setFooterText(not(eq(0))); - } - - @Test @DisableSceneContainer public void testWindowInsetAnimationProgress_updatesBottomInset() { int imeInset = 100; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt index 44917dd4ba48..198d72a41fa4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.plugins.volumeDialogController +import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType @@ -29,5 +30,6 @@ val Kosmos.volumeDialogSliderInteractor: VolumeDialogSliderInteractor by applicationCoroutineScope, volumeDialogStateInteractor, volumeDialogController, + zenModeInteractor, ) } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java index f15b8eec3f6b..cd46b38272c2 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java @@ -38,7 +38,7 @@ public class TouchState { // Pointer-related constants // This constant captures the current implementation detail that // pointer IDs are between 0 and 31 inclusive (subject to change). - // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h) + // (See MAX_POINTER_ID in frameworks/native/include/input/Input.h) public static final int MAX_POINTER_COUNT = 32; // Constant referring to the ids bits of all pointers. public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF; diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 778c6864282d..31f6ef9fc062 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -1708,7 +1708,7 @@ public class BinaryTransparencyService extends SystemService { private class PackageUpdatedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (!intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) { + if (!Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) { return; } diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index dce9760b3971..6459016eec75 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -66,8 +66,7 @@ import com.android.server.wm.WindowManagerInternal; /** * The service that listens for gestures detected in sensor firmware and starts the intent * accordingly. - * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be - * added.</p> + * * @hide */ public class GestureLauncherService extends SystemService { @@ -109,10 +108,22 @@ public class GestureLauncherService extends SystemService { @VisibleForTesting static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000; - /** Indicates camera should be launched on power double tap. */ + /** Configuration value indicating double tap power gesture is disabled. */ + @VisibleForTesting static final int DOUBLE_TAP_POWER_DISABLED_MODE = 0; + + /** Configuration value indicating double tap power gesture should launch camera. */ + @VisibleForTesting static final int DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE = 1; + + /** + * Configuration value indicating double tap power gesture should launch one of many target + * actions. + */ + @VisibleForTesting static final int DOUBLE_TAP_POWER_MULTI_TARGET_MODE = 2; + + /** Indicates camera launch is selected as target action for multi target double tap power. */ @VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0; - /** Indicates wallet should be launched on power double tap. */ + /** Indicates wallet launch is selected as target action for multi target double tap power. */ @VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1; /** Number of taps required to launch the double tap shortcut (either camera or wallet). */ @@ -228,6 +239,7 @@ public class GestureLauncherService extends SystemService { return mId; } } + public GestureLauncherService(Context context) { this(context, new MetricsLogger(), QuickAccessWalletClient.create(context), new UiEventLoggerImpl()); @@ -289,16 +301,15 @@ public class GestureLauncherService extends SystemService { Settings.Secure.getUriFor( Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE), false, mSettingObserver, mUserId); - } else { - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED), - false, mSettingObserver, mUserId); - mContext.getContentResolver().registerContentObserver( - Settings.Secure.getUriFor( - Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED), - false, mSettingObserver, mUserId); } mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED), + false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor( + Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED), + false, mSettingObserver, mUserId); + mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED), false, mSettingObserver, mUserId); mContext.getContentResolver().registerContentObserver( @@ -468,23 +479,27 @@ public class GestureLauncherService extends SystemService { Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0); } - /** Checks if camera should be launched on double press of the power button. */ public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) { - boolean res; - - if (launchWalletOptionOnPowerDoubleTap()) { - res = isDoubleTapPowerGestureSettingEnabled(context, userId) - && getDoubleTapPowerGestureAction(context, userId) - == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER; - } else { - // These are legacy settings that will be deprecated once the option to launch both - // wallet and camera has been created. - res = isCameraDoubleTapPowerEnabled(context.getResources()) + if (!launchWalletOptionOnPowerDoubleTap()) { + return isCameraDoubleTapPowerEnabled(context.getResources()) && (Settings.Secure.getIntForUser(context.getContentResolver(), Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0); } - return res; + + final int doubleTapPowerGestureSettingMode = getDoubleTapPowerGestureMode( + context.getResources()); + + return switch (doubleTapPowerGestureSettingMode) { + case DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE -> Settings.Secure.getIntForUser( + context.getContentResolver(), + Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0; + case DOUBLE_TAP_POWER_MULTI_TARGET_MODE -> + isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId) + && getDoubleTapPowerGestureAction(context, userId) + == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER; + default -> false; + }; } /** Checks if wallet should be launched on double tap of the power button. */ @@ -493,7 +508,9 @@ public class GestureLauncherService extends SystemService { return false; } - return isDoubleTapPowerGestureSettingEnabled(context, userId) + return getDoubleTapPowerGestureMode(context.getResources()) + == DOUBLE_TAP_POWER_MULTI_TARGET_MODE + && isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId) && getDoubleTapPowerGestureAction(context, userId) == LAUNCH_WALLET_ON_DOUBLE_TAP_POWER; } @@ -515,26 +532,40 @@ public class GestureLauncherService extends SystemService { isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0; } - private static int getDoubleTapPowerGestureAction(Context context, int userId) { - return Settings.Secure.getIntForUser( - context.getContentResolver(), - Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE, - LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER, - userId); + /** Gets the double tap power gesture mode. */ + private static int getDoubleTapPowerGestureMode(Resources resources) { + return resources.getInteger(R.integer.config_doubleTapPowerGestureMode); } - /** Whether the shortcut to launch app on power double press is enabled. */ - private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) { + /** + * Whether the setting for multi target double tap power gesture is enabled. + * + * <p>Multi target double tap power gesture allows the user to choose one of many target actions + * when double tapping the power button. + * </p> + */ + private static boolean isMultiTargetDoubleTapPowerGestureSettingEnabled(Context context, + int userId) { return Settings.Secure.getIntForUser( context.getContentResolver(), Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED, - isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0, + getDoubleTapPowerGestureMode(context.getResources()) + == DOUBLE_TAP_POWER_MULTI_TARGET_MODE ? 1 : 0, userId) == 1; } - private static boolean isDoubleTapConfigEnabled(Resources resources) { - return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled); + /** Gets the selected target action for the multi target double tap power gesture. + * + * <p>The target actions are defined in {@link Settings.Secure#DOUBLE_TAP_POWER_BUTTON_GESTURE}. + * </p> + */ + private static int getDoubleTapPowerGestureAction(Context context, int userId) { + return Settings.Secure.getIntForUser( + context.getContentResolver(), + Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE, + LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER, + userId); } /** @@ -595,7 +626,7 @@ public class GestureLauncherService extends SystemService { || isCameraLiftTriggerEnabled(resources) || isEmergencyGestureEnabled(resources); if (launchWalletOptionOnPowerDoubleTap()) { - res |= isDoubleTapConfigEnabled(resources); + res |= getDoubleTapPowerGestureMode(resources) != DOUBLE_TAP_POWER_DISABLED_MODE; } else { res |= isCameraDoubleTapPowerEnabled(resources); } diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 3817ba1a28b9..0b7890167c08 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -305,6 +305,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub { this.stringName = null; } + @VisibleForTesting TempAllowListDuration getAllowlistDurationLocked(IBinder allowlistToken) { + return mAllowlistDuration.get(allowlistToken); + } + void setAllowBgActivityStarts(IBinder token, int flags) { if (token == null) return; if ((flags & FLAG_ACTIVITY_SENDER) != 0) { @@ -323,6 +327,12 @@ public final class PendingIntentRecord extends IIntentSender.Stub { mAllowBgActivityStartsForActivitySender.remove(token); mAllowBgActivityStartsForBroadcastSender.remove(token); mAllowBgActivityStartsForServiceSender.remove(token); + if (mAllowlistDuration != null) { + mAllowlistDuration.remove(token); + if (mAllowlistDuration.isEmpty()) { + mAllowlistDuration = null; + } + } } public void registerCancelListenerLocked(IResultReceiver receiver) { @@ -703,7 +713,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub { return res; } - private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( + @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender( IBinder allowlistToken) { return mAllowBgActivityStartsForActivitySender.contains(allowlistToken) ? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken) diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 295e0443371d..8a63f9a24ea3 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -359,7 +359,7 @@ public class AppOpsService extends IAppOpsService.Stub { private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10); private final RateLimiter mRateLimiter = new RateLimiter(RATE_LIMITER_WINDOW); - volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this); + volatile @NonNull HistoricalRegistry mHistoricalRegistry; /* * These are app op restrictions imposed per user from various parties. @@ -1039,6 +1039,8 @@ public class AppOpsService extends IAppOpsService.Stub { // will not exist and the nonce will be UNSET. AppOpsManager.invalidateAppOpModeCache(); AppOpsManager.disableAppOpModeCache(); + + mHistoricalRegistry = new HistoricalRegistry(this, context); } public void publish() { diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java index 4d114b4ad4ac..9dd09cef88f9 100644 --- a/services/core/java/com/android/server/appop/AttributedOp.java +++ b/services/core/java/com/android/server/appop/AttributedOp.java @@ -113,7 +113,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime, AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE, - DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount); + DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount); } /** @@ -257,7 +257,8 @@ final class AttributedOp { if (isStarted) { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, uidState, flags, startTime, - attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1); + attributionFlags, attributionChainId, + DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1); } } @@ -344,8 +345,8 @@ final class AttributedOp { parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(), event.getAttributionFlags(), event.getAttributionChainId(), - isPausing ? DiscreteRegistry.ACCESS_TYPE_PAUSE_OP - : DiscreteRegistry.ACCESS_TYPE_FINISH_OP); + isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP + : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP); if (!isPausing) { mAppOpsService.mInProgressStartOpEventPool.release(event); @@ -453,7 +454,7 @@ final class AttributedOp { mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName, persistentDeviceId, tag, event.getUidState(), event.getFlags(), startTime, event.getAttributionFlags(), - event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1); + event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1); if (shouldSendActive) { mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid, parent.packageName, tag, event.getVirtualDeviceId(), true, diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java new file mode 100644 index 000000000000..e4c36cc214e8 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java @@ -0,0 +1,387 @@ +/* + * 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.server.appop; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.Context; +import android.database.DatabaseErrorHandler; +import android.database.DefaultDatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteRawStatement; +import android.os.Environment; +import android.util.IntArray; +import android.util.Slog; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +class DiscreteOpsDbHelper extends SQLiteOpenHelper { + private static final String LOG_TAG = "DiscreteOpsDbHelper"; + static final String DATABASE_NAME = "app_op_history.db"; + private static final int DATABASE_VERSION = 1; + private static final boolean DEBUG = false; + + DiscreteOpsDbHelper(@NonNull Context context, @NonNull File databaseFile) { + super(context, databaseFile.getAbsolutePath(), null, DATABASE_VERSION, + new DiscreteOpsDatabaseErrorHandler()); + setOpenParams(getDatabaseOpenParams()); + } + + private static SQLiteDatabase.OpenParams getDatabaseOpenParams() { + return new SQLiteDatabase.OpenParams.Builder() + .addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) + .build(); + } + + @NonNull + static File getDatabaseFile() { + return new File(new File(Environment.getDataSystemDirectory(), "appops"), DATABASE_NAME); + } + + @Override + public void onConfigure(SQLiteDatabase db) { + db.execSQL("PRAGMA synchronous = NORMAL"); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(DiscreteOpsTable.CREATE_TABLE_SQL); + db.execSQL(DiscreteOpsTable.CREATE_INDEX_SQL); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + } + + void insertDiscreteOps(@NonNull List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents) { + if (opEvents.isEmpty()) { + return; + } + + SQLiteDatabase db = getWritableDatabase(); + // TODO (b/383157289) what if database is busy and can't start a transaction? will read + // more about it and can be done in a follow up cl. + db.beginTransaction(); + try (SQLiteRawStatement statement = db.createRawStatement( + DiscreteOpsTable.INSERT_TABLE_SQL)) { + for (DiscreteOpsSqlRegistry.DiscreteOp event : opEvents) { + try { + statement.bindInt(DiscreteOpsTable.UID_INDEX, event.getUid()); + bindTextOrNull(statement, DiscreteOpsTable.PACKAGE_NAME_INDEX, + event.getPackageName()); + bindTextOrNull(statement, DiscreteOpsTable.DEVICE_ID_INDEX, + event.getDeviceId()); + statement.bindInt(DiscreteOpsTable.OP_CODE_INDEX, event.getOpCode()); + bindTextOrNull(statement, DiscreteOpsTable.ATTRIBUTION_TAG_INDEX, + event.getAttributionTag()); + statement.bindLong(DiscreteOpsTable.ACCESS_TIME_INDEX, event.getAccessTime()); + statement.bindLong( + DiscreteOpsTable.ACCESS_DURATION_INDEX, event.getDuration()); + statement.bindInt(DiscreteOpsTable.UID_STATE_INDEX, event.getUidState()); + statement.bindInt(DiscreteOpsTable.OP_FLAGS_INDEX, event.getOpFlags()); + statement.bindInt(DiscreteOpsTable.ATTRIBUTION_FLAGS_INDEX, + event.getAttributionFlags()); + statement.bindLong(DiscreteOpsTable.CHAIN_ID_INDEX, event.getChainId()); + statement.step(); + } catch (Exception exception) { + Slog.e(LOG_TAG, "Error inserting the discrete op: " + event, exception); + } finally { + statement.reset(); + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) { + if (text == null) { + statement.bindNull(index); + } else { + statement.bindText(index, text); + } + } + + /** + * This will be used as an offset for inserting new chain id in discrete ops table. + */ + long getLargestAttributionChainId() { + long chainId = 0; + try { + SQLiteDatabase db = getReadableDatabase(); + db.beginTransactionReadOnly(); + try (SQLiteRawStatement statement = + db.createRawStatement(DiscreteOpsTable.SELECT_MAX_ATTRIBUTION_CHAIN_ID)) { + if (statement.step()) { + chainId = statement.getColumnLong(0); + if (chainId < 0) { + chainId = 0; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } catch (SQLiteException exception) { + Slog.e(LOG_TAG, "Error reading attribution chain id", exception); + } + return chainId; + } + + void execSQL(@NonNull String sql) { + execSQL(sql, null); + } + + void execSQL(@NonNull String sql, Object[] bindArgs) { + if (DEBUG) { + Slog.i(LOG_TAG, "DB execSQL, sql: " + sql); + } + SQLiteDatabase db = getWritableDatabase(); + if (bindArgs == null) { + db.execSQL(sql); + } else { + db.execSQL(sql, bindArgs); + } + } + + /** + * Returns a list of {@link DiscreteOpsSqlRegistry.DiscreteOp} based on the given filters. + */ + List<DiscreteOpsSqlRegistry.DiscreteOp> getDiscreteOps( + @AppOpsManager.HistoricalOpsRequestFilter int requestFilters, + int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, IntArray opCodesFilter, int opFlagsFilter, + long beginTime, long endTime, int limit, String orderByColumn) { + List<SQLCondition> conditions = prepareConditions(beginTime, endTime, requestFilters, + uidFilter, packageNameFilter, + attributionTagFilter, opCodesFilter, opFlagsFilter); + String sql = buildSql(conditions, orderByColumn, limit); + + SQLiteDatabase db = getReadableDatabase(); + List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>(); + db.beginTransactionReadOnly(); + try (SQLiteRawStatement statement = db.createRawStatement(sql)) { + int size = conditions.size(); + for (int i = 0; i < size; i++) { + SQLCondition condition = conditions.get(i); + if (DEBUG) { + Slog.i(LOG_TAG, condition + ", binding value = " + condition.mFilterValue); + } + switch (condition.mColumnFilter) { + case PACKAGE_NAME, ATTR_TAG -> statement.bindText(i + 1, + condition.mFilterValue.toString()); + case UID, OP_CODE_EQUAL, OP_FLAGS -> statement.bindInt(i + 1, + Integer.parseInt(condition.mFilterValue.toString())); + case BEGIN_TIME, END_TIME -> statement.bindLong(i + 1, + Long.parseLong(condition.mFilterValue.toString())); + case OP_CODE_IN -> Slog.d(LOG_TAG, "No binding for In operator"); + default -> Slog.w(LOG_TAG, "unknown sql condition " + condition); + } + } + + while (statement.step()) { + int uid = statement.getColumnInt(0); + String packageName = statement.getColumnText(1); + String deviceId = statement.getColumnText(2); + int opCode = statement.getColumnInt(3); + String attributionTag = statement.getColumnText(4); + long accessTime = statement.getColumnLong(5); + long duration = statement.getColumnLong(6); + int uidState = statement.getColumnInt(7); + int opFlags = statement.getColumnInt(8); + int attributionFlags = statement.getColumnInt(9); + long chainId = statement.getColumnLong(10); + DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid, + packageName, attributionTag, deviceId, opCode, + opFlags, attributionFlags, uidState, chainId, accessTime, duration); + results.add(event); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return results; + } + + private String buildSql(List<SQLCondition> conditions, String orderByColumn, int limit) { + StringBuilder sql = new StringBuilder(DiscreteOpsTable.SELECT_TABLE_DATA); + if (!conditions.isEmpty()) { + sql.append(" WHERE "); + int size = conditions.size(); + for (int i = 0; i < size; i++) { + sql.append(conditions.get(i).toString()); + if (i < size - 1) { + sql.append(" AND "); + } + } + } + + if (orderByColumn != null) { + sql.append(" ORDER BY ").append(orderByColumn); + } + if (limit > 0) { + sql.append(" LIMIT ").append(limit); + } + if (DEBUG) { + Slog.i(LOG_TAG, "Sql query " + sql); + } + return sql.toString(); + } + + /** + * Creates where conditions for package, uid, attribution tag and app op codes, + * app op codes condition does not support argument binding. + */ + private List<SQLCondition> prepareConditions(long beginTime, long endTime, int requestFilters, + int uid, @Nullable String packageName, @Nullable String attributionTag, + IntArray opCodes, int opFlags) { + final List<SQLCondition> conditions = new ArrayList<>(); + + if (beginTime != -1) { + conditions.add(new SQLCondition(ColumnFilter.BEGIN_TIME, beginTime)); + } + if (endTime != -1) { + conditions.add(new SQLCondition(ColumnFilter.END_TIME, endTime)); + } + if (opFlags != 0) { + conditions.add(new SQLCondition(ColumnFilter.OP_FLAGS, opFlags)); + } + + if (requestFilters != 0) { + if ((requestFilters & AppOpsManager.FILTER_BY_PACKAGE_NAME) != 0) { + conditions.add(new SQLCondition(ColumnFilter.PACKAGE_NAME, packageName)); + } + if ((requestFilters & AppOpsManager.FILTER_BY_UID) != 0) { + conditions.add(new SQLCondition(ColumnFilter.UID, uid)); + + } + if ((requestFilters & AppOpsManager.FILTER_BY_ATTRIBUTION_TAG) != 0) { + conditions.add(new SQLCondition(ColumnFilter.ATTR_TAG, attributionTag)); + } + // filter op codes + if (opCodes != null && opCodes.size() == 1) { + conditions.add(new SQLCondition(ColumnFilter.OP_CODE_EQUAL, opCodes.get(0))); + } else if (opCodes != null && opCodes.size() > 1) { + StringBuilder b = new StringBuilder(); + int size = opCodes.size(); + for (int i = 0; i < size; i++) { + b.append(opCodes.get(i)); + if (i < size - 1) { + b.append(", "); + } + } + conditions.add(new SQLCondition(ColumnFilter.OP_CODE_IN, b.toString())); + } + } + return conditions; + } + + /** + * This class prepares a where clause condition for discrete ops table column. + */ + static final class SQLCondition { + private final ColumnFilter mColumnFilter; + private final Object mFilterValue; + + SQLCondition(ColumnFilter columnFilter, Object filterValue) { + mColumnFilter = columnFilter; + mFilterValue = filterValue; + } + + @Override + public String toString() { + if (mColumnFilter == ColumnFilter.OP_CODE_IN) { + return mColumnFilter + " ( " + mFilterValue + " )"; + } + return mColumnFilter.toString(); + } + } + + /** + * This enum describes the where clause conditions for different columns in discrete ops + * table. + */ + private enum ColumnFilter { + PACKAGE_NAME(DiscreteOpsTable.Columns.PACKAGE_NAME + " = ? "), + UID(DiscreteOpsTable.Columns.UID + " = ? "), + ATTR_TAG(DiscreteOpsTable.Columns.ATTRIBUTION_TAG + " = ? "), + END_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " < ? "), + OP_CODE_EQUAL(DiscreteOpsTable.Columns.OP_CODE + " = ? "), + BEGIN_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " + " + + DiscreteOpsTable.Columns.ACCESS_DURATION + " > ? "), + OP_FLAGS("(" + DiscreteOpsTable.Columns.OP_FLAGS + " & ? ) != 0"), + OP_CODE_IN(DiscreteOpsTable.Columns.OP_CODE + " IN "); + + final String mCondition; + + ColumnFilter(String condition) { + mCondition = condition; + } + + @Override + public String toString() { + return mCondition; + } + } + + static final class DiscreteOpsDatabaseErrorHandler implements DatabaseErrorHandler { + private final DefaultDatabaseErrorHandler mDefaultDatabaseErrorHandler = + new DefaultDatabaseErrorHandler(); + + @Override + public void onCorruption(SQLiteDatabase dbObj) { + Slog.e(LOG_TAG, "discrete ops database got corrupted."); + mDefaultDatabaseErrorHandler.onCorruption(dbObj); + } + } + + // USED for testing only + List<DiscreteOpsSqlRegistry.DiscreteOp> getAllDiscreteOps(@NonNull String sql) { + SQLiteDatabase db = getReadableDatabase(); + List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>(); + db.beginTransactionReadOnly(); + try (SQLiteRawStatement statement = db.createRawStatement(sql)) { + while (statement.step()) { + int uid = statement.getColumnInt(0); + String packageName = statement.getColumnText(1); + String deviceId = statement.getColumnText(2); + int opCode = statement.getColumnInt(3); + String attributionTag = statement.getColumnText(4); + long accessTime = statement.getColumnLong(5); + long duration = statement.getColumnLong(6); + int uidState = statement.getColumnInt(7); + int opFlags = statement.getColumnInt(8); + int attributionFlags = statement.getColumnInt(9); + long chainId = statement.getColumnLong(10); + DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid, + packageName, attributionTag, deviceId, opCode, + opFlags, attributionFlags, uidState, chainId, accessTime, duration); + results.add(event); + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + return results; + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java new file mode 100644 index 000000000000..c38ee55b4f42 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java @@ -0,0 +1,106 @@ +/* + * 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.server.appop; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class for migrating discrete ops from xml to sqlite + */ +public class DiscreteOpsMigrationHelper { + /** + * migrate discrete ops from xml to sqlite. + */ + static void migrateDiscreteOpsToSqlite(DiscreteOpsXmlRegistry xmlRegistry, + DiscreteOpsSqlRegistry sqlRegistry) { + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps(); + List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = getSqlDiscreteOps(xmlOps); + sqlRegistry.migrateXmlData(discreteOps, xmlOps.mChainIdOffset); + xmlRegistry.deleteDiscreteOpsDir(); + } + + /** + * rollback discrete ops from sqlite to xml. + */ + static void migrateDiscreteOpsToXml(DiscreteOpsSqlRegistry sqlRegistry, + DiscreteOpsXmlRegistry xmlRegistry) { + List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps(); + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps); + xmlRegistry.migrateSqliteData(xmlOps); + sqlRegistry.deleteDatabase(); + } + + /** + * Convert sqlite flat rows to hierarchical data. + */ + private static DiscreteOpsXmlRegistry.DiscreteOps getXmlDiscreteOps( + List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) { + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = + new DiscreteOpsXmlRegistry.DiscreteOps(0); + if (discreteOps.isEmpty()) { + return xmlOps; + } + + for (DiscreteOpsSqlRegistry.DiscreteOp discreteOp : discreteOps) { + xmlOps.addDiscreteAccess(discreteOp.getOpCode(), discreteOp.getUid(), + discreteOp.getPackageName(), discreteOp.getDeviceId(), + discreteOp.getAttributionTag(), discreteOp.getOpFlags(), + discreteOp.getUidState(), + discreteOp.getAccessTime(), discreteOp.getDuration(), + discreteOp.getAttributionFlags(), (int) discreteOp.getChainId()); + } + return xmlOps; + } + + /** + * Convert xml (hierarchical) data to flat row based data. + */ + private static List<DiscreteOpsSqlRegistry.DiscreteOp> getSqlDiscreteOps( + DiscreteOpsXmlRegistry.DiscreteOps discreteOps) { + List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents = new ArrayList<>(); + + if (discreteOps.isEmpty()) { + return opEvents; + } + + discreteOps.mUids.forEach((uid, discreteUidOps) -> { + discreteUidOps.mPackages.forEach((packageName, packageOps) -> { + packageOps.mPackageOps.forEach((opcode, ops) -> { + ops.mDeviceAttributedOps.forEach((deviceId, deviceOps) -> { + deviceOps.mAttributedOps.forEach((tag, attributedOps) -> { + for (DiscreteOpsXmlRegistry.DiscreteOpEvent attributedOp : + attributedOps) { + DiscreteOpsSqlRegistry.DiscreteOp + opModel = new DiscreteOpsSqlRegistry.DiscreteOp(uid, + packageName, tag, + deviceId, opcode, attributedOp.mOpFlag, + attributedOp.mAttributionFlags, + attributedOp.mUidState, attributedOp.mAttributionChainId, + attributedOp.mNoteTime, + attributedOp.mNoteDuration); + opEvents.add(opModel); + } + }); + }); + }); + }); + }); + + return opEvents; + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java new file mode 100644 index 000000000000..88b3f6dce4c2 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java @@ -0,0 +1,298 @@ +/* + * 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.server.appop; + +import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_COARSE_LOCATION; +import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION; +import static android.app.AppOpsManager.OP_FINE_LOCATION; +import static android.app.AppOpsManager.OP_FLAG_SELF; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; +import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; +import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; +import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; +import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS; +import static android.app.AppOpsManager.OP_READ_ICC_SMS; +import static android.app.AppOpsManager.OP_READ_SMS; +import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; +import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; +import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; +import static android.app.AppOpsManager.OP_SEND_SMS; +import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS; +import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; +import static android.app.AppOpsManager.OP_WRITE_ICC_SMS; +import static android.app.AppOpsManager.OP_WRITE_SMS; + +import static java.lang.Long.min; +import static java.lang.Math.max; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.AsyncTask; +import android.os.Build; +import android.permission.flags.Flags; +import android.provider.DeviceConfig; +import android.util.Slog; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FrameworkStatsLog; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.Date; +import java.util.Set; + +/** + * This class provides interface for xml and sqlite implementation. Implementation manages + * information about recent accesses to ops for permission usage timeline. + * <p> + * The discrete history is kept for limited time (initial default is 24 hours, set in + * {@link DiscreteOpsRegistry#sDiscreteHistoryCutoff} and discarded after that. + * <p> + * Discrete history is quantized to reduce resources footprint. By default, quantization is set to + * one minute in {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. All access times are + * aligned to the closest quantized time. All durations (except -1, meaning no duration) are + * rounded up to the closest quantized interval. + * <p> + * When data is queried through API, events are deduplicated and for every time quant there can + * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about + * different accesses which happened in specified time quant - across dimensions of + * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension + * it is only possible to know if at least one access happened in the time quant. + * <p> + * INITIALIZATION: We can initialize persistence only after the system is ready + * as we need to check the optional configuration override from the settings + * database which is not initialized at the time the app ops service is created. This class + * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All + * outside calls are going through {@link HistoricalRegistry}. + * + */ +abstract class DiscreteOpsRegistry { + private static final String TAG = DiscreteOpsRegistry.class.getSimpleName(); + + static final boolean DEBUG_LOG = false; + static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis"; + static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION = + "discrete_history_quantization_millis"; + static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags"; + static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; + static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION + + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + "," + + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + + "," + OP_RESERVED_FOR_TESTING; + static final int[] sDiscreteOpsToLog = + new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA, + OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA, + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS, + OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS, + OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION, + OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS, + }; + + static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); + static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); + // The duration for which the data is kept, default is 7 days and max 30 days enforced. + static long sDiscreteHistoryCutoff; + + static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = Duration.ofMinutes(1).toMillis(); + // discrete ops are rounded up to quantization time, meaning we record one op per time bucket + // in case of duplicate op events. + static long sDiscreteHistoryQuantization; + + static int[] sDiscreteOps; + static int sDiscreteFlags; + + static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED + | OP_FLAG_TRUSTED_PROXY; + + boolean mDebugMode = false; + + static final int ACCESS_TYPE_NOTE_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP; + static final int ACCESS_TYPE_START_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP; + static final int ACCESS_TYPE_FINISH_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP; + static final int ACCESS_TYPE_PAUSE_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP; + static final int ACCESS_TYPE_RESUME_OP = + FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"ACCESS_TYPE_"}, value = { + ACCESS_TYPE_NOTE_OP, + ACCESS_TYPE_START_OP, + ACCESS_TYPE_FINISH_OP, + ACCESS_TYPE_PAUSE_OP, + ACCESS_TYPE_RESUME_OP + }) + @interface AccessType {} + + void systemReady() { + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, + AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> { + setDiscreteHistoryParameters(p); + }); + setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY)); + } + + abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, + int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, + @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, + @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, + @DiscreteOpsRegistry.AccessType int accessType); + + /** + * A periodic callback from {@link AppOpsService} to flush the in memory events to disk. + * The shutdown callback is also plugged into it. + * <p> + * This method flushes in memory records to disk, and also clears old records from disk. + */ + abstract void writeAndClearOldAccessHistory(); + + /** Remove all discrete op events. */ + abstract void clearHistory(); + + /** Remove all discrete op events for given UID and package. */ + abstract void clearHistory(int uid, String packageName); + + /** + * Offset access time by given timestamp, new access time would be accessTime - offsetMillis. + */ + abstract void offsetHistory(long offset); + + abstract void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, + long beginTimeMillis, long endTimeMillis, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, + Set<String> attributionExemptPkgs); + + abstract void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, + int nDiscreteOps); + + void setDebugMode(boolean debugMode) { + this.mDebugMode = debugMode; + } + + static long discretizeTimeStamp(long timeStamp) { + return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + + } + + static long discretizeDuration(long duration) { + return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1) + / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; + } + + static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) { + if (!ArrayUtils.contains(sDiscreteOps, op)) { + return false; + } + if ((flags & (sDiscreteFlags)) == 0) { + return false; + } + return true; + } + + // could this be impl detail of discrete registry, just one test is using the method + // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps(); + + private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { + if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) { + sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF, + DEFAULT_DISCRETE_HISTORY_CUTOFF); + if (!Build.IS_DEBUGGABLE && !mDebugMode) { + sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF, + sDiscreteHistoryCutoff); + } + } else { + sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF; + } + if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) { + sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION, + DEFAULT_DISCRETE_HISTORY_QUANTIZATION); + if (!Build.IS_DEBUGGABLE && !mDebugMode) { + sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION, + sDiscreteHistoryQuantization); + } + } else { + sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION; + } + sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags = + p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE; + sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList( + p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList( + DEFAULT_DISCRETE_OPS); + } + + private static int[] parseOpsList(String opsList) { + String[] strArr; + if (opsList.isEmpty()) { + strArr = new String[0]; + } else { + strArr = opsList.split(","); + } + int nOps = strArr.length; + int[] result = new int[nOps]; + try { + for (int i = 0; i < nOps; i++) { + result[i] = Integer.parseInt(strArr[i]); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage()); + return parseOpsList(DEFAULT_DISCRETE_OPS); + } + return result; + } + + /** + * Whether app op access tacking is enabled and a metric event should be logged. + */ + static boolean shouldLogAccess(int op) { + return Flags.appopAccessTrackingLoggingEnabled() + && ArrayUtils.contains(sDiscreteOpsToLog, op); + } + + String getAttributionTag(String attributionTag, String packageName) { + if (attributionTag == null || packageName == null) { + return attributionTag; + } + int firstChar = 0; + if (attributionTag.startsWith(packageName)) { + firstChar = packageName.length(); + if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) + == '.') { + firstChar++; + } + } + return attributionTag.substring(firstChar); + } + +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java new file mode 100644 index 000000000000..4b3981cd4bc0 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java @@ -0,0 +1,689 @@ +/* + * 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.server.appop; + +import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; +import static android.app.AppOpsManager.flagsToString; +import static android.app.AppOpsManager.getUidStateName; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.util.ArraySet; +import android.util.IntArray; +import android.util.LongSparseArray; +import android.util.Slog; + +import com.android.internal.util.FrameworkStatsLog; +import com.android.server.ServiceThread; + +import java.io.File; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * This class handles sqlite persistence layer for discrete ops. + */ +public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry { + private static final String TAG = "DiscreteOpsSqlRegistry"; + + private final Context mContext; + private final DiscreteOpsDbHelper mDiscreteOpsDbHelper; + private final SqliteWriteHandler mSqliteWriteHandler; + private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512); + private static final long THREE_HOURS = Duration.ofHours(3).toMillis(); + private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1; + private static final int DELETE_OLD_OP_EVENTS = 2; + // Attribution chain id is used to identify an attribution source chain, This is + // set for startOp only. PermissionManagerService resets this ID on device restart, so + // we use previously persisted chain id as offset, and add it to chain id received from + // permission manager service. + private long mChainIdOffset; + private final File mDatabaseFile; + + DiscreteOpsSqlRegistry(Context context) { + this(context, DiscreteOpsDbHelper.getDatabaseFile()); + } + + DiscreteOpsSqlRegistry(Context context, File databaseFile) { + ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true); + thread.start(); + mContext = context; + mDatabaseFile = databaseFile; + mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper()); + mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile); + mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId(); + } + + @Override + void recordDiscreteAccess(int uid, String packageName, + @NonNull String deviceId, int op, + @Nullable String attributionTag, int flags, int uidState, + long accessTime, long accessDuration, int attributionFlags, int attributionChainId, + int accessType) { + if (shouldLogAccess(op)) { + FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, + uidState, flags, attributionFlags, + getAttributionTag(attributionTag, packageName), + attributionChainId); + } + + if (!isDiscreteOp(op, flags)) { + return; + } + + long offsetChainId = attributionChainId; + if (attributionChainId != ATTRIBUTION_CHAIN_ID_NONE) { + offsetChainId = attributionChainId + mChainIdOffset; + // PermissionManagerService chain id reached the max value, + // reset offset, it's going to be very rare. + if (attributionChainId == Integer.MAX_VALUE) { + mChainIdOffset = offsetChainId; + } + } + DiscreteOp discreteOpEvent = new DiscreteOp(uid, packageName, attributionTag, deviceId, op, + flags, attributionFlags, uidState, offsetChainId, accessTime, accessDuration); + mDiscreteOpCache.add(discreteOpEvent); + } + + @Override + void writeAndClearOldAccessHistory() { + // Let the sql impl also follow the same disk write frequencies as xml, + // controlled by AppOpsService. + mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear()); + if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) { + if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) { + Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued"); + } + } + } + + @Override + void clearHistory() { + mDiscreteOpCache.clear(); + mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_TABLE_DATA); + } + + @Override + void clearHistory(int uid, String packageName) { + mDiscreteOpCache.clear(uid, packageName); + mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_DATA_FOR_UID_PACKAGE, + new Object[]{uid, packageName}); + } + + @Override + void offsetHistory(long offset) { + mDiscreteOpCache.offsetTimestamp(offset); + mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.OFFSET_ACCESS_TIME, + new Object[]{offset}); + } + + private IntArray getAppOpCodes(@AppOpsManager.HistoricalOpsRequestFilter int filter, + @Nullable String[] opNamesFilter) { + if ((filter & AppOpsManager.FILTER_BY_OP_NAMES) != 0) { + IntArray opCodes = new IntArray(opNamesFilter.length); + for (int i = 0; i < opNamesFilter.length; i++) { + int op; + try { + op = AppOpsManager.strOpToOp(opNamesFilter[i]); + } catch (IllegalArgumentException ex) { + Slog.w(TAG, "Appop `" + opNamesFilter[i] + "` is not recognized."); + continue; + } + opCodes.add(op); + } + return opCodes; + } + return null; + } + + @Override + void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, + long beginTimeMillis, long endTimeMillis, int filter, int uidFilter, + @Nullable String packageNameFilter, + @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, int opFlagsFilter, + Set<String> attributionExemptPkgs) { + // flush the cache into database before read. + writeAndClearOldAccessHistory(); + boolean assembleChains = attributionExemptPkgs != null; + IntArray opCodes = getAppOpCodes(filter, opNamesFilter); + List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, + packageNameFilter, attributionTagFilter, opCodes, opFlagsFilter, beginTimeMillis, + endTimeMillis, -1, null); + + LongSparseArray<AttributionChain> attributionChains = null; + if (assembleChains) { + attributionChains = createAttributionChains(discreteOps, attributionExemptPkgs); + } + + int nEvents = discreteOps.size(); + for (int j = 0; j < nEvents; j++) { + DiscreteOp event = discreteOps.get(j); + AppOpsManager.OpEventProxyInfo proxy = null; + if (assembleChains && event.mChainId != ATTRIBUTION_CHAIN_ID_NONE) { + AttributionChain chain = attributionChains.get(event.mChainId); + if (chain != null && chain.isComplete() + && chain.isStart(event) + && chain.mLastVisibleEvent != null) { + DiscreteOp proxyEvent = chain.mLastVisibleEvent; + proxy = new AppOpsManager.OpEventProxyInfo(proxyEvent.mUid, + proxyEvent.mPackageName, proxyEvent.mAttributionTag); + } + } + result.addDiscreteAccess(event.mOpCode, event.mUid, event.mPackageName, + event.mAttributionTag, event.mUidState, event.mOpFlags, + event.mDiscretizedAccessTime, event.mDiscretizedDuration, proxy); + } + } + + @Override + void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, + @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, + int nDiscreteOps) { + writeAndClearOldAccessHistory(); + IntArray opCodes = new IntArray(); + if (dumpOp != AppOpsManager.OP_NONE) { + opCodes.add(dumpOp); + } + List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter, + packageNameFilter, attributionTagFilter, opCodes, 0, -1, + -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME); + + pw.print(prefix); + pw.print("Largest chain id: "); + pw.print(mDiscreteOpsDbHelper.getLargestAttributionChainId()); + pw.println(); + pw.println("UID|PACKAGE_NAME|DEVICE_ID|OP_NAME|ATTRIBUTION_TAG|UID_STATE|OP_FLAGS|" + + "ATTR_FLAGS|CHAIN_ID|ACCESS_TIME|DURATION"); + int discreteOpsCount = discreteOps.size(); + for (int i = 0; i < discreteOpsCount; i++) { + DiscreteOp event = discreteOps.get(i); + date.setTime(event.mAccessTime); + pw.println(event.mUid + "|" + event.mPackageName + "|" + event.mDeviceId + "|" + + AppOpsManager.opToName(event.mOpCode) + "|" + event.mAttributionTag + "|" + + getUidStateName(event.mUidState) + "|" + + flagsToString(event.mOpFlags) + "|" + event.mAttributionFlags + "|" + + event.mChainId + "|" + + sdf.format(date) + "|" + event.mDuration); + } + pw.println(); + } + + void migrateXmlData(List<DiscreteOp> opEvents, int chainIdOffset) { + mChainIdOffset = chainIdOffset; + mDiscreteOpsDbHelper.insertDiscreteOps(opEvents); + } + + LongSparseArray<AttributionChain> createAttributionChains( + List<DiscreteOp> discreteOps, Set<String> attributionExemptPkgs) { + LongSparseArray<AttributionChain> chains = new LongSparseArray<>(); + final int count = discreteOps.size(); + + for (int i = 0; i < count; i++) { + DiscreteOp opEvent = discreteOps.get(i); + if (opEvent.mChainId == ATTRIBUTION_CHAIN_ID_NONE + || (opEvent.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) { + continue; + } + AttributionChain chain = chains.get(opEvent.mChainId); + if (chain == null) { + chain = new AttributionChain(attributionExemptPkgs); + chains.put(opEvent.mChainId, chain); + } + chain.addEvent(opEvent); + } + return chains; + } + + static class AttributionChain { + List<DiscreteOp> mChain = new ArrayList<>(); + Set<String> mExemptPkgs; + DiscreteOp mStartEvent = null; + DiscreteOp mLastVisibleEvent = null; + + AttributionChain(Set<String> exemptPkgs) { + mExemptPkgs = exemptPkgs; + } + + boolean isComplete() { + return !mChain.isEmpty() && getStart() != null && isEnd(mChain.get(mChain.size() - 1)); + } + + DiscreteOp getStart() { + return mChain.isEmpty() || !isStart(mChain.get(0)) ? null : mChain.get(0); + } + + private boolean isEnd(DiscreteOp event) { + return event != null + && (event.mAttributionFlags & ATTRIBUTION_FLAG_ACCESSOR) != 0; + } + + private boolean isStart(DiscreteOp event) { + return event != null + && (event.mAttributionFlags & ATTRIBUTION_FLAG_RECEIVER) != 0; + } + + DiscreteOp getLastVisible() { + // Search all nodes but the first one, which is the start node + for (int i = mChain.size() - 1; i > 0; i--) { + DiscreteOp event = mChain.get(i); + if (!mExemptPkgs.contains(event.mPackageName)) { + return event; + } + } + return null; + } + + void addEvent(DiscreteOp opEvent) { + // check if we have a matching event except duration. + DiscreteOp matchingItem = null; + for (int i = 0; i < mChain.size(); i++) { + DiscreteOp item = mChain.get(i); + if (item.equalsExceptDuration(opEvent)) { + matchingItem = item; + break; + } + } + + if (matchingItem != null) { + // exact match or existing event has longer duration + if (matchingItem.mDuration == opEvent.mDuration + || matchingItem.mDuration > opEvent.mDuration) { + return; + } + mChain.remove(matchingItem); + } + + if (mChain.isEmpty() || isEnd(opEvent)) { + mChain.add(opEvent); + } else if (isStart(opEvent)) { + mChain.add(0, opEvent); + } else { + for (int i = 0; i < mChain.size(); i++) { + DiscreteOp currEvent = mChain.get(i); + if ((!isStart(currEvent) + && currEvent.mAccessTime > opEvent.mAccessTime) + || (i == mChain.size() - 1 && isEnd(currEvent))) { + mChain.add(i, opEvent); + break; + } else if (i == mChain.size() - 1) { + mChain.add(opEvent); + break; + } + } + } + mStartEvent = isComplete() ? getStart() : null; + mLastVisibleEvent = isComplete() ? getLastVisible() : null; + } + } + + /** + * Handler to write asynchronously to sqlite database. + */ + class SqliteWriteHandler extends Handler { + SqliteWriteHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case WRITE_CACHE_EVICTED_OP_EVENTS: + List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj; + mDiscreteOpsDbHelper.insertDiscreteOps(opEvents); + break; + case DELETE_OLD_OP_EVENTS: + long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff; + mDiscreteOpsDbHelper.execSQL( + DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME, + new Object[]{cutOffTimeStamp}); + break; + default: + throw new IllegalStateException("Unexpected value: " + msg.what); + } + } + } + + /** + * A write cache for discrete ops. The noteOp, start/finishOp discrete op events are written to + * the cache first. + * <p> + * These events are persisted into sqlite database + * 1) Periodic interval, controlled by {@link AppOpsService} + * 2) When total events in the cache exceeds cache limit. + * 3) During read call we flush the whole cache to sqlite. + * 4) During shutdown. + */ + class DiscreteOpCache { + private final int mCapacity; + private final ArraySet<DiscreteOp> mCache; + + DiscreteOpCache(int capacity) { + mCapacity = capacity; + mCache = new ArraySet<>(); + } + + public void add(DiscreteOp opEvent) { + synchronized (this) { + if (mCache.contains(opEvent)) { + return; + } + mCache.add(opEvent); + if (mCache.size() >= mCapacity) { + if (DEBUG_LOG) { + Slog.i(TAG, "Current discrete ops cache size: " + mCache.size()); + } + List<DiscreteOp> evictedEvents = evict(); + if (DEBUG_LOG) { + Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size()); + } + // if nothing to evict, just write the whole cache to disk + if (evictedEvents.isEmpty()) { + Slog.w(TAG, "No discrete ops event is evicted, write cache to db."); + evictedEvents.addAll(mCache); + mCache.clear(); + } + mSqliteWriteHandler.obtainMessage(WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents); + } + } + } + + /** + * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. + */ + private List<DiscreteOp> evict() { + synchronized (this) { + List<DiscreteOp> evictedEvents = new ArrayList<>(); + Set<DiscreteOp> snapshot = new ArraySet<>(mCache); + long evictionTimestamp = System.currentTimeMillis() - sDiscreteHistoryQuantization; + evictionTimestamp = discretizeTimeStamp(evictionTimestamp); + for (DiscreteOp opEvent : snapshot) { + if (opEvent.mDiscretizedAccessTime <= evictionTimestamp) { + evictedEvents.add(opEvent); + mCache.remove(opEvent); + } + } + return evictedEvents; + } + } + + /** + * Remove all the entries from cache. + * + * @return return all removed entries. + */ + public List<DiscreteOp> getAllEventsAndClear() { + synchronized (this) { + List<DiscreteOp> cachedOps = new ArrayList<>(mCache.size()); + if (mCache.isEmpty()) { + return cachedOps; + } + cachedOps.addAll(mCache); + mCache.clear(); + return cachedOps; + } + } + + /** + * Remove all entries from the cache. + */ + public void clear() { + synchronized (this) { + mCache.clear(); + } + } + + /** + * Offset access time by given offset milliseconds. + */ + public void offsetTimestamp(long offsetMillis) { + synchronized (this) { + List<DiscreteOp> cachedOps = new ArrayList<>(mCache); + mCache.clear(); + for (DiscreteOp discreteOp : cachedOps) { + add(new DiscreteOp(discreteOp.getUid(), discreteOp.mPackageName, + discreteOp.getAttributionTag(), discreteOp.getDeviceId(), + discreteOp.mOpCode, discreteOp.mOpFlags, + discreteOp.getAttributionFlags(), discreteOp.getUidState(), + discreteOp.getChainId(), discreteOp.mAccessTime - offsetMillis, + discreteOp.getDuration()) + ); + } + } + } + + /** Remove cached events for given UID and package. */ + public void clear(int uid, String packageName) { + synchronized (this) { + Set<DiscreteOp> snapshot = new ArraySet<>(mCache); + for (DiscreteOp currentEvent : snapshot) { + if (Objects.equals(packageName, currentEvent.mPackageName) + && uid == currentEvent.getUid()) { + mCache.remove(currentEvent); + } + } + } + } + } + + /** Immutable discrete op object. */ + static class DiscreteOp { + private final int mUid; + private final String mPackageName; + private final String mAttributionTag; + private final String mDeviceId; + private final int mOpCode; + private final int mOpFlags; + private final int mAttributionFlags; + private final int mUidState; + private final long mChainId; + private final long mAccessTime; + private final long mDuration; + // store discretized timestamp to avoid repeated calculations. + private final long mDiscretizedAccessTime; + private final long mDiscretizedDuration; + + DiscreteOp(int uid, String packageName, String attributionTag, String deviceId, + int opCode, + int mOpFlags, int mAttributionFlags, int uidState, long chainId, long accessTime, + long duration) { + this.mUid = uid; + this.mPackageName = packageName.intern(); + this.mAttributionTag = attributionTag; + this.mDeviceId = deviceId; + this.mOpCode = opCode; + this.mOpFlags = mOpFlags; + this.mAttributionFlags = mAttributionFlags; + this.mUidState = uidState; + this.mChainId = chainId; + this.mAccessTime = accessTime; + this.mDiscretizedAccessTime = discretizeTimeStamp(accessTime); + this.mDuration = duration; + this.mDiscretizedDuration = discretizeDuration(duration); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DiscreteOp that)) return false; + + if (mUid != that.mUid) return false; + if (mOpCode != that.mOpCode) return false; + if (mOpFlags != that.mOpFlags) return false; + if (mAttributionFlags != that.mAttributionFlags) return false; + if (mUidState != that.mUidState) return false; + if (mChainId != that.mChainId) return false; + if (!Objects.equals(mPackageName, that.mPackageName)) { + return false; + } + if (!Objects.equals(mAttributionTag, that.mAttributionTag)) { + return false; + } + if (!Objects.equals(mDeviceId, that.mDeviceId)) { + return false; + } + if (mDiscretizedAccessTime != that.mDiscretizedAccessTime) { + return false; + } + return mDiscretizedDuration == that.mDiscretizedDuration; + } + + @Override + public int hashCode() { + int result = mUid; + result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0); + result = 31 * result + (mAttributionTag != null ? mAttributionTag.hashCode() : 0); + result = 31 * result + (mDeviceId != null ? mDeviceId.hashCode() : 0); + result = 31 * result + mOpCode; + result = 31 * result + mOpFlags; + result = 31 * result + mAttributionFlags; + result = 31 * result + mUidState; + result = 31 * result + Objects.hash(mChainId); + result = 31 * result + Objects.hash(mDiscretizedAccessTime); + result = 31 * result + Objects.hash(mDiscretizedDuration); + return result; + } + + public boolean equalsExceptDuration(DiscreteOp that) { + if (mUid != that.mUid) return false; + if (mOpCode != that.mOpCode) return false; + if (mOpFlags != that.mOpFlags) return false; + if (mAttributionFlags != that.mAttributionFlags) return false; + if (mUidState != that.mUidState) return false; + if (mChainId != that.mChainId) return false; + if (!Objects.equals(mPackageName, that.mPackageName)) { + return false; + } + if (!Objects.equals(mAttributionTag, that.mAttributionTag)) { + return false; + } + if (!Objects.equals(mDeviceId, that.mDeviceId)) { + return false; + } + return mAccessTime == that.mAccessTime; + } + + @Override + public String toString() { + return "DiscreteOp{" + + "uid=" + mUid + + ", packageName='" + mPackageName + '\'' + + ", attributionTag='" + mAttributionTag + '\'' + + ", deviceId='" + mDeviceId + '\'' + + ", opCode=" + AppOpsManager.opToName(mOpCode) + + ", opFlag=" + flagsToString(mOpFlags) + + ", attributionFlag=" + mAttributionFlags + + ", uidState=" + getUidStateName(mUidState) + + ", chainId=" + mChainId + + ", accessTime=" + mAccessTime + + ", duration=" + mDuration + '}'; + } + + public int getUid() { + return mUid; + } + + public String getPackageName() { + return mPackageName; + } + + public String getAttributionTag() { + return mAttributionTag; + } + + public String getDeviceId() { + return mDeviceId; + } + + public int getOpCode() { + return mOpCode; + } + + @AppOpsManager.OpFlags + public int getOpFlags() { + return mOpFlags; + } + + + @AppOpsManager.AttributionFlags + public int getAttributionFlags() { + return mAttributionFlags; + } + + @AppOpsManager.UidState + public int getUidState() { + return mUidState; + } + + public long getChainId() { + return mChainId; + } + + public long getAccessTime() { + return mAccessTime; + } + + public long getDuration() { + return mDuration; + } + } + + // API for tests only, can be removed or changed. + void recordDiscreteAccess(DiscreteOp discreteOpEvent) { + mDiscreteOpCache.add(discreteOpEvent); + } + + // API for tests only, can be removed or changed. + List<DiscreteOp> getCachedDiscreteOps() { + return new ArrayList<>(mDiscreteOpCache.mCache); + } + + // API for tests only, can be removed or changed. + List<DiscreteOp> getAllDiscreteOps() { + List<DiscreteOp> ops = new ArrayList<>(mDiscreteOpCache.mCache); + ops.addAll(mDiscreteOpsDbHelper.getAllDiscreteOps(DiscreteOpsTable.SELECT_TABLE_DATA)); + return ops; + } + + // API for testing and migration + long getLargestAttributionChainId() { + return mDiscreteOpsDbHelper.getLargestAttributionChainId(); + } + + // API for testing and migration + void deleteDatabase() { + mDiscreteOpsDbHelper.close(); + mContext.deleteDatabase(mDatabaseFile.getName()); + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTable.java b/services/core/java/com/android/server/appop/DiscreteOpsTable.java new file mode 100644 index 000000000000..9cb19aa30a15 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsTable.java @@ -0,0 +1,128 @@ +/* + * 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.server.appop; + + +/** + * SQLite table for storing app op accesses. + */ +final class DiscreteOpsTable { + private static final String TABLE_NAME = "app_op_accesses"; + private static final String INDEX_APP_OP = "app_op_access_index"; + + static final class Columns { + /** Auto increment primary key. */ + static final String ID = "id"; + /** UID of the package accessing private data. */ + static final String UID = "uid"; + /** Package accessing private data. */ + static final String PACKAGE_NAME = "package_name"; + /** The device from which the private data is accessed. */ + static final String DEVICE_ID = "device_id"; + /** Op code representing private data i.e. location, mic etc. */ + static final String OP_CODE = "op_code"; + /** Attribution tag provided when accessing the private data. */ + static final String ATTRIBUTION_TAG = "attribution_tag"; + /** Timestamp when private data is accessed, number of milliseconds that have passed + * since Unix epoch */ + static final String ACCESS_TIME = "access_time"; + /** For how long the private data is accessed. */ + static final String ACCESS_DURATION = "access_duration"; + /** App process state, whether the app is in foreground, background or cached etc. */ + static final String UID_STATE = "uid_state"; + /** App op flags */ + static final String OP_FLAGS = "op_flags"; + /** Attribution flags */ + static final String ATTRIBUTION_FLAGS = "attribution_flags"; + /** Chain id */ + static final String CHAIN_ID = "chain_id"; + } + + static final int UID_INDEX = 1; + static final int PACKAGE_NAME_INDEX = 2; + static final int DEVICE_ID_INDEX = 3; + static final int OP_CODE_INDEX = 4; + static final int ATTRIBUTION_TAG_INDEX = 5; + static final int ACCESS_TIME_INDEX = 6; + static final int ACCESS_DURATION_INDEX = 7; + static final int UID_STATE_INDEX = 8; + static final int OP_FLAGS_INDEX = 9; + static final int ATTRIBUTION_FLAGS_INDEX = 10; + static final int CHAIN_ID_INDEX = 11; + + static final String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS " + + TABLE_NAME + "(" + + Columns.ID + " INTEGER PRIMARY KEY," + + Columns.UID + " INTEGER," + + Columns.PACKAGE_NAME + " TEXT," + + Columns.DEVICE_ID + " TEXT NOT NULL," + + Columns.OP_CODE + " INTEGER," + + Columns.ATTRIBUTION_TAG + " TEXT," + + Columns.ACCESS_TIME + " INTEGER," + + Columns.ACCESS_DURATION + " INTEGER," + + Columns.UID_STATE + " INTEGER," + + Columns.OP_FLAGS + " INTEGER," + + Columns.ATTRIBUTION_FLAGS + " INTEGER," + + Columns.CHAIN_ID + " INTEGER" + + ")"; + + static final String INSERT_TABLE_SQL = "INSERT INTO " + TABLE_NAME + "(" + + Columns.UID + ", " + + Columns.PACKAGE_NAME + ", " + + Columns.DEVICE_ID + ", " + + Columns.OP_CODE + ", " + + Columns.ATTRIBUTION_TAG + ", " + + Columns.ACCESS_TIME + ", " + + Columns.ACCESS_DURATION + ", " + + Columns.UID_STATE + ", " + + Columns.OP_FLAGS + ", " + + Columns.ATTRIBUTION_FLAGS + ", " + + Columns.CHAIN_ID + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; + + static final String SELECT_MAX_ATTRIBUTION_CHAIN_ID = "SELECT MAX(" + Columns.CHAIN_ID + ")" + + " FROM " + TABLE_NAME; + + static final String SELECT_TABLE_DATA = "SELECT DISTINCT " + + Columns.UID + "," + + Columns.PACKAGE_NAME + "," + + Columns.DEVICE_ID + "," + + Columns.OP_CODE + "," + + Columns.ATTRIBUTION_TAG + "," + + Columns.ACCESS_TIME + "," + + Columns.ACCESS_DURATION + "," + + Columns.UID_STATE + "," + + Columns.OP_FLAGS + "," + + Columns.ATTRIBUTION_FLAGS + "," + + Columns.CHAIN_ID + + " FROM " + TABLE_NAME; + + static final String DELETE_TABLE_DATA = "DELETE FROM " + TABLE_NAME; + + static final String DELETE_TABLE_DATA_BEFORE_ACCESS_TIME = "DELETE FROM " + TABLE_NAME + + " WHERE " + Columns.ACCESS_TIME + " < ?"; + + static final String DELETE_DATA_FOR_UID_PACKAGE = "DELETE FROM " + DiscreteOpsTable.TABLE_NAME + + " WHERE " + Columns.UID + " = ? AND " + Columns.PACKAGE_NAME + " = ?"; + + static final String OFFSET_ACCESS_TIME = "UPDATE " + DiscreteOpsTable.TABLE_NAME + + " SET " + Columns.ACCESS_TIME + " = ACCESS_TIME - ?"; + + // Index on access time, uid and op code + static final String CREATE_INDEX_SQL = "CREATE INDEX IF NOT EXISTS " + + INDEX_APP_OP + " ON " + TABLE_NAME + + " (" + Columns.ACCESS_TIME + ", " + Columns.UID + ", " + Columns.OP_CODE + ")"; +} diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java new file mode 100644 index 000000000000..1523cca86607 --- /dev/null +++ b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java @@ -0,0 +1,220 @@ +/* + * 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.server.appop; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AppOpsManager; +import android.os.SystemClock; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Objects; +import java.util.Set; + +/** + * A testing class, which supports both xml and sqlite persistence for discrete ops, the class + * logs warning if there is a mismatch in the behavior. + */ +class DiscreteOpsTestingShim extends DiscreteOpsRegistry { + private static final String LOG_TAG = "DiscreteOpsTestingShim"; + private final DiscreteOpsRegistry mXmlRegistry; + private final DiscreteOpsRegistry mSqlRegistry; + + DiscreteOpsTestingShim(DiscreteOpsRegistry xmlRegistry, + DiscreteOpsRegistry sqlRegistry) { + mXmlRegistry = xmlRegistry; + mSqlRegistry = sqlRegistry; + } + + @Override + void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, + @Nullable String attributionTag, int flags, int uidState, long accessTime, + long accessDuration, int attributionFlags, int attributionChainId, int accessType) { + long start = SystemClock.uptimeMillis(); + mXmlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, + uidState, accessTime, accessDuration, attributionFlags, attributionChainId, + accessType); + long start2 = SystemClock.uptimeMillis(); + mSqlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags, + uidState, accessTime, accessDuration, attributionFlags, attributionChainId, + accessType); + long end = SystemClock.uptimeMillis(); + long xmlTimeTaken = start2 - start; + long sqlTimeTaken = end - start2; + Log.i(LOG_TAG, + "recordDiscreteAccess: XML time taken : " + xmlTimeTaken + ", SQL time taken : " + + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken)); + } + + + @Override + void writeAndClearOldAccessHistory() { + mXmlRegistry.writeAndClearOldAccessHistory(); + mSqlRegistry.writeAndClearOldAccessHistory(); + } + + @Override + void clearHistory() { + mXmlRegistry.clearHistory(); + mSqlRegistry.clearHistory(); + } + + @Override + void clearHistory(int uid, String packageName) { + mXmlRegistry.clearHistory(uid, packageName); + mSqlRegistry.clearHistory(uid, packageName); + } + + @Override + void offsetHistory(long offset) { + mXmlRegistry.offsetHistory(offset); + mSqlRegistry.offsetHistory(offset); + } + + @Override + void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, + long beginTimeMillis, long endTimeMillis, int filter, int uidFilter, + @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, + @Nullable String attributionTagFilter, int flagsFilter, + Set<String> attributionExemptPkgs) { + AppOpsManager.HistoricalOps result2 = + new AppOpsManager.HistoricalOps(beginTimeMillis, endTimeMillis); + + long start = System.currentTimeMillis(); + mXmlRegistry.addFilteredDiscreteOpsToHistoricalOps(result2, beginTimeMillis, endTimeMillis, + filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter, + flagsFilter, attributionExemptPkgs); + long start2 = System.currentTimeMillis(); + mSqlRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis, endTimeMillis, + filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter, + flagsFilter, attributionExemptPkgs); + long end = System.currentTimeMillis(); + long xmlTimeTaken = start2 - start; + long sqlTimeTaken = end - start2; + try { + assertHistoricalOpsAreEquals(result, result2); + } catch (Exception ex) { + Slog.e(LOG_TAG, "different output when reading discrete ops", ex); + } + Log.i(LOG_TAG, "Read: XML time taken : " + xmlTimeTaken + ", SQL time taken : " + + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken)); + } + + void assertHistoricalOpsAreEquals(AppOpsManager.HistoricalOps sqlResult, + AppOpsManager.HistoricalOps xmlResult) { + assertEquals(sqlResult.getUidCount(), xmlResult.getUidCount()); + int uidCount = sqlResult.getUidCount(); + + for (int i = 0; i < uidCount; i++) { + AppOpsManager.HistoricalUidOps sqlUidOps = sqlResult.getUidOpsAt(i); + AppOpsManager.HistoricalUidOps xmlUidOps = xmlResult.getUidOpsAt(i); + Slog.i(LOG_TAG, "sql uid: " + sqlUidOps.getUid() + ", xml uid: " + xmlUidOps.getUid()); + assertEquals(sqlUidOps.getUid(), xmlUidOps.getUid()); + assertEquals(sqlUidOps.getPackageCount(), xmlUidOps.getPackageCount()); + + int packageCount = sqlUidOps.getPackageCount(); + for (int p = 0; p < packageCount; p++) { + AppOpsManager.HistoricalPackageOps sqlPackageOps = sqlUidOps.getPackageOpsAt(p); + AppOpsManager.HistoricalPackageOps xmlPackageOps = xmlUidOps.getPackageOpsAt(p); + Slog.i(LOG_TAG, "sql package: " + sqlPackageOps.getPackageName() + ", xml package: " + + xmlPackageOps.getPackageName()); + assertEquals(sqlPackageOps.getPackageName(), xmlPackageOps.getPackageName()); + assertEquals(sqlPackageOps.getAttributedOpsCount(), + xmlPackageOps.getAttributedOpsCount()); + + int attrCount = sqlPackageOps.getAttributedOpsCount(); + for (int a = 0; a < attrCount; a++) { + AppOpsManager.AttributedHistoricalOps sqlAttrOps = + sqlPackageOps.getAttributedOpsAt(a); + AppOpsManager.AttributedHistoricalOps xmlAttrOps = + xmlPackageOps.getAttributedOpsAt(a); + Slog.i(LOG_TAG, "sql tag: " + sqlAttrOps.getTag() + ", xml tag: " + + xmlAttrOps.getTag()); + assertEquals(sqlAttrOps.getTag(), xmlAttrOps.getTag()); + assertEquals(sqlAttrOps.getOpCount(), xmlAttrOps.getOpCount()); + + int opCount = sqlAttrOps.getOpCount(); + for (int o = 0; o < opCount; o++) { + AppOpsManager.HistoricalOp sqlHistoricalOp = sqlAttrOps.getOpAt(o); + AppOpsManager.HistoricalOp xmlHistoricalOp = xmlAttrOps.getOpAt(o); + Slog.i(LOG_TAG, "sql op: " + sqlHistoricalOp.getOpName() + ", xml op: " + + xmlHistoricalOp.getOpName()); + assertEquals(sqlHistoricalOp.getOpName(), xmlHistoricalOp.getOpName()); + assertEquals(sqlHistoricalOp.getDiscreteAccessCount(), + xmlHistoricalOp.getDiscreteAccessCount()); + + int accessCount = sqlHistoricalOp.getDiscreteAccessCount(); + for (int x = 0; x < accessCount; x++) { + AppOpsManager.AttributedOpEntry sqlOpEntry = + sqlHistoricalOp.getDiscreteAccessAt(x); + AppOpsManager.AttributedOpEntry xmlOpEntry = + xmlHistoricalOp.getDiscreteAccessAt(x); + Slog.i(LOG_TAG, "sql keys: " + sqlOpEntry.collectKeys() + ", xml keys: " + + xmlOpEntry.collectKeys()); + assertEquals(sqlOpEntry.collectKeys(), xmlOpEntry.collectKeys()); + assertEquals(sqlOpEntry.isRunning(), xmlOpEntry.isRunning()); + ArraySet<Long> keys = sqlOpEntry.collectKeys(); + final int keyCount = keys.size(); + for (int k = 0; k < keyCount; k++) { + final long key = keys.valueAt(k); + final int flags = extractFlagsFromKey(key); + assertEquals(sqlOpEntry.getLastDuration(flags), + xmlOpEntry.getLastDuration(flags)); + assertEquals(sqlOpEntry.getLastProxyInfo(flags), + xmlOpEntry.getLastProxyInfo(flags)); + assertEquals(sqlOpEntry.getLastAccessTime(flags), + xmlOpEntry.getLastAccessTime(flags)); + } + } + } + } + } + } + } + + // code duplicated for assertions + private static final int FLAGS_MASK = 0xFFFFFFFF; + + public static int extractFlagsFromKey(@AppOpsManager.DataBucketKey long key) { + return (int) (key & FLAGS_MASK); + } + + private void assertEquals(Object actual, Object expected) { + if (!Objects.equals(actual, expected)) { + throw new IllegalStateException("Actual (" + actual + ") is not equal to expected (" + + expected + ")"); + } + } + + @Override + void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, + @Nullable String attributionTagFilter, int filter, int dumpOp, + @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, + int nDiscreteOps) { + mXmlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp, + sdf, date, prefix, nDiscreteOps); + pw.println("--------------------------------------------------------"); + pw.println("--------------------------------------------------------"); + mSqlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp, + sdf, date, prefix, nDiscreteOps); + } +} diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java index 7f161f618618..a6e3fc7cc66a 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java @@ -24,48 +24,20 @@ import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG; import static android.app.AppOpsManager.FILTER_BY_OP_NAMES; import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME; import static android.app.AppOpsManager.FILTER_BY_UID; -import static android.app.AppOpsManager.OP_CAMERA; -import static android.app.AppOpsManager.OP_COARSE_LOCATION; -import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION; -import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_FLAGS_ALL; -import static android.app.AppOpsManager.OP_FLAG_SELF; -import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; -import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; -import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; -import static android.app.AppOpsManager.OP_MONITOR_LOCATION; import static android.app.AppOpsManager.OP_NONE; -import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; -import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; -import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS; -import static android.app.AppOpsManager.OP_READ_ICC_SMS; -import static android.app.AppOpsManager.OP_READ_SMS; -import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; -import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; -import static android.app.AppOpsManager.OP_RECORD_AUDIO; -import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; -import static android.app.AppOpsManager.OP_SEND_SMS; -import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS; -import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; -import static android.app.AppOpsManager.OP_WRITE_ICC_SMS; -import static android.app.AppOpsManager.OP_WRITE_SMS; import static android.app.AppOpsManager.flagsToString; import static android.app.AppOpsManager.getUidStateName; import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; -import static java.lang.Long.min; import static java.lang.Math.max; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; -import android.os.AsyncTask; -import android.os.Build; import android.os.Environment; import android.os.FileUtils; import android.permission.flags.Flags; -import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; @@ -84,10 +56,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.SimpleDateFormat; -import java.time.Duration; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -99,100 +68,30 @@ import java.util.Objects; import java.util.Set; /** - * This class manages information about recent accesses to ops for permission usage timeline. - * - * The discrete history is kept for limited time (initial default is 24 hours, set in - * {@link DiscreteRegistry#sDiscreteHistoryCutoff) and discarded after that. - * - * Discrete history is quantized to reduce resources footprint. By default quantization is set to - * one minute in {@link DiscreteRegistry#sDiscreteHistoryQuantization}. All access times are aligned - * to the closest quantized time. All durations (except -1, meaning no duration) are rounded up to - * the closest quantized interval. - * - * When data is queried through API, events are deduplicated and for every time quant there can - * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about - * different accesses which happened in specified time quant - across dimensions of - * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension - * it is only possible to know if at least one access happened in the time quant. + * Xml persistence implementation for discrete ops. * + * <p> * Every time state is saved (default is 30 minutes), memory state is dumped to a * new file and memory state is cleared. Files older than time limit are deleted * during the process. - * + * <p> * When request comes in, files are read and requested information is collected * and delivered. Information is cached in memory until the next state save (up to 30 minutes), to * avoid reading disk if more API calls come in a quick succession. - * + * <p> * THREADING AND LOCKING: - * For in-memory transactions this class relies on {@link DiscreteRegistry#mInMemoryLock}. It is - * assumed that the same lock is used for in-memory transactions in {@link AppOpsService}, - * {@link HistoricalRegistry}, and {@link DiscreteRegistry}. - * {@link DiscreteRegistry#recordDiscreteAccess(int, String, int, String, int, int, long, long)} - * must only be called while holding this lock. - * {@link DiscreteRegistry#mOnDiskLock} is used when disk transactions are performed. - * It is very important to release {@link DiscreteRegistry#mInMemoryLock} as soon as possible, as - * no AppOps related transactions across the system can be performed while it is held. + * For in-memory transactions this class relies on {@link DiscreteOpsXmlRegistry#mInMemoryLock}. + * It is assumed that the same lock is used for in-memory transactions in {@link AppOpsService}, + * {@link HistoricalRegistry}, and {@link DiscreteOpsXmlRegistry }. + * {@link DiscreteOpsRegistry#recordDiscreteAccess} must only be called while holding this lock. + * {@link DiscreteOpsXmlRegistry#mOnDiskLock} is used when disk transactions are performed. + * It is very important to release {@link DiscreteOpsXmlRegistry#mInMemoryLock} as soon as + * possible, as no AppOps related transactions across the system can be performed while it is held. * - * INITIALIZATION: We can initialize persistence only after the system is ready - * as we need to check the optional configuration override from the settings - * database which is not initialized at the time the app ops service is created. This class - * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All - * outside calls are going through {@link HistoricalRegistry}, where - * {@link HistoricalRegistry#isPersistenceInitializedMLocked()} check is done. */ - -final class DiscreteRegistry { +class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry { static final String DISCRETE_HISTORY_FILE_SUFFIX = "tl"; - private static final String TAG = DiscreteRegistry.class.getSimpleName(); - - private static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis"; - private static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION = - "discrete_history_quantization_millis"; - private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags"; - private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; - private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION - + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," - + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + "," - + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO - + "," + OP_RESERVED_FOR_TESTING; - private static final int[] sDiscreteOpsToLog = - new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA, - OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA, - OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS, - OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS, - OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION, - OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS, - }; - private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); - private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); - private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = - Duration.ofMinutes(1).toMillis(); - - static final int ACCESS_TYPE_NOTE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP; - static final int ACCESS_TYPE_START_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP; - static final int ACCESS_TYPE_FINISH_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP; - static final int ACCESS_TYPE_PAUSE_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP; - static final int ACCESS_TYPE_RESUME_OP = - FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP; - - @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = {"ACCESS_TYPE_"}, value = { - ACCESS_TYPE_NOTE_OP, - ACCESS_TYPE_START_OP, - ACCESS_TYPE_FINISH_OP, - ACCESS_TYPE_PAUSE_OP, - ACCESS_TYPE_RESUME_OP - }) - public @interface AccessType {} - - private static long sDiscreteHistoryCutoff; - private static long sDiscreteHistoryQuantization; - private static int[] sDiscreteOps; - private static int sDiscreteFlags; + private static final String TAG = DiscreteOpsXmlRegistry.class.getSimpleName(); private static final String TAG_HISTORY = "h"; private static final String ATTR_VERSION = "v"; @@ -221,9 +120,6 @@ final class DiscreteRegistry { private static final String ATTR_ATTRIBUTION_FLAGS = "af"; private static final String ATTR_CHAIN_ID = "ci"; - private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED - | OP_FLAG_TRUSTED_PROXY; - // Lock for read/write access to on disk state private final Object mOnDiskLock = new Object(); @@ -239,14 +135,12 @@ final class DiscreteRegistry { @GuardedBy("mOnDiskLock") private DiscreteOps mCachedOps = null; - private boolean mDebugMode = false; - - DiscreteRegistry(Object inMemoryLock) { - this(inMemoryLock, new File(new File(Environment.getDataSystemDirectory(), "appops"), - "discrete")); + DiscreteOpsXmlRegistry(Object inMemoryLock) { + this(inMemoryLock, getDiscreteOpsDir()); } - DiscreteRegistry(Object inMemoryLock, File discreteAccessDir) { + // constructor for tests. + DiscreteOpsXmlRegistry(Object inMemoryLock, File discreteAccessDir) { mInMemoryLock = inMemoryLock; synchronized (mOnDiskLock) { mDiscreteAccessDir = discreteAccessDir; @@ -258,40 +152,8 @@ final class DiscreteRegistry { } } - void systemReady() { - DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, - AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> { - setDiscreteHistoryParameters(p); - }); - setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY)); - } - - private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { - if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) { - sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF, - DEFAULT_DISCRETE_HISTORY_CUTOFF); - if (!Build.IS_DEBUGGABLE && !mDebugMode) { - sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF, - sDiscreteHistoryCutoff); - } - } else { - sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF; - } - if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) { - sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION, - DEFAULT_DISCRETE_HISTORY_QUANTIZATION); - if (!Build.IS_DEBUGGABLE && !mDebugMode) { - sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION, - sDiscreteHistoryQuantization); - } - } else { - sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION; - } - sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags = - p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE; - sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList( - p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList( - DEFAULT_DISCRETE_OPS); + static File getDiscreteOpsDir() { + return new File(new File(Environment.getDataSystemDirectory(), "appops"), "discrete"); } void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @@ -300,17 +162,9 @@ final class DiscreteRegistry { @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, @AccessType int accessType) { if (shouldLogAccess(op)) { - int firstChar = 0; - if (attributionTag != null && attributionTag.startsWith(packageName)) { - firstChar = packageName.length(); - if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar) - == '.') { - firstChar++; - } - } FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType, uidState, flags, attributionFlags, - attributionTag == null ? null : attributionTag.substring(firstChar), + getAttributionTag(attributionTag, packageName), attributionChainId); } @@ -331,7 +185,7 @@ final class DiscreteRegistry { } } - void writeAndClearAccessHistory() { + void writeAndClearOldAccessHistory() { synchronized (mOnDiskLock) { if (mDiscreteAccessDir == null) { Slog.d(TAG, "State not saved - persistence not initialized."); @@ -350,6 +204,22 @@ final class DiscreteRegistry { } } + void migrateSqliteData(DiscreteOps sqliteOps) { + synchronized (mOnDiskLock) { + if (mDiscreteAccessDir == null) { + Slog.d(TAG, "State not saved - persistence not initialized."); + return; + } + synchronized (mInMemoryLock) { + mDiscreteOps.mLargestChainId = sqliteOps.mLargestChainId; + mDiscreteOps.mChainIdOffset = sqliteOps.mChainIdOffset; + } + if (!sqliteOps.isEmpty()) { + persistDiscreteOpsLocked(sqliteOps); + } + } + } + void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, @@ -369,7 +239,7 @@ final class DiscreteRegistry { discreteOps.applyToHistoricalOps(result, attributionChains); } - private int readLargestChainIdFromDiskLocked() { + int readLargestChainIdFromDiskLocked() { final File[] files = mDiscreteAccessDir.listFiles(); if (files != null && files.length > 0) { File latestFile = null; @@ -497,6 +367,13 @@ final class DiscreteRegistry { } } + void deleteDiscreteOpsDir() { + synchronized (mOnDiskLock) { + mCachedOps = null; + FileUtils.deleteContentsAndDir(mDiscreteAccessDir); + } + } + void clearHistory(int uid, String packageName) { synchronized (mOnDiskLock) { DiscreteOps discreteOps; @@ -1506,26 +1383,6 @@ final class DiscreteRegistry { } } - private static int[] parseOpsList(String opsList) { - String[] strArr; - if (opsList.isEmpty()) { - strArr = new String[0]; - } else { - strArr = opsList.split(","); - } - int nOps = strArr.length; - int[] result = new int[nOps]; - try { - for (int i = 0; i < nOps; i++) { - result[i] = Integer.parseInt(strArr[i]); - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage()); - return parseOpsList(DEFAULT_DISCRETE_OPS); - } - return result; - } - private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a, List<DiscreteOpEvent> b) { int nA = a.size(); @@ -1570,34 +1427,4 @@ final class DiscreteRegistry { } return result; } - - private static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) { - if (!ArrayUtils.contains(sDiscreteOps, op)) { - return false; - } - if ((flags & (sDiscreteFlags)) == 0) { - return false; - } - return true; - } - - private static boolean shouldLogAccess(int op) { - return Flags.appopAccessTrackingLoggingEnabled() - && ArrayUtils.contains(sDiscreteOpsToLog, op); - } - - private static long discretizeTimeStamp(long timeStamp) { - return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; - - } - - private static long discretizeDuration(long duration) { - return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1) - / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; - } - - void setDebugMode(boolean debugMode) { - this.mDebugMode = debugMode; - } } - diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java index 5e67f26ba1f6..ba391d0a9995 100644 --- a/services/core/java/com/android/server/appop/HistoricalRegistry.java +++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java @@ -35,6 +35,7 @@ import android.app.AppOpsManager.OpFlags; import android.app.AppOpsManager.OpHistoryFlags; import android.app.AppOpsManager.UidState; import android.content.ContentResolver; +import android.content.Context; import android.database.ContentObserver; import android.net.Uri; import android.os.Build; @@ -45,6 +46,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteCallback; import android.os.UserHandle; +import android.permission.flags.Flags; import android.provider.Settings; import android.util.ArraySet; import android.util.LongSparseArray; @@ -135,7 +137,7 @@ final class HistoricalRegistry { private static final String PARAMETER_DELIMITER = ","; private static final String PARAMETER_ASSIGNMENT = "="; - private volatile @NonNull DiscreteRegistry mDiscreteRegistry; + private volatile @NonNull DiscreteOpsRegistry mDiscreteRegistry; @GuardedBy("mLock") private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>(); @@ -196,13 +198,30 @@ final class HistoricalRegistry { @GuardedBy("mOnDiskLock") private Persistence mPersistence; - HistoricalRegistry(@NonNull Object lock) { + private final Context mContext; + + HistoricalRegistry(@NonNull Object lock, Context context) { mInMemoryLock = lock; - mDiscreteRegistry = new DiscreteRegistry(lock); + mContext = context; + if (Flags.enableSqliteAppopsAccesses()) { + mDiscreteRegistry = new DiscreteOpsSqlRegistry(context); + if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) { + DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry; + DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context); + DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry); + } + } else { + mDiscreteRegistry = new DiscreteOpsXmlRegistry(context); + if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite + DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context); + DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry; + DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry); + } + } } HistoricalRegistry(@NonNull HistoricalRegistry other) { - this(other.mInMemoryLock); + this(other.mInMemoryLock, other.mContext); mMode = other.mMode; mBaseSnapshotInterval = other.mBaseSnapshotInterval; mIntervalCompressionMultiplier = other.mIntervalCompressionMultiplier; @@ -475,7 +494,7 @@ final class HistoricalRegistry { @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long accessTime, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteRegistry.AccessType int accessType, int accessCount) { + @DiscreteOpsRegistry.AccessType int accessType, int accessCount) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -512,7 +531,7 @@ final class HistoricalRegistry { @NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState, @OpFlags int flags, long eventStartTime, long increment, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId, - @DiscreteRegistry.AccessType int accessType) { + @DiscreteOpsRegistry.AccessType int accessType) { synchronized (mInMemoryLock) { if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) { if (!isPersistenceInitializedMLocked()) { @@ -648,7 +667,7 @@ final class HistoricalRegistry { } void writeAndClearDiscreteHistory() { - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); } void clearAllHistory() { @@ -743,7 +762,7 @@ final class HistoricalRegistry { } persistPendingHistory(pendingWrites); } - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); } private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) { diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index 5d9db65fe2b2..d89db8d5581b 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -312,6 +312,13 @@ public final class DeviceStateManagerService extends SystemService { mProcessObserver); } + @Override + public void onBootPhase(int phase) { + if (phase == PHASE_SYSTEM_SERVICES_READY) { + mDeviceStatePolicy.getDeviceStateProvider().onSystemReady(); + } + } + @VisibleForTesting Handler getHandler() { return mHandler; diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java index 8d07609cef30..8a8ebc2ffc21 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java @@ -91,6 +91,11 @@ public interface DeviceStateProvider extends Dumpable { @interface SupportedStatesUpdatedReason {} /** + * Called when the system boot phase advances to PHASE_SYSTEM_SERVICES_READY. + */ + default void onSystemReady() {}; + + /** * Registers a listener for changes in provider state. * <p> * It is <b>required</b> that diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index febf24edc294..e25ea4b43827 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -74,6 +74,8 @@ class InputSettingsObserver extends ContentObserver { Map.entry(Settings.System.getUriFor( Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED), (reason) -> updateMouseAccelerationEnabled()), + Map.entry(Settings.System.getUriFor(Settings.System.MOUSE_SCROLLING_SPEED), + (reason) -> updateMouseScrollingSpeed()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED), (reason) -> updateTouchpadPointerSpeed()), Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING), @@ -199,6 +201,11 @@ class InputSettingsObserver extends ContentObserver { InputSettings.isMousePointerAccelerationEnabled(mContext)); } + private void updateMouseScrollingSpeed() { + mNative.setMouseScrollingSpeed( + constrainPointerSpeedValue(InputSettings.getMouseScrollingSpeed(mContext))); + } + private void updateTouchpadPointerSpeed() { mNative.setTouchpadPointerSpeed( constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext))); diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 7dbde64a6412..d426e8292f14 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -136,6 +136,8 @@ interface NativeInputManagerService { void setMouseScrollingAccelerationEnabled(boolean enabled); + void setMouseScrollingSpeed(int speed); + void setMouseSwapPrimaryButtonEnabled(boolean enabled); void setMouseAccelerationEnabled(boolean enabled); @@ -428,6 +430,9 @@ interface NativeInputManagerService { public native void setMouseScrollingAccelerationEnabled(boolean enabled); @Override + public native void setMouseScrollingSpeed(int speed); + + @Override public native void setMouseSwapPrimaryButtonEnabled(boolean enabled); @Override diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index 34bb4155c943..3babc0ae065c 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -18,6 +18,7 @@ package com.android.server.media.quality; import android.content.ContentValues; import android.content.Context; +import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.media.quality.AmbientBacklightSettings; @@ -35,8 +36,13 @@ import android.media.quality.SoundProfileHandle; import android.os.Binder; import android.os.Bundle; import android.os.PersistableBundle; +import android.os.RemoteCallbackList; +import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; +import android.util.Pair; +import android.util.Slog; +import android.util.SparseArray; import com.android.server.SystemService; @@ -45,9 +51,11 @@ import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @@ -64,10 +72,13 @@ public class MediaQualityService extends SystemService { private final MediaQualityDbHelper mMediaQualityDbHelper; private final BiMap<Long, String> mPictureProfileTempIdMap; private final BiMap<Long, String> mSoundProfileTempIdMap; + private final PackageManager mPackageManager; + private final SparseArray<UserState> mUserStates = new SparseArray<>(); public MediaQualityService(Context context) { super(context); mContext = context; + mPackageManager = mContext.getPackageManager(); mPictureProfileTempIdMap = new BiMap<>(); mSoundProfileTempIdMap = new BiMap<>(); mMediaQualityDbHelper = new MediaQualityDbHelper(mContext); @@ -85,12 +96,20 @@ public class MediaQualityService extends SystemService { @Override public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) { + if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty() + && !incomingPackageEqualsCallingUidPackage(pp.getPackageName())) + && !hasGlobalPictureQualityServicePermission()) { + notifyError(null, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } + SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = getContentValues(null, pp.getProfileType(), pp.getName(), - pp.getPackageName(), + pp.getPackageName() == null || pp.getPackageName().isEmpty() + ? getPackageOfCallingUid() : pp.getPackageName(), pp.getInputId(), pp.getParameters()); @@ -104,9 +123,13 @@ public class MediaQualityService extends SystemService { @Override public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) { - Long intId = mPictureProfileTempIdMap.getKey(id); + Long dbId = mPictureProfileTempIdMap.getKey(id); + if (!hasPermissionToUpdatePictureProfile(dbId, pp)) { + notifyError(id, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } - ContentValues values = getContentValues(intId, + ContentValues values = getContentValues(dbId, pp.getProfileType(), pp.getName(), pp.getPackageName(), @@ -118,27 +141,51 @@ public class MediaQualityService extends SystemService { null, values); } + private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) { + PictureProfile fromDb = getPictureProfile(dbId); + return fromDb.getProfileType() == toUpdate.getProfileType() + && fromDb.getPackageName().equals(toUpdate.getPackageName()) + && fromDb.getName().equals(toUpdate.getName()) + && fromDb.getName().equals(getPackageOfCallingUid()); + } + @Override public void removePictureProfile(String id, UserHandle user) { - Long intId = mPictureProfileTempIdMap.getKey(id); - if (intId != null) { + Long dbId = mPictureProfileTempIdMap.getKey(id); + + if (!hasPermissionToRemovePictureProfile(dbId)) { + notifyError(id, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } + + if (dbId != null) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); String selection = BaseParameters.PARAMETER_ID + " = ?"; - String[] selectionArgs = {Long.toString(intId)}; - db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection, + String[] selectionArgs = {Long.toString(dbId)}; + int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection, selectionArgs); - mPictureProfileTempIdMap.remove(intId); + if (result == 0) { + notifyError(id, PictureProfile.ERROR_INVALID_ARGUMENT, + Binder.getCallingUid(), Binder.getCallingPid()); + } + mPictureProfileTempIdMap.remove(dbId); } } + private boolean hasPermissionToRemovePictureProfile(Long dbId) { + PictureProfile fromDb = getPictureProfile(dbId); + return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid()); + } + @Override public PictureProfile getPictureProfile(int type, String name, Bundle options, UserHandle user) { boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " - + BaseParameters.PARAMETER_NAME + " = ?"; - String[] selectionArguments = {Integer.toString(type), name}; + + BaseParameters.PARAMETER_NAME + " = ? AND " + + BaseParameters.PARAMETER_PACKAGE + " = ?"; + String[] selectionArguments = {Integer.toString(type), name, getPackageOfCallingUid()}; try ( Cursor cursor = getCursorAfterQuerying( @@ -156,13 +203,42 @@ public class MediaQualityService extends SystemService { return null; } cursor.moveToFirst(); - return getPictureProfileWithTempIdFromCursor(cursor); + return convertCursorToPictureProfileWithTempId(cursor); + } + } + + private PictureProfile getPictureProfile(Long dbId) { + String selection = BaseParameters.PARAMETER_ID + " = ?"; + String[] selectionArguments = {Long.toString(dbId)}; + + try ( + Cursor cursor = getCursorAfterQuerying( + mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, + getMediaProfileColumns(false), selection, selectionArguments) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%d" + + " in %s. Should only ever be 0 or 1.", count, dbId, + mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME)); + return null; + } + cursor.moveToFirst(); + return convertCursorToPictureProfileWithTempId(cursor); } } @Override public List<PictureProfile> getPictureProfilesByPackage( String packageName, Bundle options, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + notifyError(null, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } + boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; @@ -172,23 +248,31 @@ public class MediaQualityService extends SystemService { } @Override - public List<PictureProfile> getAvailablePictureProfiles(Bundle options, UserHandle user) { - String[] packageNames = mContext.getPackageManager().getPackagesForUid( - Binder.getCallingUid()); - if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) { - return getPictureProfilesByPackage(packageNames[0], options, user); + public List<PictureProfile> getAvailablePictureProfiles( + Bundle options, UserHandle user) { + String packageName = getPackageOfCallingUid(); + if (packageName != null) { + return getPictureProfilesByPackage(packageName, options, user); } return new ArrayList<>(); } @Override public boolean setDefaultPictureProfile(String profileId, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + notifyError(profileId, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } // TODO: pass the profile ID to MediaQuality HAL when ready. return false; } @Override public List<String> getPictureProfilePackageNames(UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + notifyError(null, PictureProfile.ERROR_NO_PERMISSION, + Binder.getCallingUid(), Binder.getCallingPid()); + } String [] column = {BaseParameters.PARAMETER_PACKAGE}; List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column, null, null); @@ -210,12 +294,19 @@ public class MediaQualityService extends SystemService { @Override public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) { + if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty() + && !incomingPackageEqualsCallingUidPackage(sp.getPackageName())) + && !hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + return null; + } SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); ContentValues values = getContentValues(null, sp.getProfileType(), sp.getName(), - sp.getPackageName(), + sp.getPackageName() == null || sp.getPackageName().isEmpty() + ? getPackageOfCallingUid() : sp.getPackageName(), sp.getInputId(), sp.getParameters()); @@ -229,9 +320,14 @@ public class MediaQualityService extends SystemService { @Override public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) { - Long intId = mSoundProfileTempIdMap.getKey(id); + Long dbId = mSoundProfileTempIdMap.getKey(id); + + if (!hasPermissionToUpdateSoundProfile(dbId, sp)) { + //TODO: error handling + return; + } - ContentValues values = getContentValues(intId, + ContentValues values = getContentValues(dbId, sp.getProfileType(), sp.getName(), sp.getPackageName(), @@ -242,27 +338,49 @@ public class MediaQualityService extends SystemService { db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values); } + private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) { + SoundProfile fromDb = getSoundProfile(dbId); + return fromDb.getProfileType() == sp.getProfileType() + && fromDb.getPackageName().equals(sp.getPackageName()) + && fromDb.getName().equals(sp.getName()) + && fromDb.getName().equals(getPackageOfCallingUid()); + } + @Override public void removeSoundProfile(String id, UserHandle user) { Long intId = mSoundProfileTempIdMap.getKey(id); + if (!hasPermissionToRemoveSoundProfile(intId)) { + //TODO: error handling + return; + } + if (intId != null) { SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase(); String selection = BaseParameters.PARAMETER_ID + " = ?"; String[] selectionArgs = {Long.toString(intId)}; - db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection, + int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection, selectionArgs); + if (result == 0) { + //TODO: error handling + } mSoundProfileTempIdMap.remove(intId); } } + private boolean hasPermissionToRemoveSoundProfile(Long dbId) { + SoundProfile fromDb = getSoundProfile(dbId); + return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid()); + } + @Override public SoundProfile getSoundProfile(int type, String id, Bundle options, UserHandle user) { boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_TYPE + " = ? AND " - + BaseParameters.PARAMETER_ID + " = ?"; - String[] selectionArguments = {String.valueOf(type), id}; + + BaseParameters.PARAMETER_ID + " = ? AND " + + BaseParameters.PARAMETER_PACKAGE + " = ?"; + String[] selectionArguments = {String.valueOf(type), id, getPackageOfCallingUid()}; try ( Cursor cursor = getCursorAfterQuerying( @@ -280,13 +398,42 @@ public class MediaQualityService extends SystemService { return null; } cursor.moveToFirst(); - return getSoundProfileWithTempIdFromCursor(cursor); + return convertCursorToSoundProfileWithTempId(cursor); + } + } + + private SoundProfile getSoundProfile(Long dbId) { + String selection = BaseParameters.PARAMETER_ID + " = ?"; + String[] selectionArguments = {Long.toString(dbId)}; + + try ( + Cursor cursor = getCursorAfterQuerying( + mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, + getMediaProfileColumns(false), selection, selectionArguments) + ) { + int count = cursor.getCount(); + if (count == 0) { + return null; + } + if (count > 1) { + Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s " + + "in %s. Should only ever be 0 or 1.", count, dbId, + mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME)); + return null; + } + cursor.moveToFirst(); + return convertCursorToSoundProfileWithTempId(cursor); } } @Override public List<SoundProfile> getSoundProfilesByPackage( String packageName, Bundle options, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } + boolean includeParams = options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false); String selection = BaseParameters.PARAMETER_PACKAGE + " = ?"; @@ -296,24 +443,30 @@ public class MediaQualityService extends SystemService { } @Override - public List<SoundProfile> getAvailableSoundProfiles( - Bundle options, UserHandle user) { - String[] packageNames = mContext.getPackageManager().getPackagesForUid( - Binder.getCallingUid()); - if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) { - return getSoundProfilesByPackage(packageNames[0], options, user); + public List<SoundProfile> getAvailableSoundProfiles(Bundle options, UserHandle user) { + String packageName = getPackageOfCallingUid(); + if (packageName != null) { + return getSoundProfilesByPackage(packageName, options, user); } return new ArrayList<>(); } @Override public boolean setDefaultSoundProfile(String profileId, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return false; + } // TODO: pass the profile ID to MediaQuality HAL when ready. return false; } @Override public List<String> getSoundProfilePackageNames(UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } String [] column = {BaseParameters.PARAMETER_NAME}; List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column, null, null); @@ -323,6 +476,37 @@ public class MediaQualityService extends SystemService { .collect(Collectors.toList()); } + private String getPackageOfCallingUid() { + String[] packageNames = mPackageManager.getPackagesForUid( + Binder.getCallingUid()); + if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) { + return packageNames[0]; + } + return null; + } + + private boolean incomingPackageEqualsCallingUidPackage(String incomingPackage) { + return incomingPackage.equalsIgnoreCase(getPackageOfCallingUid()); + } + + private boolean hasGlobalPictureQualityServicePermission() { + return mPackageManager.checkPermission(android.Manifest.permission + .MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE, + mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED; + } + + private boolean hasGlobalSoundQualityServicePermission() { + return mPackageManager.checkPermission(android.Manifest.permission + .MANAGE_GLOBAL_SOUND_QUALITY_SERVICE, + mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED; + } + + private boolean hasReadColorZonesPermission() { + return mPackageManager.checkPermission(android.Manifest.permission + .READ_COLOR_ZONES, + mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED; + } + private void populateTempIdMap(BiMap<Long, String> map, Long id) { if (id != null && map.getValue(id) == null) { String uuid; @@ -430,7 +614,7 @@ public class MediaQualityService extends SystemService { return columns.toArray(new String[0]); } - private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) { + private PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor) { return new PictureProfile( getTempId(mPictureProfileTempIdMap, cursor), getType(cursor), @@ -442,7 +626,7 @@ public class MediaQualityService extends SystemService { ); } - private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) { + private SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor) { return new SoundProfile( getTempId(mSoundProfileTempIdMap, cursor), getType(cursor), @@ -502,7 +686,7 @@ public class MediaQualityService extends SystemService { ) { List<PictureProfile> pictureProfiles = new ArrayList<>(); while (cursor.moveToNext()) { - pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor)); + pictureProfiles.add(convertCursorToPictureProfileWithTempId(cursor)); } return pictureProfiles; } @@ -517,30 +701,64 @@ public class MediaQualityService extends SystemService { ) { List<SoundProfile> soundProfiles = new ArrayList<>(); while (cursor.moveToNext()) { - soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor)); + soundProfiles.add(convertCursorToSoundProfileWithTempId(cursor)); } return soundProfiles; } } + private void notifyError(String profileId, int errorCode, int uid, int pid) { + UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM); + int n = userState.mCallbacks.beginBroadcast(); + + for (int i = 0; i < n; ++i) { + try { + IPictureProfileCallback callback = userState.mCallbacks.getBroadcastItem(i); + Pair<Integer, Integer> pidUid = userState.mCallbackPidUidMap.get(callback); + + if (pidUid.first == pid && pidUid.second == uid) { + userState.mCallbacks.getBroadcastItem(i).onError(profileId, errorCode); + } + } catch (RemoteException e) { + Slog.e(TAG, "failed to report added input to callback", e); + } + } + userState.mCallbacks.finishBroadcast(); + } + @Override public void registerPictureProfileCallback(final IPictureProfileCallback callback) { + int callingPid = Binder.getCallingPid(); + int callingUid = Binder.getCallingUid(); + + UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid()); + userState.mCallbackPidUidMap.put(callback, Pair.create(callingPid, callingUid)); } + @Override public void registerSoundProfileCallback(final ISoundProfileCallback callback) { } @Override public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) { + if (!hasReadColorZonesPermission()) { + //TODO: error handling + } } @Override public void setAmbientBacklightSettings( AmbientBacklightSettings settings, UserHandle user) { + if (!hasReadColorZonesPermission()) { + //TODO: error handling + } } @Override public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) { + if (!hasReadColorZonesPermission()) { + //TODO: error handling + } } @Override @@ -551,20 +769,34 @@ public class MediaQualityService extends SystemService { @Override public List<String> getPictureProfileAllowList(UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } return new ArrayList<>(); } @Override public void setPictureProfileAllowList(List<String> packages, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + } } @Override public List<String> getSoundProfileAllowList(UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + return new ArrayList<>(); + } return new ArrayList<>(); } @Override public void setSoundProfileAllowList(List<String> packages, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -574,6 +806,9 @@ public class MediaQualityService extends SystemService { @Override public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -583,6 +818,9 @@ public class MediaQualityService extends SystemService { @Override public void setSuperResolutionEnabled(boolean enabled, UserHandle user) { + if (!hasGlobalPictureQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -592,6 +830,9 @@ public class MediaQualityService extends SystemService { @Override public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) { + if (!hasGlobalSoundQualityServicePermission()) { + //TODO: error handling + } } @Override @@ -604,4 +845,38 @@ public class MediaQualityService extends SystemService { return false; } } + + private class MediaQualityManagerCallbackList extends + RemoteCallbackList<IPictureProfileCallback> { + @Override + public void onCallbackDied(IPictureProfileCallback callback) { + //todo + } + } + + private final class UserState { + // A list of callbacks. + private final MediaQualityManagerCallbackList mCallbacks = + new MediaQualityManagerCallbackList(); + + private final Map<IPictureProfileCallback, Pair<Integer, Integer>> mCallbackPidUidMap = + new HashMap<>(); + + private UserState(Context context, int userId) { + + } + } + + private UserState getOrCreateUserStateLocked(int userId) { + UserState userState = getUserStateLocked(userId); + if (userState == null) { + userState = new UserState(mContext, userId); + mUserStates.put(userId, userState); + } + return userState; + } + + private UserState getUserStateLocked(int userId) { + return mUserStates.get(userId); + } } diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index 1b22154c10f6..d33c860343c5 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -28,6 +28,7 @@ import android.os.IBinder; import android.os.IIdmap2; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.os.SystemService; import android.text.TextUtils; @@ -40,7 +41,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; /** * To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds @@ -66,7 +66,7 @@ class IdmapDaemon { private static IdmapDaemon sInstance; private volatile IIdmap2 mService; - private final AtomicInteger mOpenedCount = new AtomicInteger(); + private int mOpenedCount = 0; private final Object mIdmapToken = new Object(); /** @@ -74,15 +74,20 @@ class IdmapDaemon { * finalized, the idmap service will be stopped after a period of time unless another connection * to the service is open. **/ - private class Connection implements AutoCloseable { + private final class Connection implements AutoCloseable { @Nullable private final IIdmap2 mIdmap2; private boolean mOpened = true; - private Connection(IIdmap2 idmap2) { + private Connection() { + mIdmap2 = null; + mOpened = false; + } + + private Connection(@NonNull IIdmap2 idmap2) { + mIdmap2 = idmap2; synchronized (mIdmapToken) { - mOpenedCount.incrementAndGet(); - mIdmap2 = idmap2; + ++mOpenedCount; } } @@ -94,20 +99,22 @@ class IdmapDaemon { } mOpened = false; - if (mOpenedCount.decrementAndGet() != 0) { + if (--mOpenedCount != 0) { // Only post the callback to stop the service if the service does not have an // open connection. return; } + final var service = mService; FgThread.getHandler().postDelayed(() -> { synchronized (mIdmapToken) { - // Only stop the service if the service does not have an open connection. - if (mService == null || mOpenedCount.get() != 0) { + // Only stop the service if it's the one we were scheduled for and + // it does not have an open connection. + if (mService != service || mOpenedCount != 0) { return; } - stopIdmapService(); + stopIdmapServiceLocked(); mService = null; } }, mIdmapToken, SERVICE_TIMEOUT_MS); @@ -175,6 +182,8 @@ class IdmapDaemon { } boolean idmapExists(String overlayPath, int userId) { + // The only way to verify an idmap is to read its state on disk. + final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); try (Connection c = connect()) { final IIdmap2 idmap2 = c.getIdmap2(); if (idmap2 == null) { @@ -187,6 +196,8 @@ class IdmapDaemon { } catch (Exception e) { Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e); return false; + } finally { + StrictMode.setThreadPolicy(oldPolicy); } } @@ -242,14 +253,16 @@ class IdmapDaemon { } catch (Exception e) { Slog.wtf(TAG, "failed to get all fabricated overlays", e); } finally { - try { - if (c.getIdmap2() != null && iteratorId != -1) { - c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId); + if (c != null) { + try { + if (c.getIdmap2() != null && iteratorId != -1) { + c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId); + } + } catch (RemoteException e) { + // ignore } - } catch (RemoteException e) { - // ignore + c.close(); } - c.close(); } return allInfos; } @@ -271,9 +284,11 @@ class IdmapDaemon { } @Nullable - private IBinder getIdmapService() throws TimeoutException, RemoteException { + private IBinder getIdmapServiceLocked() throws TimeoutException, RemoteException { try { - SystemService.start(IDMAP_DAEMON); + if (!SystemService.isRunning(IDMAP_DAEMON)) { + SystemService.start(IDMAP_DAEMON); + } } catch (RuntimeException e) { Slog.wtf(TAG, "Failed to enable idmap2 daemon", e); if (e.getMessage().contains("failed to set system property")) { @@ -306,9 +321,11 @@ class IdmapDaemon { walltimeMillis - endWalltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS)); } - private static void stopIdmapService() { + private static void stopIdmapServiceLocked() { try { - SystemService.stop(IDMAP_DAEMON); + if (SystemService.isRunning(IDMAP_DAEMON)) { + SystemService.stop(IDMAP_DAEMON); + } } catch (RuntimeException e) { // If the idmap daemon cannot be disabled for some reason, it is okay // since we already finished invoking idmap. @@ -326,9 +343,9 @@ class IdmapDaemon { return new Connection(mService); } - IBinder binder = getIdmapService(); + IBinder binder = getIdmapServiceLocked(); if (binder == null) { - return new Connection(null); + return new Connection(); } mService = IIdmap2.Stub.asInterface(binder); diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index cc5c88b77293..d806770e5c91 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -19,7 +19,6 @@ package com.android.server.om; import android.annotation.NonNull; import android.content.om.OverlayInfo; import android.content.om.OverlayableInfo; -import android.content.res.Flags; import android.net.Uri; import android.os.Process; import android.text.TextUtils; @@ -163,15 +162,11 @@ public class OverlayActorEnforcer { return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE; } - // Framework doesn't have <overlayable> declaration by design, and we still want to be able - // to enable its overlays from the packages with the permission. - if (targetOverlayable == null - && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals( - "android"))) { + if (targetOverlayable == null) { return ActorState.MISSING_OVERLAYABLE; } - final String actor = targetOverlayable == null ? null : targetOverlayable.actor; + String actor = targetOverlayable.actor; if (TextUtils.isEmpty(actor)) { // If there's no actor defined, fallback to the legacy permission check try { diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java index fdceabe74dd8..18de9952ed19 100644 --- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java +++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java @@ -26,15 +26,13 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.CollectionUtils; import com.android.server.SystemConfig; import com.android.server.pm.pkg.AndroidPackage; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -121,20 +119,16 @@ public class OverlayReferenceMapper { return actorPair.first; } - @NonNull + @Nullable @Override - public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) { + public Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg) { String target = pkg.getOverlayTarget(); if (TextUtils.isEmpty(target)) { - return Collections.emptyMap(); + return null; } String overlayable = pkg.getOverlayTargetOverlayableName(); - Map<String, Set<String>> targetToOverlayables = new HashMap<>(); - Set<String> overlayables = new HashSet<>(); - overlayables.add(overlayable); - targetToOverlayables.put(target, overlayables); - return targetToOverlayables; + return Pair.create(target, overlayable); } }; } @@ -174,7 +168,7 @@ public class OverlayReferenceMapper { } // TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks - if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) { + if (mProvider.getTargetToOverlayables(pkg) != null) { addOverlay(pkg, otherPkgs, changed); } @@ -245,20 +239,17 @@ public class OverlayReferenceMapper { String target = targetPkg.getPackageName(); removeTarget(target, changedPackages); - Map<String, String> overlayablesToActors = targetPkg.getOverlayables(); - for (String overlayable : overlayablesToActors.keySet()) { - String actor = overlayablesToActors.get(overlayable); + final Map<String, String> overlayablesToActors = targetPkg.getOverlayables(); + for (final var entry : overlayablesToActors.entrySet()) { + final String overlayable = entry.getKey(); + final String actor = entry.getValue(); addTargetToMap(actor, target, changedPackages); for (AndroidPackage overlayPkg : otherPkgs.values()) { - Map<String, Set<String>> targetToOverlayables = + var targetToOverlayables = mProvider.getTargetToOverlayables(overlayPkg); - Set<String> overlayables = targetToOverlayables.get(target); - if (CollectionUtils.isEmpty(overlayables)) { - continue; - } - - if (overlayables.contains(overlayable)) { + if (targetToOverlayables != null && targetToOverlayables.first.equals(target) + && Objects.equals(targetToOverlayables.second, overlayable)) { String overlay = overlayPkg.getPackageName(); addOverlayToMap(actor, target, overlay, changedPackages); } @@ -310,25 +301,22 @@ public class OverlayReferenceMapper { String overlay = overlayPkg.getPackageName(); removeOverlay(overlay, changedPackages); - Map<String, Set<String>> targetToOverlayables = + Pair<String, String> targetToOverlayables = mProvider.getTargetToOverlayables(overlayPkg); - for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) { - String target = entry.getKey(); - Set<String> overlayables = entry.getValue(); + if (targetToOverlayables != null) { + String target = targetToOverlayables.first; AndroidPackage targetPkg = otherPkgs.get(target); if (targetPkg == null) { - continue; + return; } - String targetPkgName = targetPkg.getPackageName(); Map<String, String> overlayableToActor = targetPkg.getOverlayables(); - for (String overlayable : overlayables) { - String actor = overlayableToActor.get(overlayable); - if (TextUtils.isEmpty(actor)) { - continue; - } - addOverlayToMap(actor, targetPkgName, overlay, changedPackages); + String overlayable = targetToOverlayables.second; + String actor = overlayableToActor.get(overlayable); + if (TextUtils.isEmpty(actor)) { + return; } + addOverlayToMap(actor, targetPkgName, overlay, changedPackages); } } } @@ -430,11 +418,11 @@ public class OverlayReferenceMapper { String getActorPkg(@NonNull String actor); /** - * Mock response of multiple overlay tags. + * Mock response of overlay tags. * * TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests */ - @NonNull - Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg); + @Nullable + Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg); } } diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java index 3aefc5a64926..473ed6136e9a 100644 --- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java +++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java @@ -23,6 +23,7 @@ import android.os.ParcelFileDescriptor; import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.security.FileIntegrity; import libcore.io.IoUtils; @@ -121,6 +122,11 @@ final class ResilientAtomicFile implements Closeable { } public void finishWrite(FileOutputStream str) throws IOException { + finishWrite(str, true /* doFsVerity */); + } + + @VisibleForTesting + public void finishWrite(FileOutputStream str, final boolean doFsVerity) throws IOException { if (mMainOutStream != str) { throw new IllegalStateException("Invalid incoming stream."); } @@ -145,13 +151,15 @@ final class ResilientAtomicFile implements Closeable { finalizeOutStream(reserveOutStream); } - // Protect both main and reserve using fs-verity. - try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD()); - ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) { - FileIntegrity.setUpFsVerity(mainPfd); - FileIntegrity.setUpFsVerity(copyPfd); - } catch (IOException e) { - Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e); + if (doFsVerity) { + // Protect both main and reserve using fs-verity. + try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD()); + ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) { + FileIntegrity.setUpFsVerity(mainPfd); + FileIntegrity.setUpFsVerity(copyPfd); + } catch (IOException e) { + Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e); + } } } catch (IOException e) { Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 44789e4c4de2..027da4986ce6 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -179,7 +179,7 @@ abstract class ShortcutPackageItem { itemOut.endDocument(); os.flush(); - file.finishWrite(os); + mShortcutUser.mService.injectFinishWrite(file, os); } catch (XmlPullParserException | IOException e) { Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(os); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 2785da5cbdbd..373c1ed3c386 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1008,7 +1008,7 @@ public class ShortcutService extends IShortcutService.Stub { out.endDocument(); // Close. - file.finishWrite(outs); + injectFinishWrite(file, outs); } catch (IOException e) { Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e); file.failWrite(outs); @@ -1096,7 +1096,7 @@ public class ShortcutService extends IShortcutService.Stub { saveUserInternalLocked(userId, os, /* forBackup= */ false); } - file.finishWrite(os); + injectFinishWrite(file, os); // Remove all dangling bitmap files. cleanupDanglingBitmapDirectoriesLocked(userId); @@ -5067,6 +5067,12 @@ public class ShortcutService extends IShortcutService.Stub { return Build.FINGERPRINT; } + // Injection point. + void injectFinishWrite(@NonNull final ResilientAtomicFile file, + @NonNull final FileOutputStream os) throws IOException { + file.finishWrite(os); + } + final void wtf(String message) { wtf(message, /* exception= */ null); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 672eb4caf798..9d840d0c0d35 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1681,8 +1681,8 @@ public class PermissionManagerService extends IPermissionManager.Stub { // handle overflow if (attributionChainId < 0) { - attributionChainId = 0; sAttributionChainIds.set(0); + attributionChainId = sAttributionChainIds.incrementAndGet(); } return attributionChainId; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5ab59657d4ce..516213b32354 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -562,8 +562,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { volatile boolean mPowerKeyHandled; volatile boolean mBackKeyHandled; volatile boolean mEndCallKeyHandled; - volatile boolean mCameraGestureTriggered; - volatile boolean mCameraGestureTriggeredDuringGoingToSleep; + volatile boolean mPowerButtonLaunchGestureTriggered; + volatile boolean mPowerButtonLaunchGestureTriggeredDuringGoingToSleep; /** * {@code true} if the device is entering a low-power state; {@code false otherwise}. @@ -5893,7 +5893,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mGestureLauncherService == null) { return false; } - mCameraGestureTriggered = false; + mPowerButtonLaunchGestureTriggered = false; final MutableBoolean outLaunched = new MutableBoolean(false); final boolean intercept = mGestureLauncherService.interceptPowerKeyDown(event, interactive, outLaunched); @@ -5903,9 +5903,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // detector from processing the power key later on. return intercept; } - mCameraGestureTriggered = true; + mPowerButtonLaunchGestureTriggered = true; if (mRequestedOrSleepingDefaultDisplay) { - mCameraGestureTriggeredDuringGoingToSleep = true; + mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = true; // Wake device up early to prevent display doing redundant turning off/on stuff. mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture(); } @@ -6282,13 +6282,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mKeyguardDelegate != null) { mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason, - mCameraGestureTriggeredDuringGoingToSleep); + mPowerButtonLaunchGestureTriggeredDuringGoingToSleep); } if (mDisplayFoldController != null) { mDisplayFoldController.finishedGoingToSleep(); } - mCameraGestureTriggeredDuringGoingToSleep = false; - mCameraGestureTriggered = false; + mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = false; + mPowerButtonLaunchGestureTriggered = false; } // Called on the PowerManager's Notifier thread. @@ -6319,10 +6319,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mDefaultDisplayRotation.updateOrientationListener(); if (mKeyguardDelegate != null) { - mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mCameraGestureTriggered); + mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mPowerButtonLaunchGestureTriggered); } - mCameraGestureTriggered = false; + mPowerButtonLaunchGestureTriggered = false; } // Called on the PowerManager's Notifier thread. diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index da8b01ac86fb..587447b8af26 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -198,7 +198,7 @@ public class KeyguardServiceDelegate { if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE || mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) { mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN, - false /* cameraGestureTriggered */); + false /* powerButtonLaunchGestureTriggered */); } if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) { mKeyguardService.onFinishedWakingUp(); @@ -319,10 +319,10 @@ public class KeyguardServiceDelegate { } public void onStartedWakingUp( - @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { + @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) { if (mKeyguardService != null) { if (DEBUG) Log.v(TAG, "onStartedWakingUp()"); - mKeyguardService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); + mKeyguardService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered); } mKeyguardState.interactiveState = INTERACTIVE_STATE_WAKING; } @@ -383,9 +383,11 @@ public class KeyguardServiceDelegate { } public void onFinishedGoingToSleep( - @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { + @PowerManager.GoToSleepReason int pmSleepReason, + boolean powerButtonLaunchGestureTriggered) { if (mKeyguardService != null) { - mKeyguardService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered); + mKeyguardService.onFinishedGoingToSleep(pmSleepReason, + powerButtonLaunchGestureTriggered); } mKeyguardState.interactiveState = INTERACTIVE_STATE_SLEEP; } diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index cd789eaed1b3..f2342e0d5688 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -113,9 +113,10 @@ public class KeyguardServiceWrapper implements IKeyguardService { @Override public void onFinishedGoingToSleep( - @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) { + @PowerManager.GoToSleepReason int pmSleepReason, + boolean powerButtonLaunchGestureTriggered) { try { - mService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered); + mService.onFinishedGoingToSleep(pmSleepReason, powerButtonLaunchGestureTriggered); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } @@ -123,9 +124,9 @@ public class KeyguardServiceWrapper implements IKeyguardService { @Override public void onStartedWakingUp( - @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) { + @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) { try { - mService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered); + mService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered); } catch (RemoteException e) { Slog.w(TAG , "Remote Exception", e); } diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java index a75d110e3cd1..17739712d65a 100644 --- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java +++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java @@ -88,6 +88,5 @@ public class ResourcesManagerShellCommand extends ShellCommand { out.println(" Print this help text."); out.println(" dump <PROCESS>"); out.println(" Dump the Resources objects in use as well as the history of Resources"); - } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 093df8c5075c..29f1f93a844f 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3209,7 +3209,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A true /* forActivity */)) { return false; } - if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) { + if (mAppCompatController.getResizeOverrides().allowRestrictedResizability()) { return false; } // If the user preference respects aspect ratio, then it becomes non-resizable. @@ -3240,8 +3240,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The caller will check both application and activity level property. return true; } - return !AppCompatController.allowRestrictedResizability(wms.mContext.getPackageManager(), - appInfo.packageName); + return !AppCompatResizeOverrides.allowRestrictedResizability( + wms.mContext.getPackageManager(), appInfo.packageName); } boolean isResizeable() { @@ -8435,8 +8435,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ @ActivityInfo.SizeChangesSupportMode private int supportsSizeChanges() { - if (mAppCompatController.getAppCompatResizeOverrides() - .shouldOverrideForceNonResizeApp()) { + final AppCompatResizeOverrides resizeOverrides = mAppCompatController.getResizeOverrides(); + if (resizeOverrides.shouldOverrideForceNonResizeApp()) { return SIZE_CHANGES_UNSUPPORTED_OVERRIDE; } @@ -8444,8 +8444,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return SIZE_CHANGES_SUPPORTED_METADATA; } - if (mAppCompatController.getAppCompatResizeOverrides() - .shouldOverrideForceResizeApp()) { + if (resizeOverrides.shouldOverrideForceResizeApp()) { return SIZE_CHANGES_SUPPORTED_OVERRIDE; } @@ -10221,7 +10220,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mAppCompatController.getAppCompatOrientationOverrides() .shouldIgnoreOrientationRequestLoop()); proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP, - mAppCompatController.getAppCompatResizeOverrides().shouldOverrideForceResizeApp()); + mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp()); proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS, mAppCompatController.getAppCompatAspectRatioOverrides() .shouldEnableUserAspectRatioSettings()); diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 4433d64f0d00..0967078deac3 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -15,23 +15,17 @@ */ package com.android.server.wm; -import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY; - import android.annotation.NonNull; import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; import java.io.PrintWriter; -import java.util.function.BooleanSupplier; /** * Allows the interaction with all the app compat policies and configurations */ class AppCompatController { - - @NonNull - private final ActivityRecord mActivityRecord; @NonNull private final TransparentPolicy mTransparentPolicy; @NonNull @@ -50,56 +44,28 @@ class AppCompatController { private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; @NonNull private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy; - @NonNull - final BooleanSupplier mAllowRestrictedResizability; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { - mActivityRecord = activityRecord; final PackageManager packageManager = wmService.mContext.getPackageManager(); final OptPropFactory optPropBuilder = new OptPropFactory(packageManager, activityRecord.packageName); mAppCompatDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord); mTransparentPolicy = new TransparentPolicy(activityRecord, wmService.mAppCompatConfiguration); - mAppCompatOverrides = new AppCompatOverrides(activityRecord, + mAppCompatOverrides = new AppCompatOverrides(activityRecord, packageManager, wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery); mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides); mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord, mTransparentPolicy, mAppCompatOverrides); - mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord, + mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord, wmService.mAppCompatConfiguration); - mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord, + mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord, wmService.mAppCompatConfiguration); mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord, mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration); - mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord, + mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord, mAppCompatOverrides); - mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> { - // Application level. - if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) { - return true; - } - // Activity level. - try { - return packageManager.getPropertyAsUser( - PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, - mActivityRecord.mActivityComponent.getPackageName(), - mActivityRecord.mActivityComponent.getClassName(), - mActivityRecord.mUserId).getBoolean(); - } catch (PackageManager.NameNotFoundException e) { - return false; - } - }); - } - - static boolean allowRestrictedResizability(PackageManager pm, String packageName) { - try { - return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName) - .getBoolean(); - } catch (PackageManager.NameNotFoundException e) { - return false; - } } @NonNull @@ -138,8 +104,8 @@ class AppCompatController { } @NonNull - AppCompatResizeOverrides getAppCompatResizeOverrides() { - return mAppCompatOverrides.getAppCompatResizeOverrides(); + AppCompatResizeOverrides getResizeOverrides() { + return mAppCompatOverrides.getResizeOverrides(); } @NonNull diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java index 2f03105846bd..58b37becc373 100644 --- a/services/core/java/com/android/server/wm/AppCompatOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.annotation.NonNull; +import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; @@ -34,13 +35,14 @@ public class AppCompatOverrides { @NonNull private final AppCompatFocusOverrides mAppCompatFocusOverrides; @NonNull - private final AppCompatResizeOverrides mAppCompatResizeOverrides; + private final AppCompatResizeOverrides mResizeOverrides; @NonNull private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides; @NonNull private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides; AppCompatOverrides(@NonNull ActivityRecord activityRecord, + @NonNull PackageManager packageManager, @NonNull AppCompatConfiguration appCompatConfiguration, @NonNull OptPropFactory optPropBuilder, @NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) { @@ -55,7 +57,8 @@ public class AppCompatOverrides { mAppCompatReachabilityOverrides); mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration, optPropBuilder); - mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder); + mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager, + optPropBuilder); mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord, appCompatConfiguration); } @@ -81,8 +84,8 @@ public class AppCompatOverrides { } @NonNull - AppCompatResizeOverrides getAppCompatResizeOverrides() { - return mAppCompatResizeOverrides; + AppCompatResizeOverrides getResizeOverrides() { + return mResizeOverrides; } @NonNull diff --git a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java index 60c18254eca7..fa53153dd143 100644 --- a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java +++ b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java @@ -19,13 +19,17 @@ package com.android.server.wm; import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP; import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY; import static com.android.server.wm.AppCompatUtils.isChangeEnabled; import android.annotation.NonNull; +import android.content.pm.PackageManager; import com.android.server.wm.utils.OptPropFactory; +import java.util.function.BooleanSupplier; + /** * Encapsulate app compat logic about resizability. */ @@ -37,11 +41,40 @@ class AppCompatResizeOverrides { @NonNull private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp; + @NonNull + private final BooleanSupplier mAllowRestrictedResizability; + AppCompatResizeOverrides(@NonNull ActivityRecord activityRecord, + @NonNull PackageManager packageManager, @NonNull OptPropFactory optPropBuilder) { mActivityRecord = activityRecord; mAllowForceResizeOverrideOptProp = optPropBuilder.create( PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES); + mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> { + // Application level. + if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) { + return true; + } + // Activity level. + try { + return packageManager.getPropertyAsUser( + PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, + mActivityRecord.mActivityComponent.getPackageName(), + mActivityRecord.mActivityComponent.getClassName(), + mActivityRecord.mUserId).getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + }); + } + + static boolean allowRestrictedResizability(PackageManager pm, String packageName) { + try { + return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName) + .getBoolean(); + } catch (PackageManager.NameNotFoundException e) { + return false; + } } /** @@ -75,4 +108,9 @@ class AppCompatResizeOverrides { return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty( isChangeEnabled(mActivityRecord, FORCE_NON_RESIZE_APP)); } + + /** @see android.view.WindowManager#PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY */ + boolean allowRestrictedResizability() { + return mAllowRestrictedResizability.getAsBoolean(); + } } diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java index ae6e72464555..e3ffe716271c 100644 --- a/services/core/java/com/android/server/wm/InputConfigAdapter.java +++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java @@ -76,9 +76,6 @@ class InputConfigAdapter { LayoutParams.FLAG_NOT_TOUCHABLE, InputConfig.NOT_TOUCHABLE, false /* inverted */), new FlagMapping( - LayoutParams.FLAG_SPLIT_TOUCH, - InputConfig.PREVENT_SPLITTING, true /* inverted */), - new FlagMapping( LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */), new FlagMapping( diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 65cf4ee733dd..379e312e58c0 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -346,6 +346,7 @@ public: void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled); void setMouseReverseVerticalScrollingEnabled(bool enabled); void setMouseScrollingAccelerationEnabled(bool enabled); + void setMouseScrollingSpeed(int32_t speed); void setMouseSwapPrimaryButtonEnabled(bool enabled); void setMouseAccelerationEnabled(bool enabled); void setTouchpadPointerSpeed(int32_t speed); @@ -500,6 +501,9 @@ private: // True if mouse scrolling acceleration is enabled. bool mouseScrollingAccelerationEnabled{true}; + // The mouse scrolling speed, as a number from -7 (slowest) to 7 (fastest). + int32_t mouseScrollingSpeed{0}; + // True if mouse vertical scrolling is reversed. bool mouseReverseVerticalScrollingEnabled{false}; @@ -843,6 +847,9 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon mLocked.mouseScrollingAccelerationEnabled ? android::os::IInputConstants::DEFAULT_MOUSE_WHEEL_ACCELERATION : 1; + outConfig->wheelVelocityControlParameters.scale = mLocked.mouseScrollingAccelerationEnabled + ? 1 + : exp2f(mLocked.mouseScrollingSpeed * POINTER_SPEED_EXPONENT); outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled; outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest; @@ -1451,6 +1458,21 @@ void NativeInputManager::setMouseScrollingAccelerationEnabled(bool enabled) { InputReaderConfiguration::Change::POINTER_SPEED); } +void NativeInputManager::setMouseScrollingSpeed(int32_t speed) { + { // acquire lock + std::scoped_lock _l(mLock); + + if (mLocked.mouseScrollingSpeed == speed) { + return; + } + + mLocked.mouseScrollingSpeed = speed; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::Change::POINTER_SPEED); +} + void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) { { // acquire lock std::scoped_lock _l(mLock); @@ -3243,6 +3265,11 @@ static void nativeSetMouseScrollingAccelerationEnabled(JNIEnv* env, jobject nati im->setMouseScrollingAccelerationEnabled(enabled); } +static void nativeSetMouseScrollingSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->setMouseScrollingSpeed(speed); +} + static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj, bool enabled) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -3319,6 +3346,7 @@ static const JNINativeMethod gInputManagerMethods[] = { (void*)nativeSetMouseReverseVerticalScrollingEnabled}, {"setMouseScrollingAccelerationEnabled", "(Z)V", (void*)nativeSetMouseScrollingAccelerationEnabled}, + {"setMouseScrollingSpeed", "(I)V", (void*)nativeSetMouseScrollingSpeed}, {"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled}, {"setMouseAccelerationEnabled", "(Z)V", (void*)nativeSetMouseAccelerationEnabled}, {"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed}, diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java index 7277fd79fdd5..66aaa562b873 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java @@ -45,6 +45,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.SparseArray; import androidx.annotation.NonNull; @@ -78,10 +79,7 @@ import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; @Presubmit @RunWith(JUnit4.class) @@ -885,18 +883,15 @@ public class AppsFilterImplTest { return null; } - @NonNull + @Nullable @Override - public Map<String, Set<String>> getTargetToOverlayables( + public Pair<String, String> getTargetToOverlayables( @NonNull AndroidPackage pkg) { if (overlay.getPackageName().equals(pkg.getPackageName())) { - Map<String, Set<String>> map = new ArrayMap<>(); - Set<String> set = new ArraySet<>(); - set.add(overlay.getOverlayTargetOverlayableName()); - map.put(overlay.getOverlayTarget(), set); - return map; + return Pair.create(overlay.getOverlayTarget(), + overlay.getOverlayTargetOverlayableName()); } - return Collections.emptyMap(); + return null; } }, mMockHandler); @@ -977,18 +972,15 @@ public class AppsFilterImplTest { return null; } - @NonNull + @Nullable @Override - public Map<String, Set<String>> getTargetToOverlayables( + public Pair<String, String> getTargetToOverlayables( @NonNull AndroidPackage pkg) { if (overlay.getPackageName().equals(pkg.getPackageName())) { - Map<String, Set<String>> map = new ArrayMap<>(); - Set<String> set = new ArraySet<>(); - set.add(overlay.getOverlayTargetOverlayableName()); - map.put(overlay.getOverlayTarget(), set); - return map; + return Pair.create(overlay.getOverlayTarget(), + overlay.getOverlayTargetOverlayableName()); } - return Collections.emptyMap(); + return null; } }, mMockHandler); diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java index 89b48bad2358..27eada013642 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE; +import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED; import static android.os.Process.INVALID_UID; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -27,9 +29,11 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANC import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED; import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED; +import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER; import static com.android.server.am.PendingIntentRecord.cancelReasonToString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -39,9 +43,11 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppGlobals; +import android.app.BackgroundStartPrivileges; import android.app.PendingIntent; import android.content.Intent; import android.content.pm.IPackageManager; +import android.os.Binder; import android.os.Looper; import android.os.UserHandle; @@ -179,6 +185,34 @@ public class PendingIntentControllerTest { } } + @Test + public void testClearAllowBgActivityStartsClearsToken() { + final PendingIntentRecord pir = createPendingIntentRecord(0); + Binder token = new Binder(); + pir.setAllowBgActivityStarts(token, FLAG_ACTIVITY_SENDER); + assertEquals(BackgroundStartPrivileges.allowBackgroundActivityStarts(token), + pir.getBackgroundStartPrivilegesForActivitySender(token)); + pir.clearAllowBgActivityStarts(token); + assertEquals(BackgroundStartPrivileges.NONE, + pir.getBackgroundStartPrivilegesForActivitySender(token)); + } + + @Test + public void testClearAllowBgActivityStartsClearsDuration() { + final PendingIntentRecord pir = createPendingIntentRecord(0); + Binder token = new Binder(); + pir.setAllowlistDurationLocked(token, 1000, + TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, + "NotificationManagerService"); + PendingIntentRecord.TempAllowListDuration allowlistDurationLocked = + pir.getAllowlistDurationLocked(token); + assertEquals(1000, allowlistDurationLocked.duration); + pir.clearAllowBgActivityStarts(token); + PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear = + pir.getAllowlistDurationLocked(token); + assertNull(allowlistDurationLockedAfterClear); + } + private void assertCancelReason(int expectedReason, int actualReason) { final String errMsg = "Expected: " + cancelReasonToString(expectedReason) + "; Actual: " + cancelReasonToString(actualReason); diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 82efae45e1a4..92c6db5b7b96 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -21,6 +21,9 @@ import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_VIA_SYS import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap; import static android.service.quickaccesswallet.Flags.launchWalletViaSysuiCallbacks; +import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_DISABLED_MODE; +import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE; +import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_MULTI_TARGET_MODE; import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER; import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER; import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS; @@ -163,7 +166,7 @@ public class GestureLauncherServiceTest { new GestureLauncherService( mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger); - withDoubleTapPowerGestureEnableSettingValue(true); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); } @@ -215,68 +218,117 @@ public class GestureLauncherServiceTest { } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(false); - withDoubleTapPowerGestureEnableSettingValue(false); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingDisabled() { + withDoubleTapPowerModeConfigValue( + DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(false); - withDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( - mContext, FAKE_USER_ID)); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(0); - assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( - mContext, FAKE_USER_ID)); - } + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingDisabled() { + withCameraDoubleTapPowerEnableConfigValue(false); + withCameraDoubleTapPowerDisableSettingValue(1); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(false); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - } else { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(1); - } + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingEnabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test - public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); - withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); - } else { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - } + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingEnabled() { + withCameraDoubleTapPowerEnableConfigValue(false); + withCameraDoubleTapPowerDisableSettingValue(0); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingDisabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingDisabled() { + withCameraDoubleTapPowerEnableConfigValue(true); + withCameraDoubleTapPowerDisableSettingValue(1); + + assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingEnabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); + + assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingEnabled() { + withCameraDoubleTapPowerEnableConfigValue(true); + withCameraDoubleTapPowerDisableSettingValue(0); + assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingEnabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE); + withCameraDoubleTapPowerDisableSettingValue(0); + + assertTrue( + mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) + public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingDisabled() { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE); + withCameraDoubleTapPowerDisableSettingValue(1); + + assertFalse( + mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertFalse( @@ -287,8 +339,8 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertTrue( @@ -299,11 +351,11 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() { - withDoubleTapPowerEnabledConfigValue(false); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); - assertTrue( + assertFalse( mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled( mContext, FAKE_USER_ID)); } @@ -311,8 +363,8 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(false); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); assertFalse( @@ -323,8 +375,8 @@ public class GestureLauncherServiceTest { @Test @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP) public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); assertFalse( @@ -449,13 +501,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerGestureEnableSettingValue(false); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -498,13 +544,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerGestureEnableSettingValue(false); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -549,9 +589,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffInteractive() { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1031,9 +1069,7 @@ public class GestureLauncherServiceTest { public void testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() { // Enable camera double tap gesture - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + enableCameraGesture(); // Enable power button cooldown withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000); @@ -1220,10 +1256,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_longpress() { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); - withUserSetupCompleteValue(true); + enableCameraGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1400,13 +1433,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() { - if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerGestureEnableSettingValue(false); - } else { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - } - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1449,9 +1476,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffNotInteractive() { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1495,9 +1520,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffNotInteractive() { - withCameraDoubleTapPowerEnableConfigValue(false); - withCameraDoubleTapPowerDisableSettingValue(1); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + disableDoubleTapPowerGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1630,9 +1653,7 @@ public class GestureLauncherServiceTest { @Test public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnNotInteractive() { - withCameraDoubleTapPowerEnableConfigValue(true); - withCameraDoubleTapPowerDisableSettingValue(0); - mGestureLauncherService.updateCameraDoubleTapPowerEnabled(); + enableCameraGesture(); long eventTime = INITIAL_EVENT_TIME_MILLIS; KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, @@ -1823,12 +1844,13 @@ public class GestureLauncherServiceTest { .thenReturn(enableConfigValue); } - private void withDoubleTapPowerEnabledConfigValue(boolean enable) { - when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled)) - .thenReturn(enable); + private void withDoubleTapPowerModeConfigValue( + int modeConfigValue) { + when(mResources.getInteger(com.android.internal.R.integer.config_doubleTapPowerGestureMode)) + .thenReturn(modeConfigValue); } - private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) { + private void withMultiTargetDoubleTapPowerGestureEnableSettingValue(boolean enable) { Settings.Secure.putIntForUser( mContentResolver, Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED, @@ -1910,8 +1932,8 @@ public class GestureLauncherServiceTest { private void enableWalletGesture() { withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER); - withDoubleTapPowerGestureEnableSettingValue(true); - withDoubleTapPowerEnabledConfigValue(true); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE); mGestureLauncherService.updateWalletDoubleTapPowerEnabled(); withUserSetupCompleteValue(true); @@ -1926,8 +1948,9 @@ public class GestureLauncherServiceTest { private void enableCameraGesture() { if (launchWalletOptionOnPowerDoubleTap()) { - withDoubleTapPowerEnabledConfigValue(true); - withDoubleTapPowerGestureEnableSettingValue(true); + withDoubleTapPowerModeConfigValue( + DOUBLE_TAP_POWER_MULTI_TARGET_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(true); withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER); } else { withCameraDoubleTapPowerEnableConfigValue(true); @@ -1937,6 +1960,18 @@ public class GestureLauncherServiceTest { withUserSetupCompleteValue(true); } + private void disableDoubleTapPowerGesture() { + if (launchWalletOptionOnPowerDoubleTap()) { + withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE); + withMultiTargetDoubleTapPowerGestureEnableSettingValue(false); + } else { + withCameraDoubleTapPowerEnableConfigValue(false); + withCameraDoubleTapPowerDisableSettingValue(1); + } + mGestureLauncherService.updateWalletDoubleTapPowerEnabled(); + withUserSetupCompleteValue(true); + } + private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues( long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) { KeyEvent keyEvent = diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java new file mode 100644 index 000000000000..84713079c9d3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java @@ -0,0 +1,309 @@ +/* + * 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.server.appop; + +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER; +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import android.app.AppOpsManager; +import android.content.Context; +import android.os.Process; +import android.util.ArraySet; +import android.util.Log; +import android.util.LongSparseArray; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.appop.DiscreteOpsSqlRegistry.DiscreteOp; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class DiscreteAppOpSqlPersistenceTest { + private static final String DATABASE_NAME = "test_app_ops.db"; + private DiscreteOpsSqlRegistry mDiscreteRegistry; + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + + @Before + public void setUp() { + mDiscreteRegistry = new DiscreteOpsSqlRegistry(mContext, + mContext.getDatabasePath(DATABASE_NAME)); + mDiscreteRegistry.systemReady(); + } + + @After + public void cleanUp() { + mContext.deleteDatabase(DATABASE_NAME); + } + + @Test + public void discreteOpEventIsRecorded() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + List<DiscreteOp> discreteOps = mDiscreteRegistry.getCachedDiscreteOps(); + assertThat(discreteOps.size()).isEqualTo(1); + assertThat(discreteOps).contains(opEvent); + } + + @Test + public void discreteOpEventIsPersistedToDisk() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + assertThat(mDiscreteRegistry.getCachedDiscreteOps()).isEmpty(); + List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + assertThat(discreteOps.size()).isEqualTo(1); + assertThat(discreteOps).contains(opEvent); + } + + @Test + public void discreteOpEventInSameMinuteIsNotRecorded() { + long oneMinuteMillis = Duration.ofMinutes(1).toMillis(); + // round timestamp at minute level and add 5 seconds + long accessTime = System.currentTimeMillis() / oneMinuteMillis * oneMinuteMillis + 5000; + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).setAccessTime(accessTime).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + // create duplicate event in same minute, with added 30 seconds + DiscreteOp opEvent2 = + new DiscreteOpBuilder(mContext).setAccessTime(accessTime + 30000).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + + assertThat(discreteOps.size()).isEqualTo(1); + assertThat(discreteOps).contains(opEvent); + } + + @Test + public void multipleDiscreteOpEventAreRecorded() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setPackageName( + "test.package").build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + + List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + assertThat(discreteOps).contains(opEvent); + assertThat(discreteOps).contains(opEvent2); + assertThat(discreteOps.size()).isEqualTo(2); + } + + @Test + public void clearDiscreteOps() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setUid(12345).setPackageName( + "abc").build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + mDiscreteRegistry.clearHistory(); + assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty(); + } + + @Test + public void clearDiscreteOpsForPackage() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(new DiscreteOpBuilder(mContext).build()); + mDiscreteRegistry.clearHistory(Process.myUid(), mContext.getPackageName()); + + assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty(); + } + + @Test + public void offsetDiscreteOps() { + DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build(); + long event2AccessTime = System.currentTimeMillis() - 300000; + DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setAccessTime( + event2AccessTime).build(); + mDiscreteRegistry.recordDiscreteAccess(opEvent); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(opEvent2); + long offset = Duration.ofMinutes(2).toMillis(); + + mDiscreteRegistry.offsetHistory(offset); + + // adjust input for assertion + DiscreteOp e1 = new DiscreteOpBuilder(opEvent) + .setAccessTime(opEvent.getAccessTime() - offset).build(); + DiscreteOp e2 = new DiscreteOpBuilder(opEvent2) + .setAccessTime(event2AccessTime - offset).build(); + + List<DiscreteOp> results = mDiscreteRegistry.getAllDiscreteOps(); + assertThat(results.size()).isEqualTo(2); + assertThat(results).contains(e1); + assertThat(results).contains(e2); + } + + @Test + public void completeAttributionChain() { + long chainId = 100; + DiscreteOp event1 = new DiscreteOpBuilder(mContext) + .setChainId(chainId) + .setAttributionFlags(ATTRIBUTION_FLAG_RECEIVER | ATTRIBUTION_FLAG_TRUSTED) + .build(); + DiscreteOp event2 = new DiscreteOpBuilder(mContext) + .setChainId(chainId) + .setAttributionFlags(ATTRIBUTION_FLAG_ACCESSOR | ATTRIBUTION_FLAG_TRUSTED) + .build(); + List<DiscreteOp> events = new ArrayList<>(); + events.add(event1); + events.add(event2); + + LongSparseArray<DiscreteOpsSqlRegistry.AttributionChain> chains = + mDiscreteRegistry.createAttributionChains(events, new ArraySet<>()); + + assertThat(chains.size()).isGreaterThan(0); + DiscreteOpsSqlRegistry.AttributionChain chain = chains.get(chainId); + assertThat(chain).isNotNull(); + assertThat(chain.isComplete()).isTrue(); + assertThat(chain.getStart()).isEqualTo(event1); + assertThat(chain.getLastVisible()).isEqualTo(event2); + } + + @Test + public void addToHistoricalOps() { + long beginTimeMillis = System.currentTimeMillis(); + DiscreteOp event1 = new DiscreteOpBuilder(mContext) + .build(); + DiscreteOp event2 = new DiscreteOpBuilder(mContext) + .setUid(123457) + .build(); + mDiscreteRegistry.recordDiscreteAccess(event1); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(event2); + + long endTimeMillis = System.currentTimeMillis() + 500; + AppOpsManager.HistoricalOps results = new AppOpsManager.HistoricalOps(beginTimeMillis, + endTimeMillis); + + mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(results, beginTimeMillis, + endTimeMillis, 0, 0, null, null, null, 0, new ArraySet<>()); + Log.i("Manjeet", "TEST read " + results); + assertWithMessage("results shouldn't be empty").that(results.isEmpty()).isFalse(); + } + + @Test + public void dump() { + DiscreteOp event1 = new DiscreteOpBuilder(mContext) + .setAccessTime(1732221340628L) + .setUid(12345) + .build(); + DiscreteOp event2 = new DiscreteOpBuilder(mContext) + .setAccessTime(1732227340628L) + .setUid(123457) + .build(); + mDiscreteRegistry.recordDiscreteAccess(event1); + flushDiscreteOpsToDatabase(); + mDiscreteRegistry.recordDiscreteAccess(event2); + } + + /** This clears in-memory cache and push records into the database. */ + private void flushDiscreteOpsToDatabase() { + mDiscreteRegistry.writeAndClearOldAccessHistory(); + } + + /** + * Creates default op event for CAMERA app op with current time as access time + * and 1 minute duration + */ + private static class DiscreteOpBuilder { + private int mUid; + private String mPackageName; + private String mAttributionTag; + private String mDeviceId; + private int mOpCode; + private int mOpFlags; + private int mAttributionFlags; + private int mUidState; + private long mChainId; + private long mAccessTime; + private long mDuration; + + DiscreteOpBuilder(Context context) { + mUid = Process.myUid(); + mPackageName = context.getPackageName(); + mAttributionTag = null; + mDeviceId = String.valueOf(context.getDeviceId()); + mOpCode = AppOpsManager.OP_CAMERA; + mOpFlags = AppOpsManager.OP_FLAG_SELF; + mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR; + mUidState = UID_STATE_FOREGROUND; + mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; + mAccessTime = System.currentTimeMillis(); + mDuration = Duration.ofMinutes(1).toMillis(); + } + + DiscreteOpBuilder(DiscreteOp discreteOp) { + this.mUid = discreteOp.getUid(); + this.mPackageName = discreteOp.getPackageName(); + this.mAttributionTag = discreteOp.getAttributionTag(); + this.mDeviceId = discreteOp.getDeviceId(); + this.mOpCode = discreteOp.getOpCode(); + this.mOpFlags = discreteOp.getOpFlags(); + this.mAttributionFlags = discreteOp.getAttributionFlags(); + this.mUidState = discreteOp.getUidState(); + this.mChainId = discreteOp.getChainId(); + this.mAccessTime = discreteOp.getAccessTime(); + this.mDuration = discreteOp.getDuration(); + } + + public DiscreteOpBuilder setUid(int uid) { + this.mUid = uid; + return this; + } + + public DiscreteOpBuilder setPackageName(String packageName) { + this.mPackageName = packageName; + return this; + } + + public DiscreteOpBuilder setAttributionFlags(int attributionFlags) { + this.mAttributionFlags = attributionFlags; + return this; + } + + public DiscreteOpBuilder setChainId(long chainId) { + this.mChainId = chainId; + return this; + } + + public DiscreteOpBuilder setAccessTime(long accessTime) { + this.mAccessTime = accessTime; + return this; + } + + public DiscreteOp build() { + return new DiscreteOp(mUid, mPackageName, mAttributionTag, mDeviceId, mOpCode, mOpFlags, + mAttributionFlags, mUidState, mChainId, mAccessTime, mDuration); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java index 2ff0c6288ece..ae973be17904 100644 --- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java @@ -47,9 +47,12 @@ import org.junit.runner.RunWith; import java.io.File; import java.util.List; +/** + * Test xml persistence implementation for discrete ops. + */ @RunWith(AndroidJUnit4.class) -public class DiscreteAppOpPersistenceTest { - private DiscreteRegistry mDiscreteRegistry; +public class DiscreteAppOpXmlPersistenceTest { + private DiscreteOpsXmlRegistry mDiscreteRegistry; private final Object mLock = new Object(); private File mMockDataDirectory; private final Context mContext = @@ -61,13 +64,13 @@ public class DiscreteAppOpPersistenceTest { @Before public void setUp() { mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE); - mDiscreteRegistry = new DiscreteRegistry(mLock, mMockDataDirectory); + mDiscreteRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory); mDiscreteRegistry.systemReady(); } @After public void cleanUp() { - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); FileUtils.deleteContents(mMockDataDirectory); } @@ -87,14 +90,14 @@ public class DiscreteAppOpPersistenceTest { mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteRegistry.ACCESS_TYPE_FINISH_OP); + DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP); // Verify in-memory object is correct fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, duration, uidState, opFlags, attributionFlags, attributionChainId); // Write to disk and clear the in-memory object - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); // Verify the storage file is created and then verify its content is correct File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory); @@ -119,12 +122,12 @@ public class DiscreteAppOpPersistenceTest { mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags, uidState, accessTime, duration, attributionFlags, attributionChainId, - DiscreteRegistry.ACCESS_TYPE_START_OP); + DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP); fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime, duration, uidState, opFlags, attributionFlags, attributionChainId); - mDiscreteRegistry.writeAndClearAccessHistory(); + mDiscreteRegistry.writeAndClearOldAccessHistory(); File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory); assertThat(files.length).isEqualTo(1); @@ -136,30 +139,31 @@ public class DiscreteAppOpPersistenceTest { int expectedOp, String expectedDeviceId, String expectedAttrTag, long expectedAccessTime, long expectedAccessDuration, int expectedUidState, int expectedOpFlags, int expectedAttrFlags, int expectedAttrChainId) { - DiscreteRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps(); + DiscreteOpsXmlRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps(); assertThat(discreteOps.isEmpty()).isFalse(); assertThat(discreteOps.mUids.size()).isEqualTo(1); - DiscreteRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid); + DiscreteOpsXmlRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid); assertThat(discreteUidOps.mPackages.size()).isEqualTo(1); - DiscreteRegistry.DiscretePackageOps discretePackageOps = + DiscreteOpsXmlRegistry.DiscretePackageOps discretePackageOps = discreteUidOps.mPackages.get(expectedPackageName); assertThat(discretePackageOps.mPackageOps.size()).isEqualTo(1); - DiscreteRegistry.DiscreteOp discreteOp = discretePackageOps.mPackageOps.get(expectedOp); + DiscreteOpsXmlRegistry.DiscreteOp discreteOp = + discretePackageOps.mPackageOps.get(expectedOp); assertThat(discreteOp.mDeviceAttributedOps.size()).isEqualTo(1); - DiscreteRegistry.DiscreteDeviceOp discreteDeviceOp = + DiscreteOpsXmlRegistry.DiscreteDeviceOp discreteDeviceOp = discreteOp.mDeviceAttributedOps.get(expectedDeviceId); assertThat(discreteDeviceOp.mAttributedOps.size()).isEqualTo(1); - List<DiscreteRegistry.DiscreteOpEvent> discreteOpEvents = + List<DiscreteOpsXmlRegistry.DiscreteOpEvent> discreteOpEvents = discreteDeviceOp.mAttributedOps.get(expectedAttrTag); assertThat(discreteOpEvents.size()).isEqualTo(1); - DiscreteRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0); + DiscreteOpsXmlRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0); assertThat(discreteOpEvent.mNoteTime).isEqualTo(expectedAccessTime); assertThat(discreteOpEvent.mNoteDuration).isEqualTo(expectedAccessDuration); assertThat(discreteOpEvent.mUidState).isEqualTo(expectedUidState); diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java new file mode 100644 index 000000000000..21cc3bac3938 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java @@ -0,0 +1,168 @@ +/* + * 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.server.appop; + +import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR; +import static android.app.AppOpsManager.UID_STATE_FOREGROUND; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.AppOpsManager; +import android.companion.virtual.VirtualDeviceManager; +import android.content.Context; +import android.os.FileUtils; +import android.os.Process; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.time.Duration; +import java.util.List; + +@RunWith(AndroidJUnit4.class) +public class DiscreteOpsMigrationAndRollbackTest { + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); + private static final String DATABASE_NAME = "test_app_ops.db"; + private static final int RECORD_COUNT = 500; + private final File mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE); + final Object mLock = new Object(); + + @After + @Before + public void clean() { + mContext.deleteDatabase(DATABASE_NAME); + FileUtils.deleteContents(mMockDataDirectory); + } + + @Test + public void migrateFromXmlToSqlite() { + // write records to xml registry + DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory); + xmlRegistry.systemReady(); + for (int i = 1; i <= RECORD_COUNT; i++) { + DiscreteOpsSqlRegistry.DiscreteOp opEvent = + new DiscreteOpBuilder(mContext) + .setChainId(i) + .setUid(10000 + i) // make all records unique + .build(); + xmlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(), + opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), + opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), + opEvent.getDuration(), opEvent.getAttributionFlags(), + (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + } + xmlRegistry.writeAndClearOldAccessHistory(); + assertThat(xmlRegistry.readLargestChainIdFromDiskLocked()).isEqualTo(RECORD_COUNT); + assertThat(xmlRegistry.getAllDiscreteOps().mUids.size()).isEqualTo(RECORD_COUNT); + + // migration to sql registry + DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext, + mContext.getDatabasePath(DATABASE_NAME)); + sqlRegistry.systemReady(); + DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry); + List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps(); + + assertThat(xmlRegistry.getAllDiscreteOps().mUids).isEmpty(); + assertThat(sqlOps.size()).isEqualTo(RECORD_COUNT); + assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT); + } + + @Test + public void migrateFromSqliteToXml() { + // write to sql registry + DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext, + mContext.getDatabasePath(DATABASE_NAME)); + sqlRegistry.systemReady(); + for (int i = 1; i <= RECORD_COUNT; i++) { + DiscreteOpsSqlRegistry.DiscreteOp opEvent = + new DiscreteOpBuilder(mContext) + .setChainId(i) + .setUid(RECORD_COUNT + i) // make all records unique + .build(); + sqlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(), + opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(), + opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(), + opEvent.getDuration(), opEvent.getAttributionFlags(), + (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP); + } + sqlRegistry.writeAndClearOldAccessHistory(); + assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT); + assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT); + + // migration to xml registry + DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory); + xmlRegistry.systemReady(); + DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry); + DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps(); + + assertThat(sqlRegistry.getAllDiscreteOps()).isEmpty(); + assertThat(xmlOps.mLargestChainId).isEqualTo(RECORD_COUNT); + assertThat(xmlOps.mUids.size()).isEqualTo(RECORD_COUNT); + } + + private static class DiscreteOpBuilder { + private int mUid; + private String mPackageName; + private String mAttributionTag; + private String mDeviceId; + private int mOpCode; + private int mOpFlags; + private int mAttributionFlags; + private int mUidState; + private int mChainId; + private long mAccessTime; + private long mDuration; + + DiscreteOpBuilder(Context context) { + mUid = Process.myUid(); + mPackageName = context.getPackageName(); + mAttributionTag = null; + mDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; + mOpCode = AppOpsManager.OP_CAMERA; + mOpFlags = AppOpsManager.OP_FLAG_SELF; + mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR; + mUidState = UID_STATE_FOREGROUND; + mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE; + mAccessTime = System.currentTimeMillis(); + mDuration = Duration.ofMinutes(1).toMillis(); + } + + public DiscreteOpBuilder setUid(int uid) { + this.mUid = uid; + return this; + } + + public DiscreteOpBuilder setChainId(int chainId) { + this.mChainId = chainId; + return this; + } + + public DiscreteOpsSqlRegistry.DiscreteOp build() { + return new DiscreteOpsSqlRegistry.DiscreteOp(mUid, mPackageName, mAttributionTag, + mDeviceId, + mOpCode, mOpFlags, mAttributionFlags, mUidState, mChainId, mAccessTime, + mDuration); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt index 1352adef783f..ad6e467efeef 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt +++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt @@ -76,12 +76,10 @@ class OverlayReferenceMapperTests { val overlay1 = mockOverlay(1) mapper = mapper( overlayToTargetToOverlayables = mapOf( - overlay0.packageName to mapOf( - target.packageName to target.overlayables.keys - ), - overlay1.packageName to mapOf( - target.packageName to target.overlayables.keys - ) + overlay0.packageName to android.util.Pair(target.packageName, + target.overlayables.keys.first()), + overlay1.packageName to android.util.Pair(target.packageName, + target.overlayables.keys.first()) ) ) val existing = mapper.addInOrder(overlay0, overlay1) { @@ -134,42 +132,38 @@ class OverlayReferenceMapperTests { } @Test - fun overlayWithMultipleTargets() { - val target0 = mockTarget(0) - val target1 = mockTarget(1) + fun overlayWithoutTarget() { val overlay = mockOverlay() - mapper = mapper( - overlayToTargetToOverlayables = mapOf( - overlay.packageName to mapOf( - target0.packageName to target0.overlayables.keys, - target1.packageName to target1.overlayables.keys - ) - ) - ) - mapper.addInOrder(target0, target1, overlay) { - assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) - } - assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay)) - mapper.remove(target0) { - assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + mapper.addInOrder(overlay) { + assertThat(it).isEmpty() } - assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay)) - mapper.remove(target1) { - assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + // An overlay can only have visibility exposed through its target + assertEmpty() + mapper.remove(overlay) { + assertThat(it).isEmpty() } assertEmpty() } @Test - fun overlayWithoutTarget() { + fun targetWithNullOverlayable() { + val target = mockTarget() val overlay = mockOverlay() - mapper.addInOrder(overlay) { + mapper = mapper( + overlayToTargetToOverlayables = mapOf( + overlay.packageName to android.util.Pair(target.packageName, null) + ) + ) + val existing = mapper.addInOrder(overlay) { assertThat(it).isEmpty() } - // An overlay can only have visibility exposed through its target assertEmpty() - mapper.remove(overlay) { - assertThat(it).isEmpty() + mapper.addInOrder(target, existing = existing) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) + } + assertMapping(ACTOR_PACKAGE_NAME to setOf(target)) + mapper.remove(target) { + assertThat(it).containsExactly(ACTOR_PACKAGE_NAME) } assertEmpty() } @@ -219,17 +213,15 @@ class OverlayReferenceMapperTests { namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run { mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME)) }, - overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf( - mockOverlay().packageName to mapOf( - mockTarget().run { packageName to overlayables.keys } - ) - ) + overlayToTargetToOverlayables: Map<String, android.util.Pair<String, String>> = mapOf( + mockOverlay().packageName to mockTarget().run { android.util.Pair(packageName!!, + overlayables.keys.first()) }) ) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider { override fun getActorPkg(actor: String) = OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first override fun getTargetToOverlayables(pkg: AndroidPackage) = - overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap() + overlayToTargetToOverlayables[pkg.packageName] }) private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> { diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 4e030d499c25..3ef360a752f6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -112,6 +112,7 @@ import org.mockito.stubbing.Answer; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; @@ -556,6 +557,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + void injectFinishWrite(@NonNull ResilientAtomicFile file, + @NonNull FileOutputStream os) throws IOException { + file.finishWrite(os, false /* doFsVerity */); + } + + @Override void wtf(String message, Throwable th) { // During tests, WTF is fatal. fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th)); diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index c01283a236c4..0d86d4c3fa28 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -159,7 +159,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test for the first launch path, no settings file available. */ - public void FirstInitialize() { + public void testFirstInitialize() { assertResetTimes(START_TIME, START_TIME + INTERVAL); } @@ -167,7 +167,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * Test for {@link ShortcutService#getLastResetTimeLocked()} and * {@link ShortcutService#getNextResetTimeLocked()}. */ - public void UpdateAndGetNextResetTimeLocked() { + public void testUpdateAndGetNextResetTimeLocked() { assertResetTimes(START_TIME, START_TIME + INTERVAL); // Advance clock. @@ -196,7 +196,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test for the restoration from saved file. */ - public void InitializeFromSavedFile() { + public void testInitializeFromSavedFile() { mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50; assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL); @@ -220,7 +220,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Add various broken cases. } - public void LoadConfig() { + public void testLoadConfig() { mService.updateConfigurationLocked( ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123," + ConfigConstants.KEY_MAX_SHORTCUTS + "=4," @@ -261,22 +261,22 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // === Test for app side APIs === /** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */ - public void GetMaxDynamicShortcutCount() { + public void testGetMaxDynamicShortcutCount() { assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity()); } /** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */ - public void GetRemainingCallCount() { + public void testGetRemainingCallCount() { assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount()); } - public void GetIconMaxDimensions() { + public void testGetIconMaxDimensions() { assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxWidth()); assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxHeight()); } /** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */ - public void GetRateLimitResetTime() { + public void testGetRateLimitResetTime() { assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime()); mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50; @@ -284,7 +284,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime()); } - public void SetDynamicShortcuts() { + public void testSetDynamicShortcuts() { setCaller(CALLING_PACKAGE_1, USER_10); final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1); @@ -354,7 +354,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void AddDynamicShortcuts() { + public void testAddDynamicShortcuts() { setCaller(CALLING_PACKAGE_1, USER_10); final ShortcutInfo si1 = makeShortcut("shortcut1"); @@ -402,7 +402,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PushDynamicShortcut() { + public void testPushDynamicShortcut() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5," + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1"); @@ -544,7 +544,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_10)); } - public void PushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() + public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled() throws InterruptedException { mService.updateConfigurationLocked( ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500"); @@ -576,6 +576,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { Mockito.reset(mMockUsageStatsManagerInternal); for (int i = 2; i <= 10; i++) { final ShortcutInfo si = makeShortcut("s" + i); + setCaller(CALLING_PACKAGE_2, USER_10); mManager.pushDynamicShortcut(si); } verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage( @@ -595,7 +596,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { eq(CALLING_PACKAGE_2), any(), eq(USER_10)); } - public void UnlimitedCalls() { + public void testUnlimitedCalls() { setCaller(CALLING_PACKAGE_1, USER_10); final ShortcutInfo si1 = makeShortcut("shortcut1"); @@ -626,7 +627,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(3, mManager.getRemainingCallCount()); } - public void PublishWithNoActivity() { + public void testPublishWithNoActivity() { // If activity is not explicitly set, use the default one. mRunningUsers.put(USER_11, true); @@ -732,7 +733,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PublishWithNoActivity_noMainActivityInPackage() { + public void testPublishWithNoActivity_noMainActivityInPackage() { mRunningUsers.put(USER_11, true); runWithCaller(CALLING_PACKAGE_2, USER_11, () -> { @@ -751,7 +752,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void DeleteDynamicShortcuts() { + public void testDeleteDynamicShortcuts() { final ShortcutInfo si1 = makeShortcut("shortcut1"); final ShortcutInfo si2 = makeShortcut("shortcut2"); final ShortcutInfo si3 = makeShortcut("shortcut3"); @@ -792,7 +793,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(2, mManager.getRemainingCallCount()); } - public void DeleteAllDynamicShortcuts() { + public void testDeleteAllDynamicShortcuts() { final ShortcutInfo si1 = makeShortcut("shortcut1"); final ShortcutInfo si2 = makeShortcut("shortcut2"); final ShortcutInfo si3 = makeShortcut("shortcut3"); @@ -821,7 +822,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(1, mManager.getRemainingCallCount()); } - public void Icons() throws IOException { + public void testIcons() throws IOException { final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512); @@ -1035,7 +1036,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { */ } - public void CleanupDanglingBitmaps() throws Exception { + public void testCleanupDanglingBitmaps() throws Exception { assertBitmapDirectories(USER_10, EMPTY_STRINGS); assertBitmapDirectories(USER_11, EMPTY_STRINGS); @@ -1204,7 +1205,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { maxSize)); } - public void ShrinkBitmap() { + public void testShrinkBitmap() { checkShrinkBitmap(32, 32, R.drawable.black_512x512, 32); checkShrinkBitmap(511, 511, R.drawable.black_512x512, 511); checkShrinkBitmap(512, 512, R.drawable.black_512x512, 512); @@ -1227,7 +1228,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { return out.getFile(); } - public void OpenIconFileForWrite() throws IOException { + public void testOpenIconFileForWrite() throws IOException { mInjectedCurrentTimeMillis = 1000; final File p10_1_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1); @@ -1301,7 +1302,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertFalse(p11_1_3.getName().contains("_")); } - public void UpdateShortcuts() { + public void testUpdateShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), @@ -1432,7 +1433,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void UpdateShortcuts_icons() { + public void testUpdateShortcuts_icons() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1") @@ -1526,7 +1527,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ShortcutManagerGetShortcuts_shortcutTypes() { + public void testShortcutManagerGetShortcuts_shortcutTypes() { // Create 3 manifest and 3 dynamic shortcuts addManifestShortcutResource( @@ -1617,7 +1618,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s1", "s2"); } - public void CachedShortcuts() { + public void testCachedShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), @@ -1701,7 +1702,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s2"); } - public void CachedShortcuts_accessShortcutsPermission() { + public void testCachedShortcuts_accessShortcutsPermission() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"), @@ -1743,7 +1744,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3"); } - public void CachedShortcuts_canPassShortcutLimit() { + public void testCachedShortcuts_canPassShortcutLimit() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4"); @@ -1781,7 +1782,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // === Test for launcher side APIs === - public void GetShortcuts() { + public void testGetShortcuts() { // Set up shortcuts. @@ -1998,7 +1999,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { "s1", "s3"); } - public void GetShortcuts_shortcutKinds() throws Exception { + public void testGetShortcuts_shortcutKinds() throws Exception { // Create 3 manifest and 3 dynamic shortcuts addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -2109,7 +2110,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void GetShortcuts_resolveStrings() throws Exception { + public void testGetShortcuts_resolveStrings() throws Exception { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { ShortcutInfo si = new ShortcutInfo.Builder(mClientContext) .setId("id") @@ -2157,7 +2158,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void GetShortcuts_personsFlag() { + public void testGetShortcuts_personsFlag() { ShortcutInfo s = new ShortcutInfo.Builder(mClientContext, "id") .setShortLabel("label") .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class)) @@ -2205,7 +2206,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } // TODO resource - public void GetShortcutInfo() { + public void testGetShortcutInfo() { // Create shortcuts. setCaller(CALLING_PACKAGE_1); final ShortcutInfo s1_1 = makeShortcut( @@ -2280,7 +2281,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals("ABC", findById(list, "s1").getTitle()); } - public void PinShortcutAndGetPinnedShortcuts() { + public void testPinShortcutAndGetPinnedShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000); final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000); @@ -2361,7 +2362,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * This is similar to the above test, except it used "disable" instead of "remove". It also * does "enable". */ - public void DisableAndEnableShortcuts() { + public void testDisableAndEnableShortcuts() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000); final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000); @@ -2486,7 +2487,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void DisableShortcuts_thenRepublish() { + public void testDisableShortcuts_thenRepublish() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3")))); @@ -2556,7 +2557,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinShortcutAndGetPinnedShortcuts_multi() { + public void testPinShortcutAndGetPinnedShortcuts_multi() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -2832,7 +2833,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinShortcutAndGetPinnedShortcuts_assistant() { + public void testPinShortcutAndGetPinnedShortcuts_assistant() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -2888,7 +2889,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() { + public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -3477,7 +3478,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void StartShortcut() { + public void testStartShortcut() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final ShortcutInfo s1_1 = makeShortcut( @@ -3612,7 +3613,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check extra, etc } - public void LauncherCallback() throws Throwable { + public void testLauncherCallback() throws Throwable { // Disable throttling for this test. mService.updateConfigurationLocked( ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999," @@ -3778,7 +3779,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .isEmpty(); } - public void LauncherCallback_crossProfile() throws Throwable { + public void testLauncherCallback_crossProfile() throws Throwable { prepareCrossProfileDataSet(); final Handler h = new Handler(Looper.getMainLooper()); @@ -3901,7 +3902,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // === Test for persisting === - public void SaveAndLoadUser_empty() { + public void testSaveAndLoadUser_empty() { assertTrue(mManager.setDynamicShortcuts(list())); Log.i(TAG, "Saved state"); @@ -3918,7 +3919,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Try save and load, also stop/start the user. */ - public void SaveAndLoadUser() { + public void testSaveAndLoadUser() { // First, create some shortcuts and save. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16); @@ -4059,7 +4060,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields } - public void LoadCorruptedShortcuts() throws Exception { + public void testLoadCorruptedShortcuts() throws Exception { initService(); addPackage("com.android.chrome", 0, 0); @@ -4073,7 +4074,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertNull(ShortcutPackage.loadFromFile(mService, user, corruptedShortcutPackage, false)); } - public void SaveCorruptAndLoadUser() throws Exception { + public void testSaveCorruptAndLoadUser() throws Exception { // First, create some shortcuts and save. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16); @@ -4229,7 +4230,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields } - public void CleanupPackage() { + public void testCleanupPackage() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcut("s0_1")))); @@ -4506,7 +4507,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.saveDirtyInfo(); } - public void CleanupPackage_republishManifests() { + public void testCleanupPackage_republishManifests() { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_2); @@ -4574,7 +4575,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandleGonePackage_crossProfile() { + public void testHandleGonePackage_crossProfile() { // Create some shortcuts. runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( @@ -4846,7 +4847,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(expected, spi.canRestoreTo(mService, pi, true)); } - public void CanRestoreTo() { + public void testCanRestoreTo() { addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1"); addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2"); addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1"); @@ -4909,7 +4910,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi3, true, 10, true, "sig1"); } - public void HandlePackageDelete() { + public void testHandlePackageDelete() { checkHandlePackageDeleteInner((userId, packageName) -> { uninstallPackage(userId, packageName); mService.mPackageMonitor.onReceive(getTestContext(), @@ -4917,7 +4918,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageDisable() { + public void testHandlePackageDisable() { checkHandlePackageDeleteInner((userId, packageName) -> { disablePackage(userId, packageName); mService.mPackageMonitor.onReceive(getTestContext(), @@ -5049,7 +5050,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } /** Almost ame as testHandlePackageDelete, except it doesn't uninstall packages. */ - public void HandlePackageClearData() { + public void testHandlePackageClearData() { final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_32x32)); setCaller(CALLING_PACKAGE_1, USER_10); @@ -5125,7 +5126,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_11)); } - public void HandlePackageClearData_manifestRepublished() { + public void testHandlePackageClearData_manifestRepublished() { mRunningUsers.put(USER_11, true); @@ -5167,7 +5168,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageUpdate() throws Throwable { + public void testHandlePackageUpdate() throws Throwable { // Set up shortcuts and launchers. final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); @@ -5341,7 +5342,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test the case where an updated app has resource IDs changed. */ - public void HandlePackageUpdate_resIdChanged() throws Exception { + public void testHandlePackageUpdate_resIdChanged() throws Exception { final Icon icon1 = Icon.createWithResource(getTestContext(), /* res ID */ 1000); final Icon icon2 = Icon.createWithResource(getTestContext(), /* res ID */ 1001); @@ -5416,7 +5417,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageUpdate_systemAppUpdate() { + public void testHandlePackageUpdate_systemAppUpdate() { // Package1 is a system app. Package 2 is not a system app, so it's not scanned // in this test at all. @@ -5522,7 +5523,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mService.getUserShortcutsLocked(USER_10).getLastAppScanOsFingerprint()); } - public void HandlePackageChanged() { + public void testHandlePackageChanged() { final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1"); final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2"); @@ -5652,7 +5653,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HandlePackageUpdate_activityNoLongerMain() throws Throwable { + public void testHandlePackageUpdate_activityNoLongerMain() throws Throwable { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertTrue(mManager.setDynamicShortcuts(list( makeShortcutWithActivity("s1a", @@ -5738,7 +5739,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * - Unpinned dynamic shortcuts * - Bitmaps */ - public void BackupAndRestore() { + public void testBackupAndRestore() { assertFileNotExists("user-0/shortcut_dump/restore-0-start.txt"); assertFileNotExists("user-0/shortcut_dump/restore-1-payload.xml"); @@ -5759,7 +5760,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ true); } - public void BackupAndRestore_backupRestoreTwice() { + public void testBackupAndRestore_backupRestoreTwice() { prepareForBackupTest(); checkBackupAndRestore_success(/*firstRestore=*/ true); @@ -5775,7 +5776,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ false); } - public void BackupAndRestore_restoreToNewVersion() { + public void testBackupAndRestore_restoreToNewVersion() { prepareForBackupTest(); addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2); @@ -5784,7 +5785,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ true); } - public void BackupAndRestore_restoreToSuperSetSignatures() { + public void testBackupAndRestore_restoreToSuperSetSignatures() { prepareForBackupTest(); // Change package signatures. @@ -5981,7 +5982,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_publisherWrongSignature() { + public void testBackupAndRestore_publisherWrongSignature() { prepareForBackupTest(); addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature @@ -5989,7 +5990,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH); } - public void BackupAndRestore_publisherNoLongerBackupTarget() { + public void testBackupAndRestore_publisherNoLongerBackupTarget() { prepareForBackupTest(); updatePackageInfo(CALLING_PACKAGE_1, @@ -6118,7 +6119,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_launcherLowerVersion() { + public void testBackupAndRestore_launcherLowerVersion() { prepareForBackupTest(); addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version @@ -6127,7 +6128,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_success(/*firstRestore=*/ true); } - public void BackupAndRestore_launcherWrongSignature() { + public void testBackupAndRestore_launcherWrongSignature() { prepareForBackupTest(); addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature @@ -6135,7 +6136,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { checkBackupAndRestore_launcherNotRestored(true); } - public void BackupAndRestore_launcherNoLongerBackupTarget() { + public void testBackupAndRestore_launcherNoLongerBackupTarget() { prepareForBackupTest(); updatePackageInfo(LAUNCHER_1, @@ -6240,7 +6241,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_launcherAndPackageNoLongerBackupTarget() { + public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() { prepareForBackupTest(); updatePackageInfo(CALLING_PACKAGE_1, @@ -6338,7 +6339,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void BackupAndRestore_disabled() { + public void testBackupAndRestore_disabled() { prepareCrossProfileDataSet(); // Before doing backup & restore, disable s1. @@ -6403,7 +6404,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - public void BackupAndRestore_manifestRePublished() { + public void testBackupAndRestore_manifestRePublished() { // Publish two manifest shortcuts. addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -6494,7 +6495,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * logcat. * - if it has allowBackup=false, we don't touch any of the existing shortcuts. */ - public void BackupAndRestore_appAlreadyInstalledWhenRestored() { + public void testBackupAndRestore_appAlreadyInstalledWhenRestored() { // Pre-backup. Same as testBackupAndRestore_manifestRePublished(). // Publish two manifest shortcuts. @@ -6619,7 +6620,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Test for restoring the pre-P backup format. */ - public void BackupAndRestore_api27format() throws Exception { + public void testBackupAndRestore_api27format() throws Exception { final byte[] payload = readTestAsset("shortcut/shortcut_api27_backup.xml").getBytes(); addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "22222"); @@ -6657,7 +6658,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - public void SaveAndLoad_crossProfile() { + public void testSaveAndLoad_crossProfile() { prepareCrossProfileDataSet(); dumpsysOnLogcat("Before save & load"); @@ -6860,7 +6861,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .getPackageUserId()); } - public void OnApplicationActive_permission() { + public void testOnApplicationActive_permission() { assertExpectException(SecurityException.class, "Missing permission", () -> mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10)); @@ -6869,7 +6870,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10); } - public void GetShareTargets_permission() { + public void testGetShareTargets_permission() { addPackage(CHOOSER_ACTIVITY_PACKAGE, CHOOSER_ACTIVITY_UID, 10, "sig1"); mInjectedChooserActivity = ComponentName.createRelative(CHOOSER_ACTIVITY_PACKAGE, ".ChooserActivity"); @@ -6888,7 +6889,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void HasShareTargets_permission() { + public void testHasShareTargets_permission() { assertExpectException(SecurityException.class, "Missing permission", () -> mManager.hasShareTargets(CALLING_PACKAGE_1)); @@ -6897,7 +6898,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mManager.hasShareTargets(CALLING_PACKAGE_1); } - public void isSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException { + public void testisSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException { setCaller(LAUNCHER_1, USER_10); IntentFilter filter_any = new IntentFilter(); @@ -6912,18 +6913,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mManager.hasShareTargets(CALLING_PACKAGE_1); } - public void Dumpsys_crossProfile() { + public void testDumpsys_crossProfile() { prepareCrossProfileDataSet(); dumpsysOnLogcat("test1", /* force= */ true); } - public void Dumpsys_withIcons() throws IOException { - Icons(); + public void testDumpsys_withIcons() throws IOException { + testIcons(); // Dump after having some icons. dumpsysOnLogcat("test1", /* force= */ true); } - public void ManifestShortcut_publishOnUnlockUser() { + public void testManifestShortcut_publishOnUnlockUser() { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_1); @@ -7137,7 +7138,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_10)); } - public void ManifestShortcut_publishOnBroadcast() { + public void testManifestShortcut_publishOnBroadcast() { // First, no packages are installed. uninstallPackage(USER_10, CALLING_PACKAGE_1); uninstallPackage(USER_10, CALLING_PACKAGE_2); @@ -7393,7 +7394,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_missingMandatoryFields() { + public void testManifestShortcuts_missingMandatoryFields() { // Start with no apps installed. uninstallPackage(USER_10, CALLING_PACKAGE_1); uninstallPackage(USER_10, CALLING_PACKAGE_2); @@ -7462,7 +7463,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_intentDefinitions() { + public void testManifestShortcuts_intentDefinitions() { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_error_4); @@ -7604,7 +7605,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_checkAllFields() { + public void testManifestShortcuts_checkAllFields() { mService.handleUnlockUser(USER_10); // Package 1 updated, which has one valid manifest shortcut and one invalid. @@ -7709,7 +7710,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_localeChange() throws InterruptedException { + public void testManifestShortcuts_localeChange() throws InterruptedException { mService.handleUnlockUser(USER_10); // Package 1 updated, which has one valid manifest shortcut and one invalid. @@ -7813,7 +7814,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_updateAndDisabled_notPinned() { + public void testManifestShortcuts_updateAndDisabled_notPinned() { mService.handleUnlockUser(USER_10); // First, just publish a manifest shortcut. @@ -7853,7 +7854,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_updateAndDisabled_pinned() { + public void testManifestShortcuts_updateAndDisabled_pinned() { mService.handleUnlockUser(USER_10); // First, just publish a manifest shortcut. @@ -7909,7 +7910,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_duplicateInSingleActivity() { + public void testManifestShortcuts_duplicateInSingleActivity() { mService.handleUnlockUser(USER_10); // The XML has two shortcuts with the same ID. @@ -7934,7 +7935,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ManifestShortcuts_duplicateInTwoActivities() { + public void testManifestShortcuts_duplicateInTwoActivities() { mService.handleUnlockUser(USER_10); // ShortcutActivity has shortcut ms1 @@ -7986,7 +7987,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Manifest shortcuts cannot override shortcuts that were published via the APIs. */ - public void ManifestShortcuts_cannotOverrideNonManifest() { + public void testManifestShortcuts_cannotOverrideNonManifest() { mService.handleUnlockUser(USER_10); // Create a non-pinned dynamic shortcut and a non-dynamic pinned shortcut. @@ -8059,7 +8060,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Make sure the APIs won't work on manifest shortcuts. */ - public void ManifestShortcuts_immutable() { + public void testManifestShortcuts_immutable() { mService.handleUnlockUser(USER_10); // Create a non-pinned manifest shortcut, a pinned shortcut that was originally @@ -8152,7 +8153,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { /** * Make sure the APIs won't work on manifest shortcuts. */ - public void ManifestShortcuts_tooMany() { + public void testManifestShortcuts_tooMany() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8171,7 +8172,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void MaxShortcutCount_set() { + public void testMaxShortcutCount_set() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8252,7 +8253,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void MaxShortcutCount_add() { + public void testMaxShortcutCount_add() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8379,7 +8380,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void MaxShortcutCount_update() { + public void testMaxShortcutCount_update() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8470,7 +8471,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ShortcutsPushedOutByManifest() { + public void testShortcutsPushedOutByManifest() { // Change the max number of shortcuts. mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3"); @@ -8578,7 +8579,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void ReturnedByServer() { + public void testReturnedByServer() { // Package 1 updated, with manifest shortcuts. addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -8624,7 +8625,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void IsForegroundDefaultLauncher_true() { + public void testIsForegroundDefaultLauncher_true() { // random uid in the USER_10 range. final int uid = 1000024; @@ -8635,7 +8636,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } - public void IsForegroundDefaultLauncher_defaultButNotForeground() { + public void testIsForegroundDefaultLauncher_defaultButNotForeground() { // random uid in the USER_10 range. final int uid = 1000024; @@ -8645,7 +8646,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertFalse(mInternal.isForegroundDefaultLauncher("default", uid)); } - public void IsForegroundDefaultLauncher_foregroundButNotDefault() { + public void testIsForegroundDefaultLauncher_foregroundButNotDefault() { // random uid in the USER_10 range. final int uid = 1000024; @@ -8655,7 +8656,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertFalse(mInternal.isForegroundDefaultLauncher("another", uid)); } - public void ParseShareTargetsFromManifest() { + public void testParseShareTargetsFromManifest() { // These values must exactly match the content of shortcuts_share_targets.xml resource List<ShareTargetInfo> expectedValues = new ArrayList<>(); expectedValues.add(new ShareTargetInfo( @@ -8707,7 +8708,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } } - public void ShareTargetInfo_saveToXml() throws IOException, XmlPullParserException { + public void testShareTargetInfo_saveToXml() throws IOException, XmlPullParserException { List<ShareTargetInfo> expectedValues = new ArrayList<>(); expectedValues.add(new ShareTargetInfo( new ShareTargetInfo.TargetData[]{new ShareTargetInfo.TargetData( @@ -8773,7 +8774,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { } } - public void IsSharingShortcut() throws IntentFilter.MalformedMimeTypeException { + public void testIsSharingShortcut() throws IntentFilter.MalformedMimeTypeException { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), R.xml.shortcut_share_targets); @@ -8823,7 +8824,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { filter_any)); } - public void IsSharingShortcut_PinnedAndCachedOnlyShortcuts() + public void testIsSharingShortcut_PinnedAndCachedOnlyShortcuts() throws IntentFilter.MalformedMimeTypeException { addManifestShortcutResource( new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()), @@ -8880,7 +8881,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { filter_any)); } - public void AddingShortcuts_ExcludesHiddenFromLauncherShortcuts() { + public void testAddingShortcuts_ExcludesHiddenFromLauncherShortcuts() { final ShortcutInfo s1 = makeShortcutExcludedFromLauncher("s1"); final ShortcutInfo s2 = makeShortcutExcludedFromLauncher("s2"); final ShortcutInfo s3 = makeShortcutExcludedFromLauncher("s3"); @@ -8901,7 +8902,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void UpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() { + public void testUpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() { final ShortcutInfo s1 = makeShortcut("s1"); final ShortcutInfo s2 = makeShortcut("s2"); final ShortcutInfo s3 = makeShortcut("s3"); @@ -8914,7 +8915,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { }); } - public void PinHiddenShortcuts_ThrowsException() { + public void testPinHiddenShortcuts_ThrowsException() { runWithCaller(CALLING_PACKAGE_1, USER_10, () -> { assertThrown(IllegalArgumentException.class, () -> { mManager.requestPinShortcut(makeShortcutExcludedFromLauncher("s1"), null); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java index b8d554b405d1..98a4fb3c473f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java @@ -184,12 +184,12 @@ public class AppCompatResizeOverridesTest extends WindowTestsBase { void checkShouldOverrideForceResizeApp(boolean expected) { Assert.assertEquals(expected, activity().top().mAppCompatController - .getAppCompatResizeOverrides().shouldOverrideForceResizeApp()); + .getResizeOverrides().shouldOverrideForceResizeApp()); } void checkShouldOverrideForceNonResizeApp(boolean expected) { Assert.assertEquals(expected, activity().top().mAppCompatController - .getAppCompatResizeOverrides().shouldOverrideForceNonResizeApp()); + .getResizeOverrides().shouldOverrideForceNonResizeApp()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 513ba1d49258..ab9abfc4a876 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -1001,7 +1001,6 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(handleWrapper.isChanged()); assertTrue(testFlag(handle.inputConfig, InputConfig.WATCH_OUTSIDE_TOUCH)); - assertFalse(testFlag(handle.inputConfig, InputConfig.PREVENT_SPLITTING)); assertTrue(testFlag(handle.inputConfig, InputConfig.DISABLE_USER_ACTIVITY)); // The window of standard resizable task should not use surface crop as touchable region. assertFalse(handle.replaceTouchableRegionWithCrop); diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java index d49214ab718b..a9ae5f7dfc3f 100644 --- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java +++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java @@ -16,6 +16,10 @@ package com.android.server.texttospeech; +import static android.content.Context.BIND_AUTO_CREATE; +import static android.content.Context.BIND_FOREGROUND_SERVICE; +import static android.content.Context.BIND_SCHEDULE_LIKE_TOP_APP; + import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS; import android.annotation.NonNull; @@ -95,7 +99,7 @@ final class TextToSpeechManagerPerUserService extends ITextToSpeechSessionCallback callback) { super(context, new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine), - Context.BIND_AUTO_CREATE | Context.BIND_SCHEDULE_LIKE_TOP_APP, + BIND_AUTO_CREATE | BIND_SCHEDULE_LIKE_TOP_APP | BIND_FOREGROUND_SERVICE, userId, ITextToSpeechService.Stub::asInterface); mEngine = engine; diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index b5dfb631609c..e18fad3eda79 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -78,6 +78,9 @@ public interface SatelliteTransmissionUpdateCallback { /** * Called when framework receives a request to send a datagram. * + * Informs external apps that device is working on sending a datagram out and is in the process + * of checking if all the conditions required to send datagrams are met. + * * @param datagramType The type of the requested datagram. */ @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS) diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp index 449d93dd8c0b..20315561cceb 100644 --- a/tools/aapt2/cmd/Command.cpp +++ b/tools/aapt2/cmd/Command.cpp @@ -53,61 +53,67 @@ std::string GetSafePath(StringPiece arg) { void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func))); } void Command::AddRequiredFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func))); } void Command::AddOptionalFlag(StringPiece name, StringPiece description, std::optional<std::string>* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func))); } void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::vector<std::string>* value, uint32_t flags) { - auto func = [value, flags](StringPiece arg) -> bool { + auto func = [value, flags](StringPiece arg, std::ostream*) -> bool { value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg)); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func))); } void Command::AddOptionalFlagList(StringPiece name, StringPiece description, std::unordered_set<std::string>* value) { - auto func = [value](StringPiece arg) -> bool { + auto func = [value](StringPiece arg, std::ostream* out_error) -> bool { value->emplace(arg); return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func))); } void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) { - auto func = [value](StringPiece arg) -> bool { + auto func = [value](StringPiece arg, std::ostream* out_error) -> bool { *value = true; return true; }; - flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 0, func)); + flags_.emplace_back( + Flag(name, description, /* required */ false, /* num_args */ 0, std::move(func))); } void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental) { @@ -172,19 +178,74 @@ void Command::Usage(std::ostream* out) { argline = " "; } } - *out << " " << std::setw(kWidth) << std::left << "-h" - << "Displays this help menu\n"; out->flush(); } -int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_error) { +const std::string& Command::addEnvironmentArg(const Flag& flag, const char* env) { + if (*env && flag.num_args > 0) { + return environment_args_.emplace_back(flag.name + '=' + env); + } + return flag.name; +} + +// +// Looks for the flags specified in the environment and adds them to |args|. +// Expected format: +// - _AAPT2_UPPERCASE_NAME are added before all of the command line flags, so it's +// a default for the flag that may get overridden by the command line. +// - AAPT2_UPPERCASE_NAME_ are added after them, making this to be the final value +// even if there was something on the command line. +// - All dashes in the flag name get replaced with underscores, the rest of it is +// intact. +// +// E.g. +// --set-some-flag becomes either _AAPT2_SET_SOME_FLAG or AAPT2_SET_SOME_FLAG_ +// --set-param=2 is _AAPT2_SET_SOME_FLAG=2 +// +// Values get passed as it, with no processing or quoting. +// +// This way one can make sure aapt2 has the flags they need even when it is +// launched in a way they can't control, e.g. deep inside a build. +// +void Command::parseFlagsFromEnvironment(std::vector<StringPiece>& args) { + // If the first argument is a subcommand then skip it and prepend the flags past that (the root + // command should only have a single '-h' flag anyway). + const int insert_pos = args.empty() ? 0 : args.front().starts_with('-') ? 0 : 1; + + std::string env_name; + for (const Flag& flag : flags_) { + // First, the prefix version. + env_name.assign("_AAPT2_"); + // Append the uppercased flag name, skipping all dashes in front and replacing them with + // underscores later. + auto name_start = flag.name.begin(); + while (name_start != flag.name.end() && *name_start == '-') { + ++name_start; + } + std::transform(name_start, flag.name.end(), std::back_inserter(env_name), + [](char c) { return c == '-' ? '_' : toupper(c); }); + if (auto prefix_env = getenv(env_name.c_str())) { + args.insert(args.begin() + insert_pos, addEnvironmentArg(flag, prefix_env)); + } + // Now reuse the same name variable to construct a suffix version: append the + // underscore and just skip the one in front. + env_name += '_'; + if (auto suffix_env = getenv(env_name.c_str() + 1)) { + args.push_back(addEnvironmentArg(flag, suffix_env)); + } + } +} + +int Command::Execute(std::vector<StringPiece>& args, std::ostream* out_error) { TRACE_NAME_ARGS("Command::Execute", args); std::vector<std::string> file_args; + parseFlagsFromEnvironment(args); + for (size_t i = 0; i < args.size(); i++) { StringPiece arg = args[i]; if (*(arg.data()) != '-') { - // Continue parsing as the subcommand if the first argument matches one of the subcommands + // Continue parsing as a subcommand if the first argument matches one of the subcommands if (i == 0) { for (auto& subcommand : subcommands_) { if (arg == subcommand->name_ || (!subcommand->short_name_.empty() @@ -211,37 +272,67 @@ int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_err return 1; } + static constexpr auto matchShortArg = [](std::string_view arg, const Flag& flag) static { + return flag.name.starts_with("--") && + arg.compare(0, 2, std::string_view(flag.name.c_str() + 1, 2)) == 0; + }; + bool match = false; for (Flag& flag : flags_) { - // Allow both "--arg value" and "--arg=value" syntax. + // Allow both "--arg value" and "--arg=value" syntax, and look for the cases where we can + // safely deduce the "--arg" flag from the short "-a" version when there's no value expected + bool matched_current = false; if (arg.starts_with(flag.name) && (arg.size() == flag.name.size() || (flag.num_args > 0 && arg[flag.name.size()] == '='))) { - if (flag.num_args > 0) { - if (arg.size() == flag.name.size()) { - i++; - if (i >= args.size()) { - *out_error << flag.name << " missing argument.\n\n"; - Usage(out_error); - return 1; - } - arg = args[i]; - } else { - arg.remove_prefix(flag.name.size() + 1); - // Disallow empty arguments after '='. - if (arg.empty()) { - *out_error << flag.name << " has empty argument.\n\n"; - Usage(out_error); - return 1; - } + matched_current = true; + } else if (flag.num_args == 0 && matchShortArg(arg, flag)) { + matched_current = true; + // It matches, now need to make sure no other flag would match as well. + // This is really inefficient, but we don't expect to have enough flags for it to matter + // (famous last words). + for (const Flag& other_flag : flags_) { + if (&other_flag == &flag) { + continue; + } + if (matchShortArg(arg, other_flag)) { + matched_current = false; // ambiguous, skip this match + break; + } + } + } + if (!matched_current) { + continue; + } + + if (flag.num_args > 0) { + if (arg.size() == flag.name.size()) { + i++; + if (i >= args.size()) { + *out_error << flag.name << " missing argument.\n\n"; + Usage(out_error); + return 1; } - flag.action(arg); + arg = args[i]; } else { - flag.action({}); + arg.remove_prefix(flag.name.size() + 1); + // Disallow empty arguments after '='. + if (arg.empty()) { + *out_error << flag.name << " has empty argument.\n\n"; + Usage(out_error); + return 1; + } + } + if (!flag.action(arg, out_error)) { + return 1; + } + } else { + if (!flag.action({}, out_error)) { + return 1; } - flag.found = true; - match = true; - break; } + flag.found = true; + match = true; + break; } if (!match) { diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h index 1416e980ed19..767ca9b0de9f 100644 --- a/tools/aapt2/cmd/Command.h +++ b/tools/aapt2/cmd/Command.h @@ -14,10 +14,11 @@ * limitations under the License. */ -#ifndef AAPT_COMMAND_H -#define AAPT_COMMAND_H +#pragma once +#include <deque> #include <functional> +#include <memory> #include <optional> #include <ostream> #include <string> @@ -30,10 +31,17 @@ namespace aapt { class Command { public: - explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){}; + explicit Command(android::StringPiece name) : Command(name, {}) { + } explicit Command(android::StringPiece name, android::StringPiece short_name) - : name_(name), short_name_(short_name), full_subcommand_name_(name){}; + : name_(name), short_name_(short_name), full_subcommand_name_(name) { + flags_.emplace_back("--help", "Displays this help menu", false, 0, + [this](android::StringPiece arg, std::ostream* out) { + Usage(out); + return false; + }); + } Command(Command&&) = default; Command& operator=(Command&&) = default; @@ -76,41 +84,51 @@ class Command { // Parses the command line arguments, sets the flag variable values, and runs the action of // the command. If the arguments fail to parse to the command and its subcommands, then the action // will not be run and the usage will be printed instead. - int Execute(const std::vector<android::StringPiece>& args, std::ostream* outError); + int Execute(std::vector<android::StringPiece>& args, std::ostream* out_error); + + // Same, but for a temporary vector of args. + int Execute(std::vector<android::StringPiece>&& args, std::ostream* out_error) { + return Execute(args, out_error); + } // The action to preform when the command is executed. virtual int Action(const std::vector<std::string>& args) = 0; private: struct Flag { - explicit Flag(android::StringPiece name, android::StringPiece description, - const bool is_required, const size_t num_args, - std::function<bool(android::StringPiece value)>&& action) + explicit Flag(android::StringPiece name, android::StringPiece description, bool is_required, + const size_t num_args, + std::function<bool(android::StringPiece value, std::ostream* out_err)>&& action) : name(name), description(description), - is_required(is_required), + action(std::move(action)), num_args(num_args), - action(std::move(action)) { + is_required(is_required) { } - const std::string name; - const std::string description; - const bool is_required; - const size_t num_args; - const std::function<bool(android::StringPiece value)> action; + std::string name; + std::string description; + std::function<bool(android::StringPiece value, std::ostream* out_error)> action; + size_t num_args; + bool is_required; bool found = false; }; + const std::string& addEnvironmentArg(const Flag& flag, const char* env); + void parseFlagsFromEnvironment(std::vector<android::StringPiece>& args); + std::string name_; std::string short_name_; - std::string description_ = ""; + std::string description_; std::string full_subcommand_name_; std::vector<Flag> flags_; std::vector<std::unique_ptr<Command>> subcommands_; std::vector<std::unique_ptr<Command>> experimental_subcommands_; + // A collection of arguments loaded from environment variables, with stable positions + // in memory - we add them to the vector of string views so the pointers may not change, + // with or without short string buffer utilization in std::string. + std::deque<std::string> environment_args_; }; } // namespace aapt - -#endif // AAPT_COMMAND_H diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp index 20d87e0025c3..2a3cb2a0c65d 100644 --- a/tools/aapt2/cmd/Command_test.cpp +++ b/tools/aapt2/cmd/Command_test.cpp @@ -118,4 +118,45 @@ TEST(CommandTest, OptionsWithValues) { EXPECT_NE(0, command.Execute({"--flag1"s, "2"s}, &std::cerr)); } +TEST(CommandTest, ShortOptions) { + TestCommand command; + bool flag = false; + command.AddOptionalSwitch("--flag", "", &flag); + + ASSERT_EQ(0, command.Execute({"--flag"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // Short version of a switch should work. + flag = false; + ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // Ambiguous names shouldn't parse via short options. + command.AddOptionalSwitch("--flag-2", "", &flag); + ASSERT_NE(0, command.Execute({"-f"s}, &std::cerr)); + + // But when we have a proper flag like that it should still work. + flag = false; + command.AddOptionalSwitch("-f", "", &flag); + ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // A regular short flag works fine as well. + flag = false; + command.AddOptionalSwitch("-d", "", &flag); + ASSERT_EQ(0, command.Execute({"-d"s}, &std::cerr)); + EXPECT_TRUE(flag); + + // A flag with a value only works via its long name syntax. + std::optional<std::string> val; + command.AddOptionalFlag("--with-val", "", &val); + ASSERT_EQ(0, command.Execute({"--with-val"s, "1"s}, &std::cerr)); + EXPECT_TRUE(val); + EXPECT_STREQ("1", val->c_str()); + + // Make sure the flags that require a value can't be parsed via short syntax, -w=blah + // looks weird. + ASSERT_NE(0, command.Execute({"-w"s, "2"s}, &std::cerr)); +} + } // namespace aapt
\ No newline at end of file |