diff options
56 files changed, 1573 insertions, 701 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index ad849002cca1..6e37b7e55eef 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -72,6 +72,7 @@ aconfig_declarations_group { "android.service.dreams.flags-aconfig-java", "android.service.notification.flags-aconfig-java", "android.service.appprediction.flags-aconfig-java", + "android.service.quickaccesswallet.flags-aconfig-java", "android.service.voice.flags-aconfig-java", "android.speech.flags-aconfig-java", "android.systemserver.flags-aconfig-java", @@ -1774,3 +1775,18 @@ java_aconfig_library { aconfig_declarations: "aconfig_settingslib_flags", defaults: ["framework-minus-apex-aconfig-java-defaults"], } + +// Quick Access Wallet +aconfig_declarations { + name: "android.service.quickaccesswallet.flags-aconfig", + package: "android.service.quickaccesswallet", + exportable: true, + container: "system", + srcs: ["core/java/android/service/quickaccesswallet/flags.aconfig"], +} + +java_aconfig_library { + name: "android.service.quickaccesswallet.flags-aconfig-java", + aconfig_declarations: "android.service.quickaccesswallet.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 62c8a34ac1f3..ed95fdd52f45 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1378,7 +1378,8 @@ package android.app.admin { method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setDpcDownloaded(boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setMaxPolicyStorageLimit(int); method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName); - method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); + method @Deprecated @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean); + method @FlaggedApi("android.app.admin.flags.secondary_lockscreen_api_enabled") public void setSecondaryLockscreenEnabled(boolean, @Nullable android.os.PersistableBundle); method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void setUserProvisioningState(int, @NonNull android.os.UserHandle); method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean shouldAllowBypassingDevicePolicyManagementRoleQualification(); field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED"; @@ -12552,8 +12553,19 @@ package android.security.advancedprotection { } @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager { + method @NonNull public android.content.Intent createSupportIntent(@NonNull String, @Nullable String); method @NonNull @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public java.util.List<android.security.advancedprotection.AdvancedProtectionFeature> getAdvancedProtectionFeatures(); method @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public void setAdvancedProtectionEnabled(boolean); + field @FlaggedApi("android.security.aapm_api") public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG"; + field public static final String EXTRA_SUPPORT_DIALOG_FEATURE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE"; + field public static final String EXTRA_SUPPORT_DIALOG_TYPE = "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE"; + field public static final String FEATURE_ID_DISALLOW_CELLULAR_2G = "android.security.advancedprotection.feature_disallow_2g"; + field public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = "android.security.advancedprotection.feature_disallow_install_unknown_sources"; + field public static final String FEATURE_ID_DISALLOW_USB = "android.security.advancedprotection.feature_disallow_usb"; + field public static final String FEATURE_ID_DISALLOW_WEP = "android.security.advancedprotection.feature_disallow_wep"; + field public static final String FEATURE_ID_ENABLE_MTE = "android.security.advancedprotection.feature_enable_mte"; + field public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = "android.security.advancedprotection.type_blocked_interaction"; + field public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING = "android.security.advancedprotection.type_disabled_setting"; } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 0381ee0e25ac..3d9c55c0f37a 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -5003,7 +5003,7 @@ public class Notification implements Parcelable /** * Sets a very short string summarizing the most critical information contained in the - * notification. Suggested max length is 5 characters, and there is no guarantee how much or + * notification. Suggested max length is 7 characters, and there is no guarantee how much or * how little of this text will be shown. */ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING) diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index a458b4e45796..f702b85bfcbb 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -174,22 +174,54 @@ public class ResourcesManager { } /** - * Apply the registered library paths to the passed impl object - * @return the hash code for the current version of the registered paths + * Apply the registered library paths to the passed AssetManager. If may create a new + * AssetManager if any changes are needed and it isn't allowed to reuse the old one. + * + * @return new AssetManager and the hash code for the current version of the registered paths */ - public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { + public @NonNull Pair<AssetManager, Integer> updateResourceImplAssetsWithRegisteredLibs( + @NonNull AssetManager assets, boolean reuseAssets) { if (!Flags.registerResourcePaths()) { - return 0; + return new Pair<>(assets, 0); } - final var collector = new PathCollector(null); - final int size = mSharedLibAssetsMap.size(); - for (int i = 0; i < size; i++) { - final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); - collector.appendKey(libraryKey); + final int size; + final PathCollector collector; + + synchronized (mLock) { + size = mSharedLibAssetsMap.size(); + if (assets == AssetManager.getSystem()) { + return new Pair<>(assets, size); + } + collector = new PathCollector(resourcesKeyFromAssets(assets)); + for (int i = 0; i < size; i++) { + final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); + collector.appendKey(libraryKey); + } + } + if (collector.isSameAsOriginal()) { + return new Pair<>(assets, size); + } + if (reuseAssets) { + assets.addPresetApkKeys(extractApkKeys(collector.collectedKey())); + return new Pair<>(assets, size); + } + final var newAssetsBuilder = new AssetManager.Builder(); + for (final var asset : assets.getApkAssets()) { + if (!asset.isForLoader()) { + newAssetsBuilder.addApkAssets(asset); + } } - impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); - return size; + for (final var key : extractApkKeys(collector.collectedKey())) { + try { + final var asset = loadApkAssets(key); + newAssetsBuilder.addApkAssets(asset); + } catch (IOException e) { + Log.e(TAG, "Couldn't load assets for key " + key, e); + } + } + assets.getLoaders().forEach(newAssetsBuilder::addLoader); + return new Pair<>(newAssetsBuilder.build(), size); } public static class ApkKey { @@ -624,6 +656,23 @@ public class ResourcesManager { return apkKeys; } + private ResourcesKey resourcesKeyFromAssets(@NonNull AssetManager assets) { + final var libs = new ArrayList<String>(); + final var overlays = new ArrayList<String>(); + for (final ApkAssets asset : assets.getApkAssets()) { + if (asset.isSystem() || asset.isForLoader()) { + continue; + } + if (asset.isOverlay()) { + overlays.add(asset.getAssetPath()); + } else if (asset.isSharedLib()) { + libs.add(asset.getAssetPath()); + } + } + return new ResourcesKey(null, null, overlays.toArray(new String[0]), + libs.toArray(new String[0]), 0, null, null); + } + /** * Creates an AssetManager from the paths within the ResourcesKey. * @@ -752,7 +801,7 @@ public class ResourcesManager { final Configuration config = generateConfig(key); final DisplayMetrics displayMetrics = getDisplayMetrics(generateDisplayId(key), daj); - final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj); + final ResourcesImpl impl = new ResourcesImpl(assets, displayMetrics, config, daj, true); if (DEBUG) { Slog.d(TAG, "- creating impl=" + impl + " with key: " + key); @@ -1835,31 +1884,32 @@ public class ResourcesManager { for (int i = 0; i < resourcesCount; i++) { final WeakReference<Resources> ref = mAllResourceReferences.get(i); final Resources r = ref != null ? ref.get() : null; - if (r != null) { - final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); - if (key != null) { - final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); - if (impl == null) { - throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); - } - r.setImpl(impl); - } else { - // ResourcesKey is null which means the ResourcesImpl could belong to a - // Resources created by application through Resources constructor and was not - // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to - // have shared library asset paths appended if there are any. - if (r.getImpl() != null) { - final ResourcesImpl oldImpl = r.getImpl(); - final AssetManager oldAssets = oldImpl.getAssets(); - // ResourcesImpl constructor will help to append shared library asset paths. - if (oldAssets != AssetManager.getSystem() && oldAssets.isUpToDate()) { - final ResourcesImpl newImpl = new ResourcesImpl(oldAssets, - oldImpl.getMetrics(), oldImpl.getConfiguration(), - oldImpl.getDisplayAdjustments()); + if (r == null) { + continue; + } + final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); + if (key != null) { + final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(key); + if (impl == null) { + throw new Resources.NotFoundException("failed to redirect ResourcesImpl"); + } + r.setImpl(impl); + } else { + // ResourcesKey is null which means the ResourcesImpl could belong to a + // Resources created by application through Resources constructor and was not + // managed by ResourcesManager, so the ResourcesImpl needs to be recreated to + // have shared library asset paths appended if there are any. + final ResourcesImpl oldImpl = r.getImpl(); + if (oldImpl != null) { + final AssetManager oldAssets = oldImpl.getAssets(); + // ResourcesImpl constructor will help to append shared library asset paths. + if (oldAssets != AssetManager.getSystem()) { + if (oldAssets.isUpToDate()) { + final ResourcesImpl newImpl = new ResourcesImpl(oldImpl); r.setImpl(newImpl); } else { - Slog.w(TAG, "Skip appending shared library asset paths for the " - + "Resource as its assets are not up to date."); + Slog.w(TAG, "Skip appending shared library asset paths for " + + "the Resources as its assets are not up to date."); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6939bb694028..bff77f951b9f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -58,6 +58,7 @@ import static android.app.admin.flags.Flags.FLAG_DEVICE_THEFT_API_ENABLED; import static android.app.admin.flags.Flags.FLAG_REMOVE_MANAGED_PROFILE_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; import static android.app.admin.flags.Flags.onboardingConsentlessBugreports; +import static android.app.admin.flags.Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; @@ -12642,28 +12643,43 @@ public class DevicePolicyManager { * @param enabled Whether or not the lockscreen needs to be shown. * @throws SecurityException if {@code admin} is not a device or profile owner. * @see #isSecondaryLockscreenEnabled + * @deprecated Use {@link #setSecondaryLockscreenEnabled(boolean,PersistableBundle)} instead. * @hide - **/ + */ + @Deprecated @SystemApi + @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED) public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) { - setSecondaryLockscreenEnabled(admin, enabled, null); + throwIfParentInstance("setSecondaryLockscreenEnabled"); + if (mService != null) { + try { + mService.setSecondaryLockscreenEnabled(admin, enabled, null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** * Called by the system supervision app to set whether a secondary lockscreen needs to be shown. * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the - * caller is not a device admin. + * <p>The secondary lockscreen will by displayed after the primary keyguard security screen + * requirements are met. + * + * <p>This API, and associated APIs, can only be called by the default supervision app. + * * @param enabled Whether or not the lockscreen needs to be shown. * @param options A {@link PersistableBundle} to supply options to the lock screen. * @hide */ - public void setSecondaryLockscreenEnabled(@Nullable ComponentName admin, boolean enabled, + @SystemApi + @FlaggedApi(FLAG_SECONDARY_LOCKSCREEN_API_ENABLED) + public void setSecondaryLockscreenEnabled(boolean enabled, @Nullable PersistableBundle options) { throwIfParentInstance("setSecondaryLockscreenEnabled"); if (mService != null) { try { - mService.setSecondaryLockscreenEnabled(admin, enabled, options); + mService.setSecondaryLockscreenEnabled(null, enabled, options); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 5f868befa368..404471e266d2 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -381,3 +381,11 @@ flag { description: "Split up existing create and provision managed profile API." bug: "375382324" } + +flag { + name: "secondary_lockscreen_api_enabled" + is_exported: true + namespace: "enterprise" + description: "Add new API for secondary lockscreen" + bug: "336297680" +} diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 68b5d782bfbf..908999b64961 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -124,11 +124,13 @@ public final class ApkAssets { @Nullable @GuardedBy("this") - private final StringBlock mStringBlock; // null or closed if mNativePtr = 0. + private StringBlock mStringBlock; // null or closed if mNativePtr = 0. @PropertyFlags private final int mFlags; + private final boolean mIsOverlay; + @Nullable private final AssetsProvider mAssets; @@ -302,40 +304,43 @@ public final class ApkAssets { private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(path, "path"); - mFlags = flags; mNativePtr = nativeLoad(format, path, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); - mFlags = flags; mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + this(format, flags, assets); Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); - mFlags = flags; mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); - mAssets = assets; } private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { - mFlags = flags; + this(FORMAT_APK, flags, assets); mNativePtr = nativeLoadEmpty(flags, assets); mStringBlock = null; + } + + private ApkAssets(@FormatType int format, @PropertyFlags int flags, + @Nullable AssetsProvider assets) { + mFlags = flags; mAssets = assets; + mIsOverlay = format == FORMAT_IDMAP; } @UnsupportedAppUsage @@ -425,6 +430,18 @@ public final class ApkAssets { } } + public boolean isSystem() { + return (mFlags & PROPERTY_SYSTEM) != 0; + } + + public boolean isSharedLib() { + return (mFlags & PROPERTY_DYNAMIC) != 0; + } + + public boolean isOverlay() { + return mIsOverlay; + } + @Override public String toString() { return "ApkAssets{path=" + getDebugName() + "}"; diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index e6b93427f413..bcaceb24d767 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -203,9 +203,25 @@ public class ResourcesImpl { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { - mAssets = assets; - mAppliedSharedLibsHash = - ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this); + // Don't reuse assets by default as we have no control over whether they're already + // inside some other ResourcesImpl. + this(assets, metrics, config, displayAdjustments, false); + } + + public ResourcesImpl(@NonNull ResourcesImpl orig) { + // We know for sure that the other assets are in use, so can't reuse the object here. + this(orig.getAssets(), orig.getMetrics(), orig.getConfiguration(), + orig.getDisplayAdjustments(), false); + } + + public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, + @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments, + boolean reuseAssets) { + final var assetsAndHash = + ResourcesManager.getInstance().updateResourceImplAssetsWithRegisteredLibs(assets, + reuseAssets); + mAssets = assetsAndHash.first; + mAppliedSharedLibsHash = assetsAndHash.second; mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index d2435757756c..6c35d106bfb7 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -3,6 +3,16 @@ container: "system" flag { namespace: "credential_manager" + name: "ttl_fix_enabled" + description: "Enable fix for transaction too large issue" + bug: "371052524" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "credential_manager" name: "settings_activity_enabled" is_exported: true description: "Enable the Credential Manager Settings Activity APIs" diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java index 6f3e3d8f0d3b..9fe0dda136d1 100644 --- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java +++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java @@ -16,20 +16,30 @@ package android.security.advancedprotection; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; + import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.content.Intent; import android.os.Binder; import android.os.RemoteException; import android.security.Flags; import android.util.Log; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; @@ -45,6 +55,139 @@ import java.util.concurrent.Executor; public final class AdvancedProtectionManager { private static final String TAG = "AdvancedProtectionMgr"; + /** + * Advanced Protection's identifier for setting policies or restrictions in DevicePolicyManager. + * + * @hide */ + public static final String ADVANCED_PROTECTION_SYSTEM_ENTITY = + "android.security.advancedprotection"; + + /** + * Feature identifier for disallowing 2G. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_CELLULAR_2G = + "android.security.advancedprotection.feature_disallow_2g"; + + /** + * Feature identifier for disallowing install of unknown sources. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES = + "android.security.advancedprotection.feature_disallow_install_unknown_sources"; + + /** + * Feature identifier for disallowing USB. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_USB = + "android.security.advancedprotection.feature_disallow_usb"; + + /** + * Feature identifier for disallowing WEP. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_DISALLOW_WEP = + "android.security.advancedprotection.feature_disallow_wep"; + + /** + * Feature identifier for enabling MTE. + * + * @hide */ + @SystemApi + public static final String FEATURE_ID_ENABLE_MTE = + "android.security.advancedprotection.feature_enable_mte"; + + /** @hide */ + @StringDef(prefix = { "FEATURE_ID_" }, value = { + FEATURE_ID_DISALLOW_CELLULAR_2G, + FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES, + FEATURE_ID_DISALLOW_USB, + FEATURE_ID_DISALLOW_WEP, + FEATURE_ID_ENABLE_MTE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FeatureId {} + + private static final Set<String> ALL_FEATURE_IDS = Set.of( + FEATURE_ID_DISALLOW_CELLULAR_2G, + FEATURE_ID_DISALLOW_INSTALL_UNKNOWN_SOURCES, + FEATURE_ID_DISALLOW_USB, + FEATURE_ID_DISALLOW_WEP, + FEATURE_ID_ENABLE_MTE); + + /** + * Activity Action: Show a dialog with disabled by advanced protection message. + * <p> If a user action or a setting toggle is disabled by advanced protection, this dialog can + * be triggered to let the user know about this. + * <p> + * Input: + * <p>{@link #EXTRA_SUPPORT_DIALOG_FEATURE}: The feature identifier. + * <p>{@link #EXTRA_SUPPORT_DIALOG_TYPE}: The type of the action. + * <p> + * Output: Nothing. + * + * @hide */ + @SystemApi + @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) + @FlaggedApi(android.security.Flags.FLAG_AAPM_API) + public static final String ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG = + "android.security.advancedprotection.action.SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG"; + + /** + * A string extra used with {@link #createSupportIntent} to identify the feature that needs to + * show a support dialog explaining it was disabled by advanced protection. + * + * @hide */ + @FeatureId + @SystemApi + public static final String EXTRA_SUPPORT_DIALOG_FEATURE = + "android.security.advancedprotection.extra.SUPPORT_DIALOG_FEATURE"; + + /** + * A string extra used with {@link #createSupportIntent} to identify the type of the action that + * needs to be explained in the support dialog. + * + * @hide */ + @SupportDialogType + @SystemApi + public static final String EXTRA_SUPPORT_DIALOG_TYPE = + "android.security.advancedprotection.extra.SUPPORT_DIALOG_TYPE"; + + /** + * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user performed an action that was + * blocked by advanced protection. + * + * @hide */ + @SystemApi + public static final String SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION = + "android.security.advancedprotection.type_blocked_interaction"; + + /** + * Type for {@link #EXTRA_SUPPORT_DIALOG_TYPE} indicating a user pressed on a setting toggle + * that was disabled by advanced protection. + * + * @hide */ + @SystemApi + public static final String SUPPORT_DIALOG_TYPE_DISABLED_SETTING = + "android.security.advancedprotection.type_disabled_setting"; + + /** @hide */ + @StringDef(prefix = { "SUPPORT_DIALOG_TYPE_" }, value = { + SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, + SUPPORT_DIALOG_TYPE_DISABLED_SETTING, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SupportDialogType {} + + private static final Set<String> ALL_SUPPORT_DIALOG_TYPES = Set.of( + SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION, + SUPPORT_DIALOG_TYPE_DISABLED_SETTING); + private final ConcurrentHashMap<Callback, IAdvancedProtectionCallback> mCallbackMap = new ConcurrentHashMap<>(); @@ -164,6 +307,43 @@ public final class AdvancedProtectionManager { } /** + * Called by a feature to display a support dialog when a feature was disabled by advanced + * protection. This returns an intent that can be used with + * {@link Context#startActivity(Intent)} to display the dialog. + * + * <p>Note that this method doesn't check if the feature is actually disabled, i.e. this method + * will always return an intent. + * + * @param featureId The feature identifier. + * @param type The type of the feature describing the action that needs to be explained + * in the dialog or null for default explanation. + * @return Intent An intent to be used to start the dialog-activity that explains a feature was + * disabled by advanced protection. + * @hide + */ + @SystemApi + public @NonNull Intent createSupportIntent(@NonNull @FeatureId String featureId, + @Nullable @SupportDialogType String type) { + Objects.requireNonNull(featureId); + if (!ALL_FEATURE_IDS.contains(featureId)) { + throw new IllegalArgumentException(featureId + " is not a valid feature ID. See" + + " FEATURE_ID_* APIs."); + } + if (type != null && !ALL_SUPPORT_DIALOG_TYPES.contains(type)) { + throw new IllegalArgumentException(type + " is not a valid type. See" + + " SUPPORT_DIALOG_TYPE_* APIs."); + } + + Intent intent = new Intent(ACTION_SHOW_ADVANCED_PROTECTION_SUPPORT_DIALOG); + intent.setFlags(FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(EXTRA_SUPPORT_DIALOG_FEATURE, featureId); + if (type != null) { + intent.putExtra(EXTRA_SUPPORT_DIALOG_TYPE, type); + } + return intent; + } + + /** * A callback class for monitoring changes to Advanced Protection state * * <p>To register a callback, implement this interface, and register it with diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig new file mode 100644 index 000000000000..07311d5ffbe1 --- /dev/null +++ b/core/java/android/service/quickaccesswallet/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.service.quickaccesswallet" +container: "system" + +flag { + name: "launch_wallet_option_on_power_double_tap" + namespace: "wallet_integrations" + description: "Option to launch the Wallet app on double-tap of the power button" + bug: "378469025" +}
\ No newline at end of file diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java index 0001176220b5..c3fb855eb1ff 100644 --- a/core/java/android/view/HapticScrollFeedbackProvider.java +++ b/core/java/android/view/HapticScrollFeedbackProvider.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.flags.Flags.dynamicViewRotaryHapticsConfiguration; + import android.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; @@ -41,13 +43,8 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { private final View mView; private final ViewConfiguration mViewConfig; - /** - * Flag to disable the logic in this class if the View-based scroll haptics implementation is - * enabled. If {@code false}, this class will continue to run despite the View's scroll - * haptics implementation being enabled. This value should be set to {@code true} when this - * class is directly used by the View class. - */ - private final boolean mDisabledIfViewPlaysScrollHaptics; + /** Whether or not this provider is being used directly by the View class. */ + private final boolean mIsFromView; // Info about the cause of the latest scroll event. @@ -65,17 +62,23 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { private boolean mHapticScrollFeedbackEnabled = false; public HapticScrollFeedbackProvider(@NonNull View view) { - this(view, ViewConfiguration.get(view.getContext()), - /* disabledIfViewPlaysScrollHaptics= */ true); + this(view, ViewConfiguration.get(view.getContext()), /* isFromView= */ false); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public HapticScrollFeedbackProvider( - View view, ViewConfiguration viewConfig, boolean disabledIfViewPlaysScrollHaptics) { + View view, ViewConfiguration viewConfig, boolean isFromView) { mView = view; mViewConfig = viewConfig; - mDisabledIfViewPlaysScrollHaptics = disabledIfViewPlaysScrollHaptics; + mIsFromView = isFromView; + if (dynamicViewRotaryHapticsConfiguration() && !isFromView) { + // Disable the View class's rotary scroll feedback logic if this provider is not being + // directly used by the View class. This is to avoid double rotary scroll feedback: + // one from the View class, and one from this provider instance (i.e. mute the View + // class's rotary feedback and enable this provider). + view.disableRotaryScrollFeedback(); + } } @Override @@ -151,7 +154,8 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { mAxis = axis; mDeviceId = deviceId; - if (mDisabledIfViewPlaysScrollHaptics + if (!dynamicViewRotaryHapticsConfiguration() + && !mIsFromView && (source == InputDevice.SOURCE_ROTARY_ENCODER) && mViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) { mHapticScrollFeedbackEnabled = false; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c71bf4be308c..049189f8af8d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -16754,9 +16754,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED; } } - final boolean processForRotaryScrollHaptics = - isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0); - if (processForRotaryScrollHaptics) { + if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) { mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT; mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_WAITING_FOR_SCROLL_EVENT; } @@ -16773,7 +16771,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, // Process scroll haptics after `onGenericMotionEvent`, since that's where scrolling usually // happens. Some views may return false from `onGenericMotionEvent` even if they have done // scrolling, so disregard the return value when processing for scroll haptics. - if (processForRotaryScrollHaptics) { + // Check for `PFLAG4_ROTARY_HAPTICS_ENABLED` again, because the View implementation may + // call `disableRotaryScrollFeedback` in `onGenericMotionEvent`, which could change the + // value of `PFLAG4_ROTARY_HAPTICS_ENABLED`. + if (isRotaryEncoderEvent && ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_ENABLED) != 0)) { if ((mPrivateFlags4 & PFLAG4_ROTARY_HAPTICS_SCROLL_SINCE_LAST_ROTARY_INPUT) != 0) { doRotaryProgressForScrollHaptics(event); } else { @@ -18716,7 +18717,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private HapticScrollFeedbackProvider getScrollFeedbackProvider() { if (mScrollFeedbackProvider == null) { mScrollFeedbackProvider = new HapticScrollFeedbackProvider(this, - ViewConfiguration.get(mContext), /* disabledIfViewPlaysScrollHaptics= */ false); + ViewConfiguration.get(mContext), /* isFromView= */ true); } return mScrollFeedbackProvider; } @@ -18746,6 +18747,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Disables the rotary scroll feedback implementation of the View class. + * + * <p>Note that this does NOT disable all rotary scroll feedback; it just disables the logic + * implemented within the View class. The child implementation of the View may implement its own + * rotary scroll feedback logic or use {@link ScrollFeedbackProvider} to generate rotary scroll + * feedback. + */ + void disableRotaryScrollFeedback() { + // Force set PFLAG4_ROTARY_HAPTICS_DETERMINED to avoid recalculating + // PFLAG4_ROTARY_HAPTICS_ENABLED under any circumstance. + mPrivateFlags4 |= PFLAG4_ROTARY_HAPTICS_DETERMINED; + mPrivateFlags4 &= ~PFLAG4_ROTARY_HAPTICS_ENABLED; + } + + /** * This is called in response to an internal scroll in this view (i.e., the * view scrolled its own contents). This is typically as a result of * {@link #scrollBy(int, int)} or {@link #scrollTo(int, int)} having been diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig index 658aa29a3cc6..b180e58cbe49 100644 --- a/core/java/android/view/flags/scroll_feedback_flags.aconfig +++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig @@ -23,3 +23,10 @@ flag { bug: "331830899" is_fixed_read_only: true } + +flag { + namespace: "wear_frameworks" + name: "dynamic_view_rotary_haptics_configuration" + description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics." + bug: "377998870 " +} diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 73f9d9fc23dc..6026e60e5b59 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2471,6 +2471,11 @@ public final class InputMethodManager { return; } + if (Flags.refactorInsetsController()) { + showSoftInput(rootView, statsToken, flags, resultReceiver, reason); + return; + } + ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread. diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java index ac6c19e79fcb..66cf9c7ec832 100644 --- a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java +++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java @@ -17,6 +17,7 @@ package android.view; import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED; +import static android.view.flags.Flags.FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION; import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS; import static android.view.HapticFeedbackConstants.SCROLL_LIMIT; import static android.view.HapticFeedbackConstants.SCROLL_TICK; @@ -74,12 +75,13 @@ public final class HapticScrollFeedbackProviderTest { mView = new TestView(InstrumentationRegistry.getContext()); mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig, - /* disabledIfViewPlaysScrollHaptics= */ true); + /* isFromView= */ false); mSetFlagsRule.disableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); } @Test public void testRotaryEncoder_noFeedbackWhenViewBasedFeedbackIsEnabled() { + mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION); when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) .thenReturn(true); setHapticScrollTickInterval(5); @@ -97,7 +99,24 @@ public final class HapticScrollFeedbackProviderTest { } @Test + public void testRotaryEncoder_dynamicViewRotaryFeedback_enabledEvenWhenViewFeedbackIsEnabled() { + mSetFlagsRule.enableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION); + when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) + .thenReturn(true); + setHapticScrollTickInterval(5); + mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig, + /* isFromView= */ false); + + mProvider.onScrollProgress( + INPUT_DEVICE_1, InputDevice.SOURCE_ROTARY_ENCODER, MotionEvent.AXIS_SCROLL, + /* deltaInPixels= */ 10); + + assertFeedbackCount(mView, SCROLL_TICK, 1); + } + + @Test public void testRotaryEncoder_inputDeviceCustomized_noFeedbackWhenViewBasedFeedbackIsEnabled() { + mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION); mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) @@ -119,7 +138,7 @@ public final class HapticScrollFeedbackProviderTest { @Test public void testRotaryEncoder_feedbackWhenDisregardingViewBasedScrollHaptics() { mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig, - /* disabledIfViewPlaysScrollHaptics= */ false); + /* isFromView= */ true); when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) .thenReturn(true); setHapticScrollTickInterval(5); @@ -144,7 +163,7 @@ public final class HapticScrollFeedbackProviderTest { List<HapticFeedbackRequest> requests = new ArrayList<>(); mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig, - /* disabledIfViewPlaysScrollHaptics= */ false); + /* isFromView= */ true); when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) .thenReturn(true); setHapticScrollTickInterval(5); @@ -917,19 +936,20 @@ public final class HapticScrollFeedbackProviderTest { @Test public void testNonRotaryInputFeedbackNotBlockedByRotaryUnavailability() { + mSetFlagsRule.disableFlags(FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION); when(mMockViewConfig.isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()) .thenReturn(true); setHapticScrollFeedbackEnabled(true); setHapticScrollTickInterval(5); mProvider = new HapticScrollFeedbackProvider(mView, mMockViewConfig, - /* disabledIfViewPlaysScrollHaptics= */ true); + /* isFromView= */ false); // Expect one feedback here. Touch input should provide feedback since scroll feedback has // been enabled via `setHapticScrollFeedbackEnabled(true)`. mProvider.onScrollProgress( INPUT_DEVICE_1, InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.AXIS_Y, /* deltaInPixels= */ 10); - // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is false and + // Because `isViewBasedRotaryEncoderHapticScrollFeedbackEnabled()` is true and // `disabledIfViewPlaysScrollHaptics` is true, the scroll progress from rotary encoders will // produce no feedback. mProvider.onScrollProgress( diff --git a/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java index 9a5c1c5112e6..b1a56373c9d6 100644 --- a/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java +++ b/core/tests/coretests/src/android/view/RotaryScrollHapticsTest.java @@ -30,8 +30,12 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import android.annotation.Nullable; import android.content.Context; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; +import android.view.flags.Flags; import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -39,6 +43,7 @@ import androidx.test.filters.SmallTest; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -48,6 +53,8 @@ import org.mockito.Mock; @RunWith(AndroidJUnit4.class) @Presubmit public final class RotaryScrollHapticsTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final int TEST_ROTARY_DEVICE_ID = 1; private static final int TEST_RANDOM_DEVICE_ID = 2; @@ -167,6 +174,26 @@ public final class RotaryScrollHapticsTest { } @Test + @EnableFlags(Flags.FLAG_DYNAMIC_VIEW_ROTARY_HAPTICS_CONFIGURATION) + public void testChildViewImplementationUsesScrollFeedbackProvider_doesNoScrollFeedback() { + mView.configureGenericMotion(/* result= */ false, /* scroll= */ true); + mView.mUsesCustomScrollFeedbackProvider = true; + + // Send multiple generic motion events, to catch bugs where the behavior is WAI only for the + // first dispatch, but buggy for future calls. + mView.dispatchGenericMotionEvent(createRotaryEvent(20)); + mView.dispatchGenericMotionEvent(createRotaryEvent(10)); + mView.dispatchGenericMotionEvent(createRotaryEvent(30)); + + // Verify that the base View class's ScrollFeedbackProvider produces no scroll progress + // or limit events, because there's a custom ScrollFeedbackProvider used by the child + // View class implementation, which should hint the base View class to disable its own + // ScrollFeedbackProvider usage. + verifyNoScrollProgress(); + verifyNoScrollLimit(); + } + + @Test public void testScrollProgress_genericMotionEventCallbackReturningTrue_doesScrollProgress() { mView.configureGenericMotion(/* result= */ true, /* scroll= */ true); @@ -208,6 +235,9 @@ public final class RotaryScrollHapticsTest { private static final class TestGenericMotionEventControllingView extends View { private boolean mGenericMotionResult; private boolean mScrollOnGenericMotion; + private boolean mUsesCustomScrollFeedbackProvider = false; + + @Nullable private ScrollFeedbackProvider mCustomScrollFeedbackProvider; TestGenericMotionEventControllingView(Context context) { super(context); @@ -222,6 +252,19 @@ public final class RotaryScrollHapticsTest { public boolean onGenericMotionEvent(MotionEvent event) { if (mScrollOnGenericMotion) { scrollTo(100, 200); // scroll values random (not relevant for tests). + if (mUsesCustomScrollFeedbackProvider) { + // Mimic how a real child class of View would instantiate and use the + // ScrollFeedbackProvider API. + if (mCustomScrollFeedbackProvider == null) { + mCustomScrollFeedbackProvider = ScrollFeedbackProvider.createProvider(this); + } + float axisScrollValue = event.getAxisValue(AXIS_SCROLL); + mCustomScrollFeedbackProvider.onScrollProgress( + event.getDeviceId(), + event.getSource(), + MotionEvent.AXIS_SCROLL, + (int) (axisScrollValue * TEST_SCALED_VERTICAL_SCROLL_FACTOR)); + } } return mGenericMotionResult; } diff --git a/data/sounds/Android.bp b/data/sounds/Android.bp new file mode 100644 index 000000000000..65d4872cdc16 --- /dev/null +++ b/data/sounds/Android.bp @@ -0,0 +1,304 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +phony { + name: "frameworks_sounds", + required: [ + "frameworks_alarm_sounds", + "frameworks_notifications_sounds", + "frameworks_ringtones_sounds", + "frameworks_ui_sounds", + "frameworks_ui_48k_sounds", + ], +} + +prebuilt_media { + name: "frameworks_alarm_sounds", + srcs: [ + "Alarm_Beep_01.ogg", + "Alarm_Beep_02.ogg", + "Alarm_Beep_03.ogg", + "Alarm_Buzzer.ogg", + "Alarm_Classic.ogg", + "Alarm_Rooster_02.ogg", + "alarms/ogg/Argon.ogg", + "alarms/ogg/Barium.ogg", + "alarms/ogg/Carbon.ogg", + "alarms/ogg/Helium.ogg", + "alarms/ogg/Krypton.ogg", + "alarms/ogg/Neon.ogg", + "alarms/ogg/Neptunium.ogg", + "alarms/ogg/Osmium.ogg", + "alarms/ogg/Oxygen.ogg", + "alarms/ogg/Platinum.ogg", + "alarms/ogg/Promethium.ogg", + "alarms/ogg/Scandium.ogg", + ], + relative_install_path: "audio/alarms", + product_specific: true, + no_full_install: true, +} + +prebuilt_media { + name: "frameworks_notifications_sounds", + srcs: [ + "notifications/ogg/Adara.ogg", + "notifications/Aldebaran.ogg", + "notifications/Altair.ogg", + "notifications/ogg/Alya.ogg", + "notifications/Antares.ogg", + "notifications/ogg/Antimony.ogg", + "notifications/ogg/Arcturus.ogg", + "notifications/ogg/Argon.ogg", + "notifications/Beat_Box_Android.ogg", + "notifications/ogg/Bellatrix.ogg", + "notifications/ogg/Beryllium.ogg", + "notifications/Betelgeuse.ogg", + "newwavelabs/CaffeineSnake.ogg", + "notifications/Canopus.ogg", + "notifications/ogg/Capella.ogg", + "notifications/Castor.ogg", + "notifications/ogg/CetiAlpha.ogg", + "notifications/ogg/Cobalt.ogg", + "notifications/Cricket.ogg", + "newwavelabs/DearDeer.ogg", + "notifications/Deneb.ogg", + "notifications/Doink.ogg", + "newwavelabs/DontPanic.ogg", + "notifications/Drip.ogg", + "notifications/Electra.ogg", + "F1_MissedCall.ogg", + "F1_New_MMS.ogg", + "F1_New_SMS.ogg", + "notifications/ogg/Fluorine.ogg", + "notifications/Fomalhaut.ogg", + "notifications/ogg/Gallium.ogg", + "notifications/Heaven.ogg", + "notifications/ogg/Helium.ogg", + "newwavelabs/Highwire.ogg", + "notifications/ogg/Hojus.ogg", + "notifications/ogg/Iridium.ogg", + "notifications/ogg/Krypton.ogg", + "newwavelabs/KzurbSonar.ogg", + "notifications/ogg/Lalande.ogg", + "notifications/Merope.ogg", + "notifications/ogg/Mira.ogg", + "newwavelabs/OnTheHunt.ogg", + "notifications/ogg/Palladium.ogg", + "notifications/Plastic_Pipe.ogg", + "notifications/ogg/Polaris.ogg", + "notifications/ogg/Pollux.ogg", + "notifications/ogg/Procyon.ogg", + "notifications/ogg/Proxima.ogg", + "notifications/ogg/Radon.ogg", + "notifications/ogg/Rubidium.ogg", + "notifications/ogg/Selenium.ogg", + "notifications/ogg/Shaula.ogg", + "notifications/Sirrah.ogg", + "notifications/SpaceSeed.ogg", + "notifications/ogg/Spica.ogg", + "notifications/ogg/Strontium.ogg", + "notifications/ogg/Syrma.ogg", + "notifications/TaDa.ogg", + "notifications/ogg/Talitha.ogg", + "notifications/ogg/Tejat.ogg", + "notifications/ogg/Thallium.ogg", + "notifications/Tinkerbell.ogg", + "notifications/ogg/Upsilon.ogg", + "notifications/ogg/Vega.ogg", + "newwavelabs/Voila.ogg", + "notifications/ogg/Xenon.ogg", + "notifications/ogg/Zirconium.ogg", + "notifications/arcturus.ogg", + "notifications/moonbeam.ogg", + "notifications/pixiedust.ogg", + "notifications/pizzicato.ogg", + "notifications/regulus.ogg", + "notifications/sirius.ogg", + "notifications/tweeters.ogg", + "notifications/vega.ogg", + ], + relative_install_path: "audio/notifications", + product_specific: true, + no_full_install: true, +} + +prebuilt_media { + name: "frameworks_ringtones_sounds", + srcs: [ + "ringtones/ANDROMEDA.ogg", + "ringtones/ogg/Andromeda.ogg", + "ringtones/ogg/Aquila.ogg", + "ringtones/ogg/ArgoNavis.ogg", + "ringtones/ogg/Atria.ogg", + "ringtones/BOOTES.ogg", + "newwavelabs/Backroad.ogg", + "newwavelabs/BeatPlucker.ogg", + "newwavelabs/BentleyDubs.ogg", + "newwavelabs/Big_Easy.ogg", + "newwavelabs/BirdLoop.ogg", + "newwavelabs/Bollywood.ogg", + "newwavelabs/BussaMove.ogg", + "ringtones/CANISMAJOR.ogg", + "ringtones/CASSIOPEIA.ogg", + "newwavelabs/Cairo.ogg", + "newwavelabs/Calypso_Steel.ogg", + "ringtones/ogg/CanisMajor.ogg", + "newwavelabs/CaribbeanIce.ogg", + "ringtones/ogg/Carina.ogg", + "ringtones/ogg/Centaurus.ogg", + "newwavelabs/Champagne_Edition.ogg", + "newwavelabs/Club_Cubano.ogg", + "newwavelabs/CrayonRock.ogg", + "newwavelabs/CrazyDream.ogg", + "newwavelabs/CurveBall.ogg", + "ringtones/ogg/Cygnus.ogg", + "newwavelabs/DancinFool.ogg", + "newwavelabs/Ding.ogg", + "newwavelabs/DonMessWivIt.ogg", + "ringtones/ogg/Draco.ogg", + "newwavelabs/DreamTheme.ogg", + "newwavelabs/Eastern_Sky.ogg", + "newwavelabs/Enter_the_Nexus.ogg", + "ringtones/Eridani.ogg", + "newwavelabs/EtherShake.ogg", + "ringtones/FreeFlight.ogg", + "newwavelabs/FriendlyGhost.ogg", + "newwavelabs/Funk_Yall.ogg", + "newwavelabs/GameOverGuitar.ogg", + "newwavelabs/Gimme_Mo_Town.ogg", + "ringtones/ogg/Girtab.ogg", + "newwavelabs/Glacial_Groove.ogg", + "newwavelabs/Growl.ogg", + "newwavelabs/HalfwayHome.ogg", + "ringtones/ogg/Hydra.ogg", + "newwavelabs/InsertCoin.ogg", + "ringtones/ogg/Kuma.ogg", + "newwavelabs/LoopyLounge.ogg", + "newwavelabs/LoveFlute.ogg", + "ringtones/Lyra.ogg", + "ringtones/ogg/Machina.ogg", + "newwavelabs/MidEvilJaunt.ogg", + "newwavelabs/MildlyAlarming.ogg", + "newwavelabs/Nairobi.ogg", + "newwavelabs/Nassau.ogg", + "newwavelabs/NewPlayer.ogg", + "newwavelabs/No_Limits.ogg", + "newwavelabs/Noises1.ogg", + "newwavelabs/Noises2.ogg", + "newwavelabs/Noises3.ogg", + "newwavelabs/OrganDub.ogg", + "ringtones/ogg/Orion.ogg", + "ringtones/PERSEUS.ogg", + "newwavelabs/Paradise_Island.ogg", + "ringtones/ogg/Pegasus.ogg", + "ringtones/ogg/Perseus.ogg", + "newwavelabs/Playa.ogg", + "ringtones/ogg/Pyxis.ogg", + "ringtones/ogg/Rasalas.ogg", + "newwavelabs/Revelation.ogg", + "ringtones/ogg/Rigel.ogg", + "Ring_Classic_02.ogg", + "Ring_Digital_02.ogg", + "Ring_Synth_02.ogg", + "Ring_Synth_04.ogg", + "newwavelabs/Road_Trip.ogg", + "newwavelabs/RomancingTheTone.ogg", + "newwavelabs/Safari.ogg", + "newwavelabs/Savannah.ogg", + "ringtones/ogg/Scarabaeus.ogg", + "ringtones/ogg/Sceptrum.ogg", + "newwavelabs/Seville.ogg", + "newwavelabs/Shes_All_That.ogg", + "newwavelabs/SilkyWay.ogg", + "newwavelabs/SitarVsSitar.ogg", + "ringtones/ogg/Solarium.ogg", + "newwavelabs/SpringyJalopy.ogg", + "newwavelabs/Steppin_Out.ogg", + "newwavelabs/Terminated.ogg", + "ringtones/Testudo.ogg", + "ringtones/ogg/Themos.ogg", + "newwavelabs/Third_Eye.ogg", + "newwavelabs/Thunderfoot.ogg", + "newwavelabs/TwirlAway.ogg", + "ringtones/URSAMINOR.ogg", + "ringtones/ogg/UrsaMinor.ogg", + "newwavelabs/VeryAlarmed.ogg", + "ringtones/Vespa.ogg", + "newwavelabs/World.ogg", + "ringtones/ogg/Zeta.ogg", + "ringtones/hydra.ogg", + ], + relative_install_path: "audio/ringtones", + product_specific: true, + no_full_install: true, +} + +prebuilt_media { + name: "frameworks_ui_48k_sounds", + srcs: [ + "effects/ogg/Effect_Tick_48k.ogg", + "effects/ogg/KeypressDelete_120_48k.ogg", + "effects/ogg/KeypressReturn_120_48k.ogg", + "effects/ogg/KeypressSpacebar_120_48k.ogg", + "effects/ogg/KeypressStandard_120_48k.ogg", + "effects/ogg/KeypressInvalid_120_48k.ogg", + "effects/ogg/Trusted_48k.ogg", + "effects/ogg/VideoRecord_48k.ogg", + "effects/ogg/VideoStop_48k.ogg", + "effects/ogg/camera_click_48k.ogg", + ], + dsts: [ + "Effect_Tick.ogg", + "KeypressDelete.ogg", + "KeypressReturn.ogg", + "KeypressSpacebar.ogg", + "KeypressStandard.ogg", + "KeypressInvalid.ogg", + "Trusted.ogg", + "VideoRecord.ogg", + "VideoStop.ogg", + "camera_click.ogg", + ], + relative_install_path: "audio/ui", + product_specific: true, + no_full_install: true, +} + +prebuilt_media { + name: "frameworks_ui_sounds", + srcs: [ + "effects/ogg/Dock.ogg", + "effects/ogg/Lock.ogg", + "effects/ogg/LowBattery.ogg", + "effects/ogg/Undock.ogg", + "effects/ogg/Unlock.ogg", + "effects/ogg/WirelessChargingStarted.ogg", + "effects/ogg/camera_focus.ogg", + "effects/ogg/ChargingStarted.ogg", + "effects/ogg/InCallNotification.ogg", + "effects/ogg/NFCFailure.ogg", + "effects/ogg/NFCInitiated.ogg", + "effects/ogg/NFCSuccess.ogg", + "effects/ogg/NFCTransferComplete.ogg", + "effects/ogg/NFCTransferInitiated.ogg", + ], + relative_install_path: "audio/ui", + product_specific: true, + no_full_install: true, +} diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientParcelableChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientParcelableChecker.java index cae5d8e6846d..35b2375fbc46 100644 --- a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientParcelableChecker.java +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientParcelableChecker.java @@ -96,7 +96,7 @@ public final class EfficientParcelableChecker extends BugChecker } if (WRITE_PARCELABLE.matches(tree, state)) { return buildDescription(tree) - .setMessage("Recommended to use 'item.writeToParcel()' to improve " + .setMessage("Recommended to use 'writeTypedObject()' to improve " + "efficiency; saves overhead of Parcelable class name") .build(); } diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index a23845fa17e9..c25f77b12116 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -107,6 +107,7 @@ package android.nfc { method public void onRfDiscoveryStarted(boolean); method public void onRfFieldActivated(boolean); method public void onRoutingChanged(); + method public void onRoutingTableFull(); method public void onStateUpdated(int); method public void onTagConnected(boolean); method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>); diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl index b102e873d737..fb793b024288 100644 --- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl +++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl @@ -52,5 +52,6 @@ interface INfcOemExtensionCallback { void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent); void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category); void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category); + void onRoutingTableFull(); void onLogEventNotified(in OemLogItems item); } diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index abd99bc02f55..57ee981caf9c 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -394,6 +394,13 @@ public final class NfcOemExtension { void onLaunchHceTapAgainDialog(@NonNull ApduServiceInfo service, @NonNull String category); /** + * Callback to indicate that routing table is full and the OEM can optionally launch a + * dialog to request the user to remove some Card Emulation apps from the device to free + * routing table space. + */ + void onRoutingTableFull(); + + /** * Callback when OEM specified log event are notified. * @param item the log items that contains log information of NFC event. */ @@ -853,6 +860,12 @@ public final class NfcOemExtension { handleVoidCallback(enabled, cb::onReaderOptionChanged, ex)); } + public void onRoutingTableFull() throws RemoteException { + mCallbackMap.forEach((cb, ex) -> + handleVoidCallback(null, + (Object input) -> cb.onRoutingTableFull(), ex)); + } + @Override public void onGetOemAppSearchIntent(List<String> packages, ResultReceiver intentConsumer) throws RemoteException { diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index c710aa35fa95..f03014ca95e2 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -821,9 +821,9 @@ <!-- Title of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=32] --> <string name="enable_linux_terminal_title">Linux development environment</string> - <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=64] --> - <string name="enable_linux_terminal_summary">Run Linux terminal on Android</string> - <!-- Disclaimer below the checkbox that disabling the Linux terminal app would clear its data. [CHAR LIMIT=64] --> + <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=none] --> + <string name="enable_linux_terminal_summary">(Experimental) Run Linux terminal on Android</string> + <!-- Disclaimer below the checkbox that disabling the Linux terminal app would clear its data. [CHAR LIMIT=none] --> <string name="disable_linux_terminal_disclaimer">If you disable, Linux terminal data will be cleared</string> <!-- HDCP checking title, used for debug purposes only. [CHAR LIMIT=25] --> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index fbce6ca07b3e..aca2c4ef2a49 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -16,6 +16,7 @@ package com.android.providers.settings; +import static android.provider.DeviceConfig.DUMP_ARG_NAMESPACE; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT; import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; @@ -42,6 +43,7 @@ import android.provider.DeviceConfigShellCommandHandler; import android.provider.Settings; import android.provider.Settings.Config.SyncDisabledMode; import android.provider.UpdatableDeviceConfigServiceReadiness; +import android.util.Log; import android.util.Slog; import com.android.internal.util.FastPrintWriter; @@ -55,11 +57,13 @@ import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * Receives shell commands from the command line related to device config flags, and dispatches them @@ -80,6 +84,7 @@ public final class DeviceConfigService extends Binder { final SettingsProvider mProvider; private static final String TAG = "DeviceConfigService"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public DeviceConfigService(SettingsProvider provider) { mProvider = provider; @@ -97,14 +102,35 @@ public final class DeviceConfigService extends Binder { } } + // TODO(b/364399200): add unit test @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + String filter = null; if (android.provider.flags.Flags.dumpImprovements()) { - pw.print("SyncDisabledForTests: "); - MyShellCommand.getSyncDisabledForTests(pw, pw); + if (args.length > 0) { + switch (args[0]) { + case DUMP_ARG_NAMESPACE: + if (args.length < 2) { + throw new IllegalArgumentException("argument " + DUMP_ARG_NAMESPACE + + " requires an extra argument"); + } + filter = args[1]; + if (DEBUG) { + Slog.d(TAG, "dump(): setting filter as " + filter); + } + break; + default: + Slog.w(TAG, "dump(): ignoring invalid arguments: " + Arrays.toString(args)); + break; + } + } + if (filter == null) { + pw.print("SyncDisabledForTests: "); + MyShellCommand.getSyncDisabledForTests(pw, pw); - pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): "); - pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): "); + pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + } pw.println("DeviceConfig provider: "); try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) { @@ -117,8 +143,16 @@ public final class DeviceConfigService extends Binder { IContentProvider iprovider = mProvider.getIContentProvider(); pw.println("DeviceConfig flags:"); + Pattern lineFilter = filter == null ? null : Pattern.compile("^.*" + filter + ".*\\/.*$"); for (String line : MyShellCommand.listAll(iprovider)) { - pw.println(line); + if (lineFilter == null || lineFilter.matcher(line).matches()) { + pw.println(line); + } + } + + if (filter != null) { + // TODO(b/364399200): use filter to skip instead? + return; } ArrayList<String> missingFiles = new ArrayList<String>(); diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt index 9392b1afffa3..96e99b15363d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt @@ -66,13 +66,13 @@ constructor( interactionHandler = interactionHandler, dialogFactory = dialogFactory, widgetSection = widgetSection, - modifier = Modifier.element(Communal.Elements.Grid) + modifier = Modifier.element(Communal.Elements.Grid), ) } with(lockSection) { LockIcon( overrideColor = MaterialTheme.colorScheme.onPrimaryContainer, - modifier = Modifier.element(Communal.Elements.LockIcon) + modifier = Modifier.element(Communal.Elements.LockIcon), ) } with(bottomAreaSection) { @@ -80,17 +80,13 @@ constructor( Modifier.element(Communal.Elements.IndicationArea).fillMaxWidth() ) } - } + }, ) { measurables, constraints -> val communalGridMeasurable = measurables[0] val lockIconMeasurable = measurables[1] val bottomAreaMeasurable = measurables[2] - val noMinConstraints = - constraints.copy( - minWidth = 0, - minHeight = 0, - ) + val noMinConstraints = constraints.copy(minWidth = 0, minHeight = 0) val lockIconPlaceable = lockIconMeasurable.measure(noMinConstraints) val lockIconBounds = @@ -109,14 +105,8 @@ constructor( ) layout(constraints.maxWidth, constraints.maxHeight) { - communalGridPlaceable.place( - x = 0, - y = 0, - ) - lockIconPlaceable.place( - x = lockIconBounds.left, - y = lockIconBounds.top, - ) + communalGridPlaceable.place(x = 0, y = 0) + lockIconPlaceable.place(x = lockIconBounds.left, y = lockIconBounds.top) bottomAreaPlaceable.place( x = 0, y = constraints.maxHeight - bottomAreaPlaceable.height, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 5e1ac1f30354..df1185cb1a6d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -807,7 +807,6 @@ private fun BoxScope.CommunalHubLazyGrid( ) { ResizeableItemFrameViewModel() } - if (viewModel.isEditMode && dragDropState != null) { val isItemDragging = dragDropState.draggingItemKey == item.key val outlineAlpha by diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt index 6e30575a684d..16002bc709fd 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt @@ -59,7 +59,14 @@ internal constructor( private val onAddWidget: (componentName: ComponentName, user: UserHandle, rank: Int) -> Unit, private val onDeleteWidget: (id: Int, componentName: ComponentName, rank: Int) -> Unit, private val onReorderWidgets: (widgetIdToRankMap: Map<Int, Int>) -> Unit, - private val onResizeWidget: (id: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) -> Unit, + private val onResizeWidget: + ( + id: Int, + spanY: Int, + widgetIdToRankMap: Map<Int, Int>, + componentName: ComponentName, + rank: Int, + ) -> Unit, ) { var list = communalContent.toMutableStateList() private set @@ -105,7 +112,9 @@ internal constructor( } else { emptyMap() } - onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap) + val componentName = item.componentName + val rank = item.rank + onResizeWidget(item.appWidgetId, newSpan, widgetIdToRankMap, componentName, rank) } /** diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt index 82918a569990..af2d7e45ee5f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt @@ -47,20 +47,14 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { @Test fun logAddWidget_componentNotLoggable_doNotLog() { - underTest.logAddWidget( - componentName = "com.green.package/my_test_widget", - rank = 1, - ) + underTest.logAddWidget(componentName = "com.green.package/my_test_widget", rank = 1) verify(statsLogProxy, never()) - .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) } @Test fun logAddWidget_componentLoggable_logAddEvent() { - underTest.logAddWidget( - componentName = "com.blue.package/my_test_widget", - rank = 1, - ) + underTest.logAddWidget(componentName = "com.blue.package/my_test_widget", rank = 1) verify(statsLogProxy) .writeCommunalHubWidgetEventReported( SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD, @@ -71,20 +65,14 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { @Test fun logRemoveWidget_componentNotLoggable_doNotLog() { - underTest.logRemoveWidget( - componentName = "com.yellow.package/my_test_widget", - rank = 2, - ) + underTest.logRemoveWidget(componentName = "com.yellow.package/my_test_widget", rank = 2) verify(statsLogProxy, never()) - .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) } @Test fun logRemoveWidget_componentLoggable_logRemoveEvent() { - underTest.logRemoveWidget( - componentName = "com.red.package/my_test_widget", - rank = 2, - ) + underTest.logRemoveWidget(componentName = "com.red.package/my_test_widget", rank = 2) verify(statsLogProxy) .writeCommunalHubWidgetEventReported( SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE, @@ -95,20 +83,14 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { @Test fun logTapWidget_componentNotLoggable_doNotLog() { - underTest.logTapWidget( - componentName = "com.yellow.package/my_test_widget", - rank = 2, - ) + underTest.logTapWidget(componentName = "com.yellow.package/my_test_widget", rank = 2) verify(statsLogProxy, never()) - .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) } @Test fun logTapWidget_componentLoggable_logRemoveEvent() { - underTest.logTapWidget( - componentName = "com.red.package/my_test_widget", - rank = 2, - ) + underTest.logTapWidget(componentName = "com.red.package/my_test_widget", rank = 2) verify(statsLogProxy) .writeCommunalHubWidgetEventReported( SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__TAP, @@ -140,4 +122,43 @@ class CommunalMetricsLoggerTest : SysuiTestCase() { ) assertThat(statsEvents).hasSize(1) } + + @Test + fun logResizeWidget_componentNotLoggable_doNotLog() { + underTest.logResizeWidget( + componentName = "com.green.package/my_test_widget", + rank = 1, + spanY = 2, + ) + verify(statsLogProxy, never()) + .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt(), anyInt()) + } + + @Test + fun logResizeWidget_componentLoggable_logResizeEvent() { + underTest.logResizeWidget( + componentName = "com.blue.package/my_test_widget", + rank = 1, + spanY = 2, + ) + verify(statsLogProxy) + .writeCommunalHubWidgetEventReported( + SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE, + "com.blue.package/my_test_widget", + rank = 1, + spanY = 2, + ) + } + + @Test + fun logResizeWidget_defaultSpanY_usesDefaultValue() { + underTest.logResizeWidget(componentName = "com.blue.package/my_test_widget", rank = 1) + verify(statsLogProxy) + .writeCommunalHubWidgetEventReported( + SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE, + "com.blue.package/my_test_widget", + rank = 1, + spanY = 0, + ) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt index cecc11e5ffd4..6b851cb017e1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt @@ -353,6 +353,32 @@ class CommunalEditModeViewModelTest : SysuiTestCase() { assertThat(event.contentDescription).isEqualTo("Test Clock widget added to lock screen") } + @Test + fun onResizeWidget_logsMetrics() = + testScope.runTest { + val appWidgetId = 123 + val spanY = 2 + val widgetIdToRankMap = mapOf(appWidgetId to 1) + val componentName = ComponentName("test.package", "TestWidget") + val rank = 1 + + underTest.onResizeWidget( + appWidgetId = appWidgetId, + spanY = spanY, + widgetIdToRankMap = widgetIdToRankMap, + componentName = componentName, + rank = rank, + ) + + verify(communalInteractor).resizeWidget(appWidgetId, spanY, widgetIdToRankMap) + verify(metricsLogger) + .logResizeWidget( + componentName = componentName.flattenToString(), + rank = rank, + spanY = spanY, + ) + } + private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) const val WIDGET_PICKER_PACKAGE_NAME = "widget_picker_package_name" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt index 03feceb7c15a..b72668bf5be6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.composefragment.viewmodel import android.app.StatusBarManager import android.content.testableContext +import android.graphics.Rect import android.testing.TestableLooper.RunWithLooper import androidx.compose.runtime.snapshots.Snapshot import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -43,6 +44,7 @@ import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository import com.android.systemui.statusbar.sysuiStatusBarStateController +import com.android.systemui.util.animation.DisappearParameters import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope @@ -375,6 +377,92 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } } + @Test + fun applyQsScrollPositionForClipping() = + with(kosmos) { + testScope.testWithinLifecycle { + val left = 1f + val top = 3f + val right = 5f + val bottom = 7f + + underTest.applyNewQsScrollerBounds(left, top, right, bottom) + + assertThat(qsMediaHost.currentClipping) + .isEqualTo(Rect(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())) + } + } + + @Test + fun shouldUpdateMediaSquishiness_inSplitShadeFalse_mediaSquishinessSet() = + with(kosmos) { + testScope.testWithinLifecycle { + underTest.isInSplitShade = false + underTest.squishinessFraction = 0.3f + + underTest.shouldUpdateSquishinessOnMedia = true + Snapshot.sendApplyNotifications() + runCurrent() + + assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(0.3f) + + underTest.shouldUpdateSquishinessOnMedia = false + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(0.01f).of(1f) + } + } + + @Test + fun inSplitShade_differentStatusBarState_mediaSquishinessSet() = + with(kosmos) { + testScope.testWithinLifecycle { + underTest.isInSplitShade = true + underTest.squishinessFraction = 0.3f + + sysuiStatusBarStateController.setState(StatusBarState.SHADE) + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(0.3f) + + sysuiStatusBarStateController.setState(StatusBarState.KEYGUARD) + runCurrent() + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f) + + sysuiStatusBarStateController.setState(StatusBarState.SHADE_LOCKED) + runCurrent() + Snapshot.sendApplyNotifications() + runCurrent() + assertThat(underTest.qsMediaHost.squishFraction).isWithin(epsilon).of(1f) + } + } + + @Test + fun disappearParams() = + with(kosmos) { + testScope.testWithinLifecycle { + setMediaState(ACTIVE_MEDIA) + + setConfigurationForMediaInRow(false) + Snapshot.sendApplyNotifications() + runCurrent() + + assertThat(underTest.qqsMediaHost.disappearParameters) + .isEqualTo(disappearParamsColumn) + assertThat(underTest.qsMediaHost.disappearParameters) + .isEqualTo(disappearParamsColumn) + + setConfigurationForMediaInRow(true) + Snapshot.sendApplyNotifications() + runCurrent() + + assertThat(underTest.qqsMediaHost.disappearParameters).isEqualTo(disappearParamsRow) + assertThat(underTest.qsMediaHost.disappearParameters).isEqualTo(disappearParamsRow) + } + } + private fun TestScope.setMediaState(state: MediaState) { with(kosmos) { val activeMedia = state == ACTIVE_MEDIA @@ -404,6 +492,26 @@ class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() } private const val epsilon = 0.001f + + private val disappearParamsColumn = + DisappearParameters().apply { + fadeStartPosition = 0.95f + disappearStart = 0f + disappearEnd = 0.95f + disappearSize.set(1f, 0f) + gonePivot.set(0f, 0f) + contentTranslationFraction.set(0f, 1f) + } + + private val disappearParamsRow = + DisappearParameters().apply { + fadeStartPosition = 0.95f + disappearStart = 0f + disappearEnd = 0.6f + disappearSize.set(0f, 0.4f) + gonePivot.set(1f, 0f) + contentTranslationFraction.set(0.25f, 1f) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 152911a30524..af0274b1caaa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.shared.logging.BouncerUiEvent import com.android.systemui.classifier.FalsingCollector @@ -54,6 +55,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus import com.android.systemui.flags.EnableSceneContainer @@ -114,6 +116,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent @@ -2356,6 +2359,58 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(isLockscreenEnabled).isTrue() } + @Test + fun replacesLockscreenSceneOnBackStack_whenUnlockdViaAlternateBouncer_fromShade() = + testScope.runTest { + val transitionState = + prepareState( + isDeviceUnlocked = false, + initialSceneKey = Scenes.Lockscreen, + authenticationMethod = AuthenticationMethodModel.Pin, + ) + underTest.start() + + val isUnlocked by + collectLastValue( + kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked } + ) + val currentScene by collectLastValue(sceneInteractor.currentScene) + val backStack by collectLastValue(sceneBackInteractor.backStack) + val isAlternateBouncerVisible by + collectLastValue(kosmos.alternateBouncerInteractor.isVisible) + assertThat(isUnlocked).isFalse() + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isAlternateBouncerVisible).isFalse() + + // Change to shade. + sceneInteractor.changeScene(Scenes.Shade, "") + transitionState.value = ObservableTransitionState.Idle(Scenes.Shade) + runCurrent() + assertThat(isUnlocked).isFalse() + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) + assertThat(isAlternateBouncerVisible).isFalse() + + // Show the alternate bouncer. + kosmos.alternateBouncerInteractor.forceShow() + kosmos.sysuiStatusBarStateController.leaveOpen = true // leave shade open + runCurrent() + assertThat(isUnlocked).isFalse() + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Lockscreen) + assertThat(isAlternateBouncerVisible).isTrue() + + // Trigger a fingerprint unlock. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(isUnlocked).isTrue() + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(backStack?.asIterable()?.first()).isEqualTo(Scenes.Gone) + assertThat(isAlternateBouncerVisible).isFalse() + } + private fun TestScope.emulateSceneTransition( transitionStateFlow: MutableStateFlow<ObservableTransitionState>, toScene: SceneKey, diff --git a/packages/SystemUI/res/layout/split_clock_view.xml b/packages/SystemUI/res/layout/split_clock_view.xml deleted file mode 100644 index 8198f0333a94..000000000000 --- a/packages/SystemUI/res/layout/split_clock_view.xml +++ /dev/null @@ -1,54 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2014 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 - --> - -<!-- Extends LinearLayout --> -<com.android.systemui.statusbar.policy.SplitClockView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - > - <TextClock - android:id="@+id/time_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" - android:textSize="@dimen/qs_time_collapsed_size" - android:textColor="?android:attr/textColorPrimary" - /> - <TextClock - android:id="@+id/am_pm_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" - android:textSize="@dimen/qs_time_collapsed_size" - android:textColor="?android:attr/textColorPrimary" - android:importantForAccessibility="no" - /> - - <!-- Empty text view so we have the same height when expanded/collapsed--> - <TextView - android:id="@+id/empty_time_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:visibility="gone" - android:singleLine="true" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" - android:textColor="?android:attr/textColorPrimary" - /> -</com.android.systemui.statusbar.policy.SplitClockView> diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt index 7cfad60b84cd..6423f8f7783b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt @@ -30,6 +30,7 @@ constructor( @Named(LOGGABLE_PREFIXES) private val loggablePrefixes: List<String>, private val statsLogProxy: StatsLogProxy, ) { + /** Logs an add widget event for metrics. No-op if widget is not loggable. */ fun logAddWidget(componentName: String, rank: Int?) { if (!componentName.isLoggable()) { @@ -69,11 +70,21 @@ constructor( ) } + fun logResizeWidget(componentName: String, rank: Int, spanY: Int = 0) { + if (!componentName.isLoggable()) { + return + } + + statsLogProxy.writeCommunalHubWidgetEventReported( + SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__RESIZE, + componentName, + rank = rank, + spanY = spanY, + ) + } + /** Logs loggable widgets and the total widget count as a [StatsEvent]. */ - fun logWidgetsSnapshot( - statsEvents: MutableList<StatsEvent>, - componentNames: List<String>, - ) { + fun logWidgetsSnapshot(statsEvents: MutableList<StatsEvent>, componentNames: List<String>) { val loggableComponentNames = componentNames.filter { it.isLoggable() }.toTypedArray() statsEvents.add( statsLogProxy.buildCommunalHubSnapshotStatsEvent( @@ -95,6 +106,7 @@ constructor( action: Int, componentName: String, rank: Int, + spanY: Int = 0, ) /** Builds a [SysUiStatsLog.COMMUNAL_HUB_SNAPSHOT] stats event. */ @@ -112,12 +124,14 @@ class CommunalStatsLogProxyImpl @Inject constructor() : CommunalMetricsLogger.St action: Int, componentName: String, rank: Int, + spanY: Int, ) { SysUiStatsLog.write( SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED, action, componentName, rank, + spanY, ) } diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt index e25ea4cbfb25..4e0e11226faa 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt @@ -178,7 +178,13 @@ abstract class BaseCommunalViewModel( * alongside the resize, in case resizing also requires re-ordering. This ensures the * re-ordering is done in the same database transaction as the resize. */ - open fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {} + open fun onResizeWidget( + appWidgetId: Int, + spanY: Int, + widgetIdToRankMap: Map<Int, Int>, + componentName: ComponentName, + rank: Int, + ) {} /** Called as the UI requests opening the widget editor with an optional preselected widget. */ open fun onOpenWidgetEditor(shouldOpenWidgetPickerOnStart: Boolean = false) {} diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt index 6508e4b574a3..ccff23003aa0 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt @@ -141,8 +141,19 @@ constructor( override fun onReorderWidgets(widgetIdToRankMap: Map<Int, Int>) = communalInteractor.updateWidgetOrder(widgetIdToRankMap) - override fun onResizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) { + override fun onResizeWidget( + appWidgetId: Int, + spanY: Int, + widgetIdToRankMap: Map<Int, Int>, + componentName: ComponentName, + rank: Int, + ) { communalInteractor.resizeWidget(appWidgetId, spanY, widgetIdToRankMap) + metricsLogger.logResizeWidget( + componentName = componentName.flattenToString(), + rank = rank, + spanY = spanY, + ) } override fun onReorderWidgetStart() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 51ff598c580b..ec6a17b24989 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -70,6 +70,7 @@ import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.layout.positionOnScreen import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -124,6 +125,7 @@ import com.android.systemui.qs.ui.composable.QuickSettingsShade import com.android.systemui.qs.ui.composable.QuickSettingsTheme import com.android.systemui.res.R import com.android.systemui.util.LifecycleFragment +import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.asIndenting import com.android.systemui.util.printSection import com.android.systemui.util.println @@ -447,8 +449,7 @@ constructor( } override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) { - super.setShouldUpdateSquishinessOnMedia(shouldUpdate) - // TODO (b/353253280) + viewModel.shouldUpdateSquishinessOnMedia = shouldUpdate } override fun setInSplitShade(isInSplitShade: Boolean) { @@ -660,7 +661,20 @@ constructor( Column( modifier = - Modifier.offset { + Modifier.onPlaced { coordinates -> + val positionOnScreen = coordinates.positionOnScreen() + val left = positionOnScreen.x + val right = left + coordinates.size.width + val top = positionOnScreen.y + val bottom = top + coordinates.size.height + viewModel.applyNewQsScrollerBounds( + left = left, + top = top, + right = right, + bottom = bottom, + ) + } + .offset { IntOffset( x = 0, y = viewModel.qsScrollTranslationY.fastRoundToInt(), @@ -704,7 +718,10 @@ constructor( val Media = @Composable { if (viewModel.qsMediaVisible) { - MediaObject(mediaHost = viewModel.qsMediaHost) + MediaObject( + mediaHost = viewModel.qsMediaHost, + update = { translationY = viewModel.qsMediaTranslationY }, + ) } } Box( @@ -987,7 +1004,11 @@ private fun Modifier.gesturesDisabled(disabled: Boolean) = } @Composable -private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) { +private fun MediaObject( + mediaHost: MediaHost, + modifier: Modifier = Modifier, + update: UniqueObjectHostView.() -> Unit = {}, +) { Box { AndroidView( modifier = modifier, @@ -1000,6 +1021,7 @@ private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) { ) } }, + update = { view -> view.update() }, onReset = {}, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt index e912a0c7faa6..9029563b6321 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.lifecycle.LifecycleCoroutineScope +import com.android.app.animation.Interpolators import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.BouncerPanelExpansionCalculator import com.android.systemui.Dumpable @@ -270,6 +271,23 @@ constructor( val qsMediaInRow: Boolean get() = qsMediaInRowViewModel.shouldMediaShowInRow + var shouldUpdateSquishinessOnMedia by mutableStateOf(false) + + val qsMediaTranslationY by derivedStateOf { + if ( + qsExpansion > 0f && + !isKeyguardState && + !qqsMediaVisible && + !qsMediaInRow && + !isInSplitShade + ) { + val interpolation = Interpolators.ACCELERATE.getInterpolation(1f - qsExpansion) + -qsMediaHost.hostView.height * 1.3f * interpolation + } else { + 0f + } + } + val animateTilesExpansion: Boolean get() = inFirstPage && !mediaSuddenlyAppearingInLandscape @@ -297,6 +315,18 @@ constructor( MediaHostState.EXPANDED } + private val shouldApplySquishinessToMedia by derivedStateOf { + shouldUpdateSquishinessOnMedia || (isInSplitShade && statusBarState == StatusBarState.SHADE) + } + + private val mediaSquishiness by derivedStateOf { + if (shouldApplySquishinessToMedia) { + squishinessFraction + } else { + 1f + } + } + private var qsBounds by mutableStateOf(Rect()) private val constrainedSquishinessFraction: Float @@ -355,8 +385,6 @@ constructor( private val isOverscrolling: Boolean get() = overScrollAmount != 0 - private var shouldUpdateMediaSquishiness by mutableStateOf(false) - private val forceQs by derivedStateOf { (isQsExpanded || isStackScrollerOverscrolling) && (isKeyguardState && !showCollapsedOnKeyguard) @@ -394,11 +422,26 @@ constructor( ), ) + fun applyNewQsScrollerBounds(left: Float, top: Float, right: Float, bottom: Float) { + if (usingMedia) { + qsMediaHost.currentClipping.set( + left.toInt(), + top.toInt(), + right.toInt(), + bottom.toInt(), + ) + } + } + override suspend fun onActivated(): Nothing { initMediaHosts() // init regardless of using media (same as current QS). coroutineScope { launch { hydrateSquishinessInteractor() } - launch { hydrateQqsMediaExpansion() } + if (usingMedia) { + launch { hydrateQqsMediaExpansion() } + launch { hydrateMediaSquishiness() } + launch { hydrateMediaDisappearParameters() } + } launch { hydrator.activate() } launch { containerViewModel.activate() } launch { qqsMediaInRowViewModel.activate() } @@ -429,6 +472,21 @@ constructor( snapshotFlow { qqsMediaExpansion }.collect { qqsMediaHost.expansion = it } } + private suspend fun hydrateMediaSquishiness() { + snapshotFlow { mediaSquishiness }.collect { qsMediaHost.squishFraction = it } + } + + private suspend fun hydrateMediaDisappearParameters() { + coroutineScope { + launch { + snapshotFlow { qqsMediaInRow }.collect { qqsMediaHost.applyDisappearParameters(it) } + } + launch { + snapshotFlow { qsMediaInRow }.collect { qsMediaHost.applyDisappearParameters(it) } + } + } + } + override fun dump(pw: PrintWriter, args: Array<out String>) { pw.asIndenting().run { printSection("Quick Settings state") { @@ -474,6 +532,9 @@ constructor( println("qsMediaInRow", qsMediaInRow) println("collapsedLandscapeMedia", collapsedLandscapeMedia) println("qqsMediaExpansion", qqsMediaExpansion) + println("shouldUpdateSquishinessOnMedia", shouldUpdateSquishinessOnMedia) + println("mediaSquishiness", mediaSquishiness) + println("qsMediaTranslationY", qsMediaTranslationY) } } } @@ -510,3 +571,22 @@ private fun mediaHostVisible(mediaHost: MediaHost): Flow<Boolean> { // lazily. .onStart { emit(mediaHost.visible) } } + +// Taken from QSPanelControllerBase +private fun MediaHost.applyDisappearParameters(inRow: Boolean) { + disappearParameters.apply { + fadeStartPosition = 0.95f + disappearStart = 0f + if (inRow) { + disappearSize.set(0f, 0.4f) + gonePivot.set(1f, 0f) + contentTranslationFraction.set(0.25f, 1f) + disappearEnd = 0.6f + } else { + disappearSize.set(1f, 0f) + gonePivot.set(0f, 0f) + contentTranslationFraction.set(0f, 1f) + disappearEnd = 0.95f + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index 7ff43c686d7b..fe59c4d36edc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -130,15 +130,7 @@ fun Tile( // TODO(b/361789146): Draw the shapes instead of clipping val tileShape = TileDefaults.animateTileShape(uiState.state) - val animatedColor by - animateColorAsState( - if (iconOnly || !uiState.handlesSecondaryClick) { - colors.iconBackground - } else { - colors.background - }, - label = "QSTileBackgroundColor", - ) + val animatedColor by animateColorAsState(colors.background, label = "QSTileBackgroundColor") TileExpandable( color = { animatedColor }, diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 580a51a3dc0a..daeaaa52fd94 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -373,6 +373,7 @@ constructor( "device was unlocked with alternate bouncer showing" + " and shade didn't need to be left open" } else { + replaceLockscreenSceneOnBackStack() null } } @@ -391,16 +392,7 @@ constructor( val prevScene = previousScene.value val targetScene = prevScene ?: Scenes.Gone if (targetScene != Scenes.Gone) { - sceneBackInteractor.updateBackStack { stack -> - val list = stack.asIterable().toMutableList() - check(list.last() == Scenes.Lockscreen) { - "The bottommost/last SceneKey of the back stack isn't" + - " the Lockscreen scene like expected. The back" + - " stack is $stack." - } - list[list.size - 1] = Scenes.Gone - sceneStackOf(*list.toTypedArray()) - } + replaceLockscreenSceneOnBackStack() } targetScene to "device was unlocked with primary bouncer showing," + @@ -435,6 +427,20 @@ constructor( } } + /** If the [Scenes.Lockscreen] is on the backstack, replaces it with [Scenes.Gone]. */ + private fun replaceLockscreenSceneOnBackStack() { + sceneBackInteractor.updateBackStack { stack -> + val list = stack.asIterable().toMutableList() + check(list.last() == Scenes.Lockscreen) { + "The bottommost/last SceneKey of the back stack isn't" + + " the Lockscreen scene like expected. The back" + + " stack is $stack." + } + list[list.size - 1] = Scenes.Gone + sceneStackOf(*list.toTypedArray()) + } + } + private fun handlePowerState() { applicationScope.launch { powerInteractor.detailedWakefulness.collect { wakefulness -> diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 24dba59a1d54..4d77e3ecea7b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -17,8 +17,6 @@ package com.android.systemui.shade; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE; import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; @@ -27,16 +25,13 @@ import android.app.IActivityManager; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; -import android.os.Binder; import android.os.Build; import android.os.RemoteException; import android.os.Trace; import android.util.Log; import android.view.Display; -import android.view.Gravity; import android.view.IWindow; import android.view.IWindowSession; import android.view.View; @@ -271,33 +266,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW // Now that the notification shade encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. - mLp = new LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - LayoutParams.TYPE_NOTIFICATION_SHADE, - LayoutParams.FLAG_NOT_FOCUSABLE - | LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING - | LayoutParams.FLAG_SPLIT_TOUCH - | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, - PixelFormat.TRANSLUCENT); - mLp.token = new Binder(); - mLp.gravity = Gravity.TOP; - mLp.setFitInsetsTypes(0 /* types */); - mLp.setTitle("NotificationShade"); - mLp.packageName = mContext.getPackageName(); - mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mLp.privateFlags |= PRIVATE_FLAG_OPTIMIZE_MEASURE; - - if (SceneContainerFlag.isEnabled()) { - // This prevents the appearance and disappearance of the software keyboard (also known - // as the "IME") from scrolling/panning the window to make room for the keyboard. - // - // The scene container logic does its own adjustment and animation when the IME appears - // or disappears. - mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING; - } - + mLp = ShadeWindowLayoutParams.INSTANCE.create(mContext); mWindowManager.addView(mWindowRootView, mLp); // We use BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE here, however, there is special logic in diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt new file mode 100644 index 000000000000..6bb50f99b5e7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLayoutParams.kt @@ -0,0 +1,69 @@ +/* + * 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.shade + +import android.content.Context +import android.graphics.PixelFormat +import android.os.Binder +import android.view.Gravity +import android.view.ViewGroup +import android.view.WindowManager.LayoutParams +import com.android.systemui.scene.shared.flag.SceneContainerFlag + +object ShadeWindowLayoutParams { + /** + * Creates [LayoutParams] for the shade window. + * + * This is extracted to a single place as those layout params will be used by several places: + * - When sysui starts, and the shade is added the first time + * - When the shade moves to a different window (e.g. while an external display is connected) + */ + fun create(context: Context): LayoutParams { + return LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + LayoutParams.TYPE_NOTIFICATION_SHADE, + LayoutParams.FLAG_NOT_FOCUSABLE or + LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING or + LayoutParams.FLAG_SPLIT_TOUCH or + LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or + LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + // Now that the notification shade encompasses the sliding panel and its + // translucent backdrop, the entire thing is made TRANSLUCENT and is + // hardware-accelerated. + PixelFormat.TRANSLUCENT, + ) + .apply { + token = Binder() + gravity = Gravity.TOP + fitInsetsTypes = 0 + title = "NotificationShade" + packageName = context.packageName + layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS + privateFlags = privateFlags or LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE + if (SceneContainerFlag.isEnabled) { + // This prevents the appearance and disappearance of the software keyboard (also + // known as the "IME") from scrolling/panning the window to make room for the + // keyboard. + // + // The scene container logic does its own adjustment and animation when the IME + // appears or disappears. + softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index e74ed8d27ec2..c487ff5d35bd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -49,6 +49,7 @@ import android.os.SystemClock; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; +import android.util.Log; import android.view.ContentInfo; import androidx.annotation.NonNull; @@ -65,6 +66,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; import com.android.systemui.statusbar.notification.icon.IconPack; +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController; import com.android.systemui.statusbar.notification.row.NotificationGuts; @@ -194,6 +196,10 @@ public final class NotificationEntry extends ListEntry { */ private boolean mIsDemoted = false; + // TODO(b/377565433): Move into NotificationContentModel during/after + // NotificationRowContentBinderRefactor. + private PromotedNotificationContentModel mPromotedNotificationContentModel; + /** * True if both * 1) app provided full screen intent but does not have the permission to send it @@ -1061,6 +1067,32 @@ public final class NotificationEntry extends ListEntry { this.mHeadsUpStatusBarTextPublic.setValue(headsUpStatusBarModel.getPublicText()); } + /** + * Gets the content needed to render this notification as a promoted notification on various + * surfaces (like status bar chips and AOD). + */ + public PromotedNotificationContentModel getPromotedNotificationContentModel() { + if (PromotedNotificationContentModel.featureFlagEnabled()) { + return mPromotedNotificationContentModel; + } else { + Log.wtf(TAG, "getting promoted content without feature flag enabled"); + return null; + } + } + + /** + * Sets the content needed to render this notification as a promoted notification on various + * surfaces (like status bar chips and AOD). + */ + public void setPromotedNotificationContentModel( + @Nullable PromotedNotificationContentModel promotedNotificationContentModel) { + if (PromotedNotificationContentModel.featureFlagEnabled()) { + this.mPromotedNotificationContentModel = promotedNotificationContentModel; + } else { + Log.wtf(TAG, "setting promoted content without feature flag enabled"); + } + } + /** Information about a suggestion that is being edited. */ public static class EditedSuggestionInfo { @@ -1101,4 +1133,6 @@ public final class NotificationEntry extends ListEntry { private static final long INITIALIZATION_DELAY = 400; private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN; private static final int COLOR_INVALID = 1; + + private static final String TAG = "NotificationEntry"; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt index 23da90d426c7..8edbc5e8e4bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt @@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.provider.SectionSt import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel @@ -173,6 +174,7 @@ private class ActiveNotificationsStoreBuilder( isGroupSummary = sbn.notification.isGroupSummary, bucket = bucket, callType = sbn.toCallType(), + promotedContent = promotedNotificationContentModel, ) } } @@ -199,6 +201,7 @@ private fun ActiveNotificationsStore.createOrReuse( isGroupSummary: Boolean, bucket: Int, callType: CallType, + promotedContent: PromotedNotificationContentModel?, ): ActiveNotificationModel { return individuals[key]?.takeIf { it.isCurrent( @@ -223,6 +226,7 @@ private fun ActiveNotificationsStore.createOrReuse( contentIntent = contentIntent, bucket = bucket, callType = callType, + promotedContent = promotedContent, ) } ?: ActiveNotificationModel( @@ -247,6 +251,7 @@ private fun ActiveNotificationsStore.createOrReuse( contentIntent = contentIntent, bucket = bucket, callType = callType, + promotedContent = promotedContent, ) } @@ -272,6 +277,7 @@ private fun ActiveNotificationModel.isCurrent( isGroupSummary: Boolean, bucket: Int, callType: CallType, + promotedContent: PromotedNotificationContentModel?, ): Boolean { return when { key != this.key -> false @@ -295,6 +301,9 @@ private fun ActiveNotificationModel.isCurrent( contentIntent != this.contentIntent -> false bucket != this.bucket -> false callType != this.callType -> false + // QQQ: Do we need to do the same `isCurrent` thing within the content model to avoid + // recreating the active notification model constantly? + promotedContent != this.promotedContent -> false else -> true } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt new file mode 100644 index 000000000000..41ee3b992c5a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt @@ -0,0 +1,119 @@ +/* + * 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.statusbar.notification.promoted.shared.model + +import android.annotation.DrawableRes +import android.graphics.drawable.Icon +import com.android.internal.widget.NotificationProgressModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips +import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi + +/** + * The content needed to render a promoted notification to surfaces besides the notification stack, + * like the skeleton view on AOD or the status bar chip. + */ +data class PromotedNotificationContentModel( + val key: String, + + // for all styles: + val skeletonSmallIcon: Icon?, // TODO(b/377568176): Make into an IconModel. + val appName: CharSequence?, + val subText: CharSequence?, + val time: When?, + val lastAudiblyAlertedMs: Long, + @DrawableRes val profileBadgeResId: Int?, + val title: CharSequence?, + val text: CharSequence?, + val skeletonLargeIcon: Icon?, // TODO(b/377568176): Make into an IconModel. + val style: Style, + + // for CallStyle: + val personIcon: Icon?, // TODO(b/377568176): Make into an IconModel. + val personName: CharSequence?, + val verificationIcon: Icon?, // TODO(b/377568176): Make into an IconModel. + val verificationText: CharSequence?, + + // for ProgressStyle: + val progress: NotificationProgressModel?, +) { + class Builder(val key: String) { + var skeletonSmallIcon: Icon? = null + var appName: CharSequence? = null + var subText: CharSequence? = null + var time: When? = null + var lastAudiblyAlertedMs: Long = 0L + @DrawableRes var profileBadgeResId: Int? = null + var title: CharSequence? = null + var text: CharSequence? = null + var skeletonLargeIcon: Icon? = null + var style: Style = Style.Ineligible + + // for CallStyle: + var personIcon: Icon? = null + var personName: CharSequence? = null + var verificationIcon: Icon? = null + var verificationText: CharSequence? = null + + // for ProgressStyle: + var progress: NotificationProgressModel? = null + + fun build() = + PromotedNotificationContentModel( + key = key, + skeletonSmallIcon = skeletonSmallIcon, + appName = appName, + subText = subText, + time = time, + lastAudiblyAlertedMs = lastAudiblyAlertedMs, + profileBadgeResId = profileBadgeResId, + title = title, + text = text, + skeletonLargeIcon = skeletonLargeIcon, + style = style, + personIcon = personIcon, + personName = personName, + verificationIcon = verificationIcon, + verificationText = verificationText, + progress = progress, + ) + } + + /** The timestamp associated with a notification, along with the mode used to display it. */ + data class When(val time: Long, val mode: Mode) { + /** The mode used to display a notification's `when` value. */ + enum class Mode { + Absolute, + CountDown, + CountUp, + } + } + + /** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */ + enum class Style { + BigPicture, + BigText, + Call, + Progress, + Ineligible, + } + + companion object { + @JvmStatic + fun featureFlagEnabled(): Boolean = + PromotedNotificationUi.isEnabled || StatusBarNotifChips.isEnabled + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt index 19a92a2230ba..a2b71551eca8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.shared import android.app.PendingIntent import android.graphics.drawable.Icon +import android.util.Log import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.stack.PriorityBucket /** @@ -36,6 +38,7 @@ data class ActiveNotificationModel( val groupKey: String?, /** When this notification was posted. */ val whenTime: Long, + // TODO(b/377566661): Make isPromoted just check if promotedContent != null. /** True if this notification should be promoted and false otherwise. */ val isPromoted: Boolean, /** Is this entry in the ambient / minimized section (lowest priority)? */ @@ -78,7 +81,24 @@ data class ActiveNotificationModel( @PriorityBucket val bucket: Int, /** The call type set on the notification. */ val callType: CallType, -) : ActiveNotificationEntryModel() + /** + * The content needed to render this as a promoted notification on various surfaces, or null if + * this notification cannot be rendered as a promoted notification. + */ + val promotedContent: PromotedNotificationContentModel?, +) : ActiveNotificationEntryModel() { + init { + if (!PromotedNotificationContentModel.featureFlagEnabled()) { + if (promotedContent != null) { + Log.wtf(TAG, "passing non-null promoted content without feature flag enabled") + } + } + } + + companion object { + private const val TAG = "ActiveNotificationEntryModel" + } +} /** Model for a group of notifications. */ data class ActiveNotificationGroupModel( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0c511aeae3e5..aef26dea3c0d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -520,6 +520,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mListenForCanShowAlternateBouncer.cancel(null); } mListenForCanShowAlternateBouncer = null; + // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( mAlternateBouncerInteractor.getCanShowAlternateBouncer(), @@ -568,6 +569,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } private void consumeCanShowAlternateBouncer(boolean canShow) { + if (SceneContainerFlag.isEnabled()) { + // When the scene framework is enabled, the alternative bouncer is hidden from the scene + // framework logic so there's no need for this logic here. + return; + } + // Hack: this is required to fix issues where // KeyguardBouncerRepository#alternateBouncerVisible state is incorrectly set and then never // reset. This is caused by usages of show()/forceShow() that only read this flow to set the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java deleted file mode 100644 index 0d36b48e9099..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.policy; - -import android.app.ActivityManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.UserHandle; -import android.text.format.DateFormat; -import android.util.AttributeSet; -import android.widget.LinearLayout; -import android.widget.TextClock; - -import com.android.systemui.res.R; - -/** - * Container for a clock which has two separate views for the clock itself and AM/PM indicator. This - * is used to scale the clock independently of AM/PM. - */ -public class SplitClockView extends LinearLayout { - - private TextClock mTimeView; - private TextClock mAmPmView; - - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (Intent.ACTION_TIME_CHANGED.equals(action) - || Intent.ACTION_TIMEZONE_CHANGED.equals(action) - || Intent.ACTION_LOCALE_CHANGED.equals(action) - || Intent.ACTION_CONFIGURATION_CHANGED.equals(action) - || Intent.ACTION_USER_SWITCHED.equals(action)) { - updatePatterns(); - } - } - }; - - public SplitClockView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mTimeView = findViewById(R.id.time_view); - mAmPmView = findViewById(R.id.am_pm_view); - mTimeView.setShowCurrentUserTime(true); - mAmPmView.setShowCurrentUserTime(true); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_TIME_CHANGED); - filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); - filter.addAction(Intent.ACTION_LOCALE_CHANGED); - filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - filter.addAction(Intent.ACTION_USER_SWITCHED); - getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); - - updatePatterns(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getContext().unregisterReceiver(mIntentReceiver); - } - - private void updatePatterns() { - String formatString = DateFormat.getTimeFormatString(getContext(), - ActivityManager.getCurrentUser()); - int index = getAmPmPartEndIndex(formatString); - String timeString; - String amPmString; - if (index == -1) { - timeString = formatString; - amPmString = ""; - } else { - timeString = formatString.substring(0, index); - amPmString = formatString.substring(index); - } - mTimeView.setFormat12Hour(timeString); - mTimeView.setFormat24Hour(timeString); - mTimeView.setContentDescriptionFormat12Hour(formatString); - mTimeView.setContentDescriptionFormat24Hour(formatString); - mAmPmView.setFormat12Hour(amPmString); - mAmPmView.setFormat24Hour(amPmString); - } - - /** - * @return the index where the AM/PM part starts at the end in {@code formatString} including - * leading white spaces or {@code -1} if no AM/PM part is found or {@code formatString} - * doesn't end with AM/PM part - */ - private static int getAmPmPartEndIndex(String formatString) { - boolean hasAmPm = false; - int length = formatString.length(); - for (int i = length - 1; i >= 0; i--) { - char c = formatString.charAt(i); - boolean isAmPm = c == 'a'; - boolean isWhitespace = Character.isWhitespace(c); - if (isAmPm) { - hasAmPm = true; - } - if (isAmPm || isWhitespace) { - continue; - } - if (i == length - 1) { - - // First character was not AM/PM and not whitespace, so it's not ending with AM/PM. - return -1; - } else { - - // If we have AM/PM at all, return last index, or -1 to indicate that it's not - // ending with AM/PM. - return hasAmPm ? i + 1 : -1; - } - } - - // Only AM/PM and whitespaces? The whole string is AM/PM. Else: Only whitespaces in the - // string. - return hasAmPm ? 0 : -1; - } - -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt index 32c582f79ed7..2ec801620212 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.data.model import android.app.PendingIntent import android.graphics.drawable.Icon import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.notification.shared.CallType import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN @@ -46,6 +47,7 @@ fun activeNotificationModel( contentIntent: PendingIntent? = null, bucket: Int = BUCKET_UNKNOWN, callType: CallType = CallType.None, + promotedContent: PromotedNotificationContentModel? = null, ) = ActiveNotificationModel( key = key, @@ -69,4 +71,5 @@ fun activeNotificationModel( isGroupSummary = isGroupSummary, bucket = bucket, callType = callType, + promotedContent = promotedContent, ) diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java index 44ae1d1fbbbf..81e83b563945 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java @@ -16,7 +16,6 @@ package com.android.server.appfunctions; -import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -24,15 +23,21 @@ import java.util.concurrent.TimeUnit; /** Executors for App function operations. */ public final class AppFunctionExecutors { + static final int sConcurrency = Runtime.getRuntime().availableProcessors(); + /** Executor for operations that do not need to block. */ - public static final Executor THREAD_POOL_EXECUTOR = + public static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( - /* corePoolSize= */ Runtime.getRuntime().availableProcessors(), - /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(), - /* keepAliveTime= */ 0L, + /* corePoolSize= */ sConcurrency, + /* maxConcurrency= */ sConcurrency, + /* keepAliveTime= */ 1L, /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>(), new NamedThreadFactory("AppFunctionExecutors")); + static { + THREAD_POOL_EXECUTOR.allowCoreThreadTimeOut(true); + } + private AppFunctionExecutors() {} } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index 8567ccb9e7a5..b221d74e2d86 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -187,11 +187,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Simple flag to enable/disable debug logging. private static final boolean DEBUG = Build.IS_DEBUGGABLE; - // String constants for XML schema migration related to changes in keyguard package. - private static final String OLD_KEYGUARD_HOST_PACKAGE = "android"; - private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard"; - private static final int KEYGUARD_HOST_ID = 0x4b455947; - // Filename for app widgets state persisted on disk. private static final String STATE_FILENAME = "appwidgets.xml"; @@ -211,6 +206,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private static final int UNKNOWN_USER_ID = -10; // Version of XML schema for app widgets. Bump if the stored widgets need to be upgraded. + // Version 1 introduced in 2014 - Android 5.0 private static final int CURRENT_VERSION = 1; // Every widget update request is associated which an increasing sequence number. This is @@ -4428,19 +4424,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku int version = fromVersion; - // Update 1: keyguard moved from package "android" to "com.android.keyguard" + // Update 1: From version 0 to 1, was used from Android 4 to Android 5. It updated the + // location of the keyguard widget database. No modern device will have db version 0. if (version == 0) { - HostId oldHostId = new HostId(Process.myUid(), - KEYGUARD_HOST_ID, OLD_KEYGUARD_HOST_PACKAGE); - - Host host = lookupHostLocked(oldHostId); - if (host != null) { - final int uid = getUidForPackage(NEW_KEYGUARD_HOST_PACKAGE, - UserHandle.USER_SYSTEM); - if (uid >= 0) { - host.id = new HostId(uid, KEYGUARD_HOST_ID, NEW_KEYGUARD_HOST_PACKAGE); - } - } + Slog.e(TAG, "Found widget database with version 0, this should not be possible," + + " forcing upgrade to version 1"); version = 1; } @@ -4450,24 +4438,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - private static File getStateFile(int userId) { - return new File(Environment.getUserSystemDirectory(userId), STATE_FILENAME); - } - private static AtomicFile getSavedStateFile(int userId) { - File dir = Environment.getUserSystemDirectory(userId); - File settingsFile = getStateFile(userId); - if (!settingsFile.exists() && userId == UserHandle.USER_SYSTEM) { - if (!dir.exists()) { - dir.mkdirs(); - } - // Migrate old data - File oldFile = new File("/data/system/" + STATE_FILENAME); - // Method doesn't throw an exception on failure. Ignore any errors - // in moving the file (like non-existence) - oldFile.renameTo(settingsFile); - } - return new AtomicFile(settingsFile); + return new AtomicFile(new File(Environment.getUserSystemDirectory(userId), STATE_FILENAME)); } void onUserStopped(int userId) { diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index bb4ae96da53b..a132876b72a3 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -46,7 +46,6 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; -import com.android.server.integrity.model.RuleMetadata; import java.io.File; import java.nio.charset.StandardCharsets; diff --git a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java deleted file mode 100644 index b0647fc46473..000000000000 --- a/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.model; - -import android.annotation.Nullable; -import android.content.integrity.Rule; - -import java.util.Collections; -import java.util.List; - -/** - * A class encapsulating the result from the evaluation engine after evaluating rules against app - * install metadata. - * - * <p>It contains the outcome effect (whether to allow or block the install), and the rule causing - * that effect. - */ -public final class IntegrityCheckResult { - - public enum Effect { - ALLOW, - DENY - } - - private final Effect mEffect; - private final List<Rule> mRuleList; - - private IntegrityCheckResult(Effect effect, @Nullable List<Rule> ruleList) { - this.mEffect = effect; - this.mRuleList = ruleList; - } - - public Effect getEffect() { - return mEffect; - } - - public List<Rule> getMatchedRules() { - return mRuleList; - } - - /** - * Create an ALLOW evaluation outcome. - * - * @return An evaluation outcome with ALLOW effect and no rule. - */ - public static IntegrityCheckResult allow() { - return new IntegrityCheckResult(Effect.ALLOW, Collections.emptyList()); - } - - /** - * Create an ALLOW evaluation outcome. - * - * @return An evaluation outcome with ALLOW effect and rule causing that effect. - */ - public static IntegrityCheckResult allow(List<Rule> ruleList) { - return new IntegrityCheckResult(Effect.ALLOW, ruleList); - } - - /** - * Create a DENY evaluation outcome. - * - * @param ruleList All valid rules that cause the DENY effect. - * @return An evaluation outcome with DENY effect and rule causing that effect. - */ - public static IntegrityCheckResult deny(List<Rule> ruleList) { - return new IntegrityCheckResult(Effect.DENY, ruleList); - } - - /** Returns true when the {@code mEffect} is caused by an app certificate mismatch. */ - public boolean isCausedByAppCertRule() { - return mRuleList.stream().anyMatch(rule -> rule.getFormula().isAppCertificateFormula()); - } - - /** Returns true when the {@code mEffect} is caused by an installer rule. */ - public boolean isCausedByInstallerRule() { - return mRuleList.stream().anyMatch(rule -> rule.getFormula().isInstallerFormula()); - } - -} diff --git a/services/core/java/com/android/server/integrity/model/RuleMetadata.java b/services/core/java/com/android/server/integrity/model/RuleMetadata.java deleted file mode 100644 index 6b582ae7b5f2..000000000000 --- a/services/core/java/com/android/server/integrity/model/RuleMetadata.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019 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.integrity.model; - -import android.annotation.Nullable; - -/** Data class containing relevant metadata associated with a rule set. */ -public class RuleMetadata { - - private final String mRuleProvider; - private final String mVersion; - - public RuleMetadata(String ruleProvider, String version) { - mRuleProvider = ruleProvider; - mVersion = version; - } - - @Nullable - public String getRuleProvider() { - return mRuleProvider; - } - - @Nullable - public String getVersion() { - return mVersion; - } -} diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 7469c9293e22..09feb18d07bf 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -36,6 +36,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; +import android.health.connect.HealthPermissions; import android.media.RingtoneManager; import android.media.midi.MidiManager; import android.net.Uri; @@ -48,6 +49,7 @@ import android.os.Process; import android.os.UserHandle; import android.os.storage.StorageManager; import android.permission.PermissionManager; +import android.permission.flags.Flags; import android.print.PrintManager; import android.provider.CalendarContract; import android.provider.ContactsContract; @@ -214,8 +216,13 @@ final class DefaultPermissionGrantPolicy { private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>(); static { - SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS); - SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); + if (Flags.replaceBodySensorPermissionEnabled()) { + SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEART_RATE); + SENSORS_PERMISSIONS.add(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND); + } else { + SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS); + SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); + } } private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>(); diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index 93aa10b9112f..fd221185bacf 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -68,7 +68,6 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.R; import com.android.server.compat.PlatformCompat; -import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.testutils.TestUtils; import org.junit.After; diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java deleted file mode 100644 index d31ed689a7da..000000000000 --- a/services/tests/servicestests/src/com/android/server/integrity/model/IntegrityCheckResultTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2020 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.integrity.model; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.integrity.AtomicFormula; -import android.content.integrity.CompoundFormula; -import android.content.integrity.Rule; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import java.util.Arrays; -import java.util.Collections; - -@RunWith(JUnit4.class) -public class IntegrityCheckResultTest { - - @Test - public void createAllowResult() { - IntegrityCheckResult allowResult = IntegrityCheckResult.allow(); - - assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW); - assertThat(allowResult.getMatchedRules()).isEmpty(); - } - - @Test - public void createAllowResultWithRule() { - String packageName = "com.test.deny"; - Rule forceAllowRule = - new Rule( - new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, - packageName), - Rule.FORCE_ALLOW); - - IntegrityCheckResult allowResult = - IntegrityCheckResult.allow(Collections.singletonList(forceAllowRule)); - - assertThat(allowResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.ALLOW); - assertThat(allowResult.getMatchedRules()).containsExactly(forceAllowRule); - } - - @Test - public void createDenyResultWithRule() { - String packageName = "com.test.deny"; - Rule failedRule = - new Rule( - new AtomicFormula.StringAtomicFormula(AtomicFormula.PACKAGE_NAME, - packageName), - Rule.DENY); - - IntegrityCheckResult denyResult = - IntegrityCheckResult.deny(Collections.singletonList(failedRule)); - - assertThat(denyResult.getEffect()).isEqualTo(IntegrityCheckResult.Effect.DENY); - assertThat(denyResult.getMatchedRules()).containsExactly(failedRule); - } - - @Test - public void isDenyCausedByAppCertificate() { - String packageName = "com.test.deny"; - String appCert = "app-cert"; - Rule failedRule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, packageName), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.APP_CERTIFICATE, appCert))), - Rule.DENY); - Rule otherFailedRule = - new Rule( - new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, - AtomicFormula.EQ, 12), - Rule.DENY); - - IntegrityCheckResult denyResult = - IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule)); - - assertThat(denyResult.isCausedByAppCertRule()).isTrue(); - assertThat(denyResult.isCausedByInstallerRule()).isFalse(); - } - - @Test - public void isDenyCausedByInstaller() { - String packageName = "com.test.deny"; - String appCert = "app-cert"; - Rule failedRule = - new Rule( - new CompoundFormula( - CompoundFormula.AND, - Arrays.asList( - new AtomicFormula.StringAtomicFormula( - AtomicFormula.PACKAGE_NAME, packageName), - new AtomicFormula.StringAtomicFormula( - AtomicFormula.INSTALLER_CERTIFICATE, appCert))), - Rule.DENY); - Rule otherFailedRule = - new Rule( - new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE, - AtomicFormula.EQ, 12), - Rule.DENY); - - IntegrityCheckResult denyResult = - IntegrityCheckResult.deny(Arrays.asList(failedRule, otherFailedRule)); - - assertThat(denyResult.isCausedByAppCertRule()).isFalse(); - assertThat(denyResult.isCausedByInstallerRule()).isTrue(); - } -} |