diff options
174 files changed, 2603 insertions, 1473 deletions
diff --git a/Android.bp b/Android.bp index f8907f3d82e8..258440f24084 100644 --- a/Android.bp +++ b/Android.bp @@ -98,6 +98,7 @@ filegroup { ":android.frameworks.location.altitude-V2-java-source", ":android.hardware.biometrics.common-V4-java-source", ":android.hardware.biometrics.fingerprint-V5-java-source", + ":android.hardware.biometrics.fingerprint.virtualhal-java-source", ":android.hardware.biometrics.face-V4-java-source", ":android.hardware.gnss-V2-java-source", ":android.hardware.graphics.common-V3-java-source", diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index 52670a2570d3..dd0d1b6285de 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -10,14 +10,10 @@ ] }, { - "name": "CtsBRSTestCases", - "options": [ - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] + "name": "CtsBRSTestCases" }, { - "name": "FrameworksServicesTests_com_android_server_usage_Presubmit" + "name": "FrameworksServicesTests_com_android_server_usage" } ], "postsubmit": [ diff --git a/boot/hiddenapi/hiddenapi-unsupported.txt b/boot/hiddenapi/hiddenapi-unsupported.txt index adcc3df2d7fe..70e5a68745bc 100644 --- a/boot/hiddenapi/hiddenapi-unsupported.txt +++ b/boot/hiddenapi/hiddenapi-unsupported.txt @@ -183,7 +183,6 @@ Landroid/view/autofill/IAutoFillManager$Stub$Proxy;-><init>(Landroid/os/IBinder; Landroid/view/autofill/IAutoFillManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/view/autofill/IAutoFillManager; Landroid/view/IAppTransitionAnimationSpecsFuture$Stub;-><init>()V Landroid/view/IDockedStackListener$Stub;-><init>()V -Landroid/view/IRecentsAnimationRunner$Stub;-><init>()V Landroid/view/IRemoteAnimationRunner$Stub;-><init>()V Landroid/view/IRotationWatcher$Stub;-><init>()V Landroid/view/IWindow$Stub;-><init>()V diff --git a/core/api/current.txt b/core/api/current.txt index 5e8febebee0e..9875c0276bf0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -32771,6 +32771,7 @@ package android.os { field @NonNull public static final String RELEASE_OR_PREVIEW_DISPLAY; field @Deprecated public static final String SDK; field public static final int SDK_INT; + field @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static final int SDK_MINOR_INT; field public static final String SECURITY_PATCH; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index ede4a8957b6f..fb425a99732a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -18156,9 +18156,17 @@ package android.view { field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0 } + @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public static class WindowManager.InsetsParams { + ctor public WindowManager.InsetsParams(int); + method @Nullable public android.graphics.Insets getInsetsSize(); + method public int getType(); + method @NonNull public android.view.WindowManager.InsetsParams setInsetsSize(@Nullable android.graphics.Insets); + } + public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { method public final long getUserActivityTimeout(); method public boolean isSystemApplicationOverlay(); + method @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public void setInsetsParams(@NonNull java.util.List<android.view.WindowManager.InsetsParams>); method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean); method public final void setUserActivityTimeout(long); field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000 diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index b4fb4803a2b9..7273e64846c0 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -247,6 +247,14 @@ public class ActivityManager { @GuardedBy("mMemoryInfoCache") private static final MemoryInfo mRateLimitedMemInfo = new MemoryInfo(); + /** Rate-Limiting cache that allows no more than 200 calls to the service per second. */ + @GuardedBy("mMyMemoryStateCache") + private static final RateLimitingCache<RunningAppProcessInfo> mMyMemoryStateCache = + new RateLimitingCache<>(10, 2); + /** Used to store cached results for rate-limited calls to getMyMemoryState(). */ + @GuardedBy("mMyMemoryStateCache") + private static final RunningAppProcessInfo mRateLimitedMemState = new RunningAppProcessInfo(); + /** * Query handler for mGetCurrentUserIdCache - returns a cached value of the current foreground * user id if the backstage_power/android.app.cache_get_current_user_id flag is enabled. @@ -4223,6 +4231,23 @@ public class ActivityManager { lastActivityTime = source.readLong(); } + /** + * Note: only fields that are updated in ProcessList.fillInProcMemInfoLOSP() are copied. + * @hide + */ + public void copyTo(RunningAppProcessInfo other) { + other.pid = pid; + other.uid = uid; + other.flags = flags; + other.lastTrimLevel = lastTrimLevel; + other.importance = importance; + other.lru = lru; + other.importanceReasonCode = importanceReasonCode; + other.processState = processState; + other.isFocused = isFocused; + other.lastActivityTime = lastActivityTime; + } + public static final @android.annotation.NonNull Creator<RunningAppProcessInfo> CREATOR = new Creator<RunningAppProcessInfo>() { public RunningAppProcessInfo createFromParcel(Parcel source) { @@ -4854,7 +4879,21 @@ public class ActivityManager { * {@link RunningAppProcessInfo#lru}, and * {@link RunningAppProcessInfo#importanceReasonCode}. */ - static public void getMyMemoryState(RunningAppProcessInfo outState) { + public static void getMyMemoryState(RunningAppProcessInfo outState) { + if (Flags.rateLimitGetMyMemoryState()) { + synchronized (mMyMemoryStateCache) { + mMyMemoryStateCache.get(() -> { + getMyMemoryStateInternal(mRateLimitedMemState); + return mRateLimitedMemState; + }); + mRateLimitedMemState.copyTo(outState); + } + } else { + getMyMemoryStateInternal(outState); + } + } + + private static void getMyMemoryStateInternal(RunningAppProcessInfo outState) { try { getService().getMyMemoryState(outState); } catch (RemoteException e) { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 7a36fbb55dc4..7a33ab748597 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1593,6 +1593,17 @@ public class Notification implements Parcelable */ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) public static final String EXTRA_ENROUTE_LARGE_ICON_SUBTEXT = "android.enrouteLargeIconSubText"; + + /** + * {@link #extras} key: {@link Icon} of an image used as a thumb icon on + * {@link Notification} progress bar for {@link EnRouteStyle} notifications. + * This extra is an {@code Icon}. + * @hide + */ + @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) + public static final String EXTRA_ENROUTE_PROGRESS_THUMB_ICON = + "android.enrouteProgressThumbIcon"; + /** * {@link #extras} key: whether the notification should be colorized as * supplied to {@link Builder#setColorized(boolean)}. @@ -3052,6 +3063,8 @@ public class Notification implements Parcelable if (Flags.apiRichOngoing()) { visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class)); + visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_PROGRESS_THUMB_ICON, + Icon.class)); } if (mBubbleMetadata != null) { @@ -11015,6 +11028,9 @@ public class Notification implements Parcelable @Nullable private CharSequence mLargeIconSubText = null; + @Nullable + private Icon mProgressThumbIcon = null; + public EnRouteStyle() { } @@ -11058,6 +11074,25 @@ public class Notification implements Parcelable return this; } + /** + * Returns the progress thumb icon. + * @see EnRouteStyle#setProgressThumbIcon + */ + @Nullable + public Icon getProgressThumbIcon() { + return mProgressThumbIcon; + } + + /** + * Optional icon to be used as a progress thumb. + */ + @NonNull + public EnRouteStyle setProgressThumbIcon(@Nullable Icon progressThumbIcon) { + mProgressThumbIcon = progressThumbIcon; + return this; + } + + /** * @hide */ @@ -11069,7 +11104,8 @@ public class Notification implements Parcelable final EnRouteStyle enRouteStyle = (EnRouteStyle) other; return !Objects.equals(mOverlayIcon, enRouteStyle.mOverlayIcon) - || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText); + || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText) + || !Objects.equals(mProgressThumbIcon, enRouteStyle.mProgressThumbIcon); } /** @@ -11080,6 +11116,7 @@ public class Notification implements Parcelable super.addExtras(extras); extras.putParcelable(EXTRA_ENROUTE_OVERLAY_ICON, mOverlayIcon); extras.putCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT, mLargeIconSubText); + extras.putParcelable(EXTRA_ENROUTE_PROGRESS_THUMB_ICON, mProgressThumbIcon); } /** @@ -11090,6 +11127,8 @@ public class Notification implements Parcelable super.restoreFromExtras(extras); mOverlayIcon = extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class); mLargeIconSubText = extras.getCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT); + mProgressThumbIcon = + extras.getParcelable(EXTRA_ENROUTE_PROGRESS_THUMB_ICON, Icon.class); } /** @@ -11101,6 +11140,10 @@ public class Notification implements Parcelable if (mOverlayIcon != null) { mOverlayIcon.convertToAshmem(); } + + if (mProgressThumbIcon != null) { + mProgressThumbIcon.convertToAshmem(); + } } /** diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 1b29b7a294df..32e9542e91a7 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -761,14 +761,22 @@ public final class NotificationChannel implements Parcelable { this.mVibrationEnabled = effect != null; this.mVibrationEffect = effect; if (Flags.notifChannelCropVibrationEffects() && effect != null) { - // Try converting to a vibration pattern and trimming that array. If not convertible - // to a pattern directly, try trimming the vibration effect if possible and storing - // that version instead. long[] pattern = effect.computeCreateWaveformOffOnTimingsOrNull(); if (pattern != null) { - setVibrationPattern(pattern); + // If this effect has an equivalent pattern, AND the pattern needs to be truncated + // due to being too long, we delegate to setVibrationPattern to re-generate the + // effect as well. Otherwise, we use the effect (already set above) and converted + // pattern directly. + if (pattern.length > MAX_VIBRATION_LENGTH) { + setVibrationPattern(pattern); + } else { + this.mVibrationPattern = pattern; + } } else { + // If not convertible to a pattern directly, try trimming the vibration effect if + // possible and storing that version instead. this.mVibrationEffect = getTrimmedVibrationEffect(mVibrationEffect); + this.mVibrationPattern = null; } } else { this.mVibrationPattern = diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index c0c81df465e2..38bd576d607a 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -136,3 +136,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "backstage_power" + name: "rate_limit_get_my_memory_state" + description: "Rate limit calls to getMyMemoryState using a cache" + is_fixed_read_only: true + bug: "365182205" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index c4bfae98e33d..f5c5a11f45fb 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -61,6 +61,20 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { return RUNTIME_SCHEMA_TYPE + RUNTIME_SCHEMA_TYPE_SEPARATOR + Objects.requireNonNull(pkg); } + /** Returns the package name from the runtime metadata schema name. */ + @NonNull + public static String getPackageNameFromSchema(String metadataSchemaType) { + String[] split = metadataSchemaType.split(RUNTIME_SCHEMA_TYPE_SEPARATOR); + if (split.length > 2) { + throw new IllegalArgumentException( + "Invalid schema type: " + metadataSchemaType + " for app function runtime"); + } + if (split.length < 2) { + return APP_FUNCTION_INDEXER_PACKAGE; + } + return split[1]; + } + /** Returns the document id for an app function's runtime metadata. */ public static String getDocumentIdForAppFunction( @NonNull String pkg, @NonNull String functionId) { diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java index 926cc9a3642c..085e0a47d356 100644 --- a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java +++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java @@ -39,6 +39,8 @@ public class AppFunctionStaticMetadataHelper { public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault"; public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions"; + public static final String PROPERTY_FUNCTION_ID = "functionId"; + public static final String PROPERTY_PACKAGE_NAME = "packageName"; // These are constants that has to be kept the same with {@code // com.android.server.appsearch.appsindexer.appsearchtypes.AppSearchHelper}. diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 748260bc8d5f..e9fa3e15fe05 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -43,6 +43,7 @@ flag { name: "virtual_display_insets" description: "APIs for specifying virtual display insets (via cutout)" bug: "350007135" + is_exported: true } flag { @@ -88,6 +89,7 @@ flag { name: "virtual_display_rotation_api" description: "API for on-demand rotation of virtual displays" bug: "291748430" + is_exported: true } flag { @@ -110,6 +112,7 @@ flag { name: "device_aware_display_power" description: "Device awareness in power and display APIs" bug: "285020111" + is_exported: true } flag { @@ -125,4 +128,12 @@ flag { namespace: "virtual_devices" description: "Allow for status bar and insets on virtual devices" bug: "350007866" + is_exported: true +} + +flag { + namespace: "virtual_devices" + name: "camera_timestamp_from_surface" + description: "Pass the surface timestamp to the capture result" + bug: "351341245" } diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java index 43c0da9bd8ed..48c5887d80d0 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java +++ b/core/java/android/hardware/fingerprint/FingerprintSensorConfigurations.java @@ -23,12 +23,14 @@ import android.annotation.Nullable; import android.content.Context; import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.biometrics.fingerprint.SensorProps; +import android.hardware.biometrics.fingerprint.virtualhal.IVirtualHal; import android.os.Binder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.HashMap; @@ -162,6 +164,43 @@ public class FingerprintSensorConfigurations implements Parcelable { dest.writeMap(mSensorPropsMap); } + + /** + * Remap fqName of VHAL because the `virtual` instance is registered + * with IVirtulalHal now (IFingerprint previously) + * @param fqName fqName to be translated + * @return real fqName + */ + public static String remapFqName(String fqName) { + if (!fqName.contains(IFingerprint.DESCRIPTOR + "/virtual")) { + return fqName; //no remap needed for real hardware HAL + } else { + //new Vhal instance name + return fqName.replace("IFingerprint", "virtualhal.IVirtualHal"); + } + } + + /** + * @param fqName aidl interface instance name + * @return aidl interface + */ + public static IFingerprint getIFingerprint(String fqName) { + if (fqName.contains("virtual")) { + String fqNameMapped = remapFqName(fqName); + Slog.i(TAG, "getIFingerprint fqName is mapped: " + fqName + "->" + fqNameMapped); + try { + IVirtualHal vhal = IVirtualHal.Stub.asInterface( + Binder.allowBlocking(ServiceManager.waitForService(fqNameMapped))); + return vhal.getFingerprintHal(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception in vhal.getFingerprintHal() call" + fqNameMapped); + } + } + + return IFingerprint.Stub.asInterface( + Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))); + } + /** * Returns fingerprint sensor props for the HAL {@param instance}. */ @@ -176,8 +215,7 @@ public class FingerprintSensorConfigurations implements Parcelable { try { final String fqName = IFingerprint.DESCRIPTOR + "/" + instance; - final IFingerprint fp = IFingerprint.Stub.asInterface(Binder.allowBlocking( - ServiceManager.waitForDeclaredService(fqName))); + final IFingerprint fp = getIFingerprint(fqName); if (fp != null) { props = fp.getSensorProps(); } else { diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index fcd6c31d7d18..4478592ae8a5 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -117,3 +117,10 @@ flag { description: "Allow configurable timeout before key repeat and repeat delay rate for key repeats" bug: "336585002" } + +flag { + name: "mouse_reverse_vertical_scrolling" + namespace: "input" + description: "Controls whether external mouse vertical scrolling can be reversed" + bug: "352598211" +} diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 30d2dec8b4c4..a8267d1c9d8c 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -17,6 +17,7 @@ package android.os; import android.Manifest; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -28,6 +29,7 @@ import android.app.Application; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.ravenwood.annotation.RavenwoodKeepWholeClass; +import android.sdk.Flags; import android.sysprop.DeviceProperties; import android.sysprop.SocProperties; import android.sysprop.TelephonyProperties; @@ -399,12 +401,35 @@ public class Build { * device. This value never changes while a device is booted, but it may * increase when the hardware manufacturer provides an OTA update. * <p> + * Together with {@link SDK_MINOR_INT}, this constant defines the + * <pre>major.minor</pre> version of Android. <pre>SDK_INT</pre> is + * increased and <pre>SDK_MINOR_INT</pre> is set to 0 on new Android + * dessert releases. Between these, Android may also release so called + * minor releases where <pre>SDK_INT</pre> remains unchanged and + * <pre>SDK_MINOR_INT</pre> is increased. Minor releases can add new + * APIs, and have stricter guarantees around backwards compatibility + * (e.g. no changes gated by <pre>targetSdkVersion</pre>) compared to + * major releases. + * <p> * Possible values are defined in {@link Build.VERSION_CODES}. */ public static final int SDK_INT = SystemProperties.getInt( "ro.build.version.sdk", 0); /** + * The minor SDK version of the software currently running on this hardware + * device. This value never changes while a device is booted, but it may + * increase when the hardware manufacturer provides an OTA update. + * <p> + * Together with {@link SDK_INT}, this constant defines the + * <pre>major.minor</pre> version of Android. See {@link SDK_INT} for + * more information. + */ + @FlaggedApi(Flags.FLAG_MAJOR_MINOR_VERSIONING_SCHEME) + public static final int SDK_MINOR_INT = SystemProperties.getInt( + "ro.build.version.sdk_minor", 0); + + /** * The SDK version of the software that <em>initially</em> shipped on * this hardware device. It <em>never</em> changes during the lifetime * of the device, even when {@link #SDK_INT} increases due to an OTA diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java index 0ed1ab6c8d72..4c9a02c1fc49 100644 --- a/core/java/android/os/SystemClock.java +++ b/core/java/android/os/SystemClock.java @@ -109,6 +109,7 @@ public final class SystemClock { private static final String TAG = "SystemClock"; private static volatile IAlarmManager sIAlarmManager; + private static volatile ITimeDetectorService sITimeDetectorService; /** * Since {@code nanoTime()} is arbitrary, anchor our Ravenwood clocks against it. @@ -188,6 +189,14 @@ public final class SystemClock { return sIAlarmManager; } + private static ITimeDetectorService getITimeDetectorService() { + if (sITimeDetectorService == null) { + sITimeDetectorService = ITimeDetectorService.Stub + .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE)); + } + return sITimeDetectorService; + } + /** * Returns milliseconds since boot, not counting time spent in deep sleep. * @@ -314,15 +323,6 @@ public final class SystemClock { } /** - * @see #currentNetworkTimeMillis(ITimeDetectorService) - * @hide - */ - public static long currentNetworkTimeMillis() { - return currentNetworkTimeMillis(ITimeDetectorService.Stub - .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE))); - } - - /** * Returns milliseconds since January 1, 1970 00:00:00.0 UTC, synchronized * using a remote network source outside the device. * <p> @@ -346,29 +346,29 @@ public final class SystemClock { * @throws DateTimeException when no network time can be provided. * @hide */ - public static long currentNetworkTimeMillis( - ITimeDetectorService timeDetectorService) { - if (timeDetectorService != null) { - UnixEpochTime time; - try { - time = timeDetectorService.latestNetworkTime(); - } catch (ParcelableException e) { - e.maybeRethrow(DateTimeException.class); - throw new RuntimeException(e); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - - if (time == null) { - // This is not expected. - throw new DateTimeException("Network based time is not available."); - } - long currentMillis = elapsedRealtime(); - long deltaMs = currentMillis - time.getElapsedRealtimeMillis(); - return time.getUnixEpochTimeMillis() + deltaMs; - } else { + public static long currentNetworkTimeMillis() { + ITimeDetectorService timeDetectorService = getITimeDetectorService(); + if (timeDetectorService == null) { throw new RuntimeException(new DeadSystemException()); } + + UnixEpochTime time; + try { + time = timeDetectorService.latestNetworkTime(); + } catch (ParcelableException e) { + e.maybeRethrow(DateTimeException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + if (time == null) { + // This is not expected. + throw new DateTimeException("Network based time is not available."); + } + + long currentMillis = elapsedRealtime(); + long deltaMs = currentMillis - time.getElapsedRealtimeMillis(); + return time.getUnixEpochTimeMillis() + deltaMs; } /** @@ -396,14 +396,9 @@ public final class SystemClock { */ public static @NonNull Clock currentNetworkTimeClock() { return new SimpleClock(ZoneOffset.UTC) { - private ITimeDetectorService mSvc; @Override public long millis() { - if (mSvc == null) { - mSvc = ITimeDetectorService.Stub - .asInterface(ServiceManager.getService(Context.TIME_DETECTOR_SERVICE)); - } - return SystemClock.currentNetworkTimeMillis(mSvc); + return SystemClock.currentNetworkTimeMillis(); } }; } diff --git a/core/java/android/text/ClientFlags.java b/core/java/android/text/ClientFlags.java index 5f2823b7630f..c2ad508c2b44 100644 --- a/core/java/android/text/ClientFlags.java +++ b/core/java/android/text/ClientFlags.java @@ -28,20 +28,6 @@ import com.android.text.flags.Flags; */ public class ClientFlags { /** - * @see Flags#noBreakNoHyphenationSpan() - */ - public static boolean noBreakNoHyphenationSpan() { - return TextFlags.isFeatureEnabled(Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN); - } - - /** - * @see Flags#fixLineHeightForLocale() - */ - public static boolean fixLineHeightForLocale() { - return TextFlags.isFeatureEnabled(Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE); - } - - /** * @see Flags#fixMisalignedContextMenu() */ public static boolean fixMisalignedContextMenu() { diff --git a/core/java/android/text/TextFlags.java b/core/java/android/text/TextFlags.java index 8ee2ad0ef35e..076721f629ed 100644 --- a/core/java/android/text/TextFlags.java +++ b/core/java/android/text/TextFlags.java @@ -55,8 +55,6 @@ public final class TextFlags { * List of text flags to be transferred to the application process. */ public static final String[] TEXT_ACONFIGS_FLAGS = { - Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN, - Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE, Flags.FLAG_FIX_MISALIGNED_CONTEXT_MENU, }; @@ -66,8 +64,6 @@ public final class TextFlags { * The order must be the same to the TEXT_ACONFIG_FLAGS. */ public static final boolean[] TEXT_ACONFIG_DEFAULT_VALUE = { - Flags.noBreakNoHyphenationSpan(), - Flags.fixLineHeightForLocale(), Flags.fixMisalignedContextMenu(), }; diff --git a/core/java/android/text/flags/24Q3.aconfig b/core/java/android/text/flags/24Q3.aconfig new file mode 100644 index 000000000000..7035fc842a48 --- /dev/null +++ b/core/java/android/text/flags/24Q3.aconfig @@ -0,0 +1,54 @@ +package: "com.android.text.flags" +container: "system" + +# This aconfig file contains released flags in 24Q3 those cannot be removed. + +flag { + name: "use_bounds_for_width" + is_exported: true + namespace: "text" + description: "Feature flag for preventing horizontal clipping." + bug: "63938206" +} + +flag { + name: "word_style_auto" + is_exported: true + namespace: "text" + description: "A feature flag that implements line break word style auto." + bug: "280005585" +} + +flag { + name: "letter_spacing_justification" + is_exported: true + namespace: "text" + description: "A feature flag that implement inter character justification." + bug: "283193133" +} + +flag { + name: "fix_line_height_for_locale" + is_exported: true + namespace: "text" + description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin" + bug: "303326708" +} + +flag { + name: "new_fonts_fallback_xml" + is_exported: true + namespace: "text" + description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" + # Make read only, as it could be used before the Settings provider is initialized. + is_fixed_read_only: true + bug: "281769620" +} + +flag { + name: "no_break_no_hyphenation_span" + is_exported: true + namespace: "text" + description: "A feature flag that adding new spans that prevents line breaking and hyphenation." + bug: "283193586" +} diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 02f8779a4b46..3c61f4f5a33c 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -2,32 +2,6 @@ package: "com.android.text.flags" container: "system" flag { - name: "new_fonts_fallback_xml" - is_exported: true - namespace: "text" - description: "Feature flag for deprecating fonts.xml. By setting true for this feature flag, the new font configuration XML, /system/etc/font_fallback.xml is used. The new XML has a new syntax and flexibility of variable font declarations, but it is not compatible with the apps that reads fonts.xml. So, fonts.xml is maintained as a subset of the font_fallback.xml" - # Make read only, as it could be used before the Settings provider is initialized. - is_fixed_read_only: true - bug: "281769620" -} - -flag { - name: "fix_line_height_for_locale" - is_exported: true - namespace: "text" - description: "Feature flag that preserve the line height of the TextView and EditText even if the the locale is different from Latin" - bug: "303326708" -} - -flag { - name: "no_break_no_hyphenation_span" - is_exported: true - namespace: "text" - description: "A feature flag that adding new spans that prevents line breaking and hyphenation." - bug: "283193586" -} - -flag { name: "use_optimized_boottime_font_loading" namespace: "text" description: "Feature flag ensuring that font is loaded once and asynchronously." @@ -51,30 +25,6 @@ flag { } flag { - name: "use_bounds_for_width" - is_exported: true - namespace: "text" - description: "Feature flag for preventing horizontal clipping." - bug: "63938206" -} - -flag { - name: "word_style_auto" - is_exported: true - namespace: "text" - description: "A feature flag that implements line break word style auto." - bug: "280005585" -} - -flag { - name: "letter_spacing_justification" - is_exported: true - namespace: "text" - description: "A feature flag that implement inter character justification." - bug: "283193133" -} - -flag { name: "escape_clears_focus" namespace: "text" description: "Feature flag for clearing focus when the escape key is pressed." diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 67a207e34a1a..4fe11e6ce274 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -107,6 +107,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; @@ -2371,7 +2372,7 @@ public interface WindowManager extends ViewManager { public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15; /** - * Window type: the drag-and-drop pseudowindow. There is only one + * Window type: the drag-and-drop pseudowindow. There is only one * drag layer (at most), and it is placed on top of all other windows. * In multiuser systems shows only on the owning user's window. * @hide @@ -2381,7 +2382,7 @@ public interface WindowManager extends ViewManager { /** * Window type: panel that slides out from over the status bar * In multiuser systems shows on all users' windows. These windows - * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL} + * are displayed on top of the status bar and any {@link #TYPE_STATUS_BAR_PANEL} * windows. * @hide */ @@ -4649,6 +4650,30 @@ public interface WindowManager extends ViewManager { public InsetsFrameProvider[] providedInsets; /** + * Sets the insets to be provided by the window. + * + * @param insetsParams The parameters for the insets to be provided by the window. + * + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_STATUS_BAR_AND_INSETS) + @SystemApi + public void setInsetsParams(@NonNull List<InsetsParams> insetsParams) { + if (insetsParams.isEmpty()) { + providedInsets = null; + } else { + providedInsets = new InsetsFrameProvider[insetsParams.size()]; + for (int i = 0; i < insetsParams.size(); ++i) { + final InsetsParams params = insetsParams.get(i); + providedInsets[i] = + new InsetsFrameProvider(/* owner= */ this, /* index= */ i, + params.getType()) + .setInsetsSize(params.getInsetsSize()); + } + } + } + + /** * Specifies which {@link InsetsType}s should be forcibly shown. The types shown by this * method won't affect the app's layout. This field only takes effects if the caller has * {@link android.Manifest.permission#STATUS_BAR_SERVICE} or the caller has the same uid as @@ -6117,6 +6142,55 @@ public interface WindowManager extends ViewManager { } /** + * Specifies the parameters of the insets provided by a window. + * + * @see WindowManager.LayoutParams#setInsetsParams(List) + * @see android.graphics.Insets + * + * @hide + */ + @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_STATUS_BAR_AND_INSETS) + @SystemApi + public static class InsetsParams { + + private final @InsetsType int mType; + private @Nullable Insets mInsets; + + /** + * Creates an instance of InsetsParams. + * + * @param type the type of insets to provide, e.g. {@link WindowInsets.Type#statusBars()}. + * @see WindowInsets.Type + */ + public InsetsParams(@InsetsType int type) { + mType = type; + } + + /** + * Sets the size of the provided insets. If {@code null}, then the provided insets will + * have the same size as the window frame. + */ + public @NonNull InsetsParams setInsetsSize(@Nullable Insets insets) { + mInsets = insets; + return this; + } + + /** + * Returns the type of provided insets. + */ + public @InsetsType int getType() { + return mType; + } + + /** + * Returns the size of the provided insets. + */ + public @Nullable Insets getInsetsSize() { + return mInsets; + } + } + + /** * Holds the WM lock for the specified amount of milliseconds. * Intended for use by the tests that need to imitate lock contention. * The token should be obtained by diff --git a/core/java/android/webkit/UserPackage.java b/core/java/android/webkit/UserPackage.java index 1da2af459348..b18dbbc21cda 100644 --- a/core/java/android/webkit/UserPackage.java +++ b/core/java/android/webkit/UserPackage.java @@ -15,12 +15,14 @@ */ package android.webkit; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; +import android.content.pm.PackageManager; import android.os.Build; +import android.os.UserHandle; import android.os.UserManager; import java.util.ArrayList; @@ -31,30 +33,31 @@ import java.util.List; * @hide */ public class UserPackage { - private final UserInfo mUserInfo; + private final UserHandle mUser; private final PackageInfo mPackageInfo; public static final int MINIMUM_SUPPORTED_SDK = Build.VERSION_CODES.TIRAMISU; - public UserPackage(UserInfo user, PackageInfo packageInfo) { - this.mUserInfo = user; - this.mPackageInfo = packageInfo; + public UserPackage(@NonNull UserHandle user, @Nullable PackageInfo packageInfo) { + mUser = user; + mPackageInfo = packageInfo; } /** * Returns a list of (User,PackageInfo) pairs corresponding to the PackageInfos for all * device users for the package named {@param packageName}. */ - public static List<UserPackage> getPackageInfosAllUsers(Context context, - String packageName, int packageFlags) { - List<UserInfo> users = getAllUsers(context); + public static @NonNull List<UserPackage> getPackageInfosAllUsers(@NonNull Context context, + @NonNull String packageName, int packageFlags) { + UserManager userManager = context.getSystemService(UserManager.class); + List<UserHandle> users = userManager.getUserHandles(false); List<UserPackage> userPackages = new ArrayList<UserPackage>(users.size()); - for (UserInfo user : users) { + for (UserHandle user : users) { + PackageManager pm = context.createContextAsUser(user, 0).getPackageManager(); PackageInfo packageInfo = null; try { - packageInfo = context.getPackageManager().getPackageInfoAsUser( - packageName, packageFlags, user.id); - } catch (NameNotFoundException e) { + packageInfo = pm.getPackageInfo(packageName, packageFlags); + } catch (PackageManager.NameNotFoundException e) { } userPackages.add(new UserPackage(user, packageInfo)); } @@ -88,18 +91,11 @@ public class UserPackage { return packageInfo.applicationInfo.targetSdkVersion >= MINIMUM_SUPPORTED_SDK; } - public UserInfo getUserInfo() { - return mUserInfo; + public UserHandle getUser() { + return mUser; } public PackageInfo getPackageInfo() { return mPackageInfo; } - - - private static List<UserInfo> getAllUsers(Context context) { - UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE); - return userManager.getUsers(); - } - } diff --git a/core/java/com/android/internal/jank/flags.aconfig b/core/java/com/android/internal/jank/flags.aconfig index b6b8bc5ac44c..82f50ae848b3 100644 --- a/core/java/com/android/internal/jank/flags.aconfig +++ b/core/java/com/android/internal/jank/flags.aconfig @@ -3,7 +3,7 @@ container: "system" flag { name: "use_sf_frame_duration" - namespace: "android_platform_window_surfaces" + namespace: "window_surfaces" description: "Whether to get the frame duration from SurfaceFlinger, or HWUI" bug: "354763298" is_fixed_read_only: true diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 99cbf0504be6..41599aea1a80 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -299,11 +299,6 @@ android_ravenwood_test { auto_gen_config: true, } -FLAKY_OR_IGNORED = [ - "androidx.test.filters.FlakyTest", - "org.junit.Ignore", -] - test_module_config { name: "FrameworksCoreTests_Presubmit", base: "FrameworksCoreTests", @@ -312,7 +307,6 @@ test_module_config { "device-platinum-tests", ], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -337,7 +331,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["android.content.ContextTest"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -348,7 +341,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["android.app.KeyguardManagerTest"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -359,7 +351,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["android.app.PropertyInvalidatedCacheTests"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -374,7 +365,6 @@ test_module_config { "android.content.ComponentCallbacksControllerTest", "android.content.ContextWrapperTest", ], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -385,7 +375,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["android.database.sqlite.SQLiteRawStatementTest"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -397,7 +386,6 @@ test_module_config { ], include_filters: ["android.net"], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -510,7 +498,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["com.android.internal.jank"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -569,7 +556,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["com.android.internal.util.LatencyTrackerTest"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -580,7 +566,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["android.content.ContentCaptureOptionsTest"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -602,7 +587,6 @@ test_module_config { ], include_filters: ["android.content.pm."], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -614,7 +598,6 @@ test_module_config { ], include_filters: ["android.content.pm."], include_annotations: ["android.platform.test.annotations.Postsubmit"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -642,7 +625,6 @@ test_module_config { ], include_filters: ["android.content.res."], include_annotations: ["android.platform.test.annotations.Postsubmit"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -672,7 +654,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["android.view.contentcapture"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -683,7 +664,6 @@ test_module_config { "device-platinum-tests", ], include_filters: ["android.view.contentprotection"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -695,7 +675,6 @@ test_module_config { ], include_filters: ["com.android.internal.content."], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -709,21 +688,6 @@ test_module_config { } test_module_config { - name: "FrameworksCoreTests_accessibility_NO_FLAKES", - base: "FrameworksCoreTests", - test_suites: [ - "device-tests", - "device-platinum-tests", - ], - include_filters: [ - "com.android.internal.accessibility", - "android.accessibilityservice", - "android.view.accessibility", - ], - exclude_annotations: ["androidx.test.filters.FlakyTest"], -} - -test_module_config { name: "FrameworksCoreTests_accessibility", base: "FrameworksCoreTests", test_suites: [ @@ -792,7 +756,6 @@ test_module_config { "com.android.internal.jank.InteractionJankMonitorTest", "com.android.internal.util.LatencyTrackerTest", ], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { @@ -803,7 +766,6 @@ test_module_config { "device-platinum-tests", ], include_annotations: ["android.platform.test.annotations.PlatinumTest"], - exclude_annotations: FLAKY_OR_IGNORED, } test_module_config { diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java index e47ef2df48b9..e19f887c1284 100644 --- a/core/tests/coretests/src/android/app/NotificationChannelTest.java +++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java @@ -47,12 +47,13 @@ import android.os.RemoteException; import android.os.VibrationEffect; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.UsesFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.MediaStore.Audio.AudioColumns; import android.test.mock.MockContentResolver; import android.util.Xml; -import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.modules.utils.TypedXmlPullParser; @@ -61,6 +62,7 @@ import com.android.modules.utils.TypedXmlSerializer; import com.google.common.base.Strings; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -71,14 +73,28 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.util.Arrays; +import java.util.List; import java.util.function.Consumer; -@RunWith(AndroidJUnit4.class) +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +@RunWith(ParameterizedAndroidJunit4.class) +@UsesFlags(android.app.Flags.class) @SmallTest @Presubmit public class NotificationChannelTest { + @ClassRule + public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule(); + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_NOTIF_CHANNEL_CROP_VIBRATION_EFFECTS); + } + @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + public final SetFlagsRule mSetFlagsRule; private final String CLASS = "android.app.NotificationChannel"; @@ -86,6 +102,10 @@ public class NotificationChannelTest { ContentProvider mContentProvider; IContentProvider mIContentProvider; + public NotificationChannelTest(FlagsParameterization flags) { + mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule(flags); + } + @Before public void setUp() throws Exception { mContext = mock(Context.class); diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt index 71980c125f01..e4e04a028588 100644 --- a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt +++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt @@ -17,11 +17,9 @@ package android.text import android.graphics.Paint -import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -40,7 +38,6 @@ class TextLineLetterSpacingTest { @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() - @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION) @Test fun calculateRunFlagTest() { // Only one Bidi run @@ -84,7 +81,6 @@ class TextLineLetterSpacingTest { .isEqualTo(LEFT_EDGE) } - @RequiresFlagsEnabled(FLAG_LETTER_SPACING_JUSTIFICATION) @Test fun resolveRunFlagForSubSequenceTest() { val runStart = 5 diff --git a/graphics/java/android/graphics/OWNERS b/graphics/java/android/graphics/OWNERS index 9fa8f1b284bb..ef8d26cc65b9 100644 --- a/graphics/java/android/graphics/OWNERS +++ b/graphics/java/android/graphics/OWNERS @@ -11,3 +11,4 @@ per-file BLASTBufferQueue.java = file:/services/core/java/com/android/server/wm/ per-file FontFamily.java = file:fonts/OWNERS per-file FontListParser.java = file:fonts/OWNERS per-file Typeface.java = file:fonts/OWNERS +per-file Paint.java = file:fonts/OWNERS diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index f727f5b076a1..0e25c346064c 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -306,13 +306,7 @@ public final class SystemFonts { long lastModifiedDate, int configVersion ) { - final String fontsXml; - if (com.android.text.flags.Flags.newFontsFallbackXml()) { - fontsXml = FONTS_XML; - } else { - fontsXml = LEGACY_FONTS_XML; - } - return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, + return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, updatableFontMap, lastModifiedDate, configVersion); } @@ -337,13 +331,7 @@ public final class SystemFonts { * @hide */ public static @NonNull FontConfig getSystemPreinstalledFontConfig() { - final String fontsXml; - if (com.android.text.flags.Flags.newFontsFallbackXml()) { - fontsXml = FONTS_XML; - } else { - fontsXml = LEGACY_FONTS_XML; - } - return getSystemFontConfigInternal(fontsXml, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null, + return getSystemFontConfigInternal(FONTS_XML, SYSTEM_FONT_DIR, OEM_XML, OEM_FONT_DIR, null, 0, 0); } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt index 424d4bf5c6e8..b5d63bd6addc 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeFlags.kt @@ -49,6 +49,7 @@ enum class DesktopModeFlags( SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true), DISABLE_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true), DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, false), + SCALED_RESIZING(Flags::enableWindowingScaledResizing, false), ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true), BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true), EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true), diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index ebfd3571ae6d..799028a5507a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -21,8 +21,8 @@ import android.app.IApplicationThread; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; -import android.view.IRecentsAnimationRunner; +import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.shared.GroupedRecentTaskInfo; diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl index 55ad4ae6d665..ccfaab079205 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.wm.shell.recents; import android.app.ActivityManager; import android.graphics.GraphicBuffer; @@ -61,7 +61,6 @@ interface IRecentsAnimationController { * @param sendUserLeaveHint If set to true, {@link Activity#onUserLeaving} will be sent to the * top resumed app, false otherwise. */ - @UnsupportedAppUsage void finish(boolean moveHomeToTop, boolean sendUserLeaveHint, in IResultReceiver finishCb); /** @@ -71,75 +70,15 @@ interface IRecentsAnimationController { * may register the recents animation input consumer prior to starting the recents animation * and then enable it mid-animation to start receiving touch events. */ - @UnsupportedAppUsage void setInputConsumerEnabled(boolean enabled); /** - * Informs the system whether the animation targets passed into - * IRecentsAnimationRunner.onAnimationStart are currently behind the system bars. If they are, - * they can control the SystemUI flags, otherwise the SystemUI flags from home activity will be - * taken. - */ - @UnsupportedAppUsage - void setAnimationTargetsBehindSystemBars(boolean behindSystemBars); - - /** - * Clean up the screenshot of previous task which was created during recents animation that - * was cancelled by a stack order change. - * - * @see {@link IRecentsAnimationRunner#onAnimationCanceled} - */ - void cleanupScreenshot(); - - /** - * Set a state for controller whether would like to cancel recents animations with deferred - * task screenshot presentation. - * - * When we cancel the recents animation due to a stack order change, we can't just cancel it - * immediately as it would lead to a flicker in Launcher if we just remove the task from the - * leash. Instead we screenshot the previous task and replace the child of the leash with the - * screenshot, so that Launcher can still control the leash lifecycle & make the next app - * transition animate smoothly without flickering. - * - * @param defer When set {@code true}, means that the recents animation will defer canceling the - * animation when a stack order change is triggered until the subsequent app - * transition start and skip previous task's animation. - * When set to {@code false}, means that the recents animation will be canceled - * immediately when the stack order changes. - * @param screenshot When set {@code true}, means that the system will take previous task's - * screenshot and replace the contents of the leash with it when the next app - * transition starting. The runner must call #cleanupScreenshot() to end the - * recents animation. - * When set to {@code false}, means that the system will simply wait for the - * next app transition start to immediately cancel the recents animation. This - * can be useful when you want an immediate transition into a state where the - * task is shown in the home/recents activity (without waiting for a - * screenshot). - * - * @see #cleanupScreenshot() - * @see IRecentsAnimationRunner#onCancelled - */ - void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot); - - /** * Sets a state for controller to decide which surface is the destination when the recents * animation is cancelled through fail safe mechanism. */ void setWillFinishToHome(boolean willFinishToHome); /** - * Stops controlling a task that is currently controlled by this recents animation. - * - * This method should be called when a task that has been received via {@link #onAnimationStart} - * or {@link #onTaskAppeared} is no longer needed. After calling this method, the task will - * either disappear from the screen, or jump to its final position in case it was the top task. - * - * @param taskId Id of the Task target to remove - * @return {@code true} when target removed successfully, {@code false} otherwise. - */ - boolean removeTask(int taskId); - - /** * Detach navigation bar from app. * * The system reparents the leash of navigation bar to the app when the recents animation starts @@ -155,15 +94,6 @@ interface IRecentsAnimationController { void detachNavigationBarFromApp(boolean moveHomeToTop); /** - * Used for animating the navigation bar during app launch from recents in live tile mode. - * - * First fade out the navigation bar at the bottom of the display and then fade in to the app. - * - * @param duration the duration of the app launch animation - */ - void animateNavigationBarToApp(long duration); - - /** * Hand off the ongoing animation of a set of remote targets, to be run by another handler using * the given starting parameters. * diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl index 37663d59cafd..8021758bf7c3 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationRunner.aidl @@ -14,15 +14,16 @@ * limitations under the License. */ -package android.view; +package com.android.wm.shell.recents; import android.app.ActivityManager; import android.graphics.Rect; import android.view.RemoteAnimationTarget; -import android.view.IRecentsAnimationController; import android.window.TaskSnapshot; import android.os.Bundle; +import com.android.wm.shell.recents.IRecentsAnimationController; + /** * Interface that is used to callback from window manager to the process that runs a recents * animation to start or cancel it. @@ -55,7 +56,6 @@ oneway interface IRecentsAnimationRunner { * @param minimizedHomeBounds Specifies the bounds of the minimized home app, will be * {@code null} if the device is not currently in split screen */ - @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) void onAnimationStart(in IRecentsAnimationController controller, in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, in Rect homeContentInsets, in Rect minimizedHomeBounds, in Bundle extras) = 2; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 9af33a84b045..a6e25a95e1eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -37,7 +37,6 @@ import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; -import android.view.IRecentsAnimationRunner; import android.window.WindowContainerToken; import androidx.annotation.BinderThread; @@ -54,6 +53,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.shared.GroupedRecentTaskInfo; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index c90da052dd72..c660000e4f61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -49,8 +49,6 @@ import android.util.IntArray; import android.util.Pair; import android.util.Slog; import android.view.Display; -import android.view.IRecentsAnimationController; -import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.window.PictureInPictureSurfaceTransaction; @@ -1024,10 +1022,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } @Override - public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { - } - - @Override public void setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { mExecutor.execute(() -> { @@ -1254,14 +1248,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } @Override - public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { - } - - @Override - public void cleanupScreenshot() { - } - - @Override public void setWillFinishToHome(boolean willFinishToHome) { mExecutor.execute(() -> { mWillFinishToHome = willFinishToHome; @@ -1269,14 +1255,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } /** - * @see IRecentsAnimationController#removeTask - */ - @Override - public boolean removeTask(int taskId) { - return false; - } - - /** * @see IRecentsAnimationController#detachNavigationBarFromApp */ @Override @@ -1292,13 +1270,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } }); } - - /** - * @see IRecentsAnimationController#animateNavigationBarToApp(long) - */ - @Override - public void animateNavigationBarToApp(long duration) { - } }; /** Utility class to track the state of a task as-seen by recents. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java index 1e6fa2807b9b..2033902f03c7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java @@ -24,6 +24,7 @@ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED_ import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__CHILD_TASK_ENTER_PIP; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER; +import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_REQUEST; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE; import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RECREATE_SPLIT; @@ -45,6 +46,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RECREATE_SPLIT; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; @@ -211,6 +213,8 @@ public class SplitscreenEventLogger { return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT; case EXIT_REASON_DESKTOP_MODE: return SPLITSCREEN_UICHANGED__EXIT_REASON__DESKTOP_MODE; + case EXIT_REASON_FULLSCREEN_REQUEST: + return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_REQUEST; case EXIT_REASON_UNKNOWN: // Fall through default: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index dad0d4eb4d8d..1b143ebddde7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1175,6 +1175,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason); setSplitsVisible(false); mBreakOnNextWake = false; + logExit(reason); } void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { @@ -1265,6 +1266,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(stage, wct); mSplitTransitions.startDismissTransition(wct, this, stage, exitReason); + logExit(exitReason); } /** @@ -1361,6 +1363,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); mSideStage.doForAllChildTasks(taskId -> recentTasks.removeSplitPair(taskId)); }); + logExit(exitReason); } /** @@ -1579,7 +1582,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (stage == STAGE_TYPE_MAIN) { mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(), mSplitLayout.isLeftRightSplit()); - } else { + } else if (stage == STAGE_TYPE_SIDE) { mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(), mSplitLayout.isLeftRightSplit()); } @@ -2275,6 +2278,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (isOpening && inFullscreen) { // One task is opening into fullscreen mode, remove the corresponding split record. mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); + logExit(EXIT_REASON_FULLSCREEN_REQUEST); } if (isSplitActive()) { @@ -2402,6 +2406,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (triggerTask != null) { mRecentTasks.ifPresent( recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); + logExit(EXIT_REASON_CHILD_TASK_ENTER_PIP); } @StageType int topStage = STAGE_TYPE_UNDEFINED; if (isSplitScreenVisible()) { @@ -2743,7 +2748,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final int dismissTop = mainChild != null ? STAGE_TYPE_MAIN : (sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED); pendingEnter.cancel( - (cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct)); + (cancelWct, cancelT) -> { + prepareExitSplitScreen(dismissTop, cancelWct); + logExit(EXIT_REASON_UNKNOWN); + }); Log.w(TAG, splitFailureMessage("startPendingEnterAnimation", "launched 2 tasks in split, but didn't receive " + "2 tasks in transition. Possibly one of them failed to launch")); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 7c63fdad660a..7937a843b90a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -76,7 +76,6 @@ import android.os.RemoteException; import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArraySet; import android.util.Pair; -import android.view.IRecentsAnimationRunner; import android.view.Surface; import android.view.SurfaceControl; import android.window.IRemoteTransition; @@ -107,6 +106,7 @@ import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.recents.IRecentsAnimationRunner; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.shared.ShellSharedConstants; diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 5955915c9fcd..e6182454ad8a 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -1420,20 +1420,18 @@ void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo Mutex AssetManager::SharedZip::gLock; DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen; -AssetManager::SharedZip::SharedZip(const String8& path, ModDate modWhen) - : mPath(path), - mZipFile(NULL), - mModWhen(modWhen), - mResourceTableAsset(NULL), - mResourceTable(NULL) { - if (kIsDebug) { - ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str()); - } - ALOGV("+++ opening zip '%s'\n", mPath.c_str()); - mZipFile = ZipFileRO::open(mPath.c_str()); - if (mZipFile == NULL) { - ALOGD("failed to open Zip archive '%s'\n", mPath.c_str()); - } +AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen) + : mPath(path), mZipFile(NULL), mModWhen(modWhen), + mResourceTableAsset(NULL), mResourceTable(NULL) +{ + if (kIsDebug) { + ALOGI("Creating SharedZip %p %s\n", this, mPath.c_str()); + } + ALOGV("+++ opening zip '%s'\n", mPath.c_str()); + mZipFile = ZipFileRO::open(mPath.c_str()); + if (mZipFile == NULL) { + ALOGD("failed to open Zip archive '%s'\n", mPath.c_str()); + } } AssetManager::SharedZip::SharedZip(int fd, const String8& path) @@ -1455,7 +1453,7 @@ sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path, bool createIfNotPresent) { AutoMutex _l(gLock); - auto modWhen = getFileModDate(path.c_str()); + time_t modWhen = getFileModDate(path.c_str()); sp<SharedZip> zip = gOpen.valueFor(path).promote(); if (zip != NULL && zip->mModWhen == modWhen) { return zip; @@ -1522,8 +1520,8 @@ ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res) bool AssetManager::SharedZip::isUpToDate() { - auto modWhen = getFileModDate(mPath.c_str()); - return mModWhen == modWhen; + time_t modWhen = getFileModDate(mPath.c_str()); + return mModWhen == modWhen; } void AssetManager::SharedZip::addOverlay(const asset_path& ap) diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h index 376c881ea376..ce0985b38986 100644 --- a/libs/androidfw/include/androidfw/AssetManager.h +++ b/libs/androidfw/include/androidfw/AssetManager.h @@ -280,21 +280,21 @@ private: ~SharedZip(); private: - SharedZip(const String8& path, ModDate modWhen); - SharedZip(int fd, const String8& path); - SharedZip(); // <-- not implemented + SharedZip(const String8& path, time_t modWhen); + SharedZip(int fd, const String8& path); + SharedZip(); // <-- not implemented - String8 mPath; - ZipFileRO* mZipFile; - ModDate mModWhen; + String8 mPath; + ZipFileRO* mZipFile; + time_t mModWhen; - Asset* mResourceTableAsset; - ResTable* mResourceTable; + Asset* mResourceTableAsset; + ResTable* mResourceTable; - Vector<asset_path> mOverlays; + Vector<asset_path> mOverlays; - static Mutex gLock; - static DefaultKeyedVector<String8, wp<SharedZip> > gOpen; + static Mutex gLock; + static DefaultKeyedVector<String8, wp<SharedZip> > gOpen; }; /* diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h index 98f1aa86f2db..64b1f0c6ed03 100644 --- a/libs/androidfw/include/androidfw/Idmap.h +++ b/libs/androidfw/include/androidfw/Idmap.h @@ -25,9 +25,8 @@ #include "android-base/macros.h" #include "android-base/unique_fd.h" #include "androidfw/ConfigDescription.h" -#include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" -#include "androidfw/misc.h" +#include "androidfw/ResourceTypes.h" #include "utils/ByteOrder.h" namespace android { @@ -203,7 +202,7 @@ class LoadedIdmap { android::base::unique_fd idmap_fd_; std::string_view overlay_apk_path_; std::string_view target_apk_path_; - ModDate idmap_last_mod_time_; + time_t idmap_last_mod_time_; private: DISALLOW_COPY_AND_ASSIGN(LoadedIdmap); diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h index 09ae40c35369..077609d20d55 100644 --- a/libs/androidfw/include/androidfw/misc.h +++ b/libs/androidfw/include/androidfw/misc.h @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#pragma once -#include <time.h> +#include <sys/types.h> // // Handy utility functions and portability code. // +#ifndef _LIBS_ANDROID_FW_MISC_H +#define _LIBS_ANDROID_FW_MISC_H namespace android { @@ -40,35 +41,15 @@ typedef enum FileType { } FileType; /* get the file's type; follows symlinks */ FileType getFileType(const char* fileName); - -// MinGW doesn't support nanosecond resolution in stat() modification time, and given -// that it only matters on the device it's ok to keep it at the second level there. -#ifdef _WIN32 -using ModDate = time_t; -inline constexpr ModDate kInvalidModDate = ModDate(-1); -inline constexpr unsigned long long kModDateResolutionNs = 1ull * 1000 * 1000 * 1000; -inline time_t toTimeT(ModDate m) { - return m; -} -#else -using ModDate = timespec; -inline constexpr ModDate kInvalidModDate = {-1, -1}; -inline constexpr unsigned long long kModDateResolutionNs = 1; -inline time_t toTimeT(ModDate m) { - return m.tv_sec; -} -#endif - -/* get the file's modification date; returns kInvalidModDate w/errno set on failure */ -ModDate getFileModDate(const char* fileName); +/* get the file's modification date; returns -1 w/errno set on failure */ +time_t getFileModDate(const char* fileName); /* same, but also returns -1 if the file has already been deleted */ -ModDate getFileModDate(int fd); +time_t getFileModDate(int fd); // Check if |path| or |fd| resides on a readonly filesystem. bool isReadonlyFilesystem(const char* path); bool isReadonlyFilesystem(int fd); -} // namespace android +}; // namespace android -// Whoever uses getFileModDate() will need this as well -bool operator==(const timespec& l, const timespec& r); +#endif // _LIBS_ANDROID_FW_MISC_H diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp index 9bdaf18a116a..93dcaf549a90 100644 --- a/libs/androidfw/misc.cpp +++ b/libs/androidfw/misc.cpp @@ -28,13 +28,11 @@ #include <sys/vfs.h> #endif // __linux__ +#include <cstring> +#include <cstdio> #include <errno.h> #include <sys/stat.h> -#include <cstdio> -#include <cstring> -#include <tuple> - namespace android { /* @@ -75,32 +73,27 @@ FileType getFileType(const char* fileName) } } -static ModDate getModDate(const struct stat& st) { -#ifdef _WIN32 - return st.st_mtime; -#else - return st.st_mtim; -#endif -} - -ModDate getFileModDate(const char* fileName) { - struct stat sb; - if (stat(fileName, &sb) < 0) { - return kInvalidModDate; - } - return getModDate(sb); +/* + * Get a file's modification date. + */ +time_t getFileModDate(const char* fileName) { + struct stat sb; + if (stat(fileName, &sb) < 0) { + return (time_t)-1; + } + return sb.st_mtime; } -ModDate getFileModDate(int fd) { - struct stat sb; - if (fstat(fd, &sb) < 0) { - return kInvalidModDate; - } - if (sb.st_nlink <= 0) { - errno = ENOENT; - return kInvalidModDate; - } - return getModDate(sb); +time_t getFileModDate(int fd) { + struct stat sb; + if (fstat(fd, &sb) < 0) { + return (time_t)-1; + } + if (sb.st_nlink <= 0) { + errno = ENOENT; + return (time_t)-1; + } + return sb.st_mtime; } #ifndef __linux__ @@ -131,8 +124,4 @@ bool isReadonlyFilesystem(int fd) { } #endif // __linux__ -} // namespace android - -bool operator==(const timespec& l, const timespec& r) { - return std::tie(l.tv_sec, l.tv_nsec) == std::tie(r.tv_sec, l.tv_nsec); -} +}; // namespace android diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index cb2e56f5f5e4..60aa7d88925d 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -14,9 +14,6 @@ * limitations under the License. */ -#include <chrono> -#include <thread> - #include "android-base/file.h" #include "androidfw/ApkAssets.h" #include "androidfw/AssetManager2.h" @@ -30,7 +27,6 @@ #include "data/overlayable/R.h" #include "data/system/R.h" -using namespace std::chrono_literals; using ::testing::NotNull; namespace overlay = com::android::overlay; @@ -222,13 +218,10 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) { unlink(temp_file.path); ASSERT_FALSE(apk_assets->IsUpToDate()); - - const auto sleep_duration = - std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull)); - std::this_thread::sleep_for(sleep_duration); + sleep(2); base::WriteStringToFile("hello", temp_file.path); - std::this_thread::sleep_for(sleep_duration); + sleep(2); ASSERT_FALSE(apk_assets->IsUpToDate()); } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 3409c29d3c2c..defbc1142adb 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,8 +16,6 @@ package com.android.externalstorage; -import static java.util.regex.Pattern.CASE_INSENSITIVE; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -61,12 +59,15 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; -import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Presents content of the shared (a.k.a. "external") storage. @@ -89,12 +90,9 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); - /** - * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and - * {@code /Android/sandbox/} along with all their subdirectories and content. - */ - private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = - Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); + private static final String PRIMARY_EMULATED_STORAGE_PATH = "/storage/emulated/"; + + private static final String STORAGE_PATH = "/storage/"; private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -309,11 +307,70 @@ public class ExternalStorageProvider extends FileSystemProvider { return false; } - final String path = getPathFromDocId(documentId); - return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); + try { + final RootInfo root = getRootFromDocId(documentId); + final String canonicalPath = getPathFromDocId(documentId); + return isRestrictedPath(root.rootId, canonicalPath); + } catch (Exception e) { + return true; + } } /** + * Based on the given root id and path, we restrict path access if file is Android/data or + * Android/obb or Android/sandbox or one of their subdirectories. + * + * @param canonicalPath of the file + * @return true if path is restricted + */ + private boolean isRestrictedPath(String rootId, String canonicalPath) { + if (rootId == null || canonicalPath == null) { + return true; + } + + final String rootPath; + if (rootId.equalsIgnoreCase(ROOT_ID_PRIMARY_EMULATED)) { + // Creates "/storage/emulated/<user-id>" + rootPath = PRIMARY_EMULATED_STORAGE_PATH + UserHandle.myUserId(); + } else { + // Creates "/storage/<volume-uuid>" + rootPath = STORAGE_PATH + rootId; + } + List<java.nio.file.Path> restrictedPathList = Arrays.asList( + Paths.get(rootPath, "Android", "data"), + Paths.get(rootPath, "Android", "obb"), + Paths.get(rootPath, "Android", "sandbox")); + // We need to identify restricted parent paths which actually exist on the device + List<java.nio.file.Path> validRestrictedPathsToCheck = restrictedPathList.stream().filter( + Files::exists).collect(Collectors.toList()); + + boolean isRestricted = false; + java.nio.file.Path filePathToCheck = Paths.get(rootPath, canonicalPath); + try { + while (filePathToCheck != null) { + for (java.nio.file.Path restrictedPath : validRestrictedPathsToCheck) { + if (Files.isSameFile(restrictedPath, filePathToCheck)) { + isRestricted = true; + Log.v(TAG, "Restricting access for path: " + filePathToCheck); + break; + } + } + if (isRestricted) { + break; + } + + filePathToCheck = filePathToCheck.getParent(); + } + } catch (Exception e) { + Log.w(TAG, "Error in checking file equality check.", e); + isRestricted = true; + } + + return isRestricted; + } + + + /** * Check that the directory is the root of storage or blocked file from tree. * <p> * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 0cb85d8638b0..b997c35668d2 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -42,6 +42,8 @@ android_library { "SettingsLibIllustrationPreference", "SettingsLibLayoutPreference", "SettingsLibMainSwitchPreference", + "SettingsLibMetadata", + "SettingsLibPreference", "SettingsLibProfileSelector", "SettingsLibProgressBar", "SettingsLibRestrictedLockUtils", diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp index 9665dbd17e2d..17852e8e7ece 100644 --- a/packages/SettingsLib/Preference/Android.bp +++ b/packages/SettingsLib/Preference/Android.bp @@ -18,6 +18,7 @@ android_library { "SettingsLibMetadata", "androidx.annotation_annotation", "androidx.preference_preference", + "guava", ], kotlincflags: ["-Xjvm-default=all"], } diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt index 207200998b05..68f640bbb9b4 100644 --- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt +++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceFragment.kt @@ -79,7 +79,7 @@ open class PreferenceFragment : * This is for flagging purpose. If false (e.g. flag is disabled), xml resource is used to build * preference screen. */ - protected open fun usePreferenceScreenMetadata(): Boolean = true + protected open fun usePreferenceScreenMetadata(): Boolean = false /** Returns the xml resource to create preference screen. */ @XmlRes protected open fun getPreferenceScreenResId(context: Context): Int = 0 diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java index bd1e5a588968..79949248cd8a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java @@ -31,13 +31,14 @@ import android.view.MenuInflater; import android.view.MenuItem; import androidx.lifecycle.LifecycleOwner; -import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; +import com.android.settingslib.preference.PreferenceFragment; + /** - * {@link PreferenceFragmentCompat} that has hooks to observe fragment lifecycle events. + * Preference fragment that has hooks to observe fragment lifecycle events. */ -public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat +public abstract class ObservablePreferenceFragment extends PreferenceFragment implements LifecycleOwner { private final Lifecycle mLifecycle = new Lifecycle(this); diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index 3e2d8328f21e..d3c345deebad 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -98,7 +98,7 @@ interface AudioRepository { */ suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean - suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) + suspend fun setRingerModeInternal(audioStream: AudioStream, mode: RingerMode) /** Gets audio device category. */ @AudioDeviceCategory suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int @@ -248,8 +248,8 @@ class AudioRepositoryImpl( } } - override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) { - withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value } + override suspend fun setRingerModeInternal(audioStream: AudioStream, mode: RingerMode) { + withContext(backgroundCoroutineContext) { audioManager.ringerModeInternal = mode.value } } @AudioDeviceCategory diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt index 08863b56cbe0..dca890d34bc0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt @@ -68,7 +68,7 @@ class AudioVolumeInteractor( if (audioStream.value == AudioManager.STREAM_RING) { val mode = if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL - audioRepository.setRingerMode(audioStream, RingerMode(mode)) + audioRepository.setRingerModeInternal(audioStream, RingerMode(mode)) } val mutedChanged = audioRepository.setMuted(audioStream, isMuted) if (mutedChanged && !isMuted) { diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt index 52e639172af5..8a3b1dfb0846 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt @@ -74,6 +74,8 @@ class AudioRepositoryTest { private lateinit var underTest: AudioRepository + private var ringerModeInternal: RingerMode = RingerMode(AudioManager.RINGER_MODE_NORMAL) + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -82,7 +84,7 @@ class AudioRepositoryTest { `when`(audioManager.communicationDevice).thenReturn(communicationDevice) `when`(audioManager.getStreamMinVolume(anyInt())).thenReturn(MIN_VOLUME) `when`(audioManager.getStreamMaxVolume(anyInt())).thenReturn(MAX_VOLUME) - `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_NORMAL) + `when`(audioManager.ringerModeInternal).then { ringerModeInternal.value } `when`(audioManager.setStreamVolume(anyInt(), anyInt(), anyInt())).then { val streamType = it.arguments[0] as Int volumeByStream[it.arguments[0] as Int] = it.arguments[1] as Int @@ -103,6 +105,10 @@ class AudioRepositoryTest { `when`(audioManager.isStreamMute(anyInt())).thenAnswer { isMuteByStream.getOrDefault(it.arguments[0] as Int, false) } + `when`(audioManager.setRingerModeInternal(anyInt())).then { + ringerModeInternal = RingerMode(it.arguments[0] as Int) + Unit + } underTest = AudioRepositoryImpl( @@ -137,7 +143,7 @@ class AudioRepositoryTest { underTest.ringerMode.onEach { modes.add(it) }.launchIn(backgroundScope) runCurrent() - `when`(audioManager.ringerModeInternal).thenReturn(AudioManager.RINGER_MODE_SILENT) + ringerModeInternal = RingerMode(AudioManager.RINGER_MODE_SILENT) triggerEvent(AudioManagerEvent.InternalRingerModeChanged) runCurrent() @@ -150,6 +156,19 @@ class AudioRepositoryTest { } @Test + fun changingRingerMode_changesRingerModeInternal() { + testScope.runTest { + underTest.setRingerModeInternal( + AudioStream(AudioManager.STREAM_SYSTEM), + RingerMode(AudioManager.RINGER_MODE_SILENT), + ) + runCurrent() + + assertThat(ringerModeInternal).isEqualTo(RingerMode(AudioManager.RINGER_MODE_SILENT)) + } + } + + @Test fun communicationDeviceChanges_repositoryEmits() { testScope.runTest { var device: AudioDeviceInfo? = null diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt index ba885f7da2cf..1f98cd8e07c0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt @@ -65,7 +65,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformOutlinedButton import com.android.compose.animation.Easings @@ -355,7 +354,10 @@ private class PinInputRow( fun Content(modifier: Modifier) { // Wrap PIN entry in a Box so it is visible to accessibility (even if empty). - Box(modifier = modifier.fillMaxWidth().wrapContentHeight()) { + Box( + modifier = modifier.fillMaxWidth().wrapContentHeight(), + contentAlignment = Alignment.Center, + ) { Row( modifier .heightIn(min = shapeAnimations.shapeSize) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt index e1f73e304b9e..4e8121f14e4b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt @@ -17,9 +17,12 @@ package com.android.systemui.common.ui.compose +import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import com.android.systemui.common.shared.model.Text +import com.android.systemui.common.shared.model.Text.Companion.loadText /** Returns the loaded [String] or `null` if there isn't one. */ @Composable @@ -29,3 +32,7 @@ fun Text.load(): String? { is Text.Resource -> stringResource(res) } } + +fun Text.toAnnotatedString(context: Context): AnnotatedString? { + return loadText(context)?.let { AnnotatedString(it) } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 0d05f4e8c613..fb9dde345251 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -200,7 +200,7 @@ internal class MultiPointerDraggableNode( override fun onPointerEvent( pointerEvent: PointerEvent, pass: PointerEventPass, - bounds: IntSize + bounds: IntSize, ) { // The order is important here: the tracker is always called first. pointerTracker.onPointerEvent(pointerEvent, pass, bounds) @@ -234,8 +234,8 @@ internal class MultiPointerDraggableNode( pointersDown == 0 -> { startedPosition = null - // This is the last pointer up - velocityTracker.addPointerInputChange(changes.single()) + val lastPointerUp = changes.single { it.id == velocityPointerId } + velocityTracker.addPointerInputChange(lastPointerUp) } // The first pointer down, startedPosition was not set. @@ -271,7 +271,12 @@ internal class MultiPointerDraggableNode( // If the previous pointer has been removed, we use the first available // change to keep tracking the velocity. - velocityPointerId = pointerChange.id + velocityPointerId = + if (pointerChange.pressed) { + pointerChange.id + } else { + changes.first { it.pressed }.id + } velocityTracker.addPointerInputChange(pointerChange) } @@ -312,13 +317,13 @@ internal class MultiPointerDraggableNode( velocityTracker.calculateVelocity(maxVelocity) } .toFloat(), - onFling = { controller.onStop(it, canChangeContent = true) } + onFling = { controller.onStop(it, canChangeContent = true) }, ) }, onDragCancel = { controller -> startFlingGesture( initialVelocity = 0f, - onFling = { controller.onStop(it, canChangeContent = true) } + onFling = { controller.onStop(it, canChangeContent = true) }, ) }, swipeDetector = swipeDetector, @@ -369,10 +374,7 @@ internal class MultiPointerDraggableNode( // PreScroll phase val consumedByPreScroll = dispatcher - .dispatchPreScroll( - available = availableOnPreScroll.toOffset(), - source = source, - ) + .dispatchPreScroll(available = availableOnPreScroll.toOffset(), source = source) .toFloat() // Scroll phase @@ -484,12 +486,12 @@ internal class MultiPointerDraggableNode( Orientation.Horizontal -> awaitHorizontalTouchSlopOrCancellation( consumablePointer.id, - onSlopReached + onSlopReached, ) Orientation.Vertical -> awaitVerticalTouchSlopOrCancellation( consumablePointer.id, - onSlopReached + onSlopReached, ) } @@ -553,7 +555,7 @@ internal class MultiPointerDraggableNode( } private suspend fun AwaitPointerEventScope.awaitConsumableEvent( - pass: () -> PointerEventPass, + pass: () -> PointerEventPass ): PointerEvent { fun canBeConsumed(changes: List<PointerInputChange>): Boolean { // At least one pointer down AND @@ -661,7 +663,4 @@ internal fun interface PointersInfoOwner { fun pointersInfo(): PointersInfo } -internal data class PointersInfo( - val startedPosition: Offset?, - val pointersDown: Int, -) +internal data class PointersInfo(val startedPosition: Offset?, val pointersDown: Int) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index bf192e7708d0..af717ac7d50b 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -259,7 +259,7 @@ class MultiPointerDraggableTest { it } ), - Orientation.Vertical + Orientation.Vertical, ) .fillMaxSize() ) @@ -438,6 +438,9 @@ class MultiPointerDraggableTest { continueDraggingDown() assertThat(stopped).isTrue() + + // Complete the gesture + rule.onRoot().performTouchInput { up() } } @Test @@ -640,7 +643,7 @@ class MultiPointerDraggableTest { override fun onPostScroll( consumed: Offset, available: Offset, - source: NestedScrollSource + source: NestedScrollSource, ): Offset { availableOnPostScroll = available.y return Offset.Zero @@ -653,7 +656,7 @@ class MultiPointerDraggableTest { override suspend fun onPostFling( consumed: Velocity, - available: Velocity + available: Velocity, ): Velocity { availableOnPostFling = available.y return Velocity.Zero diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt index cd0c58feebed..c4fc13227eef 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadStatsInteractorTest.kt @@ -19,12 +19,15 @@ package com.android.systemui.education.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.contextualeducation.GestureType.BACK +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.education.data.repository.contextualEducationRepository import com.android.systemui.education.data.repository.fakeEduClock +import com.android.systemui.keyboard.data.repository.keyboardRepository import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.android.systemui.touchpad.data.repository.touchpadRepository import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest import org.junit.Test @@ -36,22 +39,62 @@ class KeyboardTouchpadStatsInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val underTest = kosmos.keyboardTouchpadEduStatsInteractor + private val keyboardRepository = kosmos.keyboardRepository + private val touchpadRepository = kosmos.touchpadRepository + private val repository = kosmos.contextualEducationRepository @Test - fun dataUpdatedOnIncrementSignalCount() = + fun dataUpdatedOnIncrementSignalCountWhenTouchpadConnected() = testScope.runTest { - val model by - collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK)) + touchpadRepository.setIsAnyTouchpadConnected(true) + + val model by collectLastValue(repository.readGestureEduModelFlow(BACK)) val originalValue = model!!.signalCount underTest.incrementSignalCount(BACK) + assertThat(model?.signalCount).isEqualTo(originalValue + 1) } @Test + fun dataUnchangedOnIncrementSignalCountWhenTouchpadDisconnected() = + testScope.runTest { + touchpadRepository.setIsAnyTouchpadConnected(false) + + val model by collectLastValue(repository.readGestureEduModelFlow(BACK)) + val originalValue = model!!.signalCount + underTest.incrementSignalCount(BACK) + + assertThat(model?.signalCount).isEqualTo(originalValue) + } + + @Test + fun dataUpdatedOnIncrementSignalCountWhenKeyboardConnected() = + testScope.runTest { + keyboardRepository.setIsAnyKeyboardConnected(true) + + val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS)) + val originalValue = model!!.signalCount + underTest.incrementSignalCount(ALL_APPS) + + assertThat(model?.signalCount).isEqualTo(originalValue + 1) + } + + @Test + fun dataUnchangedOnIncrementSignalCountWhenKeyboardDisconnected() = + testScope.runTest { + keyboardRepository.setIsAnyKeyboardConnected(false) + + val model by collectLastValue(repository.readGestureEduModelFlow(ALL_APPS)) + val originalValue = model!!.signalCount + underTest.incrementSignalCount(ALL_APPS) + + assertThat(model?.signalCount).isEqualTo(originalValue) + } + + @Test fun dataAddedOnUpdateShortcutTriggerTime() = testScope.runTest { - val model by - collectLastValue(kosmos.contextualEducationRepository.readGestureEduModelFlow(BACK)) + val model by collectLastValue(repository.readGestureEduModelFlow(BACK)) assertThat(model?.lastShortcutTriggeredTime).isNull() underTest.updateShortcutTriggerTime(BACK) assertThat(model?.lastShortcutTriggeredTime).isEqualTo(kosmos.fakeEduClock.instant()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt index 1e5599bfe1d5..93ba26517e37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.qs.panels.data.repository import android.content.ComponentName import android.content.packageManager import android.content.pm.PackageManager -import android.content.pm.ServiceInfo import android.content.pm.UserInfo import android.graphics.drawable.TestStubDrawable import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -35,6 +34,7 @@ import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesCompon import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker import com.android.systemui.testKosmos @@ -100,6 +100,7 @@ class IconAndNameCustomRepositoryTest : SysuiTestCase() { Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)), Text.Loaded(tileService1), Text.Loaded(appName1), + TileCategory.PROVIDED_BY_APP, ) val expectedData2 = EditTileData( @@ -107,6 +108,7 @@ class IconAndNameCustomRepositoryTest : SysuiTestCase() { Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2)), Text.Loaded(tileService2), Text.Loaded(appName2), + TileCategory.PROVIDED_BY_APP, ) assertThat(editTileDataList).containsExactly(expectedData1, expectedData2) @@ -144,6 +146,7 @@ class IconAndNameCustomRepositoryTest : SysuiTestCase() { Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)), Text.Loaded(tileService1), Text.Loaded(appName1), + TileCategory.PROVIDED_BY_APP, ) val editTileDataList = underTest.getCustomTileData() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt index deefbf585ba9..053a59aa533a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.qs.panels.shared.model.EditTileData import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig @@ -132,6 +133,7 @@ class EditTilesListInteractorTest : SysuiTestCase() { icon = Icon.Loaded(icon, ContentDescription.Loaded(tileName)), label = Text.Loaded(tileName), appName = Text.Loaded(appName), + category = TileCategory.PROVIDED_BY_APP, ) assertThat(editTiles.customTiles).hasSize(1) @@ -181,7 +183,8 @@ class EditTilesListInteractorTest : SysuiTestCase() { tileSpec = this, icon = Icon.Resource(android.R.drawable.star_on, ContentDescription.Loaded(spec)), label = Text.Loaded(spec), - appName = null + appName = null, + category = TileCategory.UNKNOWN, ) } @@ -192,6 +195,7 @@ class EditTilesListInteractorTest : SysuiTestCase() { Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)), label = Text.Resource(uiConfig.labelRes), appName = null, + category = category, ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt index 7f01fad1ce55..484a8ff973c1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.qs.panels.ui.compose +import androidx.compose.ui.text.AnnotatedString import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.shared.model.Text import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.model.GridCell @@ -28,6 +28,7 @@ import com.android.systemui.qs.panels.ui.model.SpacerGridCell import com.android.systemui.qs.panels.ui.model.TileGridCell import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -141,10 +142,11 @@ class EditTileListStateTest : SysuiTestCase() { EditTileViewModel( tileSpec = TileSpec.create(tileSpec), icon = Icon.Resource(0, null), - label = Text.Loaded("unused"), + label = AnnotatedString("unused"), appName = null, isCurrent = true, availableEditActions = emptySet(), + category = TileCategory.UNKNOWN, ), width, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt index 601779f8fb02..583db722a759 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt @@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text +import com.android.systemui.common.ui.compose.toAnnotatedString import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope @@ -42,6 +43,7 @@ import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesReposito import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.qsTileFactory +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig @@ -190,7 +192,7 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { .forEach { val data = getEditTileData(it.tileSpec) - assertThat(it.label).isEqualTo(data.label) + assertThat(it.label).isEqualTo(data.label.toAnnotatedString(context)) assertThat(it.icon).isEqualTo(data.icon) assertThat(it.appName).isNull() } @@ -224,15 +226,19 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { // service1 val model1 = tiles!!.first { it.tileSpec == TileSpec.create(component1) } - assertThat(model1.label).isEqualTo(Text.Loaded(tileService1)) - assertThat(model1.appName).isEqualTo(Text.Loaded(appName1)) + assertThat(model1.label) + .isEqualTo(Text.Loaded(tileService1).toAnnotatedString(context)) + assertThat(model1.appName) + .isEqualTo(Text.Loaded(appName1).toAnnotatedString(context)) assertThat(model1.icon) .isEqualTo(Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1))) // service2 val model2 = tiles!!.first { it.tileSpec == TileSpec.create(component2) } - assertThat(model2.label).isEqualTo(Text.Loaded(tileService2)) - assertThat(model2.appName).isEqualTo(Text.Loaded(appName2)) + assertThat(model2.label) + .isEqualTo(Text.Loaded(tileService2).toAnnotatedString(context)) + assertThat(model2.appName) + .isEqualTo(Text.Loaded(appName2).toAnnotatedString(context)) assertThat(model2.icon) .isEqualTo(Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2))) } @@ -559,7 +565,8 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { tileSpec = this, icon = Icon.Resource(R.drawable.star_on, ContentDescription.Loaded(spec)), label = Text.Loaded(spec), - appName = null + appName = null, + category = TileCategory.UNKNOWN, ) } @@ -570,6 +577,7 @@ class EditModeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)), label = Text.Resource(uiConfig.labelRes), appName = null, + category = category, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/shared/model/GroupAndSortCategoryAndNameTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/shared/model/GroupAndSortCategoryAndNameTest.kt new file mode 100644 index 000000000000..7f90e3b6f5e0 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/shared/model/GroupAndSortCategoryAndNameTest.kt @@ -0,0 +1,87 @@ +/* + * 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.qs.shared.model + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class GroupAndSortCategoryAndNameTest : SysuiTestCase() { + + private val elements = + listOf( + CategoryAndName(TileCategory.DISPLAY, "B"), + CategoryAndName(TileCategory.PRIVACY, "A"), + CategoryAndName(TileCategory.DISPLAY, "C"), + CategoryAndName(TileCategory.UTILITIES, "B"), + CategoryAndName(TileCategory.CONNECTIVITY, "A"), + CategoryAndName(TileCategory.PROVIDED_BY_APP, "B"), + CategoryAndName(TileCategory.CONNECTIVITY, "C"), + CategoryAndName(TileCategory.ACCESSIBILITY, "A") + ) + + @Test + fun allElementsInResult() { + val grouped = groupAndSort(elements) + val allValues = grouped.values.reduce { acc, el -> acc + el } + assertThat(allValues).containsExactlyElementsIn(elements) + } + + @Test + fun groupedByCategory() { + val grouped = groupAndSort(elements) + grouped.forEach { tileCategory, categoryAndNames -> + categoryAndNames.forEach { element -> + assertThat(element.category).isEqualTo(tileCategory) + } + } + } + + @Test + fun sortedAlphabeticallyInEachCategory() { + val grouped = groupAndSort(elements) + grouped.values.forEach { elements -> + assertThat(elements.map(CategoryAndName::name)).isInOrder() + } + } + + @Test + fun categoriesSortedInNaturalOrder() { + val grouped = groupAndSort(elements) + assertThat(grouped.keys).isInOrder() + } + + @Test + fun missingCategoriesAreNotInResult() { + val grouped = groupAndSort(elements.filterNot { it.category == TileCategory.CONNECTIVITY }) + assertThat(grouped.keys).doesNotContain(TileCategory.CONNECTIVITY) + } + + companion object { + private fun CategoryAndName(category: TileCategory, name: String): CategoryAndName { + return object : CategoryAndName { + override val category = category + override val name = name + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt index aaad0fc1b644..5a4506086058 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt @@ -184,7 +184,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() { ) val networkModel = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 4, ssid = "test ssid", ) @@ -219,7 +219,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() { ) val networkModel = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 4, ssid = "test ssid", hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, @@ -398,7 +398,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() { collectLastValue( underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) ) - val networkModel = WifiNetworkModel.Inactive + val networkModel = WifiNetworkModel.Inactive() connectivityRepository.setWifiConnected(validated = false) wifiRepository.setIsWifiDefault(true) @@ -416,7 +416,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() { underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) ) - val networkModel = WifiNetworkModel.Inactive + val networkModel = WifiNetworkModel.Inactive() connectivityRepository.setWifiConnected(validated = false) wifiRepository.setIsWifiDefault(true) @@ -543,7 +543,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() { private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { val networkModel = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 4, ssid = "test ssid", hotspotDeviceType = hotspot, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt index 244422943309..fa6d8bf4a317 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapperTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.qsEventLogger +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig @@ -43,7 +44,8 @@ class IssueRecordingMapperTest : SysuiTestCase() { QSTileConfig( TileSpec.create(RecordIssueModule.TILE_SPEC), uiConfig, - kosmos.qsEventLogger.getNewInstanceId() + kosmos.qsEventLogger.getNewInstanceId(), + TileCategory.UTILITIES, ) private val resources = kosmos.mainResources private val theme = resources.newTheme() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt index b9ca8fc2d181..c0a15922642e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt @@ -78,7 +78,7 @@ class WifiInteractorImplTest : SysuiTestCase() { @Test fun ssid_inactiveNetwork_outputsNull() = testScope.runTest { - wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive()) var latest: String? = "default" val job = underTest.ssid.onEach { latest = it }.launchIn(this) @@ -93,7 +93,7 @@ class WifiInteractorImplTest : SysuiTestCase() { fun ssid_carrierMergedNetwork_outputsNull() = testScope.runTest { wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged(subscriptionId = 2, level = 1) + WifiNetworkModel.CarrierMerged.of(subscriptionId = 2, level = 1) ) var latest: String? = "default" @@ -109,7 +109,7 @@ class WifiInteractorImplTest : SysuiTestCase() { fun ssid_unknownSsid_outputsNull() = testScope.runTest { wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 1, ssid = WifiManager.UNKNOWN_SSID, ) @@ -128,7 +128,7 @@ class WifiInteractorImplTest : SysuiTestCase() { fun ssid_validSsid_outputsSsid() = testScope.runTest { wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 1, ssid = "MyAwesomeWifiNetwork", ) @@ -189,7 +189,7 @@ class WifiInteractorImplTest : SysuiTestCase() { fun wifiNetwork_matchesRepoWifiNetwork() = testScope.runTest { val wifiNetwork = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( isValidated = true, level = 3, ssid = "AB", @@ -263,7 +263,7 @@ class WifiInteractorImplTest : SysuiTestCase() { val latest by collectLastValue(underTest.areNetworksAvailable) wifiRepository.wifiScanResults.value = emptyList() - wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive()) assertThat(latest).isFalse() } @@ -280,7 +280,7 @@ class WifiInteractorImplTest : SysuiTestCase() { WifiScanEntry(ssid = "ssid 3"), ) - wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive()) assertThat(latest).isTrue() } @@ -298,7 +298,7 @@ class WifiInteractorImplTest : SysuiTestCase() { ) wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( ssid = "ssid 2", level = 2, ) @@ -318,7 +318,7 @@ class WifiInteractorImplTest : SysuiTestCase() { ) wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( ssid = "ssid 2", level = 2, ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index 72a45b9b5452..141e304b63a1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -115,7 +115,7 @@ class WifiViewModelTest : SysuiTestCase() { val latestKeyguard by collectLastValue(keyguard.wifiIcon) val latestQs by collectLastValue(qs.wifiIcon) - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 1)) assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java) assertThat(latestHome).isEqualTo(latestKeyguard) @@ -129,7 +129,7 @@ class WifiViewModelTest : SysuiTestCase() { // Even WHEN the network has a valid hotspot type wifiRepository.setWifiNetwork( - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( isValidated = true, level = 1, hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP, @@ -191,7 +191,7 @@ class WifiViewModelTest : SysuiTestCase() { whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(ssid = null, level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(ssid = null, level = 1)) val activityIn by collectLastValue(underTest.isActivityInViewVisible) val activityOut by collectLastValue(underTest.isActivityOutViewVisible) @@ -214,7 +214,7 @@ class WifiViewModelTest : SysuiTestCase() { whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true) createAndSetViewModel() - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(ssid = null, level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(ssid = null, level = 1)) val activityIn by collectLastValue(underTest.isActivityInViewVisible) val activityOut by collectLastValue(underTest.isActivityOutViewVisible) @@ -463,6 +463,6 @@ class WifiViewModelTest : SysuiTestCase() { } companion object { - private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active(ssid = "AB", level = 1) + private val ACTIVE_VALID_WIFI_NETWORK = WifiNetworkModel.Active.of(ssid = "AB", level = 1) } } diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1fb1dad067f9..589d9ddd20b9 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3812,4 +3812,33 @@ Action + ESC for this.</string> <!-- Toast message for notifying users to use regular brightness bar to lower the brightness. [CHAR LIMIT=NONE] --> <string name="accessibility_deprecate_extra_dim_dialog_toast"> Extra dim shortcut removed. To lower your brightness, use the regular brightness bar.</string> + + <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to connectivity, e.g. Internet. [CHAR LIMIT=NONE] --> + <string name="qs_edit_mode_category_connectivity"> + Connectivity + </string> + <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to accessibility functions, e.g. Hearing devices. [CHAR LIMIT=NONE] --> + <string name="qs_edit_mode_category_accessibility"> + Accessibility + </string> + <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to general utilities, e.g. Flashlight. [CHAR LIMIT=NONE] --> + <string name="qs_edit_mode_category_utilities"> + Utilities + </string> + <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to privacy, e.g. Mic access. [CHAR LIMIT=NONE] --> + <string name="qs_edit_mode_category_privacy"> + Privacy + </string> + <!-- Label for category in QS Edit mode list of tiles, for tiles that are provided by an app, e.g. Calculator. [CHAR LIMIT=NONE] --> + <string name="qs_edit_mode_category_providedByApps"> + Provided by apps + </string> + <!-- Label for category in QS Edit mode list of tiles, for tiles that are related to the display, e.g. Dark theme. [CHAR LIMIT=NONE] --> + <string name="qs_edit_mode_category_display"> + Display + </string> + <!-- Label for category in QS Edit mode list of tiles, for tiles with an unknown category. [CHAR LIMIT=NONE] --> + <string name="qs_edit_mode_category_unknown"> + Unknown + </string> </resources> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java index b8319e51ec3b..c8de9f6660f9 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/view/RecentsTransition.java @@ -36,36 +36,6 @@ import java.util.function.Consumer; public class RecentsTransition { /** - * Creates a new transition aspect scaled transition activity options. - */ - public static ActivityOptions createAspectScaleAnimation(Context context, Handler handler, - boolean scaleUp, AppTransitionAnimationSpecsFuture animationSpecsFuture, - final Runnable animationStartCallback) { - final OnAnimationStartedListener animStartedListener = new OnAnimationStartedListener() { - private boolean mHandled; - - @Override - public void onAnimationStarted(long elapsedRealTime) { - // OnAnimationStartedListener can be called numerous times, so debounce here to - // prevent multiple callbacks - if (mHandled) { - return; - } - mHandled = true; - - if (animationStartCallback != null) { - animationStartCallback.run(); - } - } - }; - final ActivityOptions opts = ActivityOptions.makeMultiThumbFutureAspectScaleAnimation( - context, handler, - animationSpecsFuture != null ? animationSpecsFuture.getFuture() : null, - animStartedListener, scaleUp); - return opts; - } - - /** * Wraps a animation-start callback in a binder that can be called from window manager. */ public static IRemoteCallback wrapStartedListener(final Handler handler, diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index bbf46984208f..76af813fe2f8 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -18,13 +18,13 @@ package com.android.systemui.shared.system; import android.os.RemoteException; import android.util.Log; -import android.view.IRecentsAnimationController; import android.view.SurfaceControl; import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; import com.android.internal.os.IResultReceiver; import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.wm.shell.recents.IRecentsAnimationController; public class RecentsAnimationControllerCompat { @@ -58,14 +58,6 @@ public class RecentsAnimationControllerCompat { } } - public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { - try { - mAnimationController.setAnimationTargetsBehindSystemBars(behindSystemBars); - } catch (RemoteException e) { - Log.e(TAG, "Failed to set whether animation targets are behind system bars", e); - } - } - /** * Sets the final surface transaction on a Task. This is used by Launcher to notify the system * that animating Activity to PiP has completed and the associated task surface should be @@ -103,22 +95,6 @@ public class RecentsAnimationControllerCompat { } } - public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { - try { - mAnimationController.setDeferCancelUntilNextTransition(defer, screenshot); - } catch (RemoteException e) { - Log.e(TAG, "Failed to set deferred cancel with screenshot", e); - } - } - - public void cleanupScreenshot() { - try { - mAnimationController.cleanupScreenshot(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to clean up screenshot of recents animation", e); - } - } - /** * @see {{@link IRecentsAnimationController#setWillFinishToHome(boolean)}}. */ @@ -131,18 +107,6 @@ public class RecentsAnimationControllerCompat { } /** - * @see IRecentsAnimationController#removeTask - */ - public boolean removeTask(int taskId) { - try { - return mAnimationController.removeTask(taskId); - } catch (RemoteException e) { - Log.e(TAG, "Failed to remove remote animation target", e); - return false; - } - } - - /** * @see IRecentsAnimationController#detachNavigationBarFromApp */ public void detachNavigationBarFromApp(boolean moveHomeToTop) { @@ -152,15 +116,4 @@ public class RecentsAnimationControllerCompat { Log.e(TAG, "Failed to detach the navigation bar from app", e); } } - - /** - * @see IRecentsAnimationController#animateNavigationBarToApp(long) - */ - public void animateNavigationBarToApp(long duration) { - try { - mAnimationController.animateNavigationBarToApp(duration); - } catch (RemoteException e) { - Log.e(TAG, "Failed to animate the navigation bar to app", e); - } - } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index 24073501c946..51892aac606a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -42,14 +42,4 @@ public interface RecentsAnimationListener { * was running becomes ready for control. */ void onTasksAppeared(RemoteAnimationTarget[] app); - - /** - * Called to request that the current task tile be switched out for a screenshot (if not - * already). Once complete, onFinished should be called. - * @return true if this impl will call onFinished. No other onSwitchToScreenshot impls will - * be called afterwards (to avoid multiple calls to onFinished). - */ - default boolean onSwitchToScreenshot(Runnable onFinished) { - return false; - } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt index bb80396c70fb..cd9efaf6e6bb 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.accessibility.qs import com.android.systemui.Flags import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile @@ -179,6 +180,7 @@ interface QSAccessibilityModule { labelRes = R.string.quick_settings_color_correction_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.ACCESSIBILITY, ) /** Inject ColorCorrectionTile into tileViewModelMap in QSModule */ @@ -210,6 +212,7 @@ interface QSAccessibilityModule { labelRes = R.string.quick_settings_inversion_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.ACCESSIBILITY, ) /** Inject ColorInversionTile into tileViewModelMap in QSModule */ @@ -241,6 +244,7 @@ interface QSAccessibilityModule { labelRes = R.string.quick_settings_font_scaling_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.DISPLAY, ) /** Inject FontScaling Tile into tileViewModelMap in QSModule */ @@ -272,6 +276,7 @@ interface QSAccessibilityModule { labelRes = com.android.internal.R.string.reduce_bright_colors_feature_name, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.DISPLAY, ) @Provides @@ -286,6 +291,7 @@ interface QSAccessibilityModule { labelRes = R.string.quick_settings_hearing_devices_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.ACCESSIBILITY, ) /** @@ -322,6 +328,7 @@ interface QSAccessibilityModule { labelRes = R.string.quick_settings_onehanded_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.ACCESSIBILITY, ) /** Inject One Handed Mode Tile into tileViewModelMap in QSModule. */ @@ -355,6 +362,7 @@ interface QSAccessibilityModule { labelRes = R.string.quick_settings_night_display_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.DISPLAY, ) /** diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt index 8a9a322ff100..831bc1da5a79 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt +++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt @@ -2,6 +2,7 @@ package com.android.systemui.battery import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.BatterySaverTile import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor @@ -33,7 +34,7 @@ interface BatterySaverModule { @IntoMap @StringKey(BATTERY_SAVER_TILE_SPEC) fun provideBatterySaverAvailabilityInteractor( - impl: BatterySaverTileDataInteractor + impl: BatterySaverTileDataInteractor ): QSTileAvailabilityInteractor companion object { @@ -51,6 +52,7 @@ interface BatterySaverModule { labelRes = R.string.battery_detail_switch_title, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.UTILITIES, ) /** Inject BatterySaverTile into tileViewModelMap in QSModule */ diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt index db7ffc1bef79..037b6facc50d 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt @@ -48,6 +48,7 @@ import com.android.systemui.controls.ui.ControlsUiControllerImpl import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.DeviceControlsTile import com.android.systemui.qs.tiles.viewmodel.QSTileConfig @@ -86,15 +87,16 @@ abstract class ControlsModule { @IntoMap @StringKey(DEVICE_CONTROLS_SPEC) fun provideDeviceControlsTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = - QSTileConfig( - tileSpec = TileSpec.create(DEVICE_CONTROLS_SPEC), - uiConfig = - QSTileUIConfig.Resource( - iconRes = com.android.systemui.res.R.drawable.controls_icon, - labelRes = com.android.systemui.res.R.string.quick_controls_title - ), - instanceId = uiEventLogger.getNewInstanceId(), - ) + QSTileConfig( + tileSpec = TileSpec.create(DEVICE_CONTROLS_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = com.android.systemui.res.R.drawable.controls_icon, + labelRes = com.android.systemui.res.R.string.quick_controls_title + ), + instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.UTILITIES, + ) } @Binds @@ -115,12 +117,12 @@ abstract class ControlsModule { @Binds abstract fun provideSettingsManager( - manager: ControlsSettingsRepositoryImpl + manager: ControlsSettingsRepositoryImpl ): ControlsSettingsRepository @Binds abstract fun provideDialogManager( - manager: ControlsSettingsDialogManagerImpl + manager: ControlsSettingsDialogManagerImpl ): ControlsSettingsDialogManager @Binds @@ -141,8 +143,7 @@ abstract class ControlsModule { repository: SelectedComponentRepositoryImpl ): SelectedComponentRepository - @BindsOptionalOf - abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper + @BindsOptionalOf abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper @BindsOptionalOf abstract fun provideControlsTileResourceConfiguration(): ControlsTileResourceConfiguration @@ -157,23 +158,17 @@ abstract class ControlsModule { @Binds @IntoMap @ClassKey(ControlsFavoritingActivity::class) - abstract fun provideControlsFavoritingActivity( - activity: ControlsFavoritingActivity - ): Activity + abstract fun provideControlsFavoritingActivity(activity: ControlsFavoritingActivity): Activity @Binds @IntoMap @ClassKey(ControlsEditingActivity::class) - abstract fun provideControlsEditingActivity( - activity: ControlsEditingActivity - ): Activity + abstract fun provideControlsEditingActivity(activity: ControlsEditingActivity): Activity @Binds @IntoMap @ClassKey(ControlsRequestDialog::class) - abstract fun provideControlsRequestDialog( - activity: ControlsRequestDialog - ): Activity + abstract fun provideControlsRequestDialog(activity: ControlsRequestDialog): Activity @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index f6ac7a579140..a45ad157837b 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -38,6 +38,7 @@ import com.android.systemui.dreams.homecontrols.DreamServiceDelegateImpl; import com.android.systemui.dreams.homecontrols.HomeControlsDreamService; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.pipeline.shared.TileSpec; +import com.android.systemui.qs.shared.model.TileCategory; import com.android.systemui.qs.tiles.viewmodel.QSTileConfig; import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy; import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig; @@ -196,6 +197,7 @@ public interface DreamModule { R.drawable.ic_qs_screen_saver, R.string.quick_settings_screensaver_label), uiEventLogger.getNewInstanceId(), + TileCategory.UTILITIES, tileSpec.getSpec(), QSTilePolicy.NoRestrictions.INSTANCE ); diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt index 3223433568b9..eb4eee204834 100644 --- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduStatsInteractor.kt @@ -16,11 +16,17 @@ package com.android.systemui.education.domain.interactor +import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.contextualeducation.GestureType.ALL_APPS import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.inputdevice.data.repository.UserInputDeviceRepository +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD +import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** @@ -39,12 +45,17 @@ class KeyboardTouchpadEduStatsInteractorImpl @Inject constructor( @Background private val backgroundScope: CoroutineScope, - private val contextualEducationInteractor: ContextualEducationInteractor + private val contextualEducationInteractor: ContextualEducationInteractor, + private val inputDeviceRepository: UserInputDeviceRepository, ) : KeyboardTouchpadEduStatsInteractor { override fun incrementSignalCount(gestureType: GestureType) { - // Todo: check if keyboard/touchpad is connected before update - backgroundScope.launch { contextualEducationInteractor.incrementSignalCount(gestureType) } + backgroundScope.launch { + val targetDevice = getTargetDevice(gestureType) + if (isTargetDeviceConnected(targetDevice)) { + contextualEducationInteractor.incrementSignalCount(gestureType) + } + } } override fun updateShortcutTriggerTime(gestureType: GestureType) { @@ -52,4 +63,24 @@ constructor( contextualEducationInteractor.updateShortcutTriggerTime(gestureType) } } + + private suspend fun isTargetDeviceConnected(deviceType: DeviceType): Boolean { + if (deviceType == KEYBOARD) { + return inputDeviceRepository.isAnyKeyboardConnectedForUser.first().isConnected + } else if (deviceType == TOUCHPAD) { + return inputDeviceRepository.isAnyTouchpadConnectedForUser.first().isConnected + } + return false + } + + /** + * Keyboard shortcut education would be provided for All Apps. Touchpad gesture education would + * be provided for the rest of the gesture types (i.e. Home, Overview, Back). This method maps + * gesture to its target education device. + */ + private fun getTargetDevice(gestureType: GestureType) = + when (gestureType) { + ALL_APPS -> KEYBOARD + else -> TOUCHPAD + } } diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt index 7ecacdc7cf16..092a25aa1cad 100644 --- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartable.kt @@ -16,9 +16,17 @@ package com.android.systemui.inputdevice.tutorial +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.UserHandle import com.android.systemui.CoreStartable +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator +import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity import com.android.systemui.shared.Flags.newTouchpadGesturesTutorial import dagger.Lazy import javax.inject.Inject @@ -27,11 +35,35 @@ import javax.inject.Inject @SysUISingleton class KeyboardTouchpadTutorialCoreStartable @Inject -constructor(private val tutorialNotificationCoordinator: Lazy<TutorialNotificationCoordinator>) : - CoreStartable { +constructor( + private val tutorialNotificationCoordinator: Lazy<TutorialNotificationCoordinator>, + private val broadcastDispatcher: BroadcastDispatcher, + @Application private val applicationContext: Context, +) : CoreStartable { override fun start() { if (newTouchpadGesturesTutorial()) { tutorialNotificationCoordinator.get().start() + registerTutorialBroadcastReceiver() } } + + private fun registerTutorialBroadcastReceiver() { + broadcastDispatcher.registerReceiver( + receiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + applicationContext.startActivityAsUser( + Intent( + applicationContext, + KeyboardTouchpadTutorialActivity::class.java + ), + UserHandle.SYSTEM + ) + } + }, + filter = IntentFilter("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL"), + flags = Context.RECEIVER_EXPORTED, + user = UserHandle.ALL, + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index 63f3d52b2d2d..dcca12f29287 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -82,6 +82,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.geometry.CornerRadius @@ -92,8 +93,12 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -824,9 +829,18 @@ private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) { // from the ViewModel. var queryInternal by remember { mutableStateOf("") } val focusRequester = remember { FocusRequester() } + val focusManager = LocalFocusManager.current LaunchedEffect(Unit) { focusRequester.requestFocus() } SearchBar( - modifier = Modifier.fillMaxWidth().focusRequester(focusRequester), + modifier = + Modifier.fillMaxWidth().focusRequester(focusRequester).onKeyEvent { + if (it.key == Key.DirectionDown) { + focusManager.moveFocus(FocusDirection.Down) + return@onKeyEvent true + } else { + return@onKeyEvent false + } + }, colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright), query = queryInternal, active = false, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 362e016cc97c..df0f10acac1f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -71,6 +71,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.google.android.msdl.domain.MSDLPlayer import dagger.Lazy @@ -112,6 +113,7 @@ constructor( private val clockInteractor: KeyguardClockInteractor, private val keyguardViewMediator: KeyguardViewMediator, private val deviceEntryUnlockTrackerViewBinder: Optional<DeviceEntryUnlockTrackerViewBinder>, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, @Main private val mainDispatcher: CoroutineDispatcher, private val msdlPlayer: MSDLPlayer, ) : CoreStartable { @@ -220,6 +222,7 @@ constructor( vibratorHelper, falsingManager, keyguardViewMediator, + statusBarKeyguardViewManager, mainDispatcher, msdlPlayer, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt index f2d39dabb1b5..ecfabc3e18ce 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt @@ -18,13 +18,12 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.graphics.PointF +import android.view.InputDevice import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration import android.view.ViewPropertyAnimator -import androidx.core.animation.CycleInterpolator -import androidx.core.animation.ObjectAnimator -import com.android.systemui.res.R +import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.view.rawDistanceFrom import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel @@ -71,11 +70,10 @@ class KeyguardQuickAffordanceOnTouchListener( // Moving too far while performing a long-press gesture cancels that // gesture. if ( - event - .rawDistanceFrom( - downDisplayCoords.x, - downDisplayCoords.y, - ) > ViewConfiguration.getTouchSlop() + event.rawDistanceFrom( + downDisplayCoords.x, + downDisplayCoords.y, + ) > ViewConfiguration.getTouchSlop() ) { cancel() } @@ -151,10 +149,14 @@ class KeyguardQuickAffordanceOnTouchListener( event: MotionEvent, pointerIndex: Int = 0, ): Boolean { - return when (event.getToolType(pointerIndex)) { - MotionEvent.TOOL_TYPE_STYLUS -> true - MotionEvent.TOOL_TYPE_MOUSE -> true - else -> false + return if (Flags.nonTouchscreenDevicesBypassFalsing()) { + event.device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == false + } else { + when (event.getToolType(pointerIndex)) { + MotionEvent.TOOL_TYPE_STYLUS -> true + MotionEvent.TOOL_TYPE_MOUSE -> true + else -> false + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index 5bb7b6425729..ed82159e6160 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -24,6 +24,8 @@ import android.graphics.Point import android.graphics.Rect import android.util.Log import android.view.HapticFeedbackConstants +import android.view.InputDevice +import android.view.MotionEvent import android.view.View import android.view.View.OnLayoutChangeListener import android.view.View.VISIBLE @@ -41,6 +43,7 @@ import com.android.app.tracing.coroutines.launch import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags import com.android.systemui.Flags.msdlFeedback import com.android.systemui.Flags.newAodTransition import com.android.systemui.common.shared.model.Icon @@ -73,6 +76,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.CrossFadeHelper import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.phone.ScreenOffAnimationController +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.temporarydisplay.ViewPriority import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo @@ -115,6 +119,7 @@ object KeyguardRootViewBinder { vibratorHelper: VibratorHelper?, falsingManager: FalsingManager?, keyguardViewMediator: KeyguardViewMediator?, + statusBarKeyguardViewManager: StatusBarKeyguardViewManager?, mainImmediateDispatcher: CoroutineDispatcher, msdlPlayer: MSDLPlayer?, ): DisposableHandle { @@ -124,12 +129,30 @@ object KeyguardRootViewBinder { if (KeyguardBottomAreaRefactor.isEnabled) { disposables += view.onTouchListener { _, event -> + var consumed = false if (falsingManager?.isFalseTap(FalsingManager.LOW_PENALTY) == false) { + // signifies a primary button click down has reached keyguardrootview + // we need to return true here otherwise an ACTION_UP will never arrive + if (Flags.nonTouchscreenDevicesBypassFalsing()) { + if ( + event.action == MotionEvent.ACTION_DOWN && + event.buttonState == MotionEvent.BUTTON_PRIMARY && + !event.isTouchscreenSource() + ) { + consumed = true + } else if ( + event.action == MotionEvent.ACTION_UP && + !event.isTouchscreenSource() + ) { + statusBarKeyguardViewManager?.showBouncer(true) + consumed = true + } + } viewModel.setRootViewLastTapPosition( Point(event.x.toInt(), event.y.toInt()) ) } - false + consumed } } @@ -637,6 +660,10 @@ object KeyguardRootViewBinder { } } + private fun MotionEvent.isTouchscreenSource(): Boolean { + return device?.supportsSource(InputDevice.SOURCE_TOUCHSCREEN) == true + } + private fun ViewPropertyAnimator.animateInIconTranslation(): ViewPropertyAnimator = setInterpolator(Interpolators.DECELERATE_QUINT).translationY(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index f581a2e24546..0b8f7417a49d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -417,6 +417,7 @@ constructor( null, // device entry haptics not required for preview mode null, // falsing manager not required for preview mode null, // keyguard view mediator is not required for preview mode + null, // primary bouncer interactor is not required for preview mode mainDispatcher, null, ) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt index 4ad437c50d61..84aae652795e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt @@ -70,6 +70,7 @@ import com.android.systemui.dump.DumpManager import com.android.systemui.media.controls.domain.pipeline.MediaDataManager.Companion.isMediaNotification import com.android.systemui.media.controls.domain.resume.MediaResumeListener import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser +import com.android.systemui.media.controls.shared.MediaLogger import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC import com.android.systemui.media.controls.shared.model.MediaAction @@ -84,7 +85,7 @@ import com.android.systemui.media.controls.util.MediaControllerFactory import com.android.systemui.media.controls.util.MediaDataUtils import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger -import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.media.controls.util.SmallHash import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.res.R import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState @@ -186,7 +187,6 @@ class LegacyMediaDataManagerImpl( private val mediaDeviceManager: MediaDeviceManager, mediaDataCombineLatest: MediaDataCombineLatest, private val mediaDataFilter: LegacyMediaDataFilterImpl, - private val activityStarter: ActivityStarter, private val smartspaceMediaDataProvider: SmartspaceMediaDataProvider, private var useMediaResumption: Boolean, private val useQsMediaPlayer: Boolean, @@ -197,6 +197,7 @@ class LegacyMediaDataManagerImpl( private val smartspaceManager: SmartspaceManager?, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val mediaDataLoader: dagger.Lazy<MediaDataLoader>, + private val mediaLogger: MediaLogger, ) : Dumpable, BcSmartspaceDataPlugin.SmartspaceTargetListener, MediaDataManager { companion object { @@ -273,7 +274,6 @@ class LegacyMediaDataManagerImpl( mediaDeviceManager: MediaDeviceManager, mediaDataCombineLatest: MediaDataCombineLatest, mediaDataFilter: LegacyMediaDataFilterImpl, - activityStarter: ActivityStarter, smartspaceMediaDataProvider: SmartspaceMediaDataProvider, clock: SystemClock, tunerService: TunerService, @@ -282,6 +282,7 @@ class LegacyMediaDataManagerImpl( smartspaceManager: SmartspaceManager?, keyguardUpdateMonitor: KeyguardUpdateMonitor, mediaDataLoader: dagger.Lazy<MediaDataLoader>, + mediaLogger: MediaLogger, ) : this( context, // Loading bitmap for UMO background can take longer time, so it cannot run on the default @@ -301,7 +302,6 @@ class LegacyMediaDataManagerImpl( mediaDeviceManager, mediaDataCombineLatest, mediaDataFilter, - activityStarter, smartspaceMediaDataProvider, Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context), @@ -312,6 +312,7 @@ class LegacyMediaDataManagerImpl( smartspaceManager, keyguardUpdateMonitor, mediaDataLoader, + mediaLogger, ) private val appChangeReceiver = @@ -564,6 +565,42 @@ class LegacyMediaDataManagerImpl( val resumeAction: Runnable? = currentEntry?.resumeAction val hasCheckedForResume = currentEntry?.hasCheckedForResume == true val active = currentEntry?.active ?: true + val mediaController = mediaControllerFactory.create(result.token!!) + + val mediaData = + MediaData( + userId = sbn.normalizedUserId, + initialized = true, + app = result.appName, + appIcon = result.appIcon, + artist = result.artist, + song = result.song, + artwork = result.artworkIcon, + actions = result.actionIcons, + actionsToShowInCompact = result.actionsToShowInCompact, + semanticActions = result.semanticActions, + packageName = sbn.packageName, + token = result.token, + clickIntent = result.clickIntent, + device = result.device, + active = active, + resumeAction = resumeAction, + playbackLocation = result.playbackLocation, + notificationKey = key, + hasCheckedForResume = hasCheckedForResume, + isPlaying = result.isPlaying, + isClearable = !sbn.isOngoing, + lastActive = lastActive, + createdTimestampMillis = createdTimestampMillis, + instanceId = instanceId, + appUid = result.appUid, + isExplicit = result.isExplicit, + ) + + if (isSameMediaData(context, mediaController, mediaData, currentEntry)) { + mediaLogger.logDuplicateMediaNotification(key) + return@withContext + } // We need to log the correct media added. if (isNewlyActiveEntry) { @@ -583,40 +620,7 @@ class LegacyMediaDataManagerImpl( ) } - withContext(mainDispatcher) { - onMediaDataLoaded( - key, - oldKey, - MediaData( - userId = sbn.normalizedUserId, - initialized = true, - app = result.appName, - appIcon = result.appIcon, - artist = result.artist, - song = result.song, - artwork = result.artworkIcon, - actions = result.actionIcons, - actionsToShowInCompact = result.actionsToShowInCompact, - semanticActions = result.semanticActions, - packageName = sbn.packageName, - token = result.token, - clickIntent = result.clickIntent, - device = result.device, - active = active, - resumeAction = resumeAction, - playbackLocation = result.playbackLocation, - notificationKey = key, - hasCheckedForResume = hasCheckedForResume, - isPlaying = result.isPlaying, - isClearable = !sbn.isOngoing, - lastActive = lastActive, - createdTimestampMillis = createdTimestampMillis, - instanceId = instanceId, - appUid = result.appUid, - isExplicit = result.isExplicit, - ) - ) - } + withContext(mainDispatcher) { onMediaDataLoaded(key, oldKey, mediaData) } } /** Add a listener for changes in this class */ @@ -1100,6 +1104,47 @@ class LegacyMediaDataManagerImpl( val instanceId = currentEntry?.instanceId ?: logger.getNewInstanceId() val appUid = appInfo?.uid ?: Process.INVALID_UID + val lastActive = systemClock.elapsedRealtime() + val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L + val resumeAction: Runnable? = mediaEntries[key]?.resumeAction + val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true + val active = mediaEntries[key]?.active ?: true + var mediaData = + MediaData( + sbn.normalizedUserId, + true, + appName, + smallIcon, + artist, + song, + artWorkIcon, + actionIcons, + actionsToShowCollapsed, + semanticActions, + sbn.packageName, + token, + notif.contentIntent, + device, + active, + resumeAction = resumeAction, + playbackLocation = playbackLocation, + notificationKey = key, + hasCheckedForResume = hasCheckedForResume, + isPlaying = isPlaying, + isClearable = !sbn.isOngoing, + lastActive = lastActive, + createdTimestampMillis = createdTimestampMillis, + instanceId = instanceId, + appUid = appUid, + isExplicit = isExplicit, + smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()), + ) + + if (isSameMediaData(context, mediaController, mediaData, currentEntry)) { + mediaLogger.logDuplicateMediaNotification(key) + return + } + if (isNewlyActiveEntry) { logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId) logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation) @@ -1107,44 +1152,17 @@ class LegacyMediaDataManagerImpl( logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation) } - val lastActive = systemClock.elapsedRealtime() - val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L foregroundExecutor.execute { - val resumeAction: Runnable? = mediaEntries[key]?.resumeAction - val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true - val active = mediaEntries[key]?.active ?: true - onMediaDataLoaded( - key, - oldKey, - MediaData( - sbn.normalizedUserId, - true, - appName, - smallIcon, - artist, - song, - artWorkIcon, - actionIcons, - actionsToShowCollapsed, - semanticActions, - sbn.packageName, - token, - notif.contentIntent, - device, - active, - resumeAction = resumeAction, - playbackLocation = playbackLocation, - notificationKey = key, - hasCheckedForResume = hasCheckedForResume, - isPlaying = isPlaying, - isClearable = !sbn.isOngoing, - lastActive = lastActive, - createdTimestampMillis = createdTimestampMillis, - instanceId = instanceId, - appUid = appUid, - isExplicit = isExplicit, + val oldResumeAction: Runnable? = mediaEntries[key]?.resumeAction + val oldHasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true + val oldActive = mediaEntries[key]?.active ?: true + mediaData = + mediaData.copy( + resumeAction = oldResumeAction, + hasCheckedForResume = oldHasCheckedForResume, + active = oldActive ) - ) + onMediaDataLoaded(key, oldKey, mediaData) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt index e4047e5f96c5..9ee59d1875a6 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt @@ -80,6 +80,7 @@ constructor( field?.disconnect() field = value } + private var currentUserId: Int = context.userId @VisibleForTesting @@ -89,7 +90,7 @@ constructor( if (Intent.ACTION_USER_UNLOCKED == intent.action) { val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) if (userId == currentUserId) { - loadMediaResumptionControls() + backgroundExecutor.execute { loadMediaResumptionControls() } } } } @@ -254,15 +255,15 @@ constructor( if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) { // TODO also check for a media button receiver intended for restarting (b/154127084) // Set null action to prevent additional attempts to connect - mediaDataManager.setResumeAction(key, null) - Log.d(TAG, "Checking for service component for " + data.packageName) - val pm = context.packageManager - val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) - val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId) + backgroundExecutor.execute { + mediaDataManager.setResumeAction(key, null) + Log.d(TAG, "Checking for service component for " + data.packageName) + val pm = context.packageManager + val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE) + val resumeInfo = pm.queryIntentServicesAsUser(serviceIntent, 0, currentUserId) - val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName } - if (inf != null && inf.size > 0) { - backgroundExecutor.execute { + val inf = resumeInfo?.filter { it.serviceInfo.packageName == data.packageName } + if (inf != null && inf.size > 0) { tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt index cef1e69e7b6a..2a9fe8314349 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModel.kt @@ -32,6 +32,7 @@ import androidx.annotation.WorkerThread import androidx.core.view.GestureDetectorCompat import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.android.systemui.Flags import com.android.systemui.classifier.Classifier.MEDIA_SEEKBAR import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.FalsingManager @@ -102,9 +103,11 @@ constructor( } _progress.postValue(value) } + private val _progress = MutableLiveData<Progress>().apply { postValue(_data) } val progress: LiveData<Progress> get() = _progress + private var controller: MediaController? = null set(value) { if (field?.sessionToken != value?.sessionToken) { @@ -113,6 +116,7 @@ constructor( field = value } } + private var playbackState: PlaybackState? = null private var callback = object : MediaController.Callback() { @@ -128,6 +132,15 @@ constructor( override fun onSessionDestroyed() { clearController() } + + override fun onMetadataChanged(metadata: MediaMetadata?) { + if (!Flags.mediaControlsPostsOptimization()) return + + val (enabled, duration) = getEnabledStateAndDuration(metadata) + if (_data.duration != duration) { + _data = _data.copy(enabled = enabled, duration = duration) + } + } } private var cancel: Runnable? = null @@ -233,22 +246,13 @@ constructor( fun updateController(mediaController: MediaController?) { controller = mediaController playbackState = controller?.playbackState - val mediaMetadata = controller?.metadata + val (enabled, duration) = getEnabledStateAndDuration(controller?.metadata) val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L val position = playbackState?.position?.toInt() - val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0 val playing = NotificationMediaManager.isPlayingState( playbackState?.state ?: PlaybackState.STATE_NONE ) - val enabled = - if ( - playbackState == null || - playbackState?.getState() == PlaybackState.STATE_NONE || - (duration <= 0) - ) - false - else true _data = Progress(enabled, seekAvailable, playing, scrubbing, position, duration, listening) checkIfPollingNeeded() } @@ -368,6 +372,16 @@ constructor( } } + /** returns a pair of whether seekbar is enabled and the duration of media. */ + private fun getEnabledStateAndDuration(metadata: MediaMetadata?): Pair<Boolean, Int> { + val duration = metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt() ?: 0 + val enabled = + !(playbackState == null || + playbackState?.state == PlaybackState.STATE_NONE || + (duration <= 0)) + return Pair(enabled, duration) + } + /** * This method specifies if user made a bad seekbar grab or not. If the vertical distance from * first touch on seekbar is more than the horizontal distance, this means that the seekbar grab diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index 18c6f53630a4..4251b81226b3 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -307,7 +307,7 @@ public class MediaProjectionPermissionActivity extends Activity { private void setUpDialog(AlertDialog dialog) { SystemUIDialog.registerDismissListener(dialog); - SystemUIDialog.applyFlags(dialog); + SystemUIDialog.applyFlags(dialog, /* showWhenLocked= */ false); SystemUIDialog.setDialogSize(dialog); dialog.setOnCancelListener(this::onDialogDismissedOrCancelled); diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt index 3a8fe7198ebd..ef1f8341cb15 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.qrcodescanner.dagger import com.android.systemui.Flags import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.QRCodeScannerTile import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor @@ -51,7 +52,7 @@ interface QRCodeScannerModule { @IntoMap @StringKey(QR_CODE_SCANNER_TILE_SPEC) fun provideQrCodeScannerAvailabilityInteractor( - impl: QRCodeScannerTileDataInteractor + impl: QRCodeScannerTileDataInteractor ): QSTileAvailabilityInteractor companion object { @@ -69,6 +70,7 @@ interface QRCodeScannerModule { labelRes = R.string.qr_code_scanner_title, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.UTILITIES, ) /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt index 28c1fbf2007d..2f054b0a82c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt @@ -24,10 +24,10 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.panels.shared.model.EditTileData import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.settings.UserTracker import javax.inject.Inject import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @SysUISingleton @@ -60,6 +60,7 @@ constructor( Icon.Loaded(icon, ContentDescription.Loaded(label.toString())), Text.Loaded(label.toString()), Text.Loaded(appName.toString()), + TileCategory.PROVIDED_BY_APP, ) } else { null diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt index 3b29422ccfc3..a2cee3b68d49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.qs.panels.data.repository.IconAndNameCustomRepositor import com.android.systemui.qs.panels.data.repository.StockTilesRepository import com.android.systemui.qs.panels.domain.model.EditTilesModel import com.android.systemui.qs.panels.shared.model.EditTileData +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider import javax.inject.Inject @@ -53,6 +54,7 @@ constructor( ), Text.Resource(config.uiConfig.labelRes), null, + category = config.category, ) } else { EditTileData( @@ -62,7 +64,8 @@ constructor( ContentDescription.Loaded(it.spec) ), Text.Loaded(it.spec), - null + null, + category = TileCategory.UNKNOWN, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt index 8b70bb9f9e23..b153ef7e8a62 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt @@ -19,12 +19,14 @@ package com.android.systemui.qs.panels.shared.model import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory data class EditTileData( val tileSpec: TileSpec, val icon: Icon, val label: Text, val appName: Text?, + val category: TileCategory, ) { init { check( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt index 24af09d62313..93037d1d2572 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -34,6 +34,7 @@ import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.LocalOverscrollConfiguration +import androidx.compose.foundation.background import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.border import androidx.compose.foundation.combinedClickable @@ -95,6 +96,8 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable import com.android.compose.modifiers.background @@ -115,6 +118,7 @@ import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import java.util.function.Supplier @@ -472,31 +476,39 @@ private fun AvailableTileGrid( onClick: (TileSpec) -> Unit, dragAndDropState: DragAndDropState, ) { - // Available tiles aren't visible during drag and drop, so the row isn't needed - val (otherTilesStock, otherTilesCustom) = - tiles.map { TileGridCell(it, 0) }.partition { it.tile.appName == null } val availableTileHeight = tileHeight(true) val availableGridHeight = gridHeight(tiles.size, availableTileHeight, columns, tilePadding) + // Available tiles aren't visible during drag and drop, so the row isn't needed + val groupedTiles = + remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { + groupAndSort(tiles.fastMap { TileGridCell(it, 0) }) + } + val labelColors = TileDefaults.inactiveTileColors() // Available tiles TileLazyGrid( modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG), columns = GridCells.Fixed(columns) ) { - editTiles( - otherTilesStock, - ClickAction.ADD, - onClick, - dragAndDropState = dragAndDropState, - showLabels = true, - ) - editTiles( - otherTilesCustom, - ClickAction.ADD, - onClick, - dragAndDropState = dragAndDropState, - showLabels = true, - ) + groupedTiles.forEach { category, tiles -> + stickyHeader { + Text( + text = category.label.load() ?: "", + fontSize = 20.sp, + color = labelColors.label, + modifier = + Modifier.background(Color.Black) + .padding(start = 16.dp, bottom = 8.dp, top = 8.dp) + ) + } + editTiles( + tiles, + ClickAction.ADD, + onClick, + dragAndDropState = dragAndDropState, + showLabels = true, + ) + } } } @@ -618,7 +630,7 @@ fun EditTile( showLabels: Boolean, modifier: Modifier = Modifier, ) { - val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec + val label = tileViewModel.label.text val colors = TileDefaults.inactiveTileColors() TileContainer( @@ -638,7 +650,7 @@ fun EditTile( } else { LargeTileContent( label = label, - secondaryLabel = tileViewModel.appName?.load(), + secondaryLabel = tileViewModel.appName?.text, icon = tileViewModel.icon, colors = colors, iconShape = RoundedCornerShape(TileDefaults.InactiveCornerRadius), diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt index 8ca8de762772..08ee856a0ec6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Immutable import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.splitInRowsSequence import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.shared.model.CategoryAndName /** Represents an item from a grid associated with a row and a span */ interface GridCell { @@ -38,7 +39,7 @@ data class TileGridCell( override val row: Int, override val width: Int, override val span: GridItemSpan = GridItemSpan(width) -) : GridCell, SizedTile<EditTileViewModel> { +) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile { val key: String = "${tile.tileSpec.spec}-$row" constructor( diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt index 42715be6f6c0..4a8aa83e7d4e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt @@ -16,6 +16,9 @@ package com.android.systemui.qs.panels.ui.viewmodel +import android.content.Context +import androidx.compose.ui.util.fastMap +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.qs.panels.domain.interactor.EditTilesListInteractor @@ -27,6 +30,7 @@ import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.CoroutineScope @@ -35,6 +39,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map @@ -49,6 +54,8 @@ constructor( private val currentTilesInteractor: CurrentTilesInteractor, private val tilesAvailabilityInteractor: TilesAvailabilityInteractor, private val minTilesInteractor: MinimumTilesInteractor, + private val configurationInteractor: ConfigurationInteractor, + @Application private val applicationContext: Context, @Named("Default") private val defaultGridLayout: GridLayout, @Application private val applicationScope: CoroutineScope, gridLayoutTypeInteractor: GridLayoutTypeInteractor, @@ -99,38 +106,45 @@ constructor( .map { it.tileSpec } .minus(currentTilesInteractor.currentTilesSpecs.toSet()) ) - currentTilesInteractor.currentTiles.map { tiles -> - val currentSpecs = tiles.map { it.spec } - val canRemoveTiles = currentSpecs.size > minimumTiles - val allTiles = editTilesData.stockTiles + editTilesData.customTiles - val allTilesMap = allTiles.associate { it.tileSpec to it } - val currentTiles = currentSpecs.map { allTilesMap.get(it) }.filterNotNull() - val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs } - - (currentTiles + nonCurrentTiles) - .filterNot { it.tileSpec in unavailable } - .map { - val current = it.tileSpec in currentSpecs - val availableActions = buildSet { - if (current) { - add(AvailableEditActions.MOVE) - if (canRemoveTiles) { - add(AvailableEditActions.REMOVE) + currentTilesInteractor.currentTiles + .map { tiles -> + val currentSpecs = tiles.map { it.spec } + val canRemoveTiles = currentSpecs.size > minimumTiles + val allTiles = editTilesData.stockTiles + editTilesData.customTiles + val allTilesMap = allTiles.associate { it.tileSpec to it } + val currentTiles = currentSpecs.map { allTilesMap.get(it) }.filterNotNull() + val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs } + + (currentTiles + nonCurrentTiles) + .filterNot { it.tileSpec in unavailable } + .map { + val current = it.tileSpec in currentSpecs + val availableActions = buildSet { + if (current) { + add(AvailableEditActions.MOVE) + if (canRemoveTiles) { + add(AvailableEditActions.REMOVE) + } + } else { + add(AvailableEditActions.ADD) } - } else { - add(AvailableEditActions.ADD) } + UnloadedEditTileViewModel( + it.tileSpec, + it.icon, + it.label, + it.appName, + current, + availableActions, + it.category, + ) } - EditTileViewModel( - it.tileSpec, - it.icon, - it.label, - it.appName, - current, - availableActions - ) - } - } + } + .combine(configurationInteractor.onAnyConfigurationChange.emitOnStart()) { + tiles, + _ -> + tiles.fastMap { it.load(applicationContext) } + } } else { emptyFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt index a4c86381b785..ee12736f6db4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt @@ -16,9 +16,15 @@ package com.android.systemui.qs.panels.ui.viewmodel +import android.content.Context +import androidx.compose.runtime.Immutable +import androidx.compose.ui.text.AnnotatedString import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.Text +import com.android.systemui.common.ui.compose.toAnnotatedString import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.CategoryAndName +import com.android.systemui.qs.shared.model.TileCategory /** * View model for each tile that is available to be added/removed/moved in Edit mode. @@ -26,14 +32,41 @@ import com.android.systemui.qs.pipeline.shared.TileSpec * [isCurrent] indicates whether this tile is part of the current set of tiles that the user sees in * Quick Settings. */ -data class EditTileViewModel( +data class UnloadedEditTileViewModel( val tileSpec: TileSpec, val icon: Icon, val label: Text, val appName: Text?, val isCurrent: Boolean, val availableEditActions: Set<AvailableEditActions>, -) + val category: TileCategory, +) { + fun load(context: Context): EditTileViewModel { + return EditTileViewModel( + tileSpec, + icon, + label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec), + appName?.toAnnotatedString(context), + isCurrent, + availableEditActions, + category, + ) + } +} + +@Immutable +data class EditTileViewModel( + val tileSpec: TileSpec, + val icon: Icon, + val label: AnnotatedString, + val appName: AnnotatedString?, + val isCurrent: Boolean, + val availableEditActions: Set<AvailableEditActions>, + override val category: TileCategory, +) : CategoryAndName { + override val name + get() = label.text +} enum class AvailableEditActions { ADD, diff --git a/packages/SystemUI/src/com/android/systemui/qs/shared/model/TileCategory.kt b/packages/SystemUI/src/com/android/systemui/qs/shared/model/TileCategory.kt new file mode 100644 index 000000000000..59cb7d3d5345 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/shared/model/TileCategory.kt @@ -0,0 +1,46 @@ +/* + * 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.qs.shared.model + +import com.android.systemui.common.shared.model.Text +import com.android.systemui.res.R + +/** Categories for tiles. This can be used to sort tiles in edit mode. */ +enum class TileCategory(val label: Text) { + CONNECTIVITY(Text.Resource(R.string.qs_edit_mode_category_connectivity)), + UTILITIES(Text.Resource(R.string.qs_edit_mode_category_utilities)), + DISPLAY(Text.Resource(R.string.qs_edit_mode_category_display)), + PRIVACY(Text.Resource(R.string.qs_edit_mode_category_privacy)), + ACCESSIBILITY(Text.Resource(R.string.qs_edit_mode_category_accessibility)), + PROVIDED_BY_APP(Text.Resource(R.string.qs_edit_mode_category_providedByApps)), + UNKNOWN(Text.Resource(R.string.qs_edit_mode_category_unknown)), +} + +interface CategoryAndName { + val category: TileCategory + val name: String +} + +/** + * Groups the elements of the list by [CategoryAndName.category] (with the keys sorted in the + * natural order of [TileCategory]), and sorts the elements of each group based on the + * [CategoryAndName.name]. + */ +fun <T : CategoryAndName> groupAndSort(list: List<T>): Map<TileCategory, List<T>> { + val groupedByCategory = list.groupBy { it.category }.toSortedMap() + return groupedByCategory.mapValues { it.value.sortedBy { it.name } } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt index cdcefdb50b0f..3a9cb170040c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt @@ -21,6 +21,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import com.android.internal.logging.InstanceId import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory data class QSTileConfig @JvmOverloads @@ -28,6 +29,7 @@ constructor( val tileSpec: TileSpec, val uiConfig: QSTileUIConfig, val instanceId: InstanceId, + val category: TileCategory, val metricsSpec: String = tileSpec.spec, val policy: QSTilePolicy = QSTilePolicy.NoRestrictions, val autoRemoveOnUnavailable: Boolean = true, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt index 0609e797d53b..4dbddd91092a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt @@ -20,6 +20,7 @@ import com.android.internal.util.Preconditions import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import javax.inject.Inject interface QSTileConfigProvider { @@ -73,6 +74,7 @@ constructor( spec, QSTileUIConfig.Empty, qsEventLogger.getNewInstanceId(), + category = TileCategory.PROVIDED_BY_APP, ) is TileSpec.Invalid -> throw IllegalArgumentException("TileSpec.Invalid doesn't support configs") diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt index 907b92ce4c9b..c092c2f86799 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.recordissue import com.android.systemui.Flags import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.RecordIssueTile import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory @@ -61,6 +62,7 @@ interface RecordIssueModule { labelRes = R.string.qs_record_issue_label ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.UTILITIES, ) /** Inject FlashlightTile into tileViewModelMap in QSModule */ diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt index 0589e6c63931..c9712fcdaa27 100644 --- a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt +++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt @@ -19,6 +19,7 @@ package com.android.systemui.rotationlock import com.android.systemui.camera.CameraRotationModule import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileDataInteractor @@ -42,8 +43,9 @@ interface RotationLockNewModule { @IntoMap @StringKey(ROTATION_TILE_SPEC) fun provideRotationAvailabilityInteractor( - impl: RotationLockTileDataInteractor + impl: RotationLockTileDataInteractor ): QSTileAvailabilityInteractor + companion object { private const val ROTATION_TILE_SPEC = "rotation" @@ -60,6 +62,7 @@ interface RotationLockNewModule { labelRes = R.string.quick_settings_rotation_unlocked_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.DISPLAY, ) /** Inject Rotation tile into tileViewModelMap in QSModule */ diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index 738b18495f3f..e477efe0808e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.NotificationPresenter import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor @@ -34,6 +35,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -45,6 +47,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** Business logic about the visibility of various parts of the window root view. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class WindowRootViewVisibilityInteractor @Inject @@ -80,9 +83,9 @@ constructor( is ObservableTransitionState.Idle -> flowOf( state.currentScene == Scenes.Shade || - state.currentScene == Scenes.NotificationsShade || - state.currentScene == Scenes.QuickSettingsShade || - state.currentScene == Scenes.Lockscreen + state.currentScene == Scenes.Lockscreen || + Overlays.NotificationsShade in state.currentOverlays || + Overlays.QuickSettingsShade in state.currentOverlays ) is ObservableTransitionState.Transition -> if ( @@ -94,12 +97,12 @@ constructor( } else { flowOf( state.toContent == Scenes.Shade || - state.toContent == Scenes.NotificationsShade || - state.toContent == Scenes.QuickSettingsShade || + state.toContent == Overlays.NotificationsShade || + state.toContent == Overlays.QuickSettingsShade || state.toContent == Scenes.Lockscreen || state.fromContent == Scenes.Shade || - state.fromContent == Scenes.NotificationsShade || - state.fromContent == Scenes.QuickSettingsShade || + state.fromContent == Overlays.NotificationsShade || + state.fromContent == Overlays.QuickSettingsShade || state.fromContent == Scenes.Lockscreen ) } @@ -115,10 +118,9 @@ constructor( * false if the device is asleep. */ val isLockscreenOrShadeVisibleAndInteractive: StateFlow<Boolean> = - combine( - isLockscreenOrShadeVisible, - powerInteractor.isAwake, - ) { isKeyguardAodOrShadeVisible, isAwake -> + combine(isLockscreenOrShadeVisible, powerInteractor.isAwake) { + isKeyguardAodOrShadeVisible, + isAwake -> isKeyguardAodOrShadeVisible && isAwake } .stateIn(scope, SharingStarted.Eagerly, initialValue = false) diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt index d1629c799732..e352bfe938f6 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt @@ -19,7 +19,9 @@ package com.android.systemui.scene.domain.startable import androidx.annotation.VisibleForTesting +import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.OverlayKey import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor @@ -33,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor import com.android.systemui.statusbar.phone.DozeServiceHost @@ -74,6 +77,7 @@ constructor( deviceEntryInteractor.isDeviceEntered, occlusionInteractor.invisibleDueToOcclusion, sceneInteractor.currentScene, + sceneInteractor.currentOverlays, sceneInteractor.transitionState, keyguardInteractor.isDozing, keyguardInteractor.isDreaming, @@ -95,34 +99,29 @@ constructor( val isDeviceEntered = flowValues[0] as Boolean val isOccluded = flowValues[1] as Boolean val currentScene = flowValues[2] as SceneKey - val transitionState = flowValues[3] as ObservableTransitionState - val isDozing = flowValues[4] as Boolean - val isDreaming = flowValues[5] as Boolean - val biometricUnlockState = flowValues[6] as BiometricUnlockModel - val isBrightnessMirrorVisible = flowValues[7] as Boolean - val isPulsing = flowValues[8] as Boolean - val hasPendingScreenOffCallback = flowValues[9] as Boolean + val currentOverlays = flowValues[3] as Set<OverlayKey> + val transitionState = flowValues[4] as ObservableTransitionState + val isDozing = flowValues[5] as Boolean + val isDreaming = flowValues[6] as Boolean + val biometricUnlockState = flowValues[7] as BiometricUnlockModel + val isBrightnessMirrorVisible = flowValues[8] as Boolean + val isPulsing = flowValues[9] as Boolean + val hasPendingScreenOffCallback = flowValues[10] as Boolean // This is true when the lockscreen scene is either the current scene or somewhere - // in the - // navigation back stack of scenes. + // in the navigation back stack of scenes. val isOnKeyguard = !isDeviceEntered val isCurrentSceneBouncer = currentScene == Scenes.Bouncer // This is true when moving away from one of the keyguard scenes to the gone scene. - // It - // happens only when unlocking or when dismissing a dismissible lockscreen. + // It happens only when unlocking or when dismissing a dismissible lockscreen. val isTransitioningAwayFromKeyguard = transitionState is ObservableTransitionState.Transition.ChangeScene && transitionState.fromScene.isKeyguard() && transitionState.toScene == Scenes.Gone - // This is true when any of the shade scenes is the current scene. - val isCurrentSceneShade = currentScene.isShade() - // This is true when moving into one of the shade scenes when a non-shade scene. - val isTransitioningToShade = - transitionState is ObservableTransitionState.Transition.ChangeScene && - !transitionState.fromScene.isShade() && - transitionState.toScene.isShade() + // This is true when any of the shade scenes or overlays is the current content. + val isCurrentContentShade = + currentScene.isShade() || currentOverlays.any { it.isShade() } // This is true after completing a transition to communal. val isIdleOnCommunal = transitionState.isIdle(Scenes.Communal) @@ -137,10 +136,10 @@ constructor( if (alternateBouncerInteractor.isVisibleState()) { // This will cancel the keyguardFadingAway animation if it is running. We need - // to do - // this as otherwise it can remain pending and leave keyguard in a weird state. + // to do this as otherwise it can remain pending and leave keyguard in a weird + // state. onKeyguardFadedAway(isTransitioningAwayFromKeyguard) - if (!isTransitioningToShade) { + if (!transitionState.isTransitioningToShade()) { // Safeguard which prevents the scrim from being stuck in the wrong // state Model(scrimState = ScrimState.KEYGUARD, unlocking = unlocking) @@ -163,7 +162,7 @@ constructor( ) } else if (isBrightnessMirrorVisible) { Model(scrimState = ScrimState.BRIGHTNESS_MIRROR, unlocking = unlocking) - } else if (isCurrentSceneShade && !isDeviceEntered) { + } else if (isCurrentContentShade && !isDeviceEntered) { Model(scrimState = ScrimState.SHADE_LOCKED, unlocking = unlocking) } else if (isPulsing) { Model(scrimState = ScrimState.PULSING, unlocking = unlocking) @@ -171,8 +170,8 @@ constructor( Model(scrimState = ScrimState.OFF, unlocking = unlocking) } else if (isDozing && !unlocking) { // This will cancel the keyguardFadingAway animation if it is running. We need - // to do - // this as otherwise it can remain pending and leave keyguard in a weird state. + // to do this as otherwise it can remain pending and leave keyguard in a weird + // state. onKeyguardFadedAway(isTransitioningAwayFromKeyguard) Model(scrimState = ScrimState.AOD, unlocking = false) } else if (isIdleOnCommunal) { @@ -222,15 +221,24 @@ constructor( return this == Scenes.Lockscreen || this == Scenes.Bouncer } - private fun SceneKey.isShade(): Boolean { + private fun ContentKey.isShade(): Boolean { return this == Scenes.Shade || this == Scenes.QuickSettings || - this == Scenes.NotificationsShade || - this == Scenes.QuickSettingsShade + this == Overlays.NotificationsShade || + this == Overlays.QuickSettingsShade + } + + private fun ObservableTransitionState.isTransitioningToShade(): Boolean { + return when (this) { + is ObservableTransitionState.Idle -> false + is ObservableTransitionState.Transition.ChangeScene -> + !fromScene.isShade() && toScene.isShade() + is ObservableTransitionState.Transition.ReplaceOverlay -> + !fromOverlay.isShade() && toOverlay.isShade() + is ObservableTransitionState.Transition.ShowOrHideOverlay -> + !fromContent.isShade() && toContent.isShade() + } } - private data class Model( - val scrimState: ScrimState, - val unlocking: Boolean, - ) + private data class Model(val scrimState: ScrimState, val unlocking: Boolean) } diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt index a830e1bbfe72..9a9c576c5af6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordModule.kt @@ -21,6 +21,7 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.ScreenRecordTile import com.android.systemui.qs.tiles.base.interactor.QSTileAvailabilityInteractor @@ -74,6 +75,7 @@ interface ScreenRecordModule { labelRes = R.string.quick_settings_screen_record_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.DISPLAY, ) /** Inject ScreenRecord Tile into tileViewModelMap in QSModule */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index f88fd7d00a2b..862f33bb4ec3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -63,6 +63,7 @@ import android.view.accessibility.Flags; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.internal.annotations.KeepForWeakReference; import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.IAddTileResultCallback; import com.android.internal.statusbar.IStatusBar; @@ -192,10 +193,10 @@ public class CommandQueue extends IStatusBar.Stub implements private static final String SHOW_IME_SWITCHER_KEY = "showImeSwitcherKey"; private final Object mLock = new Object(); - private ArrayList<Callbacks> mCallbacks = new ArrayList<>(); - private Handler mHandler = new H(Looper.getMainLooper()); + private final ArrayList<Callbacks> mCallbacks = new ArrayList<>(); + private final Handler mHandler = new H(Looper.getMainLooper()); /** A map of display id - disable flag pair */ - private SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>(); + private final SparseArray<Pair<Integer, Integer>> mDisplayDisabled = new SparseArray<>(); /** * The last ID of the display where IME window for which we received setImeWindowStatus * event. @@ -207,6 +208,21 @@ public class CommandQueue extends IStatusBar.Stub implements private final @Nullable DumpHandler mDumpHandler; private final @Nullable Lazy<PowerInteractor> mPowerInteractor; + @KeepForWeakReference + private final DisplayTracker.Callback mDisplayTrackerCallback = new DisplayTracker.Callback() { + @Override + public void onDisplayRemoved(int displayId) { + synchronized (mLock) { + mDisplayDisabled.remove(displayId); + } + // This callback is registered with {@link #mHandler} that already posts to run on + // main thread, so it is safe to dispatch directly. + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + mCallbacks.get(i).onDisplayRemoved(displayId); + } + } + }; + /** * These methods are called back on the main thread. */ @@ -576,19 +592,8 @@ public class CommandQueue extends IStatusBar.Stub implements mDisplayTracker = displayTracker; mRegistry = registry; mDumpHandler = dumpHandler; - mDisplayTracker.addDisplayChangeCallback(new DisplayTracker.Callback() { - @Override - public void onDisplayRemoved(int displayId) { - synchronized (mLock) { - mDisplayDisabled.remove(displayId); - } - // This callback is registered with {@link #mHandler} that already posts to run on - // main thread, so it is safe to dispatch directly. - for (int i = mCallbacks.size() - 1; i >= 0; i--) { - mCallbacks.get(i).onDisplayRemoved(displayId); - } - } - }, new HandlerExecutor(mHandler)); + mDisplayTracker.addDisplayChangeCallback(mDisplayTrackerCallback, + new HandlerExecutor(mHandler)); // We always have default display. setDisabled(mDisplayTracker.getDefaultDisplayId(), DISABLE_NONE, DISABLE2_NONE); mPowerInteractor = powerInteractor; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index 400f8af99e07..dac01028ef64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt @@ -21,6 +21,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.SIGNAL_CALLBACK_DEPRECATION import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.AirplaneModeTile import com.android.systemui.qs.tiles.BluetoothTile @@ -95,21 +96,21 @@ interface ConnectivityModule { @IntoMap @StringKey(AIRPLANE_MODE_TILE_SPEC) fun provideAirplaneModeAvailabilityInteractor( - impl: AirplaneModeTileDataInteractor + impl: AirplaneModeTileDataInteractor ): QSTileAvailabilityInteractor @Binds @IntoMap @StringKey(DATA_SAVER_TILE_SPEC) fun provideDataSaverAvailabilityInteractor( - impl: DataSaverTileDataInteractor + impl: DataSaverTileDataInteractor ): QSTileAvailabilityInteractor @Binds @IntoMap @StringKey(INTERNET_TILE_SPEC) fun provideInternetAvailabilityInteractor( - impl: InternetTileDataInteractor + impl: InternetTileDataInteractor ): QSTileAvailabilityInteractor companion object { @@ -149,6 +150,7 @@ interface ConnectivityModule { ), instanceId = uiEventLogger.getNewInstanceId(), policy = QSTilePolicy.Restricted(listOf(UserManager.DISALLOW_AIRPLANE_MODE)), + category = TileCategory.CONNECTIVITY, ) /** Inject AirplaneModeTile into tileViewModelMap in QSModule */ @@ -180,6 +182,7 @@ interface ConnectivityModule { labelRes = R.string.data_saver, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, ) /** Inject DataSaverTile into tileViewModelMap in QSModule */ @@ -211,6 +214,7 @@ interface ConnectivityModule { labelRes = R.string.quick_settings_internet_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, ) /** Inject InternetTile into tileViewModelMap in QSModule */ @@ -242,6 +246,7 @@ interface ConnectivityModule { labelRes = R.string.quick_settings_hotspot_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, ) @Provides @@ -256,6 +261,7 @@ interface ConnectivityModule { labelRes = R.string.quick_settings_cast_title, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, ) @Provides @@ -270,6 +276,7 @@ interface ConnectivityModule { labelRes = R.string.quick_settings_bluetooth_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index 0efd5f15cb09..ec0827b4c478 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -61,6 +61,7 @@ import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.util.NotificationChannels import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SystemSettings import com.android.systemui.util.time.SystemClock import com.android.wm.shell.bubbles.Bubbles import java.util.Optional @@ -279,7 +280,8 @@ class AvalancheSuppressor( private val uiEventLogger: UiEventLogger, private val context: Context, private val notificationManager: NotificationManager, - private val logger: VisualInterruptionDecisionLogger + private val logger: VisualInterruptionDecisionLogger, + private val systemSettings: SystemSettings, ) : VisualInterruptionFilter( types = setOf(PEEK, PULSE), @@ -300,6 +302,11 @@ class AvalancheSuppressor( // education HUNs. private var hasShownOnceForDebug = false + // Sometimes the kotlin flow value is false even when the cooldown setting is true (b/356768397) + // so let's directly check settings until we confirm that the flow is initialized and in sync + // with the real settings value. + private var isCooldownFlowInSync = false + private fun shouldShowEdu(): Boolean { val forceShowOnce = SystemProperties.get(FORCE_SHOW_AVALANCHE_EDU_ONCE, "").equals("1") return !hasSeenEdu || (forceShowOnce && !hasShownOnceForDebug) @@ -479,6 +486,15 @@ class AvalancheSuppressor( } private fun isCooldownEnabled(): Boolean { - return settingsInteractor.isCooldownEnabled.value + val isEnabledFromFlow = settingsInteractor.isCooldownEnabled.value + if (isCooldownFlowInSync) { + return isEnabledFromFlow + } + val isEnabled = + systemSettings.getInt(Settings.System.NOTIFICATION_COOLDOWN_ENABLED, /* def */ 1) == 1 + if (isEnabled == isEnabledFromFlow) { + isCooldownFlowInSync = true + } + return isEnabled } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index 2f8711a586ef..d4466f8771a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -195,7 +195,8 @@ constructor( uiEventLogger, context, notificationManager, - logger + logger, + systemSettings ) ) avalancheProvider.register() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index c04616847ebe..0ad1042a665f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -480,10 +480,16 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh } public static AlertDialog applyFlags(AlertDialog dialog) { + return applyFlags(dialog, true); + } + + public static AlertDialog applyFlags(AlertDialog dialog, boolean showWhenLocked) { final Window window = dialog.getWindow(); window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL); - window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + if (showWhenLocked) { + window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } window.getAttributes().setFitInsetsTypes( window.getAttributes().getFitInsetsTypes() & ~Type.statusBars()); return dialog; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt index fc7a67233bb6..bc7d376a8740 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt @@ -64,9 +64,6 @@ interface WifiRepository { const val COL_NAME_IS_ENABLED = "isEnabled" /** Column name to use for [isWifiDefault] for table logging. */ const val COL_NAME_IS_DEFAULT = "isDefault" - - const val CARRIER_MERGED_INVALID_SUB_ID_REASON = - "Wifi network was carrier merged but had invalid sub ID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index 7163e67eaa5d..f4bb1a34b05f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -46,7 +46,7 @@ constructor( private val _isWifiDefault = MutableStateFlow(false) override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault - private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive) + private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive()) override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork private val _secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList()) @@ -82,7 +82,7 @@ constructor( _isWifiEnabled.value = false _isWifiDefault.value = false _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false) - _wifiNetwork.value = WifiNetworkModel.Inactive + _wifiNetwork.value = WifiNetworkModel.Inactive() } private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) { @@ -100,7 +100,7 @@ constructor( } private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( isValidated = validated ?: true, level = level ?: 0, ssid = ssid ?: DEMO_NET_SSID, @@ -108,7 +108,7 @@ constructor( ) private fun FakeWifiEventModel.CarrierMerged.toCarrierMergedModel(): WifiNetworkModel = - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = subscriptionId, level = level, numberOfLevels = numberOfLevels, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt index b6e73e0f4b9e..76024cd565d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod import android.annotation.SuppressLint import android.net.wifi.ScanResult import android.net.wifi.WifiManager -import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -39,18 +38,14 @@ import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel import com.android.systemui.statusbar.pipeline.wifi.data.repository.RealWifiRepository -import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel -import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Unavailable.toHotspotDeviceType import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry import com.android.wifitrackerlib.HotspotNetworkEntry import com.android.wifitrackerlib.MergedCarrierEntry import com.android.wifitrackerlib.WifiEntry -import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX -import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN -import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.android.wifitrackerlib.WifiPickerTracker import java.util.concurrent.Executor import javax.inject.Inject @@ -246,36 +241,28 @@ constructor( } private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel { - return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) { - WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON) - } else { - WifiNetworkModel.CarrierMerged( - subscriptionId = this.subscriptionId, - level = this.level, - // WifiManager APIs to calculate the signal level start from 0, so - // maxSignalLevel + 1 represents the total level buckets count. - numberOfLevels = wifiManager.maxSignalLevel + 1, - ) - } + // WifiEntry instance values aren't guaranteed to be stable between method calls + // because + // WifiPickerTracker is continuously updating the same object. Save the level in a + // local + // variable so that checking the level validity here guarantees that the level will + // still be + // valid when we create the `WifiNetworkModel.Active` instance later. Otherwise, the + // level + // could be valid here but become invalid later, and `WifiNetworkModel.Active` will + // throw + // an exception. See b/362384551. + + return WifiNetworkModel.CarrierMerged.of( + subscriptionId = this.subscriptionId, + level = this.level, + // WifiManager APIs to calculate the signal level start from 0, so + // maxSignalLevel + 1 represents the total level buckets count. + numberOfLevels = wifiManager.maxSignalLevel + 1, + ) } private fun WifiEntry.convertNormalToModel(): WifiNetworkModel { - // WifiEntry instance values aren't guaranteed to be stable between method calls because - // WifiPickerTracker is continuously updating the same object. Save the level in a local - // variable so that checking the level validity here guarantees that the level will still be - // valid when we create the `WifiNetworkModel.Active` instance later. Otherwise, the level - // could be valid here but become invalid later, and `WifiNetworkModel.Active` will throw - // an exception. See b/362384551. - val currentLevel = this.level - if ( - currentLevel == WIFI_LEVEL_UNREACHABLE || - currentLevel !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX - ) { - // If our level means the network is unreachable or the level is otherwise invalid, we - // don't have an active network. - return WifiNetworkModel.Inactive - } - val hotspotDeviceType = if (this is HotspotNetworkEntry) { this.deviceType.toHotspotDeviceType() @@ -283,9 +270,9 @@ constructor( WifiNetworkModel.HotspotDeviceType.NONE } - return WifiNetworkModel.Active( + return WifiNetworkModel.Active.of( isValidated = this.hasInternetAccess(), - level = currentLevel, + level = this.level, ssid = this.title, hotspotDeviceType = hotspotDeviceType, ) @@ -421,7 +408,7 @@ constructor( companion object { // Start out with no known wifi network. - @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive + @VisibleForTesting val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive() private const val WIFI_STATE_DEFAULT = WifiManager.WIFI_STATE_DISABLED diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt index 39842fb39e24..32203774afd1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.wifi.shared.model +import android.net.wifi.WifiManager import android.net.wifi.WifiManager.UNKNOWN_SSID import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo import android.telephony.SubscriptionManager @@ -23,8 +24,12 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.isValid +import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.of import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType import com.android.wifitrackerlib.WifiEntry +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE /** Provides information about the current wifi network. */ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { @@ -64,7 +69,7 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { /** A description of why the wifi information was invalid. */ val invalidReason: String, ) : WifiNetworkModel() { - override fun toString() = "WifiNetwork.Invalid[$invalidReason]" + override fun toString() = "WifiNetwork.Invalid[reason=$invalidReason]" override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { if (prevVal !is Invalid) { @@ -73,12 +78,12 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } if (invalidReason != prevVal.invalidReason) { - row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason") + row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE[reason=$invalidReason]") } } override fun logFull(row: TableRowLogger) { - row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE $invalidReason") + row.logChange(COL_NETWORK_TYPE, "$TYPE_UNAVAILABLE[reason=$invalidReason]") row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) @@ -89,20 +94,25 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } /** A model representing that we have no active wifi network. */ - object Inactive : WifiNetworkModel() { - override fun toString() = "WifiNetwork.Inactive" + data class Inactive( + /** An optional description of why the wifi information was inactive. */ + val inactiveReason: String? = null, + ) : WifiNetworkModel() { + override fun toString() = "WifiNetwork.Inactive[reason=$inactiveReason]" override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) { - if (prevVal is Inactive) { + if (prevVal !is Inactive) { + logFull(row) return } - // When changing to Inactive, we need to log diffs to all the fields. - logFull(row) + if (inactiveReason != prevVal.inactiveReason) { + row.logChange(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=$inactiveReason]") + } } override fun logFull(row: TableRowLogger) { - row.logChange(COL_NETWORK_TYPE, TYPE_INACTIVE) + row.logChange(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=$inactiveReason]") row.logChange(COL_SUB_ID, SUB_ID_DEFAULT) row.logChange(COL_VALIDATED, false) row.logChange(COL_LEVEL, LEVEL_DEFAULT) @@ -117,31 +127,71 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { * treated as more of a mobile network. * * See [android.net.wifi.WifiInfo.isCarrierMerged] for more information. + * + * IMPORTANT: Do *not* call [copy] on this class. Instead, use the factory [of] methods. [of] + * will verify preconditions correctly. */ - data class CarrierMerged( + data class CarrierMerged + private constructor( /** * The subscription ID that this connection represents. * * Comes from [android.net.wifi.WifiInfo.getSubscriptionId]. * - * Per that method, this value must not be [INVALID_SUBSCRIPTION_ID] (if it was invalid, - * then this is *not* a carrier merged network). + * Per that method, this value must not be [SubscriptionManager.INVALID_SUBSCRIPTION_ID] (if + * it was invalid, then this is *not* a carrier merged network). */ val subscriptionId: Int, - /** The signal level, guaranteed to be 0 <= level <= numberOfLevels. */ + /** The signal level, required to be 0 <= level <= numberOfLevels. */ val level: Int, /** The maximum possible level. */ - val numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS, + val numberOfLevels: Int, ) : WifiNetworkModel() { - init { - require(level in MIN_VALID_LEVEL..numberOfLevels) { - "CarrierMerged: $MIN_VALID_LEVEL <= wifi level <= $numberOfLevels required; " + + companion object { + /** + * Creates a [CarrierMerged] instance, or an [Invalid] instance if any of the arguments + * are invalid. + */ + fun of( + subscriptionId: Int, + level: Int, + numberOfLevels: Int = MobileConnectionRepository.DEFAULT_NUM_LEVELS + ): WifiNetworkModel { + if (!subscriptionId.isSubscriptionIdValid()) { + return Invalid(INVALID_SUB_ID_ERROR_STRING) + } + if (!level.isLevelValid(numberOfLevels)) { + return Invalid(getInvalidLevelErrorString(level, numberOfLevels)) + } + return CarrierMerged(subscriptionId, level, numberOfLevels) + } + + private fun Int.isLevelValid(maxLevel: Int): Boolean { + return this != WIFI_LEVEL_UNREACHABLE && this in MIN_VALID_LEVEL..maxLevel + } + + private fun getInvalidLevelErrorString(level: Int, maxLevel: Int): String { + return "Wifi network was carrier merged but had invalid level. " + + "$MIN_VALID_LEVEL <= wifi level <= $maxLevel required; " + "level was $level" } - require(subscriptionId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { - "subscription ID cannot be invalid" + + private fun Int.isSubscriptionIdValid(): Boolean { + return this != SubscriptionManager.INVALID_SUBSCRIPTION_ID + } + + private const val INVALID_SUB_ID_ERROR_STRING = + "Wifi network was carrier merged but had invalid sub ID" + } + + init { + require(level.isLevelValid(numberOfLevels)) { + "${getInvalidLevelErrorString(level, numberOfLevels)}. $DO_NOT_USE_COPY_ERROR" + } + require(subscriptionId.isSubscriptionIdValid()) { + "$INVALID_SUB_ID_ERROR_STRING. $DO_NOT_USE_COPY_ERROR" } } @@ -173,28 +223,64 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { } } - /** Provides information about an active wifi network. */ - data class Active( + /** + * Provides information about an active wifi network. + * + * IMPORTANT: Do *not* call [copy] on this class. Instead, use the factory [of] method. [of] + * will verify preconditions correctly. + */ + data class Active + private constructor( /** See [android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED]. */ - val isValidated: Boolean = false, + val isValidated: Boolean, - /** The wifi signal level, guaranteed to be 0 <= level <= 4. */ + /** The wifi signal level, required to be 0 <= level <= 4. */ val level: Int, /** See [android.net.wifi.WifiInfo.ssid]. */ - val ssid: String? = null, + val ssid: String?, /** * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this * isn't a hotspot connection. */ - val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, + val hotspotDeviceType: HotspotDeviceType, ) : WifiNetworkModel() { - init { - require(level in MIN_VALID_LEVEL..MAX_VALID_LEVEL) { - "Active: $MIN_VALID_LEVEL <= wifi level <= $MAX_VALID_LEVEL required; " + + companion object { + /** + * Creates an [Active] instance, or an [Inactive] instance if any of the arguments are + * invalid. + */ + @JvmStatic + fun of( + isValidated: Boolean = false, + level: Int, + ssid: String? = null, + hotspotDeviceType: HotspotDeviceType = HotspotDeviceType.NONE, + ): WifiNetworkModel { + if (!level.isValid()) { + return Inactive(getInvalidLevelErrorString(level)) + } + return Active(isValidated, level, ssid, hotspotDeviceType) + } + + private fun Int.isValid(): Boolean { + return this != WIFI_LEVEL_UNREACHABLE && this in MIN_VALID_LEVEL..MAX_VALID_LEVEL + } + + private fun getInvalidLevelErrorString(level: Int): String { + return "Wifi network was active but had invalid level. " + + "$MIN_VALID_LEVEL <= wifi level <= $MAX_VALID_LEVEL required; " + "level was $level" } + + @VisibleForTesting internal const val MAX_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MAX + } + + init { + require(level.isValid()) { + "${getInvalidLevelErrorString(level)}. $DO_NOT_USE_COPY_ERROR" + } } /** Returns true if this network has a valid SSID and false otherwise. */ @@ -231,10 +317,6 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> { row.logChange(COL_SSID, ssid) row.logChange(COL_HOTSPOT, hotspotDeviceType.name) } - - companion object { - @VisibleForTesting internal const val MAX_VALID_LEVEL = WifiEntry.WIFI_LEVEL_MAX - } } companion object { @@ -292,3 +374,7 @@ const val COL_HOTSPOT = "hotspot" val LEVEL_DEFAULT: String? = null val NUM_LEVELS_DEFAULT: String? = null val SUB_ID_DEFAULT: String? = null + +private const val DO_NOT_USE_COPY_ERROR = + "This should only be an issue if the caller incorrectly used `copy` to get a new instance. " + + "Please use the `of` method instead." diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 591d7af44db1..cf9f9f4a2a81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -23,6 +23,7 @@ import android.os.UserManager.DISALLOW_SHARE_LOCATION import com.android.systemui.Flags import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.AlarmTile import com.android.systemui.qs.tiles.CameraToggleTile @@ -157,6 +158,7 @@ interface PolicyModule { labelRes = R.string.quick_settings_flashlight_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.UTILITIES, ) /** Inject FlashlightTile into tileViewModelMap in QSModule */ @@ -192,7 +194,8 @@ interface PolicyModule { policy = QSTilePolicy.Restricted( listOf(DISALLOW_SHARE_LOCATION, DISALLOW_CONFIG_LOCATION) - ) + ), + category = TileCategory.PRIVACY, ) /** Inject LocationTile into tileViewModelMap in QSModule */ @@ -225,6 +228,7 @@ interface PolicyModule { labelRes = R.string.status_bar_alarm, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.UTILITIES, ) /** Inject AlarmTile into tileViewModelMap in QSModule */ @@ -257,6 +261,7 @@ interface PolicyModule { labelRes = R.string.quick_settings_ui_mode_night_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.DISPLAY, ) /** Inject uimodenight into tileViewModelMap in QSModule */ @@ -290,6 +295,7 @@ interface PolicyModule { ), instanceId = uiEventLogger.getNewInstanceId(), autoRemoveOnUnavailable = false, + category = TileCategory.PRIVACY, ) /** Inject work mode into tileViewModelMap in QSModule */ @@ -323,6 +329,7 @@ interface PolicyModule { ), instanceId = uiEventLogger.getNewInstanceId(), policy = QSTilePolicy.Restricted(listOf(DISALLOW_CAMERA_TOGGLE)), + category = TileCategory.PRIVACY, ) /** Inject camera toggle tile into tileViewModelMap in QSModule */ @@ -365,6 +372,7 @@ interface PolicyModule { ), instanceId = uiEventLogger.getNewInstanceId(), policy = QSTilePolicy.Restricted(listOf(DISALLOW_MICROPHONE_TOGGLE)), + category = TileCategory.PRIVACY, ) /** Inject microphone toggle tile into tileViewModelMap in QSModule */ @@ -407,6 +415,7 @@ interface PolicyModule { labelRes = R.string.quick_settings_modes_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, ) } else { QSTileConfig( @@ -417,6 +426,7 @@ interface PolicyModule { labelRes = R.string.quick_settings_dnd_label, ), instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.CONNECTIVITY, ) } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java index ea213cba9567..dd1c11d11d1e 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/dagger/WalletModule.java @@ -25,6 +25,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.pipeline.shared.TileSpec; +import com.android.systemui.qs.shared.model.TileCategory; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.QuickAccessWalletTile; import com.android.systemui.qs.tiles.viewmodel.QSTileConfig; @@ -34,8 +35,6 @@ import com.android.systemui.res.R; import com.android.systemui.wallet.controller.WalletContextualLocationsService; import com.android.systemui.wallet.ui.WalletActivity; -import java.util.concurrent.Executor; - import dagger.Binds; import dagger.Module; import dagger.Provides; @@ -43,6 +42,8 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; import dagger.multibindings.StringKey; +import java.util.concurrent.Executor; + /** * Module for injecting classes in Wallet. */ @@ -90,6 +91,7 @@ public abstract class WalletModule { R.string.wallet_title ), uiEventLogger.getNewInstanceId(), + TileCategory.UTILITIES, tileSpec.getSpec(), QSTilePolicy.NoRestrictions.INSTANCE ); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index d85b77413338..bf13ceb5666a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java @@ -445,7 +445,7 @@ public class CarrierTextManagerTest extends SysuiTestCase { assertFalse(mWifiRepository.isWifiConnectedWithValidSsid()); mWifiRepository.setWifiNetwork( - new WifiNetworkModel.Active( + WifiNetworkModel.Active.Companion.of( /* isValidated= */ false, /* level= */ 0, /* ssid= */ "", diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt new file mode 100644 index 000000000000..9da68853a5aa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt @@ -0,0 +1,60 @@ +/* + * 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.inputdevice.tutorial + +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator +import com.android.systemui.testKosmos +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyboardTouchpadTutorialCoreStartableTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val broadcastDispatcher = kosmos.broadcastDispatcher + private val context = mock<Context>() + private val underTest = + KeyboardTouchpadTutorialCoreStartable( + { mock<TutorialNotificationCoordinator>() }, + broadcastDispatcher, + context + ) + + @Test + fun registersBroadcastReceiverStartingActivityAsSystemUser() { + underTest.start() + + broadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL") + ) + + verify(context).startActivityAsUser(any(), eq(UserHandle.SYSTEM)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt index 3c743744dd58..823a23dec4ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt @@ -38,6 +38,8 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.net.Uri import android.os.Bundle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.service.notification.StatusBarNotification @@ -61,6 +63,8 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.domain.resume.MediaResumeListener import com.android.systemui.media.controls.domain.resume.ResumeMediaBrowser +import com.android.systemui.media.controls.shared.mediaLogger +import com.android.systemui.media.controls.shared.mockMediaLogger import com.android.systemui.media.controls.shared.model.EXTRA_KEY_TRIGGER_SOURCE import com.android.systemui.media.controls.shared.model.EXTRA_VALUE_TRIGGER_PERIODIC import com.android.systemui.media.controls.shared.model.MediaData @@ -69,7 +73,6 @@ import com.android.systemui.media.controls.shared.model.SmartspaceMediaDataProvi import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.media.controls.util.fakeMediaControllerFactory import com.android.systemui.media.controls.util.mediaFlags -import com.android.systemui.plugins.activityStarter import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.testKosmos @@ -186,11 +189,10 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa mSetFlagsRule.setFlagsParameterization(flags) } - private val kosmos = testKosmos() + private val kosmos = testKosmos().apply { mediaLogger = mockMediaLogger } private val testDispatcher = kosmos.testDispatcher private val testScope = kosmos.testScope private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic - private val activityStarter = kosmos.activityStarter private val mediaControllerFactory = kosmos.fakeMediaControllerFactory private val instanceIdSequence = InstanceIdSequenceFake(1 shl 20) @@ -240,7 +242,6 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa mediaDeviceManager = mediaDeviceManager, mediaDataCombineLatest = mediaDataCombineLatest, mediaDataFilter = mediaDataFilter, - activityStarter = activityStarter, smartspaceMediaDataProvider = smartspaceMediaDataProvider, useMediaResumption = true, useQsMediaPlayer = true, @@ -251,6 +252,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa smartspaceManager = smartspaceManager, keyguardUpdateMonitor = keyguardUpdateMonitor, mediaDataLoader = { kosmos.mediaDataLoader }, + mediaLogger = kosmos.mediaLogger, ) verify(tunerService) .addTunable(capture(tunableCaptor), eq(Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION)) @@ -2404,6 +2406,45 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa assertThat(mediaDataCaptor.value.artwork).isNull() } + @Test + @EnableFlags(Flags.FLAG_MEDIA_CONTROLS_POSTS_OPTIMIZATION) + fun postDuplicateNotification_doesNotCallListeners() { + addNotificationAndLoad() + reset(listener) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + + testScope.assertRunAllReady(foreground = 0, background = 1) + verify(listener, never()) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + verify(kosmos.mediaLogger).logDuplicateMediaNotification(eq(KEY)) + } + + @Test + @DisableFlags(Flags.FLAG_MEDIA_CONTROLS_POSTS_OPTIMIZATION) + fun postDuplicateNotification_callsListeners() { + addNotificationAndLoad() + reset(listener) + mediaDataManager.onNotificationAdded(KEY, mediaNotification) + testScope.assertRunAllReady(foreground = 1, background = 1) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + capture(mediaDataCaptor), + eq(true), + eq(0), + eq(false) + ) + verify(kosmos.mediaLogger, never()).logDuplicateMediaNotification(eq(KEY)) + } + private fun TestScope.assertRunAllReady(foreground: Int = 0, background: Int = 0) { runCurrent() if (Flags.mediaLoadMetadataViaMediaDataLoader()) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt index 02d741385cf9..bc29d2a8ba0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt @@ -138,6 +138,7 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(mockContext.packageManager).thenReturn(context.packageManager) whenever(mockContext.contentResolver).thenReturn(context.contentResolver) whenever(mockContext.userId).thenReturn(context.userId) + whenever(mockContext.resources).thenReturn(context.resources) whenever(mediaFlags.isRemoteResumeAllowed()).thenReturn(false) executor = FakeExecutor(clock) @@ -210,7 +211,7 @@ class MediaResumeListenerTest : SysuiTestCase() { @Test fun testOnLoad_checksForResume_noService() { // When media data is loaded that has not been checked yet, and does not have a MBS - resumeListener.onMediaDataLoaded(KEY, null, data) + onMediaDataLoaded(KEY, null, data) // Then we report back to the manager verify(mediaDataManager).setResumeAction(KEY, null) @@ -223,8 +224,7 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() } // When media data is loaded that has not been checked yet, and does not have a MBS - resumeListener.onMediaDataLoaded(KEY, null, data) - executor.runAllReady() + onMediaDataLoaded(KEY, null, data) // Then we report back to the manager verify(mediaDataManager).setResumeAction(eq(KEY), eq(null)) @@ -234,7 +234,7 @@ class MediaResumeListenerTest : SysuiTestCase() { fun testOnLoad_localCast_doesNotCheck() { // When media data is loaded that has not been checked yet, and is a local cast val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) - resumeListener.onMediaDataLoaded(KEY, null, dataCast) + onMediaDataLoaded(KEY, null, dataCast, false) // Then we do not take action verify(mediaDataManager, never()).setResumeAction(any(), any()) @@ -244,7 +244,7 @@ class MediaResumeListenerTest : SysuiTestCase() { fun testOnload_remoteCast_doesNotCheck() { // When media data is loaded that has not been checked yet, and is a remote cast val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE) - resumeListener.onMediaDataLoaded(KEY, null, dataRcn) + onMediaDataLoaded(KEY, null, dataRcn, resume = false) // Then we do not take action verify(mediaDataManager, never()).setResumeAction(any(), any()) @@ -257,7 +257,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // When media data is loaded that has not been checked yet, and is a local cast val dataCast = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_LOCAL) - resumeListener.onMediaDataLoaded(KEY, null, dataCast) + onMediaDataLoaded(KEY, null, dataCast) // Then we report back to the manager verify(mediaDataManager).setResumeAction(KEY, null) @@ -270,7 +270,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // When media data is loaded that has not been checked yet, and is a remote cast val dataRcn = data.copy(playbackLocation = MediaData.PLAYBACK_CAST_REMOTE) - resumeListener.onMediaDataLoaded(KEY, null, dataRcn) + onMediaDataLoaded(KEY, null, dataRcn, false) // Then we do not take action verify(mediaDataManager, never()).setResumeAction(any(), any()) @@ -288,10 +288,9 @@ class MediaResumeListenerTest : SysuiTestCase() { // When media data is loaded that has not been checked yet, and does have a MBS val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false) - resumeListener.onMediaDataLoaded(KEY, null, dataCopy) + onMediaDataLoaded(KEY, null, dataCopy) // Then we test whether the service is valid - executor.runAllReady() verify(mediaDataManager).setResumeAction(eq(KEY), eq(null)) verify(resumeBrowser).testConnection() @@ -307,7 +306,7 @@ class MediaResumeListenerTest : SysuiTestCase() { fun testOnLoad_doesNotCheckAgain() { // When a media data is loaded that has been checked already var dataCopy = data.copy(hasCheckedForResume = true) - resumeListener.onMediaDataLoaded(KEY, null, dataCopy) + onMediaDataLoaded(KEY, null, dataCopy, resume = false) // Then we should not check it again verify(resumeBrowser, never()).testConnection() @@ -320,17 +319,15 @@ class MediaResumeListenerTest : SysuiTestCase() { setUpMbsWithValidResolveInfo() resumeListener.onMediaDataLoaded(KEY, null, data) - // We notify the manager to set a null action - verify(mediaDataManager).setResumeAction(KEY, null) - // If we then get another update from the app before the first check completes assertThat(executor.numPending()).isEqualTo(1) var dataWithCheck = data.copy(hasCheckedForResume = true) resumeListener.onMediaDataLoaded(KEY, null, dataWithCheck) // We do not try to start another check - assertThat(executor.numPending()).isEqualTo(1) + executor.runAllReady() verify(mediaDataManager).setResumeAction(KEY, null) + verify(resumeBrowserFactory, times(1)).create(any(), any(), anyInt()) } @Test @@ -363,6 +360,7 @@ class MediaResumeListenerTest : SysuiTestCase() { resumeListener.userUnlockReceiver.onReceive(context, intent) // Then we should attempt to find recent media for each saved component + executor.runAllReady() verify(resumeBrowser, times(3)).findRecentMedia() // Then since the mock service found media, the manager should be informed @@ -382,10 +380,9 @@ class MediaResumeListenerTest : SysuiTestCase() { // When media data is loaded that has not been checked yet, and does have a MBS val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false) - resumeListener.onMediaDataLoaded(KEY, null, dataCopy) + onMediaDataLoaded(KEY, null, dataCopy) // Then we test whether the service is valid and set the resume action - executor.runAllReady() verify(mediaDataManager).setResumeAction(eq(KEY), eq(null)) verify(resumeBrowser).testConnection() verify(mediaDataManager, times(2)).setResumeAction(eq(KEY), capture(actionCaptor)) @@ -455,6 +452,7 @@ class MediaResumeListenerTest : SysuiTestCase() { resumeListener.userUnlockReceiver.onReceive(mockContext, intent) // We add its resume controls + executor.runAllReady() verify(resumeBrowser).findRecentMedia() verify(mediaDataManager) .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME)) @@ -527,7 +525,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // When media data is loaded that has not been checked yet, and does have a MBS val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false) - resumeListener.onMediaDataLoaded(KEY, null, dataCopy) + onMediaDataLoaded(KEY, null, dataCopy) // Then we store the new lastPlayed time verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor))) @@ -546,10 +544,9 @@ class MediaResumeListenerTest : SysuiTestCase() { fun testOnMediaDataLoaded_newKeyDifferent_oldMediaBrowserDisconnected() { setUpMbsWithValidResolveInfo() - resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data) - executor.runAllReady() + onMediaDataLoaded(key = KEY, oldKey = null, data) - resumeListener.onMediaDataLoaded(key = "newKey", oldKey = KEY, data) + onMediaDataLoaded(key = "newKey", oldKey = KEY, data) verify(resumeBrowser).disconnect() } @@ -561,8 +558,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // Set up mocks to return with an error whenever(resumeBrowser.testConnection()).thenAnswer { callbackCaptor.value.onError() } - resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data) - executor.runAllReady() + onMediaDataLoaded(key = KEY, oldKey = null, data) // Ensure we disconnect the browser verify(resumeBrowser).disconnect() @@ -579,8 +575,7 @@ class MediaResumeListenerTest : SysuiTestCase() { callbackCaptor.value.addTrack(description, component, resumeBrowser) } - resumeListener.onMediaDataLoaded(key = KEY, oldKey = null, data) - executor.runAllReady() + onMediaDataLoaded(key = KEY, oldKey = null, data) // Ensure we disconnect the browser verify(resumeBrowser).disconnect() @@ -598,8 +593,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // Load media data that will require us to get the resume action val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false) - resumeListener.onMediaDataLoaded(KEY, null, dataCopy) - executor.runAllReady() + onMediaDataLoaded(KEY, null, dataCopy) verify(mediaDataManager, times(2)).setResumeAction(eq(KEY), capture(actionCaptor)) // Set up our factory to return a new browser so we can verify we disconnected the old one @@ -634,6 +628,7 @@ class MediaResumeListenerTest : SysuiTestCase() { // When the first user unlocks and we query their recent media userCallbackCaptor.value.onUserChanged(firstUserId, context) resumeListener.userUnlockReceiver.onReceive(context, unlockIntent) + executor.runAllReady() whenever(resumeBrowser.userId).thenReturn(userIdCaptor.value) verify(resumeBrowser, times(3)).findRecentMedia() @@ -688,4 +683,16 @@ class MediaResumeListenerTest : SysuiTestCase() { whenever(pm.resolveServiceAsUser(any(), anyInt(), anyInt())).thenReturn(resolveInfo) whenever(pm.getApplicationLabel(any())).thenReturn(PACKAGE_NAME) } + + private fun onMediaDataLoaded( + key: String, + oldKey: String?, + data: MediaData, + resume: Boolean = true + ) { + resumeListener.onMediaDataLoaded(key, oldKey, data) + if (resume) { + assertThat(executor.runAllReady()).isEqualTo(1) + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt index d9faa30cb072..70af5e75105d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt @@ -31,17 +31,18 @@ import androidx.compose.ui.test.onChildren import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.text.AnnotatedString import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon -import com.android.systemui.common.shared.model.Text import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -199,10 +200,11 @@ class DragAndDropTest : SysuiTestCase() { android.R.drawable.star_on, ContentDescription.Loaded(tileSpec) ), - label = Text.Loaded(tileSpec), + label = AnnotatedString(tileSpec), appName = null, isCurrent = true, availableEditActions = emptySet(), + category = TileCategory.UNKNOWN, ), getWidth(tileSpec), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt index e6ec07e97d17..9f84e346d54a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt @@ -258,7 +258,7 @@ class InternetTileNewImplTest : SysuiTestCase() { companion object { const val WIFI_SSID = "test ssid" val ACTIVE_WIFI = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( isValidated = true, level = 4, ssid = WIFI_SSID, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index ed99705b194e..b177e4a3e22e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -101,7 +101,7 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro private fun getAvalancheSuppressor() : AvalancheSuppressor { return AvalancheSuppressor( avalancheProvider, systemClock, settingsInteractor, packageManager, - uiEventLogger, context, notificationManager, logger + uiEventLogger, context, notificationManager, logger, systemSettings ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt index b6e23c1f42ee..715e3b472373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -85,7 +85,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this) val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this) - wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive) + wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive()) assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected) assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType) @@ -104,7 +104,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this) val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this) - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1)) assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected) assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType) @@ -123,7 +123,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setIsWifiDefault(true) wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = SUB_ID, level = 3, ) @@ -143,7 +143,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { wifiRepository.setIsWifiEnabled(true) wifiRepository.setIsWifiDefault(true) wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = SUB_ID, level = 3, ) @@ -180,7 +180,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this) wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = SUB_ID + 10, level = 3, ) @@ -201,7 +201,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this) wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = SUB_ID, level = 3, ) @@ -221,7 +221,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this) wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = SUB_ID, level = 3, ) @@ -240,7 +240,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { val job = underTest.numberOfLevels.onEach { latest = it }.launchIn(this) wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = SUB_ID, level = 1, numberOfLevels = 6, @@ -303,7 +303,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { whenever(telephonyManager.simOperatorName).thenReturn("New SIM name") wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = SUB_ID, level = 3, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt index a03980a9d45f..fd23655ffc1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt @@ -488,7 +488,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { // WHEN we set up carrier merged info wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( SUB_ID, level = 3, ) @@ -499,7 +499,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { // WHEN we update the info wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( SUB_ID, level = 1, ) @@ -538,7 +538,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { // WHEN isCarrierMerged is set to true wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( SUB_ID, level = 3, ) @@ -550,7 +550,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { // WHEN the carrier merge network is updated wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( SUB_ID, level = 4, ) @@ -602,7 +602,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { // THEN updates to the carrier merged level aren't logged wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( SUB_ID, level = 4, ) @@ -610,7 +610,7 @@ class FullMobileConnectionRepositoryTest : SysuiTestCase() { assertThat(dumpBuffer()).doesNotContain("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}4") wifiRepository.setWifiNetwork( - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( SUB_ID, level = 3, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt index a1cb29b8e95c..c0a206afe64b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt @@ -574,7 +574,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { val latest by collectLastValue(underTest.isWifiActive) // WHEN wifi is active - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1)) // THEN the interactor returns true due to the wifi network being active assertThat(latest).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index c1abf9826ea3..e7e496938033 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -375,7 +375,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { repo.isSatelliteProvisioned.value = true // GIVEN wifi network is active - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1)) // THEN icon is null because the device is connected to wifi assertThat(latest).isNull() @@ -573,7 +573,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { repo.isSatelliteProvisioned.value = true // GIVEN wifi network is active - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(level = 1)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(level = 1)) // THEN carrier text is null because the device is connected to wifi assertThat(latest).isNull() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index fed33179250b..975e2caef2fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -154,7 +154,7 @@ class InternetTileViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.tileModel) val networkModel = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 4, ssid = "test ssid", ) @@ -183,7 +183,7 @@ class InternetTileViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.tileModel) val networkModel = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 4, ssid = "test ssid", hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE, @@ -295,7 +295,7 @@ class InternetTileViewModelTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.tileModel) - val networkModel = WifiNetworkModel.Inactive + val networkModel = WifiNetworkModel.Inactive() connectivityRepository.setWifiConnected(validated = false) wifiRepository.setIsWifiDefault(true) @@ -310,7 +310,7 @@ class InternetTileViewModelTest : SysuiTestCase() { testScope.runTest { val latest by collectLastValue(underTest.tileModel) - val networkModel = WifiNetworkModel.Inactive + val networkModel = WifiNetworkModel.Inactive() connectivityRepository.setWifiConnected(validated = false) wifiRepository.setIsWifiDefault(true) @@ -390,7 +390,7 @@ class InternetTileViewModelTest : SysuiTestCase() { private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { val networkModel = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = 4, ssid = "test ssid", hotspotDeviceType = hotspot, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt index 46f34e82c688..fd4b77d7fb95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt @@ -295,7 +295,10 @@ class WifiRepositoryImplTest : SysuiTestCase() { whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + assertThat(latest).isInstanceOf(WifiNetworkModel.Inactive::class.java) + val inactiveReason = (latest as WifiNetworkModel.Inactive).inactiveReason + assertThat(inactiveReason).contains("level") + assertThat(inactiveReason).contains("$WIFI_LEVEL_UNREACHABLE") } @Test @@ -311,7 +314,10 @@ class WifiRepositoryImplTest : SysuiTestCase() { whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + assertThat(latest).isInstanceOf(WifiNetworkModel.Inactive::class.java) + val inactiveReason = (latest as WifiNetworkModel.Inactive).inactiveReason + assertThat(inactiveReason).contains("level") + assertThat(inactiveReason).contains("${WIFI_LEVEL_MAX + 1}") } @Test @@ -327,7 +333,10 @@ class WifiRepositoryImplTest : SysuiTestCase() { whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + assertThat(latest).isInstanceOf(WifiNetworkModel.Inactive::class.java) + val inactiveReason = (latest as WifiNetworkModel.Inactive).inactiveReason + assertThat(inactiveReason).contains("level") + assertThat(inactiveReason).contains("${WIFI_LEVEL_MIN - 1}") } @Test @@ -530,6 +539,25 @@ class WifiRepositoryImplTest : SysuiTestCase() { } @Test + fun wifiNetwork_carrierMergedButInvalidLevel_flowHasInvalid() = + testScope.runTest { + val latest by collectLastValue(underTest.wifiNetwork) + + val mergedEntry = + mock<MergedCarrierEntry>().apply { + whenever(this.isPrimaryNetwork).thenReturn(true) + whenever(this.subscriptionId).thenReturn(3) + whenever(this.isDefaultNetwork).thenReturn(true) + whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) + } + whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry) + + getCallback().onWifiEntriesChanged() + + assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java) + } + + @Test fun wifiNetwork_notValidated_networkNotValidated() = testScope.runTest { val latest by collectLastValue(underTest.wifiNetwork) @@ -571,7 +599,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry) getCallback().onWifiEntriesChanged() - assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive()) } @Test @@ -587,7 +615,7 @@ class WifiRepositoryImplTest : SysuiTestCase() { whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null) getCallback().onWifiEntriesChanged() - assertThat(latest).isEqualTo(WifiNetworkModel.Inactive) + assertThat(latest).isEqualTo(WifiNetworkModel.Inactive()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt index 92860efc0c35..1495519cc1a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt @@ -24,6 +24,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.log.table.TableRowLogger import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Active.Companion.MAX_VALID_LEVEL import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Companion.MIN_VALID_LEVEL +import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -32,59 +33,109 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class WifiNetworkModelTest : SysuiTestCase() { @Test - fun active_levelsInValidRange_noException() { + fun active_levelsInValidRange_createsActive() { (MIN_VALID_LEVEL..MAX_VALID_LEVEL).forEach { level -> - WifiNetworkModel.Active(level = level) - // No assert, just need no crash + val result = WifiNetworkModel.Active.of(level = level) + assertThat(result).isInstanceOf(WifiNetworkModel.Active::class.java) } } + fun active_levelTooLow_returnsInactive() { + val result = WifiNetworkModel.Active.of(level = MIN_VALID_LEVEL - 1) + assertThat(result).isInstanceOf(WifiNetworkModel.Inactive::class.java) + } + @Test(expected = IllegalArgumentException::class) - fun active_levelNegative_exceptionThrown() { - WifiNetworkModel.Active(level = MIN_VALID_LEVEL - 1) + fun active_levelTooLow_createdByCopy_exceptionThrown() { + val starting = WifiNetworkModel.Active.of(level = MIN_VALID_LEVEL) + + (starting as WifiNetworkModel.Active).copy(level = MIN_VALID_LEVEL - 1) + } + + fun active_levelTooHigh_returnsInactive() { + val result = WifiNetworkModel.Active.of(level = MAX_VALID_LEVEL + 1) + + assertThat(result).isInstanceOf(WifiNetworkModel.Inactive::class.java) } @Test(expected = IllegalArgumentException::class) - fun active_levelTooHigh_exceptionThrown() { - WifiNetworkModel.Active(level = MAX_VALID_LEVEL + 1) + fun active_levelTooHigh_createdByCopy_exceptionThrown() { + val starting = WifiNetworkModel.Active.of(level = MAX_VALID_LEVEL) + + (starting as WifiNetworkModel.Active).copy(level = MAX_VALID_LEVEL + 1) + } + + fun active_levelUnreachable_returnsInactive() { + val result = WifiNetworkModel.Active.of(level = WIFI_LEVEL_UNREACHABLE) + + assertThat(result).isInstanceOf(WifiNetworkModel.Inactive::class.java) } @Test(expected = IllegalArgumentException::class) - fun carrierMerged_invalidSubId_exceptionThrown() { - WifiNetworkModel.CarrierMerged(INVALID_SUBSCRIPTION_ID, 1) + fun active_levelUnreachable_createdByCopy_exceptionThrown() { + val starting = WifiNetworkModel.Active.of(level = MAX_VALID_LEVEL) + + (starting as WifiNetworkModel.Active).copy(level = WIFI_LEVEL_UNREACHABLE) + } + + fun carrierMerged_invalidSubId_returnsInvalid() { + val result = WifiNetworkModel.CarrierMerged.of(INVALID_SUBSCRIPTION_ID, level = 1) + + assertThat(result).isInstanceOf(WifiNetworkModel.Invalid::class.java) + } + + @Test(expected = IllegalArgumentException::class) + fun carrierMerged_invalidSubId_createdByCopy_exceptionThrown() { + val starting = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1) + + (starting as WifiNetworkModel.CarrierMerged).copy(subscriptionId = INVALID_SUBSCRIPTION_ID) + } + + fun carrierMerged_levelUnreachable_returnsInvalid() { + val result = + WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = WIFI_LEVEL_UNREACHABLE) + + assertThat(result).isInstanceOf(WifiNetworkModel.Invalid::class.java) + } + + @Test(expected = IllegalArgumentException::class) + fun carrierMerged_levelUnreachable_createdByCopy_exceptionThrown() { + val starting = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1) + + (starting as WifiNetworkModel.CarrierMerged).copy(level = WIFI_LEVEL_UNREACHABLE) } @Test fun active_hasValidSsid_nullSsid_false() { val network = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = MAX_VALID_LEVEL, ssid = null, ) - assertThat(network.hasValidSsid()).isFalse() + assertThat((network as WifiNetworkModel.Active).hasValidSsid()).isFalse() } @Test fun active_hasValidSsid_unknownSsid_false() { val network = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = MAX_VALID_LEVEL, ssid = UNKNOWN_SSID, ) - assertThat(network.hasValidSsid()).isFalse() + assertThat((network as WifiNetworkModel.Active).hasValidSsid()).isFalse() } @Test fun active_hasValidSsid_validSsid_true() { val network = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( level = MAX_VALID_LEVEL, ssid = "FakeSsid", ) - assertThat(network.hasValidSsid()).isTrue() + assertThat((network as WifiNetworkModel.Active).hasValidSsid()).isTrue() } // Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken @@ -93,14 +144,15 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_carrierMergedToInactive_resetsAllFields() { val logger = TestLogger() val prevVal = - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = 3, level = 1, ) - WifiNetworkModel.Inactive.logDiffs(prevVal, logger) + WifiNetworkModel.Inactive(inactiveReason = "TestReason").logDiffs(prevVal, logger) - assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE)) + assertThat(logger.changes) + .contains(Pair(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=TestReason]")) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) @@ -110,12 +162,12 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_inactiveToCarrierMerged_logsAllFields() { val logger = TestLogger() val carrierMerged = - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = 3, level = 2, ) - carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) + carrierMerged.logDiffs(prevVal = WifiNetworkModel.Inactive(), logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_CARRIER_MERGED)) assertThat(logger.changes).contains(Pair(COL_SUB_ID, "3")) @@ -128,14 +180,14 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_inactiveToActive_logsAllActiveFields() { val logger = TestLogger() val activeNetwork = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( isValidated = true, level = 3, ssid = "Test SSID", hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP, ) - activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger) + activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive(), logger) assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_ACTIVE)) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true")) @@ -148,11 +200,13 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_activeToInactive_resetsAllActiveFields() { val logger = TestLogger() val activeNetwork = - WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID") + WifiNetworkModel.Active.of(isValidated = true, level = 3, ssid = "Test SSID") - WifiNetworkModel.Inactive.logDiffs(prevVal = activeNetwork, logger) + WifiNetworkModel.Inactive(inactiveReason = "TestReason") + .logDiffs(prevVal = activeNetwork, logger) - assertThat(logger.changes).contains(Pair(COL_NETWORK_TYPE, TYPE_INACTIVE)) + assertThat(logger.changes) + .contains(Pair(COL_NETWORK_TYPE, "$TYPE_INACTIVE[reason=TestReason]")) assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false")) assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString())) assertThat(logger.changes).contains(Pair(COL_SSID, "null")) @@ -163,14 +217,14 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_carrierMergedToActive_logsAllActiveFields() { val logger = TestLogger() val activeNetwork = - WifiNetworkModel.Active( + WifiNetworkModel.Active.of( isValidated = true, level = 3, ssid = "Test SSID", hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO, ) val prevVal = - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = 3, level = 1, ) @@ -188,9 +242,9 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_activeToCarrierMerged_logsAllFields() { val logger = TestLogger() val activeNetwork = - WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID") + WifiNetworkModel.Active.of(isValidated = true, level = 3, ssid = "Test SSID") val carrierMerged = - WifiNetworkModel.CarrierMerged( + WifiNetworkModel.CarrierMerged.of( subscriptionId = 3, level = 2, ) @@ -208,9 +262,9 @@ class WifiNetworkModelTest : SysuiTestCase() { fun logDiffs_activeChangesLevel_onlyLevelLogged() { val logger = TestLogger() val prevActiveNetwork = - WifiNetworkModel.Active(isValidated = true, level = 3, ssid = "Test SSID") + WifiNetworkModel.Active.of(isValidated = true, level = 3, ssid = "Test SSID") val newActiveNetwork = - WifiNetworkModel.Active(isValidated = true, level = 2, ssid = "Test SSID") + WifiNetworkModel.Active.of(isValidated = true, level = 2, ssid = "Test SSID") newActiveNetwork.logDiffs(prevActiveNetwork, logger) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index ff398f988636..37c7a484a117 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -195,7 +195,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun isIconVisible_notEnabled_outputsFalse() { wifiRepository.setIsWifiEnabled(false) - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 2)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 2)) val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) @@ -210,7 +210,7 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { @Test fun isIconVisible_enabled_outputsTrue() { wifiRepository.setIsWifiEnabled(true) - wifiRepository.setWifiNetwork(WifiNetworkModel.Active(isValidated = true, level = 2)) + wifiRepository.setWifiNetwork(WifiNetworkModel.Active.of(isValidated = true, level = 2)) val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index 82acb40ec90c..96a01949b243 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -206,51 +206,51 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // Enabled = false => no networks shown TestCase( enabled = false, - network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1), + network = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1), expected = null, ), TestCase( enabled = false, - network = WifiNetworkModel.Inactive, + network = WifiNetworkModel.Inactive(), expected = null, ), TestCase( enabled = false, - network = WifiNetworkModel.Active(isValidated = false, level = 1), + network = WifiNetworkModel.Active.of(isValidated = false, level = 1), expected = null, ), TestCase( enabled = false, - network = WifiNetworkModel.Active(isValidated = true, level = 3), + network = WifiNetworkModel.Active.of(isValidated = true, level = 3), expected = null, ), // forceHidden = true => no networks shown TestCase( forceHidden = true, - network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1), + network = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1), expected = null, ), TestCase( forceHidden = true, - network = WifiNetworkModel.Inactive, + network = WifiNetworkModel.Inactive(), expected = null, ), TestCase( enabled = false, - network = WifiNetworkModel.Active(isValidated = false, level = 2), + network = WifiNetworkModel.Active.of(isValidated = false, level = 2), expected = null, ), TestCase( forceHidden = true, - network = WifiNetworkModel.Active(isValidated = true, level = 1), + network = WifiNetworkModel.Active.of(isValidated = true, level = 1), expected = null, ), // alwaysShowIconWhenEnabled = true => all Inactive and Active networks shown TestCase( alwaysShowIconWhenEnabled = true, - network = WifiNetworkModel.Inactive, + network = WifiNetworkModel.Inactive(), expected = Expected( iconResource = WIFI_NO_NETWORK, @@ -263,7 +263,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( alwaysShowIconWhenEnabled = true, - network = WifiNetworkModel.Active(isValidated = false, level = 4), + network = WifiNetworkModel.Active.of(isValidated = false, level = 4), expected = Expected( iconResource = WIFI_NO_INTERNET_ICONS[4], @@ -276,7 +276,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( alwaysShowIconWhenEnabled = true, - network = WifiNetworkModel.Active(isValidated = true, level = 2), + network = WifiNetworkModel.Active.of(isValidated = true, level = 2), expected = Expected( iconResource = WIFI_FULL_ICONS[2], @@ -290,7 +290,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // hasDataCapabilities = false => all Inactive and Active networks shown TestCase( hasDataCapabilities = false, - network = WifiNetworkModel.Inactive, + network = WifiNetworkModel.Inactive(), expected = Expected( iconResource = WIFI_NO_NETWORK, @@ -303,7 +303,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( hasDataCapabilities = false, - network = WifiNetworkModel.Active(isValidated = false, level = 2), + network = WifiNetworkModel.Active.of(isValidated = false, level = 2), expected = Expected( iconResource = WIFI_NO_INTERNET_ICONS[2], @@ -316,7 +316,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( hasDataCapabilities = false, - network = WifiNetworkModel.Active(isValidated = true, level = 0), + network = WifiNetworkModel.Active.of(isValidated = true, level = 0), expected = Expected( iconResource = WIFI_FULL_ICONS[0], @@ -330,7 +330,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // isDefault = true => all Inactive and Active networks shown TestCase( isDefault = true, - network = WifiNetworkModel.Inactive, + network = WifiNetworkModel.Inactive(), expected = Expected( iconResource = WIFI_NO_NETWORK, @@ -343,7 +343,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( isDefault = true, - network = WifiNetworkModel.Active(isValidated = false, level = 3), + network = WifiNetworkModel.Active.of(isValidated = false, level = 3), expected = Expected( iconResource = WIFI_NO_INTERNET_ICONS[3], @@ -356,7 +356,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( isDefault = true, - network = WifiNetworkModel.Active(isValidated = true, level = 1), + network = WifiNetworkModel.Active.of(isValidated = true, level = 1), expected = Expected( iconResource = WIFI_FULL_ICONS[1], @@ -372,14 +372,14 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase enabled = true, isDefault = true, forceHidden = false, - network = WifiNetworkModel.CarrierMerged(subscriptionId = 1, level = 1), + network = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1), expected = null, ), // isDefault = false => no networks shown TestCase( isDefault = false, - network = WifiNetworkModel.Inactive, + network = WifiNetworkModel.Inactive(), expected = null, ), TestCase( @@ -389,7 +389,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase ), TestCase( isDefault = false, - network = WifiNetworkModel.Active(isValidated = false, level = 3), + network = WifiNetworkModel.Active.of(isValidated = false, level = 3), expected = null, ), @@ -397,7 +397,7 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase // because wifi isn't the default connection (b/272509965). TestCase( isDefault = false, - network = WifiNetworkModel.Active(isValidated = true, level = 4), + network = WifiNetworkModel.Active.of(isValidated = true, level = 4), expected = null, ), ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt index 811c6533c656..251a6b10a0da 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorKosmos.kt @@ -50,6 +50,13 @@ var Kosmos.keyboardTouchpadEduStatsInteractor by Kosmos.Fixture { KeyboardTouchpadEduStatsInteractorImpl( backgroundScope = testScope.backgroundScope, - contextualEducationInteractor = contextualEducationInteractor + contextualEducationInteractor = contextualEducationInteractor, + inputDeviceRepository = + UserInputDeviceRepository( + testDispatcher, + keyboardRepository, + touchpadRepository, + userRepository + ) ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt index b03542cb569e..33227a4fcc62 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.qs.panels.ui.viewmodel +import android.content.applicationContext +import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor @@ -33,6 +35,8 @@ val Kosmos.editModeViewModel by currentTilesInteractor, tilesAvailabilityInteractor, minimumTilesInteractor, + configurationInteractor, + applicationContext, infiniteGridLayout, applicationCoroutineScope, gridLayoutTypeInteractor, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt index dceb8bff0ae7..f66125a6087e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt @@ -20,6 +20,7 @@ import android.os.UserHandle import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.instanceIdSequenceFake import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileState @@ -47,6 +48,7 @@ val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by tileSpec, QSTileUIConfig.Empty, instanceIdSequenceFake.newInstanceId(), + category = TileCategory.PROVIDED_BY_APP, ) object : QSTileViewModel { override val state: StateFlow<QSTileState?> = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt index 2a0ee888db35..73d9b3233375 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigTestBuilder.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.viewmodel import com.android.internal.logging.InstanceId import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.shared.model.TileCategory object QSTileConfigTestBuilder { @@ -30,12 +31,14 @@ object QSTileConfigTestBuilder { var instanceId: InstanceId = InstanceId.fakeInstanceId(0) var metricsSpec: String = tileSpec.spec var policy: QSTilePolicy = QSTilePolicy.NoRestrictions + var category: TileCategory = TileCategory.UNKNOWN fun build() = QSTileConfig( tileSpec, uiConfig, instanceId, + category, metricsSpec, policy, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt index 709be5edf4c0..7ca90ea4bd7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt @@ -32,7 +32,7 @@ class FakeWifiRepository : WifiRepository { override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> = - MutableStateFlow(WifiNetworkModel.Inactive) + MutableStateFlow(WifiNetworkModel.Inactive()) override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork override val secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList()) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt index 888351f0a882..ba6ffd742611 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt @@ -33,11 +33,7 @@ import kotlinx.coroutines.flow.update class FakeAudioRepository : AudioRepository { - private val unMutableStreams = - setOf( - AudioManager.STREAM_VOICE_CALL, - AudioManager.STREAM_ALARM, - ) + private val unMutableStreams = setOf(AudioManager.STREAM_VOICE_CALL, AudioManager.STREAM_ALARM) private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL) override val mode: StateFlow<Int> = mutableMode.asStateFlow() @@ -126,7 +122,7 @@ class FakeAudioRepository : AudioRepository { lastAudibleVolumes[audioStream] = volume } - override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) { + override suspend fun setRingerModeInternal(audioStream: AudioStream, mode: RingerMode) { mutableRingerMode.value = mode } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index eebe5e9fc054..8ff99e4af30d 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -129,9 +129,9 @@ java_library { ], libs: [ "framework-minus-apex.ravenwood", - "ravenwood-junit", "ravenwood-helper-libcore-runtime", ], + sdk_version: "core_current", visibility: ["//visibility:private"], } @@ -165,6 +165,8 @@ java_library { "services.core.ravenwood", "junit", "framework-annotations-lib", + "ravenwood-helper-framework-runtime", + "ravenwood-helper-libcore-runtime", ], sdk_version: "core_current", visibility: ["//frameworks/base"], diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java index 92a1cb7fc3bf..68472c12a568 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java @@ -28,6 +28,7 @@ import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.os.RuntimeInit; +import com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook; import com.android.ravenwood.common.RavenwoodCommonUtils; import org.junit.runner.Description; @@ -75,6 +76,9 @@ public class RavenwoodAwareTestRunnerHook { // We haven't initialized liblog yet, so directly write to System.out here. RavenwoodCommonUtils.log(TAG, "initOnce()"); + // Make sure libandroid_runtime is loaded. + ClassLoadHook.onClassLoaded(Log.class); + // Redirect stdout/stdin to liblog. RuntimeInit.redirectLogStreams(); @@ -82,6 +86,10 @@ public class RavenwoodAwareTestRunnerHook { System.setProperty("android.junit.runner", "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner"); System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1"); + + // Do the basic set up for the android sysprops. + RavenwoodRuntimeEnvironmentController.setSystemProperties( + RavenwoodSystemProperties.DEFAULT_VALUES); } /** diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java index 3bf116da19b8..e12ff240c4d9 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java +++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java @@ -15,21 +15,10 @@ */ package com.android.internal.ravenwood; -import android.os.SystemProperties_host; -import android.platform.test.ravenwood.RavenwoodSystemProperties; -import android.util.Log; - import com.android.ravenwood.common.JvmWorkaround; import com.android.ravenwood.common.RavenwoodCommonUtils; public class RavenwoodEnvironment_host { - private static final String TAG = RavenwoodEnvironment.TAG; - - private static final Object sInitializeLock = new Object(); - - // @GuardedBy("sInitializeLock") - private static boolean sInitialized; - private RavenwoodEnvironment_host() { } @@ -37,27 +26,8 @@ public class RavenwoodEnvironment_host { * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}. */ public static void ensureRavenwoodInitialized() { - - // TODO Unify it with the initialization code in RavenwoodAwareTestRunnerHook. - - synchronized (sInitializeLock) { - if (sInitialized) { - return; - } - Log.i(TAG, "Initializing Ravenwood environment"); - - // Set the default values. - var sysProps = RavenwoodSystemProperties.DEFAULT_VALUES; - - // We have a method that does it in RavenwoodRuleImpl, but we can't use that class - // here, So just inline it. - SystemProperties_host.initializeIfNeeded( - sysProps.getValues(), - sysProps.getKeyReadablePredicate(), - sysProps.getKeyWritablePredicate()); - - sInitialized = true; - } + // Initialization is now done by RavenwoodAwareTestRunner. + // Should we remove it? } /** diff --git a/services/accessibility/TEST_MAPPING b/services/accessibility/TEST_MAPPING index 3f85a9005582..0bc25e2dd3c4 100644 --- a/services/accessibility/TEST_MAPPING +++ b/services/accessibility/TEST_MAPPING @@ -1,34 +1,19 @@ { "presubmit": [ { - "name": "CtsAccessibilityServiceTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsAccessibilityServiceTestCases" }, { - "name": "CtsAccessibilityTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsAccessibilityTestCases" }, { - "name": "CtsUiAutomationTestCases", - "options": [ - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] + "name": "CtsUiAutomationTestCases" }, { - "name": "FrameworksServicesTests_accessibility_Presubmit" + "name": "FrameworksServicesTests_accessibility" }, { - "name": "FrameworksCoreTests_accessibility_NO_FLAKES" + "name": "FrameworksCoreTests_accessibility" } ], "postsubmit": [ @@ -45,12 +30,7 @@ "name": "CtsUiAutomationTestCases" }, { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.accessibility" - } - ] + "name": "FrameworksServicesTests_accessibility" }, { "name": "FrameworksCoreTests_accessibility" diff --git a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java index 35905edbcf24..618a5aedd9bb 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java @@ -79,17 +79,20 @@ class CallerValidatorImpl implements CallerValidator { // TODO(b/360864791): Add and honor apps that opt-out from EXECUTE_APP_FUNCTIONS caller. public boolean verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName) { + if (callerPackageName.equals(targetPackageName)) { + return true; + } + int pid = Binder.getCallingPid(); int uid = Binder.getCallingUid(); - boolean hasExecutionPermission = + boolean hasTrustedExecutionPermission = mContext.checkPermission( Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, pid, uid) == PackageManager.PERMISSION_GRANTED; - boolean hasTrustedExecutionPermission = + boolean hasExecutionPermission = mContext.checkPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid) == PackageManager.PERMISSION_GRANTED; - boolean isSamePackage = callerPackageName.equals(targetPackageName); - return hasExecutionPermission || hasTrustedExecutionPermission || isSamePackage; + return hasExecutionPermission || hasTrustedExecutionPermission; } @Override diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java index 094723814e17..39aa27ae794e 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java @@ -27,6 +27,7 @@ import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByDocumentIdRequest; import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.PutDocumentsRequest; +import android.app.appsearch.RemoveByDocumentIdRequest; import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; @@ -146,6 +147,22 @@ public class FutureAppSearchSession implements Closeable { }); } + /** Removes documents from the AppSearchSession database. */ + public AndroidFuture<AppSearchBatchResult<String, Void>> remove( + @NonNull RemoveByDocumentIdRequest removeRequest) { + return getSessionAsync() + .thenCompose( + session -> { + AndroidFuture<AppSearchBatchResult<String, Void>> + settableBatchResultFuture = new AndroidFuture<>(); + session.remove( + removeRequest, + mExecutor, + new BatchResultCallbackAdapter<>(settableBatchResultFuture)); + return settableBatchResultFuture; + }); + } + /** * Retrieves documents from the open AppSearchSession that match a given query string and type * of search provided. @@ -200,9 +217,7 @@ public class FutureAppSearchSession implements Closeable { Objects.requireNonNull(namespace); GetByDocumentIdRequest request = - new GetByDocumentIdRequest.Builder(namespace) - .addIds(documentId) - .build(); + new GetByDocumentIdRequest.Builder(namespace).addIds(documentId).build(); return getSessionAsync() .thenCompose( session -> { diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index be5770b280dc..01e10086a03a 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -18,6 +18,7 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.annotation.WorkerThread; +import android.app.appsearch.PropertyPath; import android.app.appsearch.SearchResult; import android.app.appsearch.SearchSpec; import android.util.ArrayMap; @@ -60,8 +61,11 @@ public class MetadataSyncAdapter { @NonNull @VisibleForTesting static ArrayMap<String, ArraySet<String>> getAddedFunctionsDiffMap( - ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap, - ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) { + @NonNull ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap, + @NonNull ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) { + Objects.requireNonNull(staticPackageToFunctionMap); + Objects.requireNonNull(runtimePackageToFunctionMap); + return getFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap); } @@ -79,15 +83,21 @@ public class MetadataSyncAdapter { @NonNull @VisibleForTesting static ArrayMap<String, ArraySet<String>> getRemovedFunctionsDiffMap( - ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap, - ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) { + @NonNull ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap, + @NonNull ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) { + Objects.requireNonNull(staticPackageToFunctionMap); + Objects.requireNonNull(runtimePackageToFunctionMap); + return getFunctionsDiffMap(runtimePackageToFunctionMap, staticPackageToFunctionMap); } @NonNull private static ArrayMap<String, ArraySet<String>> getFunctionsDiffMap( - ArrayMap<String, ArraySet<String>> packageToFunctionMapA, - ArrayMap<String, ArraySet<String>> packageToFunctionMapB) { + @NonNull ArrayMap<String, ArraySet<String>> packageToFunctionMapA, + @NonNull ArrayMap<String, ArraySet<String>> packageToFunctionMapB) { + Objects.requireNonNull(packageToFunctionMapA); + Objects.requireNonNull(packageToFunctionMapB); + ArrayMap<String, ArraySet<String>> diffMap = new ArrayMap<>(); for (String packageName : packageToFunctionMapA.keySet()) { if (!packageToFunctionMapB.containsKey(packageName)) { @@ -110,26 +120,32 @@ public class MetadataSyncAdapter { } /** - * This method returns a map of package names to a set of function ids. + * This method returns a map of package names to a set of function ids from the AppFunction + * metadata. * - * @param queryExpression The query expression to use when searching for AppFunction metadata. - * @param metadataSearchSpec The search spec to use when searching for AppFunction metadata. - * @return A map of package names to a set of function ids. - * @throws ExecutionException If the future search results fail to execute. - * @throws InterruptedException If the future search results are interrupted. + * @param schemaType The name space of the AppFunction metadata. + * @return A map of package names to a set of function ids from the AppFunction metadata. */ @NonNull @VisibleForTesting @WorkerThread ArrayMap<String, ArraySet<String>> getPackageToFunctionIdMap( - @NonNull String queryExpression, - @NonNull SearchSpec metadataSearchSpec, + @NonNull String schemaType, @NonNull String propertyFunctionId, @NonNull String propertyPackageName) throws ExecutionException, InterruptedException { + Objects.requireNonNull(schemaType); + Objects.requireNonNull(propertyFunctionId); + Objects.requireNonNull(propertyPackageName); ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>(); + FutureSearchResults futureSearchResults = - mFutureAppSearchSession.search(queryExpression, metadataSearchSpec).get(); + mFutureAppSearchSession + .search( + "", + buildMetadataSearchSpec( + schemaType, propertyFunctionId, propertyPackageName)) + .get(); List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get(); // TODO(b/357551503): This could be expensive if we have more functions while (!searchResultsList.isEmpty()) { @@ -146,4 +162,30 @@ public class MetadataSyncAdapter { } return packageToFunctionIds; } + + /** + * This method returns a {@link SearchSpec} for searching the AppFunction metadata. + * + * @param schemaType The schema type of the AppFunction metadata. + * @param propertyFunctionId The property name of the function id in the AppFunction metadata. + * @param propertyPackageName The property name of the package name in the AppFunction metadata. + * @return A {@link SearchSpec} for searching the AppFunction metadata. + */ + @NonNull + private static SearchSpec buildMetadataSearchSpec( + @NonNull String schemaType, + @NonNull String propertyFunctionId, + @NonNull String propertyPackageName) { + Objects.requireNonNull(schemaType); + Objects.requireNonNull(propertyFunctionId); + Objects.requireNonNull(propertyPackageName); + return new SearchSpec.Builder() + .addFilterSchemas(schemaType) + .addProjectionPaths( + schemaType, + List.of( + new PropertyPath(propertyFunctionId), + new PropertyPath(propertyPackageName))) + .build(); + } } diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 5044e93b293d..2c261fe668fb 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -842,9 +842,13 @@ public final class PresentationStatsEventLogger { + "event"); return; } + PresentationStatsEventInternal event = mEventInternal.get(); + boolean ignoreLogging = !event.mIsDatasetAvailable; + if (sVerbose) { Slog.v(TAG, "(" + caller + ") " + + (ignoreLogging ? "IGNORING - following event won't be logged: " : "") + "Log AutofillPresentationEventReported:" + " requestId=" + event.mRequestId + " sessionId=" + mSessionId @@ -907,7 +911,8 @@ public final class PresentationStatsEventLogger { } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. - if (!event.mIsDatasetAvailable) { + if (ignoreLogging) { + Slog.w(TAG, "Empty dataset. Autofill ignoring log"); mEventInternal = Optional.empty(); return; } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 29a17f51b0af..93b228f37c26 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -4125,19 +4125,6 @@ public final class ProcessList { return false; } - private static int procStateToImportance(int procState, int memAdj, - ActivityManager.RunningAppProcessInfo currApp, - int clientTargetSdk) { - int imp = ActivityManager.RunningAppProcessInfo.procStateToImportanceForTargetSdk( - procState, clientTargetSdk); - if (imp == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { - currApp.lru = memAdj; - } else { - currApp.lru = 0; - } - return imp; - } - @GuardedBy(anyOf = {"mService", "mProcLock"}) void fillInProcMemInfoLOSP(ProcessRecord app, ActivityManager.RunningAppProcessInfo outInfo, @@ -4155,14 +4142,20 @@ public final class ProcessList { } outInfo.lastTrimLevel = app.mProfile.getTrimMemoryLevel(); final ProcessStateRecord state = app.mState; - int adj = state.getCurAdj(); - int procState = state.getCurProcState(); - outInfo.importance = procStateToImportance(procState, adj, outInfo, - clientTargetSdk); + final int procState = state.getCurProcState(); + outInfo.importance = ActivityManager.RunningAppProcessInfo + .procStateToImportanceForTargetSdk(procState, clientTargetSdk); + if (outInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { + outInfo.lru = state.getCurAdj(); + } else { + outInfo.lru = 0; + } outInfo.importanceReasonCode = state.getAdjTypeCode(); outInfo.processState = procState; outInfo.isFocused = (app == mService.getTopApp()); outInfo.lastActivityTime = app.getLastActivityTime(); + // Note: ActivityManager$RunningAppProcessInfo.copyTo() must be updated if what gets + // "filled into" outInfo in this method changes. } @GuardedBy(anyOf = {"mService", "mProcLock"}) diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 99c3ecaba2e0..439bca0ea36a 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -453,7 +453,9 @@ public class SettingsToPropertiesMapper { proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName); proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName); proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue); - proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal); + proto.write(StorageRequestMessage.FlagOverrideMessage.OVERRIDE_TYPE, isLocal + ? StorageRequestMessage.LOCAL_ON_REBOOT + : StorageRequestMessage.SERVER_ON_REBOOT); proto.end(msgToken); proto.end(msgsToken); } diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index 2a1687209aad..dba6c3372ae7 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -50,7 +50,6 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorPropertiesInternal; import android.hardware.biometrics.face.IFace; -import android.hardware.biometrics.fingerprint.IFingerprint; import android.hardware.face.FaceSensorConfigurations; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; @@ -74,6 +73,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.server.SystemService; +import com.android.server.biometrics.sensors.fingerprint.FingerprintService; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import java.util.ArrayList; @@ -203,7 +203,7 @@ public class AuthService extends SystemService { */ @VisibleForTesting public String[] getFingerprintAidlInstances() { - return ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR); + return FingerprintService.getDeclaredInstances(); } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 60cfd5a5a6ae..2f6ba0b852ee 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -26,6 +26,7 @@ import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPR import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED; import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_VENDOR; import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG; +import static android.hardware.fingerprint.FingerprintSensorConfigurations.getIFingerprint; import android.annotation.NonNull; import android.annotation.Nullable; @@ -78,6 +79,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; @@ -1015,7 +1017,7 @@ public class FingerprintService extends SystemService { this(context, BiometricContext.getInstance(context), () -> IBiometricService.Stub.asInterface( ServiceManager.getService(Context.BIOMETRIC_SERVICE)), - () -> ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR), + () -> getDeclaredInstances(), null /* fingerprintProvider */, null /* fingerprintProviderFunction */); } @@ -1039,8 +1041,7 @@ public class FingerprintService extends SystemService { mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider : (name) -> { final String fqName = IFingerprint.DESCRIPTOR + "/" + name; - final IFingerprint fp = IFingerprint.Stub.asInterface( - Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName))); + final IFingerprint fp = getIFingerprint(fqName); if (fp != null) { try { return new FingerprintProvider(getContext(), @@ -1129,6 +1130,24 @@ public class FingerprintService extends SystemService { publishBinderService(Context.FINGERPRINT_SERVICE, mServiceWrapper); } + /** + * Get all fingerprint hal instances declared in manifest + * @return instance names + */ + public static String[] getDeclaredInstances() { + String[] a = ServiceManager.getDeclaredInstances(IFingerprint.DESCRIPTOR); + Slog.i(TAG, "Before:getDeclaredInstances: IFingerprint instance found, a.length=" + + a.length); + if (!ArrayUtils.contains(a, "virtual")) { + // Now, the virtual hal is registered with IVirtualHal interface and it is also + // moved from vendor to system_ext partition without a device manifest. So + // if the old vhal is not declared, add here. + a = ArrayUtils.appendElement(String.class, a, "virtual"); + } + Slog.i(TAG, "After:getDeclaredInstances: a.length=" + a.length); + return a; + } + @NonNull private List<Fingerprint> getEnrolledFingerprintsDeprecated(int userId, String opPackageName) { final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index caa2c1c34ff7..fd3d9963c5e3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -20,9 +20,9 @@ import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; -import android.hardware.biometrics.fingerprint.AcquiredInfoAndVendorCode; -import android.hardware.biometrics.fingerprint.EnrollmentProgressStep; -import android.hardware.biometrics.fingerprint.NextEnrollment; +import android.hardware.biometrics.fingerprint.virtualhal.AcquiredInfoAndVendorCode; +import android.hardware.biometrics.fingerprint.virtualhal.EnrollmentProgressStep; +import android.hardware.biometrics.fingerprint.virtualhal.NextEnrollment; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; @@ -300,4 +300,4 @@ class BiometricTestSessionImpl extends ITestSession.Stub { super.getSensorId_enforcePermission(); return mSensorId; } -}
\ No newline at end of file +} diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 9edaa4e6d818..8195efe6a56a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -17,6 +17,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import static android.hardware.fingerprint.FingerprintManager.SENSOR_ID_ANY; +import static android.hardware.fingerprint.FingerprintSensorConfigurations.getIFingerprint; +import static android.hardware.fingerprint.FingerprintSensorConfigurations.remapFqName; import android.annotation.NonNull; import android.annotation.Nullable; @@ -34,9 +36,9 @@ import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.fingerprint.IFingerprint; -import android.hardware.biometrics.fingerprint.IVirtualHal; import android.hardware.biometrics.fingerprint.PointerContext; import android.hardware.biometrics.fingerprint.SensorProps; +import android.hardware.biometrics.fingerprint.virtualhal.IVirtualHal; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintAuthenticateOptions; import android.hardware.fingerprint.FingerprintEnrollOptions; @@ -291,7 +293,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi if (mTestHalEnabled) { return true; } - return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + mHalInstanceName) + return (ServiceManager.checkService( + remapFqName(IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)) != null); } @@ -330,10 +333,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi Slog.d(getTag(), "Daemon was null, reconnecting"); - mDaemon = IFingerprint.Stub.asInterface( - Binder.allowBlocking( - ServiceManager.waitForDeclaredService( - IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent))); + mDaemon = getIFingerprint(IFingerprint.DESCRIPTOR + "/" + mHalInstanceNameCurrent); if (mDaemon == null) { Slog.e(getTag(), "Unable to get daemon"); return null; @@ -905,8 +905,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi void setTestHalEnabled(boolean enabled) { final boolean changed = enabled != mTestHalEnabled; mTestHalEnabled = enabled; - Slog.i(getTag(), "setTestHalEnabled(): useVhalForTesting=" + Flags.useVhalForTesting() - + "mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed); + Slog.i(getTag(), "setTestHalEnabled(): useVhalForTestingFlags=" + Flags.useVhalForTesting() + + " mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed); if (changed && useVhalForTesting()) { getHalInstance(); } @@ -999,12 +999,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi */ public IVirtualHal getVhal() throws RemoteException { if (mVhal == null && useVhalForTesting()) { - mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension()); - if (mVhal == null) { - Slog.e(getTag(), "Unable to get fingerprint virtualhal interface"); - } + mVhal = IVirtualHal.Stub.asInterface( + Binder.allowBlocking( + ServiceManager.waitForService( + IVirtualHal.DESCRIPTOR + "/" + + mHalInstanceNameCurrent))); + Slog.d(getTag(), "getVhal " + mHalInstanceNameCurrent); } - return mVhal; } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java index d12d7b2dc89a..25d1fe7d32ba 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; +import static android.hardware.fingerprint.FingerprintSensorConfigurations.remapFqName; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -356,8 +358,8 @@ public class Sensor { if (mTestHalEnabled) { return true; } - return (ServiceManager.checkService(IFingerprint.DESCRIPTOR + "/" + halInstance) - != null); + return (ServiceManager.checkService( + remapFqName(IFingerprint.DESCRIPTOR + "/" + halInstance)) != null); } @NonNull protected BiometricContext getBiometricContext() { diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 8a3e39257145..1d68ee54d96c 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -16,6 +16,7 @@ package com.android.server.display; +import android.annotation.Nullable; import android.hardware.display.BrightnessInfo; import android.os.Handler; import android.os.IBinder; @@ -92,7 +93,7 @@ class BrightnessRangeController { return mHbmController.getNormalBrightnessMax(); } - void loadFromConfig(HighBrightnessModeMetadata hbmMetadata, IBinder token, + void loadFromConfig(@Nullable HighBrightnessModeMetadata hbmMetadata, IBinder token, DisplayDeviceInfo info, DisplayDeviceConfig displayDeviceConfig) { applyChanges( () -> mNormalBrightnessModeController.resetNbmData( diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ed16b1472ee5..9644b1dbb977 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -2170,9 +2170,7 @@ public final class DisplayManagerService extends SystemService { HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display); - if (hbmMetadata != null) { - dpc.onDisplayChanged(hbmMetadata, leadDisplayId); - } + dpc.onDisplayChanged(hbmMetadata, leadDisplayId); } } @@ -2291,9 +2289,7 @@ public final class DisplayManagerService extends SystemService { HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display); - if (hbmMetadata != null) { - dpc.onDisplayChanged(hbmMetadata, leadDisplayId); - } + dpc.onDisplayChanged(hbmMetadata, leadDisplayId); } } @@ -3560,6 +3556,18 @@ public final class DisplayManagerService extends SystemService { DisplayManagerFlags getFlags() { return new DisplayManagerFlags(); } + + DisplayPowerController getDisplayPowerController(Context context, + DisplayPowerController.Injector injector, + DisplayManagerInternal.DisplayPowerCallbacks callbacks, Handler handler, + SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, + BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, + Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata, + boolean bootCompleted, DisplayManagerFlags flags) { + return new DisplayPowerController(context, injector, callbacks, handler, sensorManager, + blanker, logicalDisplay, brightnessTracker, brightnessSetting, + onBrightnessChangeRunnable, hbmMetadata, bootCompleted, flags); + } } @VisibleForTesting @@ -3612,7 +3620,7 @@ public final class DisplayManagerService extends SystemService { // with the corresponding displaydevice. HighBrightnessModeMetadata hbmMetadata = mHighBrightnessModeMetadataMapper.getHighBrightnessModeMetadataLocked(display); - displayPowerController = new DisplayPowerController( + displayPowerController = mInjector.getDisplayPowerController( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted, mFlags); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 7c591e3a2c03..c3faec007552 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -849,7 +849,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call * * Make sure DisplayManagerService.mSyncRoot lock is held when this is called */ - public void onDisplayChanged(HighBrightnessModeMetadata hbmMetadata, int leadDisplayId) { + public void onDisplayChanged(@Nullable HighBrightnessModeMetadata hbmMetadata, + int leadDisplayId) { mLeadDisplayId = leadDisplayId; final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); if (device == null) { @@ -949,7 +950,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info, - HighBrightnessModeMetadata hbmMetadata) { + @Nullable HighBrightnessModeMetadata hbmMetadata) { // All properties that depend on the associated DisplayDevice and the DDC must be // updated here. mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness( diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index da9eef2d7459..135cab6d0614 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -266,7 +266,7 @@ class HighBrightnessModeController { mSettingsObserver.stopObserving(); } - void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) { + void setHighBrightnessModeMetadata(@Nullable HighBrightnessModeMetadata hbmInfo) { mHighBrightnessModeMetadata = hbmInfo; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 81204ef5d7ed..a8d5696e8c77 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -927,12 +927,14 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { protected int handleVendorCommandWithId(HdmiCecMessage message) { byte[] params = message.getParams(); int vendorId = HdmiUtils.threeBytesToInt(params); - if (message.getDestination() == Constants.ADDR_BROADCAST - || message.getSource() == Constants.ADDR_UNREGISTERED) { - Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); - } else if (!mService.invokeVendorCommandListenersOnReceived( + if (!mService.invokeVendorCommandListenersOnReceived( mDeviceType, message.getSource(), message.getDestination(), params, true)) { - return Constants.ABORT_REFUSED; + if (message.getDestination() == Constants.ADDR_BROADCAST + || message.getSource() == Constants.ADDR_UNREGISTERED) { + Slog.v(TAG, "Broadcast vendor command with no listeners. Ignoring"); + } else { + return Constants.ABORT_REFUSED; + } } return Constants.HANDLED; } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 1285a61d08f2..dfcea9f8e46f 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -2269,14 +2269,8 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") private void notifyTouchpadHardwareState(TouchpadHardwareState hardwareStates, int deviceId) { - Slog.d(TAG, "notifyTouchpadHardwareState: Time: " - + hardwareStates.getTimestamp() + ", No. Buttons: " - + hardwareStates.getButtonsDown() + ", No. Fingers: " - + hardwareStates.getFingerCount() + ", No. Touch: " - + hardwareStates.getTouchCount() + ", Id: " - + deviceId); if (mTouchpadDebugViewController != null) { - mTouchpadDebugViewController.updateTouchpadHardwareState(hardwareStates); + mTouchpadDebugViewController.updateTouchpadHardwareState(hardwareStates, deviceId); } } diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java index ba56ad073e6a..7d8ce5fdb918 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java @@ -22,6 +22,7 @@ import android.content.res.Configuration; import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.hardware.input.InputManager; import android.util.Slog; import android.view.Gravity; import android.view.MotionEvent; @@ -65,7 +66,7 @@ public class TouchpadDebugView extends LinearLayout { mTouchpadId = touchpadId; mWindowManager = Objects.requireNonNull(getContext().getSystemService(WindowManager.class)); - init(context); + init(context, touchpadId); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); // TODO(b/360137366): Use the hardware properties to initialise layout parameters. @@ -88,32 +89,40 @@ public class TouchpadDebugView extends LinearLayout { mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; } - private void init(Context context) { + private void init(Context context, int touchpadId) { setOrientation(VERTICAL); setLayoutParams(new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); - setBackgroundColor(Color.RED); - - // TODO(b/286551975): Replace this content with the touchpad debug view. - TextView textView1 = new TextView(context); - textView1.setBackgroundColor(Color.TRANSPARENT); - textView1.setTextSize(20); - textView1.setText("Touchpad Debug View 1"); - textView1.setGravity(Gravity.CENTER); - textView1.setTextColor(Color.WHITE); - textView1.setLayoutParams(new LayoutParams(1000, 200)); - - TextView textView2 = new TextView(context); - textView2.setBackgroundColor(Color.TRANSPARENT); - textView2.setTextSize(20); - textView2.setText("Touchpad Debug View 2"); - textView2.setGravity(Gravity.CENTER); - textView2.setTextColor(Color.WHITE); - textView2.setLayoutParams(new LayoutParams(1000, 200)); - - addView(textView1); - addView(textView2); + setBackgroundColor(Color.TRANSPARENT); + + TextView nameView = new TextView(context); + nameView.setBackgroundColor(Color.RED); + nameView.setTextSize(20); + nameView.setText(Objects.requireNonNull(Objects.requireNonNull( + mContext.getSystemService(InputManager.class)) + .getInputDevice(touchpadId)).getName()); + nameView.setGravity(Gravity.CENTER); + nameView.setTextColor(Color.WHITE); + nameView.setLayoutParams(new LayoutParams(1000, 200)); + + TouchpadVisualisationView touchpadVisualisationView = + new TouchpadVisualisationView(context); + touchpadVisualisationView.setBackgroundColor(Color.WHITE); + touchpadVisualisationView.setLayoutParams(new LayoutParams(1000, 200)); + + //TODO(b/365562952): Add a display for recognized gesture info here + TextView gestureInfoView = new TextView(context); + gestureInfoView.setBackgroundColor(Color.GRAY); + gestureInfoView.setTextSize(20); + gestureInfoView.setText("Touchpad Debug View 3"); + gestureInfoView.setGravity(Gravity.CENTER); + gestureInfoView.setTextColor(Color.BLACK); + gestureInfoView.setLayoutParams(new LayoutParams(1000, 200)); + + addView(nameView); + addView(touchpadVisualisationView); + addView(gestureInfoView); updateScreenDimensions(); } @@ -204,7 +213,15 @@ public class TouchpadDebugView extends LinearLayout { return mWindowLayoutParams; } - public void updateHardwareState(TouchpadHardwareState touchpadHardwareState) { + /** + * Notify the view of a change in TouchpadHardwareState and changing the + * color of the view based on the status of the button click. + */ + public void updateHardwareState(TouchpadHardwareState touchpadHardwareState, int deviceId) { + if (deviceId != mTouchpadId) { + return; + } + if (mLastTouchpadState.getButtonsDown() == 0) { if (touchpadHardwareState.getButtonsDown() > 0) { onTouchpadButtonPress(); @@ -219,18 +236,11 @@ public class TouchpadDebugView extends LinearLayout { private void onTouchpadButtonPress() { Slog.d("TouchpadDebugView", "You clicked me!"); - - // Iterate through all child views - // Temporary demonstration for testing - for (int i = 0; i < getChildCount(); i++) { - getChildAt(i).setBackgroundColor(Color.BLUE); - } + getChildAt(0).setBackgroundColor(Color.BLUE); } private void onTouchpadButtonRelease() { Slog.d("TouchpadDebugView", "You released the click"); - for (int i = 0; i < getChildCount(); i++) { - getChildAt(i).setBackgroundColor(Color.RED); - } + getChildAt(0).setBackgroundColor(Color.RED); } } diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java index bc53c4947a71..4d527bdff0d3 100644 --- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java +++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java @@ -68,6 +68,13 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList @Override public void onInputDeviceRemoved(int deviceId) { hideDebugView(deviceId); + if (mTouchpadDebugView == null) { + final InputManager inputManager = Objects.requireNonNull( + mContext.getSystemService(InputManager.class)); + for (int id : inputManager.getInputDeviceIds()) { + onInputDeviceAdded(id); + } + } } @Override @@ -134,9 +141,13 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList Slog.d(TAG, "Touchpad debug view removed."); } - public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState) { + /** + * Notify the TouchpadDebugView with the new TouchpadHardwareState. + */ + public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState, + int deviceId) { if (mTouchpadDebugView != null) { - mTouchpadDebugView.updateHardwareState(touchpadHardwareState); + mTouchpadDebugView.updateHardwareState(touchpadHardwareState, deviceId); } } } diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualisationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualisationView.java new file mode 100644 index 000000000000..38442bcc0292 --- /dev/null +++ b/services/core/java/com/android/server/input/debug/TouchpadVisualisationView.java @@ -0,0 +1,26 @@ +/* + * Copyright 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.input.debug; + +import android.content.Context; +import android.view.View; + +public class TouchpadVisualisationView extends View { + public TouchpadVisualisationView(Context context) { + super(context); + } +} diff --git a/services/core/java/com/android/server/logcat/TEST_MAPPING b/services/core/java/com/android/server/logcat/TEST_MAPPING index 5b07cd960fbe..688dbe9ec444 100644 --- a/services/core/java/com/android/server/logcat/TEST_MAPPING +++ b/services/core/java/com/android/server/logcat/TEST_MAPPING @@ -1,15 +1,12 @@ { "presubmit": [ { - "name": "FrameworksServicesTests_android_server_logcat_Presubmit" + "name": "FrameworksServicesTests_android_server_logcat" } ], "postsubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - {"include-filter": "com.android.server.logcat"} - ] + "name": "FrameworksServicesTests_android_server_logcat" } ] } diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java index b8900d7acee5..3d6f9cfed697 100644 --- a/services/core/java/com/android/server/notification/BubbleExtractor.java +++ b/services/core/java/com/android/server/notification/BubbleExtractor.java @@ -27,10 +27,11 @@ import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationChannel; -import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.util.Slog; @@ -47,6 +48,7 @@ public class BubbleExtractor implements NotificationSignalExtractor { private ShortcutHelper mShortcutHelper; private RankingConfig mConfig; private ActivityManager mActivityManager; + private PackageManager mPackageManager; private Context mContext; boolean mSupportsBubble; @@ -76,6 +78,11 @@ public class BubbleExtractor implements NotificationSignalExtractor { return null; } + if (mPackageManager == null) { + if (DBG) Slog.d(TAG, "missing package manager"); + return null; + } + boolean notifCanPresentAsBubble = canPresentAsBubble(record) && !mActivityManager.isLowRamDevice() && record.isConversation() @@ -133,6 +140,10 @@ public class BubbleExtractor implements NotificationSignalExtractor { mShortcutHelper = helper; } + public void setPackageManager(PackageManager packageManager) { + mPackageManager = packageManager; + } + @VisibleForTesting public void setActivityManager(ActivityManager manager) { mActivityManager = manager; @@ -176,30 +187,25 @@ public class BubbleExtractor implements NotificationSignalExtractor { // TODO: check the shortcut intent / ensure it can show in activity view return true; } - return canLaunchInTaskView(mContext, metadata.getIntent(), pkg); + return canLaunchInTaskView(metadata.getIntent().getIntent(), pkg, + r.getUser().getIdentifier()); } /** - * Whether an intent is properly configured to display in an {@link - * TaskView} for bubbling. + * Whether an intent is properly configured to display in a TaskView for bubbling. * - * @param context the context to use. - * @param pendingIntent the pending intent of the bubble. - * @param packageName the notification package name for this bubble. + * @param intent the intent of the bubble. + * @param packageName the notification package name for this bubble. */ - // Keep checks in sync with BubbleController#canLaunchInTaskView. - @VisibleForTesting - protected boolean canLaunchInTaskView(Context context, PendingIntent pendingIntent, - String packageName) { - if (pendingIntent == null) { + // Keep checks in sync with BubbleController#isResizableActivity. + private boolean canLaunchInTaskView(Intent intent, String packageName, int userId) { + if (intent == null) { Slog.w(TAG, "Unable to create bubble -- no intent"); return false; } - Intent intent = pendingIntent.getIntent(); - ActivityInfo info = intent != null - ? intent.resolveActivityInfo(context.getPackageManager(), 0) - : null; + ResolveInfo resolveInfo = mPackageManager.resolveActivityAsUser(intent, 0, userId); + ActivityInfo info = resolveInfo != null ? resolveInfo.activityInfo : null; if (info == null) { FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName, diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 54e918972d46..e2ec0063c711 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3020,6 +3020,7 @@ public class NotificationManagerService extends SystemService { BubbleExtractor bubbsExtractor = mRankingHelper.findExtractor(BubbleExtractor.class); if (bubbsExtractor != null) { bubbsExtractor.setShortcutHelper(mShortcutHelper); + bubbsExtractor.setPackageManager(mPackageManagerClient); } registerNotificationPreferencesPullers(); if (mLockUtils == null) { diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 6303ecd53dbb..a41675a4aac5 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -298,12 +298,13 @@ public final class OverlayManagerService extends SystemService { restoreSettings(); - // Wipe all shell overlays on boot, to recover from a potentially broken device - String shellPkgName = TextUtils.emptyIfNull( - getContext().getString(android.R.string.config_systemShell)); - mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated - && shellPkgName.equals(overlayInfo.packageName)); - + if (Build.IS_USER) { + // Wipe all shell overlays on boot, to recover from a potentially broken device + String shellPkgName = TextUtils.emptyIfNull( + getContext().getString(android.R.string.config_systemShell)); + mSettings.removeIf(overlayInfo -> overlayInfo.isFabricated + && shellPkgName.equals(overlayInfo.packageName)); + } initIfNeeded(); onStartUser(UserHandle.USER_SYSTEM); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 4dcc6e112ecc..78359bd15717 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2224,15 +2224,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId, IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId, boolean getCropped) { - final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL) - || hasPermission(MANAGE_EXTERNAL_STORAGE); + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL); if (!hasPrivilege) { - mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, - Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId); + boolean hasManageExternalStorage = hasPermission(MANAGE_EXTERNAL_STORAGE) + || hasAppOpPermission(MANAGE_EXTERNAL_STORAGE, callingUid, callingPkg, + callingFeatureId, "getWallpaperWithFeature from package: " + callingPkg); + if (!hasManageExternalStorage) { + mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, + callingPid, callingUid, callingPkg, callingFeatureId); + } } - wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), - Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null); + wallpaperUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, + wallpaperUserId, false, true, "getWallpaper", null); if (which != FLAG_SYSTEM && which != FLAG_LOCK) { throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to read"); @@ -2348,6 +2354,22 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED; } + private boolean hasAppOpPermission(String permission, int callingUid, String callingPackage, + String attributionTag, String message) { + final String op = AppOpsManager.permissionToOp(permission); + final int opMode = mAppOpsManager.noteOpNoThrow(op, callingUid, callingPackage, + attributionTag, message); + switch (opMode) { + case AppOpsManager.MODE_ALLOWED: + case AppOpsManager.MODE_FOREGROUND: + return true; + case AppOpsManager.MODE_DEFAULT: + return hasPermission(permission); + default: + return false; + } + } + @Override public WallpaperInfo getWallpaperInfo(int userId) { return getWallpaperInfoWithFlags(FLAG_SYSTEM, userId); diff --git a/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt new file mode 100644 index 000000000000..650e520ebb7d --- /dev/null +++ b/services/tests/appfunctions/src/android/app/appfunctions/AppFunctionRuntimeMetadataTest.kt @@ -0,0 +1,104 @@ +/* + * 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 android.app.appfunctions + +import android.app.appsearch.AppSearchSchema +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class AppFunctionRuntimeMetadataTest { + + @Test + fun getRuntimeSchemaNameForPackage() { + val actualSchemaName = + AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage("com.example.app") + + assertThat(actualSchemaName).isEqualTo("AppFunctionRuntimeMetadata-com.example.app") + } + + @Test + fun testCreateChildRuntimeSchema() { + val runtimeSchema: AppSearchSchema = + AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema("com.example.app") + + assertThat(runtimeSchema.schemaType).isEqualTo("AppFunctionRuntimeMetadata-com.example.app") + val propertyNameSet = runtimeSchema.properties.map { it.name }.toSet() + assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID)) + .isTrue() + assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME)) + .isTrue() + assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_ENABLED)).isTrue() + assertThat( + propertyNameSet.contains( + AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID + ) + ) + .isTrue() + } + + @Test + fun testCreateParentRuntimeSchema() { + val runtimeSchema: AppSearchSchema = + AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema() + + assertThat(runtimeSchema.schemaType).isEqualTo("AppFunctionRuntimeMetadata") + val propertyNameSet = runtimeSchema.properties.map { it.name }.toSet() + assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID)) + .isTrue() + assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME)) + .isTrue() + assertThat(propertyNameSet.contains(AppFunctionRuntimeMetadata.PROPERTY_ENABLED)).isTrue() + assertThat( + propertyNameSet.contains( + AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID + ) + ) + .isTrue() + } + + @Test + fun testGetPackageNameFromSchema() { + val expectedPackageName = "com.foo.test" + val expectedPackageName2 = "com.bar.test" + val testSchemaType = + AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(expectedPackageName) + .schemaType + val testSchemaType2 = + AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(expectedPackageName2) + .schemaType + + val actualPackageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema(testSchemaType) + val actualPackageName2 = + AppFunctionRuntimeMetadata.getPackageNameFromSchema(testSchemaType2) + + assertThat(actualPackageName).isEqualTo(expectedPackageName) + assertThat(actualPackageName2).isEqualTo(expectedPackageName2) + } + + @Test + fun testGetPackageNameFromParentSchema() { + val expectedPackageName = AppFunctionRuntimeMetadata.APP_FUNCTION_INDEXER_PACKAGE + val testSchemaType = + AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema().schemaType + + val actualPackageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema(testSchemaType) + + assertThat(actualPackageName).isEqualTo(expectedPackageName) + } +} diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt index a0f1a559bb52..3bc44111ba08 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt @@ -19,8 +19,10 @@ import android.app.appfunctions.AppFunctionRuntimeMetadata import android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema +import android.app.appsearch.AppSearchBatchResult import android.app.appsearch.AppSearchManager import android.app.appsearch.PutDocumentsRequest +import android.app.appsearch.RemoveByDocumentIdRequest import android.app.appsearch.SearchSpec import android.app.appsearch.SetSchemaRequest import androidx.test.platform.app.InstrumentationRegistry @@ -56,7 +58,7 @@ class FutureAppSearchSessionTest { SetSchemaRequest.Builder() .addSchemas( createParentAppFunctionRuntimeSchema(), - createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME), ) .build() @@ -74,7 +76,7 @@ class FutureAppSearchSessionTest { SetSchemaRequest.Builder() .addSchemas( createParentAppFunctionRuntimeSchema(), - createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME), ) .build() val schema = session.setSchema(setSchemaRequest) @@ -93,6 +95,40 @@ class FutureAppSearchSessionTest { } @Test + fun remove() { + val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME), + ) + .build() + val schema = session.setSchema(setSchemaRequest) + assertThat(schema.get()).isNotNull() + val appFunctionRuntimeMetadata = + AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build() + val putDocumentsRequest: PutDocumentsRequest = + PutDocumentsRequest.Builder() + .addGenericDocuments(appFunctionRuntimeMetadata) + .build() + val putResult = session.put(putDocumentsRequest) + assertThat(putResult.get().isSuccess).isTrue() + val removeDocumentRequest = + RemoveByDocumentIdRequest.Builder(APP_FUNCTION_RUNTIME_NAMESPACE) + .addIds(appFunctionRuntimeMetadata.id) + .build() + + val removeResult: AppSearchBatchResult<String, Void> = + session.remove(removeDocumentRequest).get() + + assertThat(removeResult).isNotNull() + assertThat(removeResult.isSuccess).isTrue() + } + } + + @Test fun search() { val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> @@ -100,7 +136,7 @@ class FutureAppSearchSessionTest { SetSchemaRequest.Builder() .addSchemas( createParentAppFunctionRuntimeSchema(), - createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME), ) .build() val schema = session.setSchema(setSchemaRequest) @@ -132,7 +168,7 @@ class FutureAppSearchSessionTest { SetSchemaRequest.Builder() .addSchemas( createParentAppFunctionRuntimeSchema(), - createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME) + createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME), ) .build() val schema = session.setSchema(setSchemaRequest) @@ -144,12 +180,13 @@ class FutureAppSearchSessionTest { .build() val putResult = session.put(putDocumentsRequest) - val genricDocument = session - .getByDocumentId( - /* documentId= */ "${TEST_PACKAGE_NAME}/${TEST_FUNCTION_ID}", - APP_FUNCTION_RUNTIME_NAMESPACE - ) - .get() + val genricDocument = + session + .getByDocumentId( + /* documentId= */ "${TEST_PACKAGE_NAME}/${TEST_FUNCTION_ID}", + APP_FUNCTION_RUNTIME_NAMESPACE, + ) + .get() val foundAppFunctionRuntimeMetadata = AppFunctionRuntimeMetadata(genricDocument) assertThat(foundAppFunctionRuntimeMetadata.functionId).isEqualTo(TEST_FUNCTION_ID) diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt index 1061da28f799..5769e07ee58d 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt @@ -16,12 +16,9 @@ package com.android.server.appfunctions import android.app.appfunctions.AppFunctionRuntimeMetadata -import android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID -import android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME import android.app.appsearch.AppSearchManager import android.app.appsearch.AppSearchManager.SearchContext import android.app.appsearch.PutDocumentsRequest -import android.app.appsearch.SearchSpec import android.app.appsearch.SetSchemaRequest import android.util.ArrayMap import android.util.ArraySet @@ -76,20 +73,11 @@ class MetadataSyncAdapterTest { testExecutor, FutureAppSearchSession(appSearchManager, testExecutor, searchContext), ) - val searchSpec: SearchSpec = - SearchSpec.Builder() - .addFilterSchemas( - AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE, - AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME) - .schemaType, - ) - .build() val packageToFunctionIdMap = metadataSyncAdapter.getPackageToFunctionIdMap( - "", - searchSpec, - PROPERTY_FUNCTION_ID, - PROPERTY_PACKAGE_NAME, + AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE, + AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, + AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME, ) assertThat(packageToFunctionIdMap).isNotNull() @@ -135,21 +123,11 @@ class MetadataSyncAdapterTest { testExecutor, FutureAppSearchSession(appSearchManager, testExecutor, searchContext), ) - val searchSpec: SearchSpec = - SearchSpec.Builder() - .setResultCountPerPage(1) - .addFilterSchemas( - AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE, - AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME) - .schemaType, - ) - .build() val packageToFunctionIdMap = metadataSyncAdapter.getPackageToFunctionIdMap( - "", - searchSpec, - PROPERTY_FUNCTION_ID, - PROPERTY_PACKAGE_NAME, + AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE, + AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, + AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME, ) assertThat(packageToFunctionIdMap).isNotNull() diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 52f1cbd89e13..76c8e6862211 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -143,6 +143,7 @@ import com.android.server.display.DisplayManagerService.DeviceStateListener; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DisplayManagerFlags; +import com.android.server.display.layout.Layout; import com.android.server.display.notifications.DisplayNotificationManager; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; @@ -3201,6 +3202,45 @@ public class DisplayManagerServiceTest { argThat(matchesFilter)); } + @Test + public void testOnDisplayChanged_HbmMetadataNull() { + DisplayPowerController dpc = mock(DisplayPowerController.class); + DisplayManagerService.Injector injector = new BasicInjector() { + @Override + DisplayPowerController getDisplayPowerController(Context context, + DisplayPowerController.Injector injector, + DisplayManagerInternal.DisplayPowerCallbacks callbacks, Handler handler, + SensorManager sensorManager, DisplayBlanker blanker, + LogicalDisplay logicalDisplay, BrightnessTracker brightnessTracker, + BrightnessSetting brightnessSetting, Runnable onBrightnessChangeRunnable, + HighBrightnessModeMetadata hbmMetadata, boolean bootCompleted, + DisplayManagerFlags flags) { + return dpc; + } + }; + DisplayManagerService displayManager = new DisplayManagerService(mContext, injector); + DisplayManagerInternal localService = displayManager.new LocalService(); + registerDefaultDisplays(displayManager); + displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY); + + // Add the FakeDisplayDevice + FakeDisplayDevice displayDevice = new FakeDisplayDevice(); + DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo(); + displayDeviceInfo.state = Display.STATE_ON; + displayDevice.setDisplayDeviceInfo(displayDeviceInfo); + displayManager.getDisplayDeviceRepository() + .onDisplayDeviceEvent(displayDevice, DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED); + initDisplayPowerController(localService); + + // Simulate DisplayDevice change + DisplayDeviceInfo displayDeviceInfo2 = new DisplayDeviceInfo(); + displayDeviceInfo2.copyFrom(displayDeviceInfo); + displayDeviceInfo2.state = Display.STATE_DOZE; + updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo2); + + verify(dpc).onDisplayChanged(/* hbmMetadata= */ null, Layout.NO_LEAD_DISPLAY); + } + private void initDisplayPowerController(DisplayManagerInternal localService) { localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() { @Override diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp index 5a7693194d88..01435ff7f253 100644 --- a/services/tests/mockingservicestests/Android.bp +++ b/services/tests/mockingservicestests/Android.bp @@ -138,8 +138,6 @@ android_ravenwood_test { auto_gen_config: true, } -FLAKY = ["androidx.test.filters.FlakyTest"] - test_module_config { name: "FrameworksMockingServicesTests_blob", base: "FrameworksMockingServicesTests", @@ -152,7 +150,6 @@ test_module_config { base: "FrameworksMockingServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.DeviceIdleControllerTest"], - exclude_annotations: FLAKY, } test_module_config { @@ -161,7 +158,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.AppStateTrackerTest"], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY, } test_module_config { @@ -177,7 +173,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.alarm"], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY, } test_module_config { @@ -185,7 +180,7 @@ test_module_config { base: "FrameworksMockingServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.job"], - exclude_annotations: FLAKY + ["androidx.test.filters.LargeTest"], + exclude_annotations: ["androidx.test.filters.LargeTest"], } test_module_config { @@ -200,7 +195,6 @@ test_module_config { base: "FrameworksMockingServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.tare"], - exclude_annotations: FLAKY, } test_module_config { @@ -215,7 +209,6 @@ test_module_config { base: "FrameworksMockingServicesTests", test_suites: ["device-tests"], include_filters: ["android.service.games"], - exclude_annotations: FLAKY, } test_module_config { @@ -245,7 +238,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.am."], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY, } test_module_config { @@ -265,7 +257,6 @@ test_module_config { test_suites: ["device-tests"], // Matches appop too include_filters: ["com.android.server.app"], - exclude_annotations: FLAKY, } test_module_config { @@ -301,7 +292,6 @@ test_module_config { base: "FrameworksMockingServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.pm"], - exclude_annotations: FLAKY + ["org.junit.Ignore"], } test_module_config { @@ -309,7 +299,6 @@ test_module_config { base: "FrameworksMockingServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.power"], - exclude_annotations: FLAKY, } test_module_config { @@ -324,7 +313,6 @@ test_module_config { base: "FrameworksMockingServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.trust"], - exclude_annotations: FLAKY, } test_module_config { diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index fe2ac6f2a252..914b29f44bb1 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -274,21 +274,12 @@ java_genrule { "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res", } -FLAKY = [ - "androidx.test.filters.FlakyTest", -] - -FLAKY_AND_IGNORED = [ - "androidx.test.filters.FlakyTest", - "org.junit.Ignore", -] // Used by content protection TEST_MAPPING test_module_config { name: "FrameworksServicesTests_contentprotection", base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.contentprotection"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -296,7 +287,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.om."], - exclude_annotations: FLAKY_AND_IGNORED, } // Used by contexthub TEST_MAPPING @@ -307,7 +297,6 @@ test_module_config { include_filters: ["com.android.server.location.contexthub."], // TODO(ron): are these right, does it run anything? include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -317,7 +306,6 @@ test_module_config { include_filters: ["com.android.server.location.contexthub."], // TODO(ron): are these right, does it run anything? include_annotations: ["android.platform.test.annotations.Postsubmit"], - exclude_annotations: FLAKY_AND_IGNORED, } // Used by contentcapture @@ -326,7 +314,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.contentcapture"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -334,7 +321,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.recoverysystem."], - exclude_annotations: FLAKY, } // server pm TEST_MAPPING @@ -344,7 +330,6 @@ test_module_config { test_suites: ["device-tests"], include_annotations: ["android.platform.test.annotations.Presubmit"], include_filters: ["com.android.server.pm."], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -353,7 +338,6 @@ test_module_config { test_suites: ["device-tests"], include_annotations: ["android.platform.test.annotations.Postsubmit"], include_filters: ["com.android.server.pm."], - exclude_annotations: FLAKY_AND_IGNORED, } // server os TEST_MAPPING @@ -369,7 +353,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -391,14 +374,6 @@ test_module_config { } test_module_config { - name: "FrameworksServicesTests_com_android_server_tare_Presubmit", - base: "FrameworksServicesTests", - test_suites: ["device-tests"], - include_filters: ["com.android.server.tare"], - exclude_annotations: FLAKY, -} - -test_module_config { name: "FrameworksServicesTests_com_android_server_tare", base: "FrameworksServicesTests", test_suites: ["device-tests"], @@ -406,14 +381,6 @@ test_module_config { } test_module_config { - name: "FrameworksServicesTests_com_android_server_usage_Presubmit", - base: "FrameworksServicesTests", - test_suites: ["device-tests"], - include_filters: ["com.android.server.usage"], - exclude_annotations: FLAKY, -} - -test_module_config { name: "FrameworksServicesTests_com_android_server_usage", base: "FrameworksServicesTests", test_suites: ["device-tests"], @@ -428,14 +395,6 @@ test_module_config { } test_module_config { - name: "FrameworksServicesTests_accessibility_Presubmit", - base: "FrameworksServicesTests", - test_suites: ["device-tests"], - include_filters: ["com.android.server.accessibility"], - exclude_annotations: FLAKY, -} - -test_module_config { name: "FrameworksServicesTests_accessibility", base: "FrameworksServicesTests", test_suites: ["device-tests"], @@ -463,7 +422,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.am."], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY, } test_module_config { @@ -486,7 +444,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.audio"], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -502,7 +459,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.hdmi"], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -510,7 +466,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.hdmi"], - exclude_annotations: ["org.junit.Ignore"], } test_module_config { @@ -525,7 +480,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.lights"], - exclude_annotations: FLAKY, } test_module_config { @@ -541,7 +495,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.location.contexthub."], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { @@ -549,15 +502,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.locksettings."], - exclude_annotations: FLAKY, -} - -test_module_config { - name: "FrameworksServicesTests_android_server_logcat_Presubmit", - base: "FrameworksServicesTests", - test_suites: ["device-tests"], - include_filters: ["com.android.server.logcat"], - exclude_annotations: FLAKY, } test_module_config { @@ -573,7 +517,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.net."], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY, } test_module_config { @@ -603,7 +546,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.policy."], include_annotations: ["android.platform.test.annotations.Presubmit"], - exclude_annotations: FLAKY, } test_module_config { @@ -625,7 +567,6 @@ test_module_config { base: "FrameworksServicesTests", test_suites: ["device-tests"], include_filters: ["com.android.server.power.hint"], - exclude_annotations: FLAKY, } test_module_config { @@ -655,7 +596,6 @@ test_module_config { test_suites: ["device-tests"], include_filters: ["com.android.server.location.contexthub."], include_annotations: ["android.platform.test.annotations.Postsubmit"], - exclude_annotations: FLAKY_AND_IGNORED, } test_module_config { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index 004c6c68781c..21129a7a9d85 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -19,6 +19,7 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; +import static com.android.server.hdmi.Constants.ADDR_BROADCAST; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.MESSAGE_DEVICE_VENDOR_ID; @@ -529,21 +530,32 @@ public class HdmiCecLocalDeviceTest { public void handleVendorCommand_notHandled() { HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommand(ADDR_TV, ADDR_PLAYBACK_1, new byte[]{0}); - mNativeWrapper.onCecMessage(vendorCommand); + @Constants.HandleMessageResult int result = + mHdmiLocalDevice.handleVendorCommand(vendorCommand); mTestLooper.dispatchAll(); - HdmiCecMessageBuilder.buildFeatureAbortCommand(ADDR_PLAYBACK_1, ADDR_TV, - vendorCommand.getOpcode(), Constants.ABORT_REFUSED); + assertEquals(Constants.ABORT_REFUSED, result); } @Test public void handleVendorCommandWithId_notHandled_Cec14() { HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommandWithId(ADDR_TV, ADDR_PLAYBACK_1, 0x1234, new byte[]{0}); - mNativeWrapper.onCecMessage(vendorCommand); + @Constants.HandleMessageResult int result = + mHdmiLocalDevice.handleVendorCommandWithId(vendorCommand); mTestLooper.dispatchAll(); - HdmiCecMessageBuilder.buildFeatureAbortCommand(ADDR_PLAYBACK_1, ADDR_TV, - vendorCommand.getOpcode(), Constants.ABORT_REFUSED); + assertEquals(Constants.ABORT_REFUSED, result); + } + + @Test + public void handleVendorCommandWithId_broadcasted_handled() { + HdmiCecMessage vendorCommand = HdmiCecMessageBuilder.buildVendorCommandWithId(ADDR_TV, + ADDR_BROADCAST, 0x1234, new byte[]{0}); + @Constants.HandleMessageResult int result = + mHdmiLocalDevice.handleVendorCommandWithId(vendorCommand); + mTestLooper.dispatchAll(); + + assertEquals(Constants.HANDLED, result); } } diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java index cbf79356e9c4..def33551a820 100644 --- a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java +++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java @@ -19,7 +19,7 @@ package com.android.server.webkit; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; +import android.os.UserHandle; import android.webkit.UserPackage; import android.webkit.WebViewProviderInfo; @@ -137,16 +137,12 @@ public class TestSystemImpl implements SystemInterface { List<UserPackage> ret = new ArrayList(); // Loop over defined users, and find the corresponding package for each user. for (int userId : mUsers) { - ret.add(new UserPackage(createUserInfo(userId), + ret.add(new UserPackage(UserHandle.of(userId), userPackages == null ? null : userPackages.get(userId))); } return ret; } - private static UserInfo createUserInfo(int userId) { - return new UserInfo(userId, "User nr. " + userId, 0 /* flags */); - } - /** * Set package for primary user. */ diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java index b5bc610f82ea..2effc692e877 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java @@ -30,9 +30,9 @@ import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -44,6 +44,8 @@ import android.app.PendingIntent; import android.app.Person; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.ShortcutInfo; import android.os.SystemClock; import android.os.UserHandle; @@ -89,6 +91,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { @Mock ShortcutHelper mShortcutHelper; @Mock + PackageManager mPackageManager; + @Mock ActivityManager mActivityManager; @Before @@ -98,6 +102,7 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.initialize(mContext, mock(NotificationUsageStats.class)); mBubbleExtractor.setConfig(mConfig); mBubbleExtractor.setShortcutHelper(mShortcutHelper); + mBubbleExtractor.setPackageManager(mPackageManager); mBubbleExtractor.setActivityManager(mActivityManager); mChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, IMPORTANCE_DEFAULT); @@ -106,7 +111,7 @@ public class BubbleExtractorTest extends UiServiceTestCase { } /* NotificationRecord that fulfills conversation requirements (message style + shortcut) */ - private NotificationRecord getNotificationRecord(boolean addBubble) { + private NotificationRecord getNotificationRecord(boolean addBubble, UserHandle user) { final Builder builder = new Builder(getContext()) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon) @@ -127,13 +132,13 @@ public class BubbleExtractorTest extends UiServiceTestCase { n.setBubbleMetadata(mBubbleMetadata); } StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, ID, TAG, UID, - PID, n, mUser, null, System.currentTimeMillis()); + PID, n, user, null, System.currentTimeMillis()); NotificationRecord r = new NotificationRecord(getContext(), sbn, mChannel); r.setShortcutInfo(mShortcutInfo); return r; } - void setUpIntentBubble(boolean isValid) { + void setUpIntentBubble(boolean isValid, UserHandle user) { when(mPendingIntent.getIntent()).thenReturn(mIntent); when(mBubbleMetadata.getIntent()).thenReturn(mPendingIntent); when(mBubbleMetadata.getShortcutId()).thenReturn(null); @@ -143,18 +148,21 @@ public class BubbleExtractorTest extends UiServiceTestCase { info.resizeMode = isValid ? RESIZE_MODE_RESIZEABLE : RESIZE_MODE_UNRESIZEABLE; - when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(info); + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.activityInfo = info; + when(mPackageManager.resolveActivityAsUser(eq(mIntent), eq(0), eq(user.getIdentifier()))) + .thenReturn(resolveInfo); } - void setUpShortcutBubble(boolean isValid) { + void setUpShortcutBubble(boolean isValid, UserHandle user) { when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID); when(mBubbleMetadata.getIntent()).thenReturn(null); ShortcutInfo answer = isValid ? mShortcutInfo : null; - when(mShortcutHelper.getValidShortcutInfo(SHORTCUT_ID, PKG, mUser)).thenReturn(answer); + when(mShortcutHelper.getValidShortcutInfo(SHORTCUT_ID, PKG, user)).thenReturn(answer); } - void setUpBubblesEnabled(boolean feature, int app, int channel) { - when(mConfig.bubblesEnabled(mUser)).thenReturn(feature); + void setUpBubblesEnabled(boolean feature, int app, int channel, UserHandle user) { + when(mConfig.bubblesEnabled(user)).thenReturn(feature); when(mConfig.getBubblePreference(anyString(), anyInt())).thenReturn(app); mChannel.setAllowBubbles(channel); } @@ -167,10 +175,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppYesChannelNo() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - ALLOW_BUBBLE_OFF /* channel */); + ALLOW_BUBBLE_OFF /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); assertFalse(r.canBubble()); @@ -181,10 +190,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppYesChannelDefault() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -195,10 +205,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppYesChannelYes() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - ALLOW_BUBBLE_ON /* channel */); + ALLOW_BUBBLE_ON /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -209,10 +220,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppYesChannelYesFeatureNo() { setUpBubblesEnabled(false /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - ALLOW_BUBBLE_ON /* channel */); + ALLOW_BUBBLE_ON /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -224,10 +236,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppNoChannelYes() throws Exception { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_NONE /* app */, - ALLOW_BUBBLE_ON /* channel */); + ALLOW_BUBBLE_ON /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -239,10 +252,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppNoChannelDefault() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_NONE /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -254,10 +268,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppSelectedChannelDefault() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -269,10 +284,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppSelectedChannelNo() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, - ALLOW_BUBBLE_OFF /* channel */); + ALLOW_BUBBLE_OFF /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); - NotificationRecord r = getNotificationRecord(true /* bubble */); + setUpShortcutBubble(true /* isValid */, mUser); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -284,11 +300,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppSeletedChannelYes() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, - ALLOW_BUBBLE_ON /* channel */); + ALLOW_BUBBLE_ON /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); + setUpShortcutBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -299,11 +316,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testAppSeletedChannelYesFeatureNo() { setUpBubblesEnabled(false /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, - ALLOW_BUBBLE_ON /* channel */); + ALLOW_BUBBLE_ON /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); + setUpShortcutBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); @@ -319,11 +337,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_previouslyRemoved() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); + setUpShortcutBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); r.setFlagBubbleRemoved(true); mBubbleExtractor.process(r); @@ -337,11 +356,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_true_shortcutBubble() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(true /* isValid */); + setUpShortcutBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); assertTrue(r.canBubble()); @@ -353,11 +373,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_true_intentBubble() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpIntentBubble(true /* isValid */); + setUpIntentBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); assertTrue(r.canBubble()); @@ -369,11 +390,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_noIntentInvalidShortcut() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpShortcutBubble(false /* isValid */); + setUpShortcutBubble(false /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); r.setShortcutInfo(null); mBubbleExtractor.process(r); @@ -386,11 +408,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_invalidIntentNoShortcut() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpIntentBubble(false /* isValid */); + setUpIntentBubble(false /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); r.setShortcutInfo(null); mBubbleExtractor.process(r); @@ -403,11 +426,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_noIntentNoShortcut() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); // Shortcut here is for the notification not the bubble - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); assertFalse(r.canBubble()); @@ -419,10 +443,11 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_noMetadata() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - NotificationRecord r = getNotificationRecord(false /* bubble */); + NotificationRecord r = getNotificationRecord(false /* bubble */, mUser); mBubbleExtractor.process(r); assertFalse(r.canBubble()); @@ -434,11 +459,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_noShortcut() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpIntentBubble(true /* isValid */); + setUpIntentBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); r.setShortcutInfo(null); r.getNotification().extras.putString(Notification.EXTRA_TEMPLATE, null); @@ -453,11 +479,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_notConversation() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(false); - setUpIntentBubble(true /* isValid */); + setUpIntentBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); r.userDemotedAppFromConvoSpace(true); r.getNotification().extras.putString(Notification.EXTRA_TEMPLATE, null); @@ -472,11 +499,12 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_lowRamDevice() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(true); - setUpIntentBubble(true /* isValid */); + setUpIntentBubble(true /* isValid */, mUser); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); assertFalse(r.canBubble()); @@ -488,12 +516,13 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_noIntent() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(true); - setUpIntentBubble(true /* isValid */); + setUpIntentBubble(true /* isValid */, mUser); when(mPendingIntent.getIntent()).thenReturn(null); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); assertFalse(r.canBubble()); @@ -505,13 +534,15 @@ public class BubbleExtractorTest extends UiServiceTestCase { public void testFlagBubble_false_noActivityInfo() { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, - DEFAULT_ALLOW_BUBBLE /* channel */); + DEFAULT_ALLOW_BUBBLE /* channel */, + mUser); when(mActivityManager.isLowRamDevice()).thenReturn(true); - setUpIntentBubble(true /* isValid */); + setUpIntentBubble(true /* isValid */, mUser); when(mPendingIntent.getIntent()).thenReturn(mIntent); - when(mIntent.resolveActivityInfo(any(), anyInt())).thenReturn(null); + when(mPackageManager.resolveActivityAsUser(eq(mIntent), eq(0), eq(mUser.getIdentifier()))) + .thenReturn(null); - NotificationRecord r = getNotificationRecord(true /* bubble */); + NotificationRecord r = getNotificationRecord(true /* bubble */, mUser); mBubbleExtractor.process(r); assertFalse(r.canBubble()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 6a1140cc84e9..196bc47572ba 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -1255,7 +1255,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { info.resizeMode = RESIZE_MODE_RESIZEABLE; ResolveInfo ri = new ResolveInfo(); ri.activityInfo = info; - when(mPackageManagerClient.resolveActivity(any(), anyInt())).thenReturn(ri); + when(mPackageManagerClient.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(ri); return new Notification.BubbleMetadata.Builder( mActivityIntent, diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java index 0f08be215033..2a49eb12c3ff 100644 --- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java +++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java @@ -296,33 +296,31 @@ public class TouchpadDebugViewTest { @Test public void testTouchpadClick() { - View child; + View child = mTouchpadDebugView.getChildAt(0); mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0, - new TouchpadFingerState[0])); + new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - for (int i = 0; i < mTouchpadDebugView.getChildCount(); i++) { - child = mTouchpadDebugView.getChildAt(i); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); - } + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, - new TouchpadFingerState[0])); + new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - for (int i = 0; i < mTouchpadDebugView.getChildCount(); i++) { - child = mTouchpadDebugView.getChildAt(i); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED); - } + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.RED); mTouchpadDebugView.updateHardwareState( new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0, - new TouchpadFingerState[0])); + new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID); - for (int i = 0; i < mTouchpadDebugView.getChildCount(); i++) { - child = mTouchpadDebugView.getChildAt(i); - assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); - } + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); + + // Color should not change because hardware state of a different touchpad + mTouchpadDebugView.updateHardwareState( + new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0, + new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1); + + assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.BLUE); } } diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 60c4bf5c4131..5e0f87f0dcaf 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -292,12 +292,13 @@ bool processFile(Bundle* bundle, ZipFile* zip, } if (!hasData) { const String8& srcName = file->getSourceFile(); - auto fileModWhen = getFileModDate(srcName.c_str()); - if (fileModWhen == kInvalidModDate) { // file existence tested earlier, - return false; // not expecting an error here + time_t fileModWhen; + fileModWhen = getFileModDate(srcName.c_str()); + if (fileModWhen == (time_t) -1) { // file existence tested earlier, + return false; // not expecting an error here } - - if (toTimeT(fileModWhen) > entry->getModWhen()) { + + if (fileModWhen > entry->getModWhen()) { // mark as deleted so add() will succeed if (bundle->getVerbose()) { printf(" (removing old '%s')\n", storageName.c_str()); |