diff options
100 files changed, 2640 insertions, 468 deletions
diff --git a/Android.bp b/Android.bp index 48f0928f24d7..54cb2684068d 100644 --- a/Android.bp +++ b/Android.bp @@ -107,7 +107,6 @@ filegroup { ":android.hardware.radio.data-V3-java-source", ":android.hardware.radio.network-V3-java-source", ":android.hardware.radio.voice-V3-java-source", - ":android.hardware.security.keymint-V3-java-source", ":android.hardware.security.secureclock-V1-java-source", ":android.hardware.thermal-V3-java-source", ":android.hardware.tv.tuner-V3-java-source", @@ -116,7 +115,6 @@ filegroup { ":android.security.legacykeystore-java-source", ":android.security.maintenance-java-source", ":android.security.metrics-java-source", - ":android.system.keystore2-V4-java-source", ":android.hardware.cas-V1-java-source", ":credstore_aidl", ":dumpstate_aidl", @@ -149,7 +147,16 @@ filegroup { ":statslog-framework-java-gen", // FrameworkStatsLog.java ":statslog-hwui-java-gen", // HwuiStatsLog.java ":audio_policy_configuration_V7_0", - ], + ] + select(release_flag("RELEASE_ATTEST_MODULES"), { + true: [ + ":android.hardware.security.keymint-V4-java-source", + ":android.system.keystore2-V5-java-source", + ], + default: [ + ":android.hardware.security.keymint-V3-java-source", + ":android.system.keystore2-V4-java-source", + ], + }), } java_library { @@ -398,6 +405,7 @@ java_defaults { "bouncycastle-repackaged-unbundled", "com.android.sysprop.foldlockbehavior", "com.android.sysprop.view", + "configinfra_framework_flags_java_lib", "framework-internal-utils", "dynamic_instrumentation_manager_aidl-java", // If MimeMap ever becomes its own APEX, then this dependency would need to be removed diff --git a/FF_LEADS_OWNERS b/FF_LEADS_OWNERS new file mode 100644 index 000000000000..a650c6b7a26f --- /dev/null +++ b/FF_LEADS_OWNERS @@ -0,0 +1,10 @@ +bills@google.com +carmenjackson@google.com +nalini@google.com +nosh@google.com +olilan@google.com +philipcuadra@google.com +rajekumar@google.com +shayba@google.com +timmurray@google.com +zezeozue@google.com diff --git a/core/api/current.txt b/core/api/current.txt index 5b9f1ef3408a..f66015841a4d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -13569,6 +13569,7 @@ package android.content.pm { field public static final String PROPERTY_MEDIA_CAPABILITIES = "android.media.PROPERTY_MEDIA_CAPABILITIES"; field public static final String PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES = "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"; field public static final String PROPERTY_SPECIAL_USE_FGS_SUBTYPE = "android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"; + field @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"; field public static final int SIGNATURE_FIRST_NOT_SIGNED = -1; // 0xffffffff field public static final int SIGNATURE_MATCH = 0; // 0x0 field public static final int SIGNATURE_NEITHER_SIGNED = 1; // 0x1 @@ -13805,6 +13806,7 @@ package android.content.pm { public final class SharedLibraryInfo implements android.os.Parcelable { method public int describeContents(); + method @FlaggedApi("android.content.pm.sdk_dependency_installer") @NonNull public java.util.List<java.lang.String> getCertDigests(); method @NonNull public android.content.pm.VersionedPackage getDeclaringPackage(); method @NonNull public java.util.List<android.content.pm.VersionedPackage> getDependentPackages(); method @IntRange(from=0xffffffff) public long getLongVersion(); @@ -17472,6 +17474,7 @@ package android.graphics { method public void setFloatUniform(@NonNull String, @NonNull float[]); method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter); method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); + method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode); method public void setIntUniform(@NonNull String, int); method public void setIntUniform(@NonNull String, int, int); method public void setIntUniform(@NonNull String, int, int, int); @@ -17490,7 +17493,9 @@ package android.graphics { method public void setFloatUniform(@NonNull String, float, float, float, float); method public void setFloatUniform(@NonNull String, @NonNull float[]); method public void setInputBuffer(@NonNull String, @NonNull android.graphics.BitmapShader); + method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter); method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); + method @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode); method public void setIntUniform(@NonNull String, int); method public void setIntUniform(@NonNull String, int, int); method public void setIntUniform(@NonNull String, int, int, int); @@ -17510,6 +17515,7 @@ package android.graphics { method public void setFloatUniform(@NonNull String, @NonNull float[]); method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter); method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader); + method public void setInputXfermode(@NonNull String, @NonNull android.graphics.RuntimeXfermode); method public void setIntUniform(@NonNull String, int); method public void setIntUniform(@NonNull String, int, int); method public void setIntUniform(@NonNull String, int, int, int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0ea00f552c06..9197a0129593 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -57,6 +57,7 @@ package android { field @Deprecated public static final String BIND_CONNECTION_SERVICE = "android.permission.BIND_CONNECTION_SERVICE"; field public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission.BIND_CONTENT_CAPTURE_SERVICE"; field public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission.BIND_CONTENT_SUGGESTIONS_SERVICE"; + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String BIND_DEPENDENCY_INSTALLER = "android.permission.BIND_DEPENDENCY_INSTALLER"; field public static final String BIND_DIRECTORY_SEARCH = "android.permission.BIND_DIRECTORY_SEARCH"; field public static final String BIND_DISPLAY_HASHING_SERVICE = "android.permission.BIND_DISPLAY_HASHING_SERVICE"; field @FlaggedApi("com.android.internal.telephony.flags.use_oem_domain_selection_service") public static final String BIND_DOMAIN_SELECTION_SERVICE = "android.permission.BIND_DOMAIN_SELECTION_SERVICE"; @@ -165,6 +166,7 @@ package android { field public static final String HDMI_CEC = "android.permission.HDMI_CEC"; field @Deprecated public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS = "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"; field public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS"; + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String INSTALL_DEPENDENCY_SHARED_LIBRARIES = "android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES"; field public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES"; field public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission.INSTALL_DYNAMIC_SYSTEM"; field public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission.INSTALL_EXISTING_PACKAGES"; @@ -531,6 +533,7 @@ package android { field public static final int config_systemCallStreaming = 17039431; // 0x1040047 field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039 field public static final int config_systemContacts = 17039403; // 0x104002b + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final int config_systemDependencyInstaller; field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046 field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035 @@ -1937,6 +1940,7 @@ package android.app.backup { method public android.os.IBinder getBinder(); method public long getCurrentRestoreSet(); method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor); + method @FlaggedApi("com.android.server.backup.enable_restricted_mode_changes") @NonNull public java.util.List<java.lang.String> getPackagesThatShouldNotUseRestrictedMode(@NonNull java.util.List<java.lang.String>, int); method public int getRestoreData(android.os.ParcelFileDescriptor); method public int getTransportFlags(); method public int initializeDevice(); @@ -4590,6 +4594,24 @@ package android.content.pm { } +package android.content.pm.dependencyinstaller { + + @FlaggedApi("android.content.pm.sdk_dependency_installer") public final class DependencyInstallerCallback implements android.os.Parcelable { + method public int describeContents(); + method public void onAllDependenciesResolved(@NonNull int[]); + method public void onFailureToResolveAllDependencies(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.dependencyinstaller.DependencyInstallerCallback> CREATOR; + } + + @FlaggedApi("android.content.pm.sdk_dependency_installer") public abstract class DependencyInstallerService extends android.app.Service { + ctor public DependencyInstallerService(); + method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); + method public abstract void onDependenciesRequired(@NonNull java.util.List<android.content.pm.SharedLibraryInfo>, @NonNull android.content.pm.dependencyinstaller.DependencyInstallerCallback); + } + +} + package android.content.pm.dex { public class ArtManager { @@ -5401,6 +5423,7 @@ package android.hardware.display { method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getDefaultBrightnessConfiguration(); method public android.util.Pair<float[],float[]> getMinimumBrightnessCurve(); method public android.graphics.Point getStableDisplaySize(); + method @FlaggedApi("com.android.server.display.feature.flags.is_always_on_available_api") public boolean isAlwaysOnDisplayCurrentlyAvailable(); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration); method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String); method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 136c6365b313..8fd2cd55b8a8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2318,7 +2318,7 @@ package android.net.wifi.sharedconnectivity.app { public class SharedConnectivityManager { method @Nullable public static android.net.wifi.sharedconnectivity.app.SharedConnectivityManager create(@NonNull android.content.Context, @NonNull String, @NonNull String); - method @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") @NonNull public android.content.BroadcastReceiver getBroadcastReceiver(); + method @NonNull public android.content.BroadcastReceiver getBroadcastReceiver(); method @Nullable public android.content.ServiceConnection getServiceConnection(); method public void setService(@Nullable android.os.IInterface); } diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 6c03b32a4816..ce0ec602e612 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1628,9 +1628,15 @@ public class AppOpsManager { public static final int OP_WRITE_SYSTEM_PREFERENCES = AppOpEnums.APP_OP_WRITE_SYSTEM_PREFERENCES; + /** @hide Access to audio playback and control APIs. */ + public static final int OP_CONTROL_AUDIO = AppOpEnums.APP_OP_CONTROL_AUDIO; + + /** @hide Similar to {@link OP_CONTROL_AUDIO}, but doesn't require capabilities. */ + public static final int OP_CONTROL_AUDIO_PARTIAL = AppOpEnums.APP_OP_CONTROL_AUDIO_PARTIAL; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 154; + public static final int _NUM_OP = 156; /** * All app ops represented as strings. @@ -1788,6 +1794,8 @@ public class AppOpsManager { OPSTR_RANGING, OPSTR_READ_OXYGEN_SATURATION, OPSTR_WRITE_SYSTEM_PREFERENCES, + OPSTR_CONTROL_AUDIO, + OPSTR_CONTROL_AUDIO_PARTIAL, }) public @interface AppOpString {} @@ -2548,6 +2556,12 @@ public class AppOpsManager { /** @hide Access to system preferences write services */ public static final String OPSTR_WRITE_SYSTEM_PREFERENCES = "android:write_system_preferences"; + /** @hide Access to audio playback and control APIs */ + public static final String OPSTR_CONTROL_AUDIO = "android:control_audio"; + + /** @hide Access to a audio playback and control APIs without capability requirements */ + public static final String OPSTR_CONTROL_AUDIO_PARTIAL = "android:control_audio_partial"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -3157,6 +3171,10 @@ public class AppOpsManager { "WRITE_SYSTEM_PREFERENCES").setPermission( com.android.settingslib.flags.Flags.writeSystemPreferencePermissionEnabled() ? Manifest.permission.WRITE_SYSTEM_PREFERENCES : null).build(), + new AppOpInfo.Builder(OP_CONTROL_AUDIO, OPSTR_CONTROL_AUDIO, + "CONTROL_AUDIO").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(), + new AppOpInfo.Builder(OP_CONTROL_AUDIO_PARTIAL, OPSTR_CONTROL_AUDIO_PARTIAL, + "CONTROL_AUDIO_PARTIAL").setDefaultMode(AppOpsManager.MODE_FOREGROUND).build(), }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 34a3ad19b270..a8412fa66609 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -358,7 +358,7 @@ interface IActivityManager { @UnsupportedAppUsage void resumeAppSwitches(); boolean bindBackupAgent(in String packageName, int backupRestoreMode, int targetUserId, - int backupDestination); + int backupDestination, boolean useRestrictedMode); void backupAgentCreated(in String packageName, in IBinder agent, int userId); void unbindBackupAgent(in ApplicationInfo appInfo); int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index a0639177266c..53a7dad76788 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -17,6 +17,7 @@ package android.app; import static android.app.appfunctions.flags.Flags.enableAppFunctionManager; +import static android.provider.flags.Flags.stageFlagsForBuild; import static android.server.Flags.removeGameManagerServiceFromWear; import android.accounts.AccountManager; @@ -216,6 +217,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.os.VibratorManager; +import android.os.flagging.ConfigInfrastructureFrameworkInitializer; import android.os.health.SystemHealthManager; import android.os.image.DynamicSystemManager; import android.os.image.IDynamicSystemService; @@ -1837,6 +1839,10 @@ public final class SystemServiceRegistry { VirtualizationFrameworkInitializer.registerServiceWrappers(); ConnectivityFrameworkInitializerBaklava.registerServiceWrappers(); + if (stageFlagsForBuild()) { + ConfigInfrastructureFrameworkInitializer.registerServiceWrappers(); + } + if (com.android.server.telecom.flags.Flags.telecomMainlineBlockedNumbersManager()) { ProviderFrameworkInitializer.registerServiceWrappers(); } diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index dcac59c19df4..5004c02194ea 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -16,6 +16,8 @@ package android.app.backup; +import android.annotation.FlaggedApi; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Intent; @@ -27,6 +29,7 @@ import android.os.RemoteException; import com.android.internal.backup.IBackupTransport; import com.android.internal.backup.ITransportStatusCallback; import com.android.internal.infra.AndroidFuture; +import com.android.server.backup.Flags; import java.util.Arrays; import java.util.List; @@ -671,6 +674,22 @@ public class BackupTransport { } /** + * Ask the transport whether packages that are about to be backed up or restored should not be + * put into a restricted mode by the framework and started normally instead. + * + * @param operationType 0 for backup, 1 for restore. + * @return a subset of the {@code packageNames} passed in, indicating + * which packages should NOT be put into restricted mode for the given operation type. + */ + @NonNull + @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public List<String> getPackagesThatShouldNotUseRestrictedMode( + @NonNull List<String> packageNames, + @BackupAnnotations.OperationType int operationType) { + return List.of(); + } + + /** * Bridge between the actual IBackupTransport implementation and the stable API. If the * binder interface needs to change, we use this layer to translate so that we can * (if appropriate) decouple those framework-side changes from the BackupTransport @@ -977,5 +996,19 @@ public class BackupTransport { resultFuture.cancel(/* mayInterruptIfRunning */ true); } } + + @Override + @FlaggedApi(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames, + int operationType, AndroidFuture<List<String>> resultFuture) { + try { + List<String> result = + BackupTransport.this.getPackagesThatShouldNotUseRestrictedMode(packageNames, + operationType); + resultFuture.complete(result); + } catch (RuntimeException e) { + resultFuture.cancel(/* mayInterruptIfRunning */ true); + } + } } } diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index c47fe236faf0..de01280f293f 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -148,6 +148,14 @@ flag { flag { namespace: "virtual_devices" + name: "notifications_for_device_streaming" + description: "Add notifications permissions to device streaming role" + bug: "375240276" + is_exported: true +} + +flag { + namespace: "virtual_devices" name: "default_device_camera_access_policy" description: "API for default device camera access policy" bug: "371173368" diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 13b13b9e4179..37295ac94823 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -193,6 +193,42 @@ public abstract class PackageManager { "android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"; /** + * <application> level {@link android.content.pm.PackageManager.Property} tag + * specifying whether the app should be put into the "restricted" backup mode when it's started + * for backup and restore operations. + * + * <p> See <a + * href="https://developer.android.com/identity/data/autobackup#ImplementingBackupAgent"> for + * information about restricted mode</a>. + * + * <p> Starting with Android 16 apps may not be started in restricted mode based on this + * property. + * + * <p><b>Syntax:</b> + * <pre> + * <application> + * <property + * android:name="android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE" + * android:value="true|false"/> + * </application> + * </pre> + * + * <p>If this property is set, the operating system will respect it for now (see Note below). + * If it's not set, the behavior depends on the SDK level that the app is targeting. For apps + * targeting SDK level {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or lower, the + * property defaults to {@code true}. For apps targeting SDK level + * {@link android.os.Build.VERSION_CODES#BAKLAVA} or higher, the operating system will make a + * decision dynamically. + * + * <p>Note: It's not recommended to set this property to {@code true} unless absolutely + * necessary. In a future Android version, this property may be deprecated in favor of removing + * restricted mode completely. + */ + @FlaggedApi(com.android.server.backup.Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public static final String PROPERTY_USE_RESTRICTED_BACKUP_MODE = + "android.app.backup.PROPERTY_USE_RESTRICTED_BACKUP_MODE"; + + /** * Application level property that an app can specify to opt-out from having private data * directories both on the internal and external storages. * diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index f7191e605fb8..5dfec9809f6e 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -105,6 +105,8 @@ public final class SharedLibraryInfo implements Parcelable { private final List<VersionedPackage> mOptionalDependentPackages; private List<SharedLibraryInfo> mDependencies; + private final List<String> mCertDigests; + /** * Creates a new instance. * @@ -134,6 +136,7 @@ public final class SharedLibraryInfo implements Parcelable { mDependencies = dependencies; mIsNative = isNative; mOptionalDependentPackages = null; + mCertDigests = null; } /** @@ -165,6 +168,7 @@ public final class SharedLibraryInfo implements Parcelable { mDeclaringPackage = declaringPackage; mDependencies = dependencies; mIsNative = isNative; + mCertDigests = null; var allDependents = allDependentPackages.first; var usesLibOptional = allDependentPackages.second; @@ -206,6 +210,7 @@ public final class SharedLibraryInfo implements Parcelable { mIsNative = parcel.readBoolean(); mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(), VersionedPackage.class.getClassLoader(), VersionedPackage.class); + mCertDigests = parcel.createStringArrayList(); } /** @@ -214,6 +219,29 @@ public final class SharedLibraryInfo implements Parcelable { * @param versionMajor */ public SharedLibraryInfo(String name, long versionMajor, int type) { + //TODO: change to this(name, versionMajor, type, /* certDigest= */null); after flag removal + mPath = null; + mPackageName = null; + mName = name; + mVersion = versionMajor; + mType = type; + mDeclaringPackage = null; + mDependentPackages = null; + mDependencies = null; + mIsNative = false; + mOptionalDependentPackages = null; + mCertDigests = null; + } + + /** + * @hide + * @param name The lib name. + * @param versionMajor The lib major version. + * @param type The type of shared library. + * @param certDigests The list of certificate digests for this shared library. + */ + @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + public SharedLibraryInfo(String name, long versionMajor, int type, List<String> certDigests) { mPath = null; mPackageName = null; mName = name; @@ -224,6 +252,7 @@ public final class SharedLibraryInfo implements Parcelable { mDependencies = null; mIsNative = false; mOptionalDependentPackages = null; + mCertDigests = certDigests; } /** @@ -433,6 +462,19 @@ public final class SharedLibraryInfo implements Parcelable { return mOptionalDependentPackages; } + /** + * Gets the list of certificate digests for the shared library. + * + * @return The list of certificate digests + */ + @FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + public @NonNull List<String> getCertDigests() { + if (mCertDigests == null) { + return Collections.emptyList(); + } + return mCertDigests; + } + @Override public int describeContents() { return 0; @@ -463,6 +505,7 @@ public final class SharedLibraryInfo implements Parcelable { parcel.writeTypedList(mDependencies); parcel.writeBoolean(mIsNative); parcel.writeParcelableList(mOptionalDependentPackages, flags); + parcel.writeStringList(mCertDigests); } private static String typeToString(int type) { diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl new file mode 100644 index 000000000000..06fcabcf55e9 --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +parcelable DependencyInstallerCallback;
\ No newline at end of file diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java new file mode 100644 index 000000000000..ba089f7fd33e --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerCallback.java @@ -0,0 +1,100 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.pm.Flags; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +/** + * Callbacks for {@link DependencyInstallerService}. The implementation of + * DependencyInstallerService uses this interface to indicate completion of the session creation + * request given by the system server. + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) +public final class DependencyInstallerCallback implements Parcelable { + private final IBinder mBinder; + private final IDependencyInstallerCallback mCallback; + + /** @hide */ + public DependencyInstallerCallback(IBinder binder) { + mBinder = binder; + mCallback = IDependencyInstallerCallback.Stub.asInterface(binder); + } + + private DependencyInstallerCallback(Parcel in) { + mBinder = in.readStrongBinder(); + mCallback = IDependencyInstallerCallback.Stub.asInterface(mBinder); + } + + /** + * Callback to indicate that all the requested dependencies have been resolved and their + * sessions created. See {@link DependencyInstallerService#onDependenciesRequired}. + * + * @param sessionIds the install session IDs for all requested dependencies + */ + public void onAllDependenciesResolved(@NonNull int[] sessionIds) { + try { + mCallback.onAllDependenciesResolved(sessionIds); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Callback to indicate that at least one of the required dependencies could not be resolved + * and any associated sessions have been abandoned. + */ + public void onFailureToResolveAllDependencies() { + try { + mCallback.onFailureToResolveAllDependencies(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeStrongBinder(mBinder); + } + + public static final @NonNull Creator<DependencyInstallerCallback> CREATOR = + new Creator<>() { + @Override + public DependencyInstallerCallback createFromParcel(Parcel in) { + return new DependencyInstallerCallback(in); + } + + @Override + public DependencyInstallerCallback[] newArray(int size) { + return new DependencyInstallerCallback[size]; + } + }; +} diff --git a/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java new file mode 100644 index 000000000000..11864150e072 --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/DependencyInstallerService.java @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.Intent; +import android.content.pm.Flags; +import android.content.pm.SharedLibraryInfo; +import android.os.IBinder; + +import java.util.List; + +/** + * Service that needs to be implemented by the holder of the DependencyInstaller role. This service + * will be invoked by the system during application installations if it depends on + * {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or + * {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE} and those dependencies aren't + * already installed. + * <p> + * Below is an example manifest registration for a {@code DependencyInstallerService}. + * <pre> + * {@code + * <service android:name=".ExampleDependencyInstallerService" + * android:permission="android.permission.BIND_DEPENDENCY_INSTALLER" > + * ... + * <intent-filter> + * <action android:name="android.content.pm.action.INSTALL_DEPENDENCY" /> + * </intent-filter> + * </service> + * } + * </pre> + * + * @hide + */ +@SystemApi +@FlaggedApi(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) +public abstract class DependencyInstallerService extends Service { + + private IDependencyInstallerService mBinder; + + @Override + public final @NonNull IBinder onBind(@Nullable Intent intent) { + if (mBinder == null) { + mBinder = new IDependencyInstallerService.Stub() { + @Override + public void onDependenciesRequired(List<SharedLibraryInfo> neededLibraries, + DependencyInstallerCallback callback) { + DependencyInstallerService.this.onDependenciesRequired(neededLibraries, + callback); + } + }; + } + return mBinder.asBinder(); + } + + /** + * Notify the holder of the DependencyInstaller role of the missing dependencies required for + * the completion of an active install session. + * + * @param neededLibraries the list of shared library dependencies needed to be obtained and + * installed. + */ + public abstract void onDependenciesRequired(@NonNull List<SharedLibraryInfo> neededLibraries, + @NonNull DependencyInstallerCallback callback); +} diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl new file mode 100644 index 000000000000..92d1d9e118e6 --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerCallback.aidl @@ -0,0 +1,41 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import java.util.List; + +/** +* Callbacks for Dependency Installer. The app side invokes on this interface to indicate +* completion of the async dependency install request given by the system server. +* +* {@hide} +*/ +oneway interface IDependencyInstallerCallback { + /** + * Callback to indicate that all the requested dependencies have been resolved and have been + * committed for installation. See {@link DependencyInstallerService#onDependenciesRequired}. + * + * @param sessionIds the install session IDs for all requested dependencies + */ + void onAllDependenciesResolved(in int[] sessionIds); + + /** + * Callback to indicate that at least one of the required dependencies could not be resolved + * and any associated sessions have been abandoned. + */ + void onFailureToResolveAllDependencies(); +}
\ No newline at end of file diff --git a/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl new file mode 100644 index 000000000000..94f5bf45ca9c --- /dev/null +++ b/core/java/android/content/pm/dependencyinstaller/IDependencyInstallerService.aidl @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm.dependencyinstaller; + +import android.content.pm.dependencyinstaller.DependencyInstallerCallback; +import android.content.pm.SharedLibraryInfo; +import java.util.List; + +/** +* Interface used to communicate with the application code that holds the Dependency Installer role. +* {@hide} +*/ +oneway interface IDependencyInstallerService { + /** + * Notify dependency installer of the required dependencies to complete the current install + * session. + * + * @param neededLibraries the list of shared library dependencies needed to be obtained and + * installed. + */ + void onDependenciesRequired(in List<SharedLibraryInfo> neededLibraries, + in DependencyInstallerCallback callback); + }
\ No newline at end of file diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index e6a1640781ed..25327a9b1d52 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -61,6 +61,7 @@ import android.view.Surface; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.server.display.feature.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -102,6 +103,7 @@ public final class DisplayManager { private final WeakDisplayCache mDisplayCache = new WeakDisplayCache(); private int mDisplayIdToMirror = INVALID_DISPLAY; + private AmbientDisplayConfiguration mAmbientDisplayConfiguration; /** * Broadcast receiver that indicates when the Wifi display status changes. @@ -1613,6 +1615,17 @@ public final class DisplayManager { } /** + * Returns whether this device supports Always On Display. + * + * @hide + */ + @SystemApi + @FlaggedApi(Flags.FLAG_IS_ALWAYS_ON_AVAILABLE_API) + public boolean isAlwaysOnDisplayCurrentlyAvailable() { + return getAmbientDisplayConfiguration().alwaysOnAvailableForUser(mContext.getUserId()); + } + + /** * Returns whether device supports seamless refresh rate switching. * * Match content frame rate setting has three options: seamless, non-seamless and never. @@ -1674,6 +1687,15 @@ public final class DisplayManager { } } + private AmbientDisplayConfiguration getAmbientDisplayConfiguration() { + synchronized (this) { + if (mAmbientDisplayConfiguration == null) { + mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); + } + } + return mAmbientDisplayConfiguration; + } + /** * Creates a VirtualDisplay that will mirror the content of displayIdToMirror * @param name The name for the virtual display diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java index 9ad2e7f82ce4..036ccd84a600 100644 --- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java +++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java @@ -118,18 +118,33 @@ public final class MessageQueue { mUseConcurrent = UserHandle.isCore(Process.myUid()); // Even then, we don't use it if instrumentation is loaded as it breaks some // platform tests. - final ActivityThread activityThread = ActivityThread.currentActivityThread(); - if (activityThread != null) { - final Instrumentation instrumentation = activityThread.getInstrumentation(); - mUseConcurrent &= instrumentation == null || !instrumentation.isInstrumenting(); - } + final Instrumentation instrumentation = getInstrumentation(); + mUseConcurrent &= instrumentation == null || !instrumentation.isInstrumenting(); // We can lift this restriction in the future after we've made it possible for test authors // to test Looper and MessageQueue without resorting to reflection. + // Holdback study. + if (mUseConcurrent && Flags.messageQueueForceLegacy()) { + mUseConcurrent = false; + } + mQuitAllowed = quitAllowed; mPtr = nativeInit(); } + @android.ravenwood.annotation.RavenwoodReplace(blockedBy = ActivityThread.class) + private static Instrumentation getInstrumentation() { + final ActivityThread activityThread = ActivityThread.currentActivityThread(); + if (activityThread != null) { + return activityThread.getInstrumentation(); + } + return null; + } + + private static Instrumentation getInstrumentation$ravenwood() { + return null; // Instrumentation not supported on Ravenwood yet. + } + @Override protected void finalize() throws Throwable { try { diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 24e1d6666093..e63b6648a9ef 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -128,3 +128,6 @@ per-file StatsServiceManager.java = file:/services/core/java/com/android/server/ # Dropbox per-file DropBoxManager* = mwachens@google.com + +# Flags +per-file flags.aconfig = file:/FF_LEADS_OWNERS diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 118167d02c48..01445069af67 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -4,6 +4,15 @@ container: "system" # keep-sorted start block=yes newline_separated=yes flag { + # Holdback study for concurrent MessageQueue. + # Do not promote beyond trunkfood. + namespace: "system_performance" + name: "message_queue_force_legacy" + description: "Whether to holdback concurrent MessageQueue (force legacy)." + bug: "336880969" +} + +flag { name: "adpf_gpu_report_actual_work_duration" is_exported: true namespace: "game" diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 5a71282dab0a..8cb96ae1d611 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -930,8 +930,8 @@ public final class Choreographer { // of the next vsync event. int totalFrameDelays = mBufferStuffingData.numberFrameDelays + mBufferStuffingData.numberWaitsForNextVsync + 1; - long vsyncsSinceLastCallback = - (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos; + long vsyncsSinceLastCallback = mLastFrameIntervalNanos > 0 + ? (frameTimeNanos - mLastNoOffsetFrameTimeNanos) / mLastFrameIntervalNanos : 0; // Detected idle state due to a longer inactive period since the last vsync callback // than the total expected number of vsync frame delays. End buffer stuffing recovery. diff --git a/core/java/android/view/flags/scroll_feedback_flags.aconfig b/core/java/android/view/flags/scroll_feedback_flags.aconfig index b180e58cbe49..ebda4d472b0d 100644 --- a/core/java/android/view/flags/scroll_feedback_flags.aconfig +++ b/core/java/android/view/flags/scroll_feedback_flags.aconfig @@ -28,5 +28,5 @@ flag { namespace: "wear_frameworks" name: "dynamic_view_rotary_haptics_configuration" description: "Whether ScrollFeedbackProvider dynamically disables View-based rotary haptics." - bug: "377998870 " + bug: "377998870" } diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index e619ab064005..deaf95797127 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -175,3 +175,12 @@ flag { bug: "342672560" is_fixed_read_only: true } + +flag { + name: "adaptive_handwriting_bounds" + is_exported: true + namespace: "input_method" + description: "Feature flag for adaptively increasing handwriting bounds." + bug: "350047836" + is_fixed_read_only: true +} diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index f474b34ac390..eebdeadcdeb2 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -402,4 +402,11 @@ flag { namespace: "lse_desktop_experience" description: "Enables HSUM on desktop mode." bug: "366397912" +} + +flag { + name: "enable_multiple_desktops" + namespace: "lse_desktop_experience" + description: "Enable multiple desktop sessions for desktop windowing." + bug: "379158791" }
\ No newline at end of file diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 21c7baab4e83..469ab48385da 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -410,4 +410,15 @@ oneway interface IBackupTransport { * however backups initiated by the framework will call this method to retrieve one. */ void getBackupManagerMonitor(in AndroidFuture<IBackupManagerMonitor> resultFuture); + + /** + * Ask the transport whether packages that are about to be backed up or restored should not be + * put into a restricted mode by the framework and started normally instead. The + * {@code resultFuture} should be completed with a subset of the packages passed in, indicating + * which packages should NOT be put into restricted mode for the given operation type. + * + * @param operationType 0 for backup, 1 for restore. + */ + void getPackagesThatShouldNotUseRestrictedMode(in List<String> packageNames, int operationType, + in AndroidFuture<List<String>> resultFuture); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 95d07df388d0..00424590b2c8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7921,7 +7921,31 @@ <!-- @SystemApi Allows an application to access shared libraries. @hide --> <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES" - android:protectionLevel="signature|installer" /> + android:protectionLevel="signature|installer" + android:featureFlag="!android.content.pm.sdk_dependency_installer" /> + + <!-- @SystemApi Allows an application to access shared libraries. + @hide --> + <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES" + android:protectionLevel="signature|installer|role" + android:featureFlag="android.content.pm.sdk_dependency_installer" /> + + <!-- @SystemApi Permission held by the system to allow binding to the dependency installer role + holder. + @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + @hide --> + <permission android:name="android.permission.BIND_DEPENDENCY_INSTALLER" + android:protectionLevel="signature" + android:featureFlag="android.content.pm.sdk_dependency_installer" /> + + <!-- @SystemApi Allows an application to install shared libraries of types + {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or + {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE}. + @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + @hide --> + <permission android:name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES" + android:protectionLevel="signature|role" + android:featureFlag="android.content.pm.sdk_dependency_installer" /> <!-- Allows an app to log compat change usage. @hide <p>Not for use by third-party applications.</p> --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 7799ff951997..5088b5af4725 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4205,9 +4205,17 @@ must match the value of config_cameraLaunchGestureSensorType in OEM's HAL --> <string translatable="false" name="config_cameraLaunchGestureSensorStringType"></string> + <!-- Allow the gesture to double tap the power button to trigger a target action. --> + <bool name="config_doubleTapPowerGestureEnabled">true</bool> <!-- Allow the gesture to double tap the power button twice to start the camera while the device is non-interactive. --> <bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool> + <!-- Allow the gesture to double tap the power button twice to launch the wallet. --> + <bool name="config_walletDoubleTapPowerGestureEnabled">true</bool> + <!-- Default target action for double tap of the power button gesture. + 0: Launch camera + 1: Launch wallet --> + <integer name="config_defaultDoubleTapPowerGestureAction">0</integer> <!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos experience while the device is non-interactive. --> @@ -7217,4 +7225,7 @@ <!-- Whether to enable fp unlock when screen turns off on udfps devices --> <bool name="config_screen_off_udfps_enabled">false</bool> + + <!-- The name of the system package that will hold the dependency installer role. --> + <string name="config_systemDependencyInstaller" translatable="false" /> </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index a0bf89d66923..ce46c649148c 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -142,6 +142,9 @@ </staging-public-group> <staging-public-group type="string" first-id="0x01b40000"> + <!-- @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + @hide @SystemApi --> + <public name="config_systemDependencyInstaller" /> </staging-public-group> <staging-public-group type="dimen" first-id="0x01b30000"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 7fe09128de05..0e120758a144 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3140,9 +3140,12 @@ <!-- Gesture --> <java-symbol type="integer" name="config_cameraLaunchGestureSensorType" /> <java-symbol type="string" name="config_cameraLaunchGestureSensorStringType" /> - <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" /> <java-symbol type="integer" name="config_cameraLiftTriggerSensorType" /> <java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" /> + <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" /> + <java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" /> + <java-symbol type="bool" name="config_walletDoubleTapPowerGestureEnabled" /> + <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" /> <java-symbol type="bool" name="config_emergencyGestureEnabled" /> <java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" /> <java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" /> diff --git a/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java new file mode 100644 index 000000000000..df5cd4e95418 --- /dev/null +++ b/core/tests/coretests/src/android/content/pm/SharedLibraryInfoTest.java @@ -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 android.content.pm; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import com.google.common.collect.ImmutableList; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.List; + +@Presubmit +@RunWith(JUnit4.class) +public final class SharedLibraryInfoTest { + + @Rule + public final CheckFlagsRule checkFlagsRule = + DeviceFlagsValueProvider.createCheckFlagsRule(); + + private static final String LIBRARY_NAME = "name"; + private static final long VERSION_MAJOR = 1L; + private static final List<String> CERT_DIGESTS = ImmutableList.of("digest1", "digest2"); + + @Test + @RequiresFlagsEnabled(Flags.FLAG_SDK_DEPENDENCY_INSTALLER) + public void sharedLibraryInfo_serializedAndDeserialized_retainsCertDigestInfo() { + SharedLibraryInfo toParcel = new SharedLibraryInfo(LIBRARY_NAME, VERSION_MAJOR, + SharedLibraryInfo.TYPE_SDK_PACKAGE, CERT_DIGESTS); + + SharedLibraryInfo fromParcel = parcelAndUnparcel(toParcel); + + assertThat(fromParcel.getCertDigests().size()).isEqualTo(toParcel.getCertDigests().size()); + assertThat(fromParcel.getCertDigests().get(0)).isEqualTo(toParcel.getCertDigests().get(0)); + assertThat(fromParcel.getCertDigests().get(1)).isEqualTo(toParcel.getCertDigests().get(1)); + } + + private SharedLibraryInfo parcelAndUnparcel(SharedLibraryInfo sharedLibraryInfo) { + Parcel parcel = Parcel.obtain(); + parcel.setDataPosition(0); + sharedLibraryInfo.writeToParcel(parcel, /* flags= */0); + + parcel.setDataPosition(0); + return SharedLibraryInfo.CREATOR.createFromParcel(parcel); + } +} diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java index 52724ceaf301..d112f7153fca 100644 --- a/graphics/java/android/graphics/RuntimeColorFilter.java +++ b/graphics/java/android/graphics/RuntimeColorFilter.java @@ -283,6 +283,23 @@ public class RuntimeColorFilter extends ColorFilter { nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance()); } + /** + * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does + * not have a uniform xfermode with that name then an IllegalArgumentException is thrown. + * + * @param xfermodeName name matching the uniform declared in the AGSL program + * @param xfermode filter passed into the AGSL program for sampling + */ + public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) { + if (xfermodeName == null) { + throw new NullPointerException("The xfermodeName parameter must not be null"); + } + if (xfermode == null) { + throw new NullPointerException("The xfermode parameter must not be null"); + } + nativeUpdateChild(getNativeInstance(), xfermodeName, xfermode.createNativeInstance()); + } + /** @hide */ @Override protected long createNativeInstance() { diff --git a/graphics/java/android/graphics/RuntimeShader.java b/graphics/java/android/graphics/RuntimeShader.java index 78d257f86613..6316c1fb8b47 100644 --- a/graphics/java/android/graphics/RuntimeShader.java +++ b/graphics/java/android/graphics/RuntimeShader.java @@ -18,10 +18,13 @@ package android.graphics; import android.annotation.ColorInt; import android.annotation.ColorLong; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.util.ArrayMap; import android.view.Window; +import com.android.graphics.hwui.flags.Flags; + import libcore.util.NativeAllocationRegistry; /** @@ -525,6 +528,45 @@ public class RuntimeShader extends Shader { discardNativeInstance(); } + /** + * Assigns the uniform color filter to the provided color filter parameter. If the shader + * program does not have a uniform color filter with that name then an IllegalArgumentException + * is thrown. + * + * @param filterName name matching the uniform declared in the AGSL program + * @param colorFilter filter passed into the AGSL program for sampling + */ + @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS) + public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) { + if (filterName == null) { + throw new NullPointerException("The filterName parameter must not be null"); + } + if (colorFilter == null) { + throw new NullPointerException("The colorFilter parameter must not be null"); + } + nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, filterName, + colorFilter.getNativeInstance()); + } + + /** + * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does + * not have a uniform xfermode with that name then an IllegalArgumentException is thrown. + * + * @param xfermodeName name matching the uniform declared in the AGSL program + * @param xfermode filter passed into the AGSL program for sampling + */ + @FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS) + public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) { + if (xfermodeName == null) { + throw new NullPointerException("The xfermodeName parameter must not be null"); + } + if (xfermode == null) { + throw new NullPointerException("The xfermode parameter must not be null"); + } + nativeUpdateChild(mNativeInstanceRuntimeShaderBuilder, xfermodeName, + xfermode.createNativeInstance()); + } + /** @hide */ @Override @@ -552,5 +594,7 @@ public class RuntimeShader extends Shader { int value4, int count); private static native void nativeUpdateShader( long shaderBuilder, String shaderName, long shader); + private static native void nativeUpdateChild( + long shaderBuilder, String childName, long child); } diff --git a/graphics/java/android/graphics/RuntimeXfermode.java b/graphics/java/android/graphics/RuntimeXfermode.java index f5a656862bf9..51d97a4b7487 100644 --- a/graphics/java/android/graphics/RuntimeXfermode.java +++ b/graphics/java/android/graphics/RuntimeXfermode.java @@ -288,6 +288,23 @@ public class RuntimeXfermode extends Xfermode { nativeUpdateChild(mBuilderNativeInstance, filterName, colorFilter.getNativeInstance()); } + /** + * Assigns the uniform xfermode to the provided xfermode parameter. If the shader program does + * not have a uniform xfermode with that name then an IllegalArgumentException is thrown. + * + * @param xfermodeName name matching the uniform declared in the AGSL program + * @param xfermode xfermode function passed into the AGSL program for sampling + */ + public void setInputXfermode(@NonNull String xfermodeName, @NonNull RuntimeXfermode xfermode) { + if (xfermodeName == null) { + throw new NullPointerException("The xfermodeName parameter must not be null"); + } + if (xfermode == null) { + throw new NullPointerException("The xfermode parameter must not be null"); + } + nativeUpdateChild(mBuilderNativeInstance, xfermodeName, xfermode.createNativeInstance()); + } + /** @hide */ public long createNativeInstance() { return nativeCreateNativeInstance(mBuilderNativeInstance); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java index 220fc6f82a71..819cf3492d24 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java @@ -94,7 +94,6 @@ class BackupHelper { */ void scheduleBackup() { if (!mSaveEmbeddingState) { - // TODO(b/289875940): enabled internally for broader testing. return; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java index cb280c530c1b..0f1246cd17e3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableSplitContainerData.java @@ -44,7 +44,6 @@ class ParcelableSplitContainerData implements Parcelable { @NonNull private final IBinder mSecondaryContainerToken; - // TODO(b/289875940): making this as non-null once the tag can be auto-generated from the rule. @Nullable final String mSplitRuleTag; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java index a79a89a210ac..bf342d76f4c2 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/ParcelableTaskFragmentContainerData.java @@ -25,6 +25,9 @@ import android.os.Parcelable; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + /** * This class holds the Parcelable data of a {@link TaskFragmentContainer}. */ @@ -61,6 +64,12 @@ class ParcelableTaskFragmentContainerData implements Parcelable { @NonNull final Rect mLastRequestedBounds; + /** + * Individual associated activity tokens in different containers that should be finished on + * exit. + */ + final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>(); + ParcelableTaskFragmentContainerData(@NonNull IBinder token, @Nullable String overlayTag, @Nullable IBinder associatedActivityToken) { mToken = token; @@ -74,6 +83,7 @@ class ParcelableTaskFragmentContainerData implements Parcelable { mOverlayTag = in.readString(); mAssociatedActivityToken = in.readStrongBinder(); mLastRequestedBounds = in.readTypedObject(Rect.CREATOR); + in.readBinderList(mActivitiesToFinishOnExit); } public static final Creator<ParcelableTaskFragmentContainerData> CREATOR = new Creator<>() { @@ -99,7 +109,7 @@ class ParcelableTaskFragmentContainerData implements Parcelable { dest.writeString(mOverlayTag); dest.writeStrongBinder(mAssociatedActivityToken); dest.writeTypedObject(mLastRequestedBounds, flags); + dest.writeBinderList(mActivitiesToFinishOnExit); } - } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java index faf73c24073f..5ba30ddd2f18 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -98,10 +98,20 @@ class SplitContainer { mCurrentSplitAttributes = mDefaultSplitAttributes; if (shouldFinishPrimaryWithSecondary(splitRule)) { - mSecondaryContainer.addContainerToFinishOnExit(mPrimaryContainer); + addContainerToFinishOnExitWhenRestore(mSecondaryContainer, mPrimaryContainer); } if (shouldFinishSecondaryWithPrimary(splitRule)) { - mPrimaryContainer.addContainerToFinishOnExit(mSecondaryContainer); + addContainerToFinishOnExitWhenRestore(mPrimaryContainer, mSecondaryContainer); + } + } + + private void addContainerToFinishOnExitWhenRestore( + @NonNull TaskFragmentContainer containerToAdd, + @NonNull TaskFragmentContainer containerToFinish) { + // If an activity was already added to be finished after the restoration, then that's it. + // Otherwise, add the container to finish on exit. + if (!containerToAdd.hasActivityToFinishOnExit(containerToFinish)) { + containerToAdd.addContainerToFinishOnExit(containerToFinish); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index dc1d983997c6..b3e003e7ad95 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -96,12 +96,6 @@ class TaskFragmentContainer { new ArrayList<>(); /** - * Individual associated activity tokens in different containers that should be finished on - * exit. - */ - private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>(); - - /** * The launch options that was used to create this container. Must not {@link Bundle#isEmpty()} * for {@link #isOverlay()} container. */ @@ -114,7 +108,6 @@ class TaskFragmentContainer { /** * Windowing mode that was requested last via {@link android.window.WindowContainerTransaction}. */ - // TODO(b/289875940): review this and other field that might need to be moved in the base class. @WindowingMode private int mLastRequestedWindowingMode = WINDOWING_MODE_UNDEFINED; @@ -443,7 +436,7 @@ class TaskFragmentContainer { // Remove the activity now because there can be a delay before the server callback. mInfo.getActivities().remove(activityToken); } - mActivitiesToFinishOnExit.remove(activityToken); + mParcelableData.mActivitiesToFinishOnExit.remove(activityToken); finishSelfWithActivityIfNeeded(wct, activityToken); } @@ -624,7 +617,20 @@ class TaskFragmentContainer { if (mIsFinished) { return; } - mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken()); + mParcelableData.mActivitiesToFinishOnExit.add(activityToFinish.getActivityToken()); + } + + /** + * Returns {@code true} if an Activity from the given {@code container} was added to be + * finished on exit. Otherwise, return {@code false}. + */ + boolean hasActivityToFinishOnExit(@NonNull TaskFragmentContainer container) { + for (IBinder activity : mParcelableData.mActivitiesToFinishOnExit) { + if (container.hasActivity(activity)) { + return true; + } + } + return false; } /** @@ -634,7 +640,7 @@ class TaskFragmentContainer { if (mIsFinished) { return; } - mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken()); + mParcelableData.mActivitiesToFinishOnExit.remove(activityToRemove.getActivityToken()); } /** Removes all dependencies that should be finished when this container is finished. */ @@ -643,7 +649,7 @@ class TaskFragmentContainer { return; } mContainersToFinishOnExit.clear(); - mActivitiesToFinishOnExit.clear(); + mParcelableData.mActivitiesToFinishOnExit.clear(); } /** @@ -721,7 +727,7 @@ class TaskFragmentContainer { mContainersToFinishOnExit.clear(); // Finish associated activities - for (IBinder activityToken : mActivitiesToFinishOnExit) { + for (IBinder activityToken : mParcelableData.mActivitiesToFinishOnExit) { final Activity activity = mController.getActivity(activityToken); if (activity == null || activity.isFinishing() || controller.shouldRetainAssociatedActivity(this, activity)) { @@ -729,7 +735,7 @@ class TaskFragmentContainer { } wct.finishActivity(activity.getActivityToken()); } - mActivitiesToFinishOnExit.clear(); + mParcelableData.mActivitiesToFinishOnExit.clear(); } @GuardedBy("mController.mLock") @@ -1082,7 +1088,7 @@ class TaskFragmentContainer { + " pendingAppearedActivities=" + mPendingAppearedActivities + (includeContainersToFinishOnExit ? " containersToFinishOnExit=" + containersToFinishOnExitToString() : "") - + " activitiesToFinishOnExit=" + mActivitiesToFinishOnExit + + " activitiesToFinishOnExit=" + mParcelableData.mActivitiesToFinishOnExit + " info=" + mInfo + "}"; } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt index 23e7441ff86b..f14dfdbc7d30 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt @@ -50,6 +50,22 @@ abstract class ManageWindowsViewContainer( fun createMenu(snapshotList: List<Pair<Int, TaskSnapshot>>, onIconClickListener: ((Int) -> Unit), onOutsideClickListener: (() -> Unit)): ManageWindowsView { + val bitmapList = snapshotList.map { (index, snapshot) -> + index to Bitmap.wrapHardwareBuffer(snapshot.hardwareBuffer, snapshot.colorSpace) + } + return createAndShowMenuView( + bitmapList, + onIconClickListener, + onOutsideClickListener + ) + } + + /** Creates the menu view with the given bitmaps, and displays it. */ + fun createAndShowMenuView( + snapshotList: List<Pair<Int, Bitmap?>>, + onIconClickListener: ((Int) -> Unit), + onOutsideClickListener: (() -> Unit) + ): ManageWindowsView { menuView = ManageWindowsView(context, menuBackgroundColor).apply { this.onOutsideClickListener = onOutsideClickListener this.onIconClickListener = onIconClickListener @@ -120,7 +136,7 @@ abstract class ManageWindowsViewContainer( } fun generateIconViews( - snapshotList: List<Pair<Int, TaskSnapshot>> + snapshotList: List<Pair<Int, Bitmap?>> ) { menuWidth = 0 menuHeight = 0 @@ -133,7 +149,7 @@ abstract class ManageWindowsViewContainer( // Add each icon to the menu, adding a new row when needed. for ((iconCount, taskInfoSnapshotPair) in snapshotList.withIndex()) { val taskId = taskInfoSnapshotPair.first - val snapshot = taskInfoSnapshotPair.second + val snapshotBitmap = taskInfoSnapshotPair.second // Once a row is filled, make a new row and increase the menu height. if (iconCount % MENU_MAX_ICONS_PER_ROW == 0) { rowLayout = LinearLayout(context) @@ -141,10 +157,7 @@ abstract class ManageWindowsViewContainer( rootView.addView(rowLayout) menuHeight += (instanceIconHeight + iconMargin).toInt() } - val snapshotBitmap = Bitmap.wrapHardwareBuffer( - snapshot.hardwareBuffer, - snapshot.colorSpace - ) + val croppedBitmap = snapshotBitmap?.let { cropBitmap(it) } val scaledSnapshotBitmap = croppedBitmap?.let { Bitmap.createScaledBitmap( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index f532be6b8277..12d20bf0e517 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -96,6 +96,14 @@ public class DisplayController { } /** + * Get all the displays from DisplayManager. + */ + public Display[] getDisplays() { + final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); + return displayManager.getDisplays(); + } + + /** * Gets the DisplayLayout associated with a display. */ public @Nullable DisplayLayout getDisplayLayout(int displayId) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index e455985c87c3..02df38e03d7c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -795,13 +795,14 @@ public abstract class WMShellBaseModule { static KeyguardTransitionHandler provideKeyguardTransitionHandler( ShellInit shellInit, ShellController shellController, + DisplayController displayController, Transitions transitions, TaskStackListenerImpl taskStackListener, @ShellMainThread Handler mainHandler, @ShellMainThread ShellExecutor mainExecutor) { return new KeyguardTransitionHandler( - shellInit, shellController, transitions, taskStackListener, mainHandler, - mainExecutor); + shellInit, shellController, displayController, transitions, taskStackListener, + mainHandler, mainExecutor); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index e848b889b314..2ae9828ca0db 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -254,8 +254,13 @@ public class FreeformTaskTransitionHandler finishT.hide(sc); final Rect startBounds = new Rect(change.getStartAbsBounds()); animator.addUpdateListener(animation -> { - t.setPosition(sc, startBounds.left, - startBounds.top + (animation.getAnimatedFraction() * screenHeight)); + final float newTop = startBounds.top + (animation.getAnimatedFraction() * screenHeight); + t.setPosition(sc, startBounds.left, newTop); + if (newTop > screenHeight) { + // At this point the task surface is off-screen, so hide it to prevent flicker + // failures. See b/377651666. + t.hide(sc); + } t.apply(); }); animator.addListener( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index b618bf1215ac..319bfac734ba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -42,6 +42,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; +import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManager; import android.window.IRemoteTransition; @@ -54,6 +55,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.protolog.ProtoLog; import com.android.window.flags.Flags; +import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; @@ -80,6 +82,8 @@ public class KeyguardTransitionHandler private final Transitions mTransitions; private final ShellController mShellController; + + private final DisplayController mDisplayController; private final Handler mMainHandler; private final ShellExecutor mMainExecutor; @@ -121,12 +125,14 @@ public class KeyguardTransitionHandler public KeyguardTransitionHandler( @NonNull ShellInit shellInit, @NonNull ShellController shellController, + @NonNull DisplayController displayController, @NonNull Transitions transitions, @NonNull TaskStackListenerImpl taskStackListener, @NonNull Handler mainHandler, @NonNull ShellExecutor mainExecutor) { mTransitions = transitions; mShellController = shellController; + mDisplayController = displayController; mMainHandler = mainHandler; mMainExecutor = mainExecutor; mTaskStackListener = taskStackListener; @@ -429,10 +435,10 @@ public class KeyguardTransitionHandler @Override public void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - final KeyguardState keyguardState = - new KeyguardState.Builder(android.view.Display.DEFAULT_DISPLAY) - .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build(); - wct.addKeyguardState(keyguardState); + for (Display display : mDisplayController.getDisplays()) { + wct.addKeyguardState(new KeyguardState.Builder(display.getDisplayId()) + .setKeyguardShowing(keyguardShowing).setAodShowing(aodShowing).build()); + } mMainExecutor.execute(() -> { mTransitions.startTransition(keyguardShowing ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, KeyguardTransitionHandler.this); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java index eb33ff4c1c8e..35c90acd306f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java @@ -52,6 +52,9 @@ public class PipEnterAnimator extends ValueAnimator { private final SurfaceControl.Transaction mStartTransaction; private final SurfaceControl.Transaction mFinishTransaction; + private final int mCornerRadius; + private final int mShadowRadius; + // Bounds updated by the evaluator as animator is running. private final Rect mAnimatedRect = new Rect(); @@ -128,6 +131,8 @@ public class PipEnterAnimator extends ValueAnimator { final int enterAnimationDuration = context.getResources() .getInteger(R.integer.config_pipEnterAnimationDuration); + mCornerRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); + mShadowRadius = context.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius); setDuration(enterAnimationDuration); setFloatValues(0f, 1f); setInterpolator(Interpolators.FAST_OUT_SLOW_IN); @@ -177,6 +182,8 @@ public class PipEnterAnimator extends ValueAnimator { mTransformTensor.postRotate(degrees); tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp); + tx.setCornerRadius(mLeash, mCornerRadius).setShadowRadius(mLeash, mShadowRadius); + if (mContentOverlay != null) { mContentOverlay.onAnimationUpdate(tx, 1f / scaleX, fraction, mEndBounds); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java index 4558a9f141c8..06e8349259b4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java @@ -29,6 +29,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.R; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.shared.animation.Interpolators; @@ -50,6 +51,9 @@ public class PipResizeAnimator extends ValueAnimator { private Runnable mAnimationEndCallback; private RectEvaluator mRectEvaluator; + private final int mCornerRadius; + private final int mShadowRadius; + // Bounds relative to which scaling/cropping must be done. private final Rect mBaseBounds = new Rect(); @@ -74,7 +78,8 @@ public class PipResizeAnimator extends ValueAnimator { mAnimationStartCallback.run(); } if (mStartTx != null) { - setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta); + setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta, + mCornerRadius, mShadowRadius); mStartTx.apply(); } } @@ -83,7 +88,8 @@ public class PipResizeAnimator extends ValueAnimator { public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); if (mFinishTx != null) { - setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f); + setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f, + mCornerRadius, mShadowRadius); } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); @@ -99,7 +105,8 @@ public class PipResizeAnimator extends ValueAnimator { mSurfaceControlTransactionFactory.getTransaction(); final float fraction = getAnimatedFraction(); final float degrees = (1.0f - fraction) * mDelta; - setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees); + setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees, + mCornerRadius, mShadowRadius); tx.apply(); } }; @@ -128,6 +135,9 @@ public class PipResizeAnimator extends ValueAnimator { mRectEvaluator = new RectEvaluator(mAnimatedRect); + mCornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); + mShadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius); + setObjectValues(startBounds, endBounds); setInterpolator(Interpolators.FAST_OUT_SLOW_IN); addListener(mAnimatorListener); @@ -152,7 +162,7 @@ public class PipResizeAnimator extends ValueAnimator { * @param degrees degrees of rotation - counter-clockwise is positive by convention. */ private static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash, - Rect baseBounds, Rect targetBounds, float degrees) { + Rect baseBounds, Rect targetBounds, float degrees, int cornerRadius, int shadowRadius) { Matrix transformTensor = new Matrix(); final float[] mMatrixTmp = new float[9]; final float scaleX = (float) targetBounds.width() / baseBounds.width(); @@ -162,7 +172,9 @@ public class PipResizeAnimator extends ValueAnimator { transformTensor.postTranslate(targetBounds.left, targetBounds.top); transformTensor.postRotate(degrees, targetBounds.centerX(), targetBounds.centerY()); - tx.setMatrix(leash, transformTensor, mMatrixTmp); + tx.setMatrix(leash, transformTensor, mMatrixTmp) + .setCornerRadius(leash, cornerRadius) + .setShadowRadius(leash, shadowRadius); } @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java index 3738353dd0a3..fd387d1811fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java @@ -785,8 +785,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private void handleFlingTransition(SurfaceControl.Transaction startTx, SurfaceControl.Transaction finishTx, Rect destinationBounds) { - startTx.setPosition(mPipTransitionState.getPinnedTaskLeash(), - destinationBounds.left, destinationBounds.top); + SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash(); + int cornerRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius); + int shadowRadius = mContext.getResources().getDimensionPixelSize(R.dimen.pip_shadow_radius); + + // merge transactions so everything is done on startTx + startTx.merge(finishTx); + + startTx.setPosition(pipLeash, destinationBounds.left, destinationBounds.top) + .setCornerRadius(pipLeash, cornerRadius) + .setShadowRadius(pipLeash, shadowRadius); startTx.apply(); // All motion operations have actually finished, so make bounds cache updates. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 99f37999a2d2..852eee5f6672 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -600,13 +600,25 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private Rect calculateBoundingRectLocal(@NonNull OccludingCaptionElement element, int elementWidthPx, @NonNull Rect captionRect) { + final boolean isRtl = + mDecorWindowContext.getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_RTL; switch (element.mAlignment) { case START -> { - return new Rect(0, 0, elementWidthPx, captionRect.height()); + if (isRtl) { + return new Rect(captionRect.width() - elementWidthPx, 0, + captionRect.width(), captionRect.height()); + } else { + return new Rect(0, 0, elementWidthPx, captionRect.height()); + } } case END -> { - return new Rect(captionRect.width() - elementWidthPx, 0, - captionRect.width(), captionRect.height()); + if (isRtl) { + return new Rect(0, 0, elementWidthPx, captionRect.height()); + } else { + return new Rect(captionRect.width() - elementWidthPx, 0, + captionRect.width(), captionRect.height()); + } } } throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt index 503ad92d4d71..5f25f42039ef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt @@ -114,7 +114,7 @@ internal class AppHandleViewHolder( // If handle is not in status bar region(i.e., bottom stage in vertical split), // do not create an input layer if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return - if (!isCaptionVisible && statusBarInputLayerExists) { + if (!isCaptionVisible) { disposeStatusBarInputLayer() return } @@ -227,6 +227,7 @@ internal class AppHandleViewHolder( * is not visible. */ fun disposeStatusBarInputLayer() { + if (!statusBarInputLayerExists) return statusBarInputLayerExists = false handler.post { statusBarInputLayer?.releaseView() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java index a4008c1e6995..72c466663a56 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -39,6 +40,7 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.R; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import com.android.wm.shell.pip2.phone.PipAppIconOverlay; @@ -49,33 +51,25 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** - * Unit test again {@link PipEnterAnimator}. + * Unit test against {@link PipEnterAnimator}. */ @SmallTest @TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner.class) public class PipEnterAnimatorTest { + private static final float TEST_CORNER_RADIUS = 1f; + private static final float TEST_SHADOW_RADIUS = 2f; @Mock private Context mMockContext; - @Mock private Resources mMockResources; - @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; - @Mock private SurfaceControl.Transaction mMockAnimateTransaction; - @Mock private SurfaceControl.Transaction mMockStartTransaction; - @Mock private SurfaceControl.Transaction mMockFinishTransaction; - @Mock private Runnable mMockStartCallback; - @Mock private Runnable mMockEndCallback; - @Mock private PipAppIconOverlay mMockPipAppIconOverlay; - @Mock private SurfaceControl mMockAppIconOverlayLeash; - @Mock private ActivityInfo mMockActivityInfo; @Surface.Rotation private int mRotation; @@ -89,13 +83,15 @@ public class PipEnterAnimatorTest { when(mMockContext.getResources()).thenReturn(mMockResources); when(mMockResources.getInteger(anyInt())).thenReturn(0); when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction); - when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) - .thenReturn(mMockAnimateTransaction); - when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) - .thenReturn(mMockStartTransaction); - when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) - .thenReturn(mMockFinishTransaction); when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash); + when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius)) + .thenReturn((int) TEST_CORNER_RADIUS); + when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius)) + .thenReturn((int) TEST_SHADOW_RADIUS); + + prepareTransaction(mMockAnimateTransaction); + prepareTransaction(mMockStartTransaction); + prepareTransaction(mMockFinishTransaction); mTestLeash = new SurfaceControl.Builder() .setContainerLayer() @@ -122,6 +118,12 @@ public class PipEnterAnimatorTest { verify(mMockStartCallback).run(); verifyZeroInteractions(mMockEndCallback); + + // Check corner and shadow radii were set + verify(mMockAnimateTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockAnimateTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); } @Test @@ -142,6 +144,12 @@ public class PipEnterAnimatorTest { verify(mMockStartCallback).run(); verify(mMockEndCallback).run(); + + // Check corner and shadow radii were set + verify(mMockAnimateTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockAnimateTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); } @Test @@ -197,5 +205,21 @@ public class PipEnterAnimatorTest { verify(mMockPipAppIconOverlay).onAnimationUpdate( eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds)); + + // Check corner and shadow radii were set + verify(mMockAnimateTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockAnimateTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); + } + + // set up transaction chaining + private void prepareTransaction(SurfaceControl.Transaction tx) { + when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(tx); + when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(tx); + when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(tx); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java index 0adb50b81896..23fbad05ec99 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java @@ -16,15 +16,18 @@ package com.android.wm.shell.pip2.animation; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.kotlin.MatchersKt.eq; -import static org.junit.Assert.assertEquals; import android.content.Context; +import android.content.res.Resources; import android.graphics.Matrix; import android.graphics.Rect; import android.testing.AndroidTestingRunner; @@ -34,12 +37,14 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.wm.shell.R; import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -52,40 +57,40 @@ import org.mockito.MockitoAnnotations; public class PipResizeAnimatorTest { private static final float FLOAT_COMPARISON_DELTA = 0.001f; + private static final float TEST_CORNER_RADIUS = 1f; + private static final float TEST_SHADOW_RADIUS = 2f; @Mock private Context mMockContext; - + @Mock private Resources mMockResources; @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory; - @Mock private SurfaceControl.Transaction mMockTransaction; - @Mock private SurfaceControl.Transaction mMockStartTransaction; - @Mock private SurfaceControl.Transaction mMockFinishTransaction; - @Mock private Runnable mMockStartCallback; - @Mock private Runnable mMockEndCallback; + @Captor private ArgumentCaptor<Matrix> mArgumentCaptor; + private PipResizeAnimator mPipResizeAnimator; private Rect mBaseBounds; private Rect mStartBounds; private Rect mEndBounds; private SurfaceControl mTestLeash; - private ArgumentCaptor<Matrix> mArgumentCaptor; @Before public void setUp() { MockitoAnnotations.initMocks(this); when(mMockFactory.getTransaction()).thenReturn(mMockTransaction); - when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) - .thenReturn(mMockTransaction); - when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) - .thenReturn(mMockStartTransaction); - when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) - .thenReturn(mMockFinishTransaction); - - mArgumentCaptor = ArgumentCaptor.forClass(Matrix.class); + when(mMockContext.getResources()).thenReturn(mMockResources); + when(mMockResources.getDimensionPixelSize(R.dimen.pip_corner_radius)) + .thenReturn((int) TEST_CORNER_RADIUS); + when(mMockResources.getDimensionPixelSize(R.dimen.pip_shadow_radius)) + .thenReturn((int) TEST_SHADOW_RADIUS); + + prepareTransaction(mMockTransaction); + prepareTransaction(mMockStartTransaction); + prepareTransaction(mMockFinishTransaction); + mTestLeash = new SurfaceControl.Builder() .setContainerLayer() .setName("PipResizeAnimatorTest") @@ -187,6 +192,12 @@ public class PipResizeAnimatorTest { assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA); assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA); assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA); + + // Check corner and shadow radii were set + verify(mMockTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); } @Test @@ -237,6 +248,12 @@ public class PipResizeAnimatorTest { assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA); assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA); assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA); + + // Check corner and shadow radii were set + verify(mMockTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); } @Test @@ -272,5 +289,21 @@ public class PipResizeAnimatorTest { mArgumentCaptor.getValue().getValues(matrix); assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA); assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA); + + // Check corner and shadow radii were set + verify(mMockTransaction, atLeastOnce()) + .setCornerRadius(eq(mTestLeash), eq(TEST_CORNER_RADIUS)); + verify(mMockTransaction, atLeastOnce()) + .setShadowRadius(eq(mTestLeash), eq(TEST_SHADOW_RADIUS)); + } + + // set up transaction chaining + private void prepareTransaction(SurfaceControl.Transaction tx) { + when(tx.setMatrix(any(SurfaceControl.class), any(Matrix.class), any())) + .thenReturn(tx); + when(tx.setCornerRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(tx); + when(tx.setShadowRadius(any(SurfaceControl.class), anyFloat())) + .thenReturn(tx); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index f7b190ce4e2d..f653622d0460 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -220,7 +220,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Captor private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable; - private final InsetsState mInsetsState = new InsetsState(); + private final InsetsState mInsetsState = createInsetsState(statusBars(), /* visible= */true); private SurfaceControl.Transaction mMockTransaction; private StaticMockitoSession mMockitoSession; private TestableContext mTestableContext; @@ -1433,8 +1433,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void notifyCaptionStateChanged_flagDisabled_doNoNotify() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), /* visible= */true)); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); @@ -1448,8 +1446,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void notifyCaptionStateChanged_inFullscreenMode_notifiesAppHandleVisible() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), /* visible= */true)); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( @@ -1469,8 +1465,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void notifyCaptionStateChanged_inWindowingMode_notifiesAppHeaderVisible() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), /* visible= */true)); when(mMockAppHeaderViewHolder.getAppChipLocationInWindow()).thenReturn( new Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3)); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); @@ -1498,8 +1492,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void notifyCaptionStateChanged_taskNotVisible_notifiesNoCaptionVisible() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ false); - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), /* visible= */true)); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_UNDEFINED); ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( @@ -1518,8 +1510,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void notifyCaptionStateChanged_captionHandleExpanded_notifiesHandleMenuExpanded() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), /* visible= */true)); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( @@ -1543,8 +1533,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { public void notifyCaptionStateChanged_captionHandleClosed_notifiesHandleMenuClosed() { when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true); final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - when(mMockDisplayController.getInsetsState(taskInfo.displayId)) - .thenReturn(createInsetsState(statusBars(), /* visible= */true)); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); ArgumentCaptor<CaptionState> captionStateArgumentCaptor = ArgumentCaptor.forClass( diff --git a/libs/hwui/jni/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp index 46db8633c66e..ad0e540b5b40 100644 --- a/libs/hwui/jni/RuntimeEffectUtils.cpp +++ b/libs/hwui/jni/RuntimeEffectUtils.cpp @@ -90,7 +90,7 @@ void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* child SkFlattenable* childEffect) { SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName); if (builderChild.fChild == nullptr) { - ThrowIAEFmt(env, "unable to find shader named %s", childName); + ThrowIAEFmt(env, "unable to find child named %s", childName); return; } diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index 2a057e7a4cdc..018c2b1374d0 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -2,6 +2,7 @@ #include "Gainmap.h" #include "GraphicsJNI.h" +#include "RuntimeEffectUtils.h" #include "SkBitmap.h" #include "SkBlendMode.h" #include "SkColor.h" @@ -280,50 +281,6 @@ static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) { return ret; } -static bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) { - switch (type) { - case SkRuntimeEffect::Uniform::Type::kFloat: - case SkRuntimeEffect::Uniform::Type::kFloat2: - case SkRuntimeEffect::Uniform::Type::kFloat3: - case SkRuntimeEffect::Uniform::Type::kFloat4: - case SkRuntimeEffect::Uniform::Type::kFloat2x2: - case SkRuntimeEffect::Uniform::Type::kFloat3x3: - case SkRuntimeEffect::Uniform::Type::kFloat4x4: - return false; - case SkRuntimeEffect::Uniform::Type::kInt: - case SkRuntimeEffect::Uniform::Type::kInt2: - case SkRuntimeEffect::Uniform::Type::kInt3: - case SkRuntimeEffect::Uniform::Type::kInt4: - return true; - } -} - -static void UpdateFloatUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, - const char* uniformName, const float values[], int count, - bool isColor) { - SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName); - if (uniform.fVar == nullptr) { - ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); - } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) { - if (isColor) { - jniThrowExceptionFmt( - env, "java/lang/IllegalArgumentException", - "attempting to set a color uniform using the non-color specific APIs: %s %x", - uniformName, uniform.fVar->flags); - } else { - ThrowIAEFmt(env, - "attempting to set a non-color uniform using the setColorUniform APIs: %s", - uniformName); - } - } else if (isIntUniformType(uniform.fVar->type)) { - ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s", - uniformName); - } else if (!uniform.set<float>(values, count)) { - ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", - uniform.fVar->sizeInBytes(), sizeof(float) * count); - } -} - static void RuntimeShader_updateFloatUniforms(JNIEnv* env, jobject, jlong shaderBuilder, jstring jUniformName, jfloat value1, jfloat value2, jfloat value3, jfloat value4, jint count) { @@ -342,20 +299,6 @@ static void RuntimeShader_updateFloatArrayUniforms(JNIEnv* env, jobject, jlong s UpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(), isColor); } -static void UpdateIntUniforms(JNIEnv* env, SkRuntimeShaderBuilder* builder, const char* uniformName, - const int values[], int count) { - SkRuntimeShaderBuilder::BuilderUniform uniform = builder->uniform(uniformName); - if (uniform.fVar == nullptr) { - ThrowIAEFmt(env, "unable to find uniform named %s", uniformName); - } else if (!isIntUniformType(uniform.fVar->type)) { - ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s", - uniformName); - } else if (!uniform.set<int>(values, count)) { - ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]", - uniform.fVar->sizeInBytes(), sizeof(float) * count); - } -} - static void RuntimeShader_updateIntUniforms(JNIEnv* env, jobject, jlong shaderBuilder, jstring jUniformName, jint value1, jint value2, jint value3, jint value4, jint count) { @@ -388,6 +331,15 @@ static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder builder->child(name.c_str()) = sk_ref_sp(shader); } +static void RuntimeShader_updateChild(JNIEnv* env, jobject, jlong shaderBuilder, + jstring jUniformName, jlong childHandle) { + SkRuntimeShaderBuilder* builder = reinterpret_cast<SkRuntimeShaderBuilder*>(shaderBuilder); + ScopedUtfChars name(env, jUniformName); + auto* childEffect = reinterpret_cast<SkFlattenable*>(childHandle); + + UpdateChild(env, builder, name.c_str(), childEffect); +} + /////////////////////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gShaderMethods[] = { @@ -428,6 +380,7 @@ static const JNINativeMethod gRuntimeShaderMethods[] = { {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V", (void*)RuntimeShader_updateIntUniforms}, {"nativeUpdateShader", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateShader}, + {"nativeUpdateChild", "(JLjava/lang/String;J)V", (void*)RuntimeShader_updateChild}, }; int register_android_graphics_Shader(JNIEnv* env) diff --git a/media/java/android/media/ImageWriter.java b/media/java/android/media/ImageWriter.java index 93259992d339..0a79f41e1ff0 100644 --- a/media/java/android/media/ImageWriter.java +++ b/media/java/android/media/ImageWriter.java @@ -1157,7 +1157,11 @@ public class ImageWriter implements AutoCloseable { @Override public void setFence(@NonNull SyncFence fence) throws IOException { throwISEIfImageIsInvalid(); - nativeSetFenceFd(fence.getFdDup().detachFd()); + if (fence.isValid()) { + nativeSetFenceFd(fence.getFdDup().detachFd()); + } else { + nativeSetFenceFd(-1); + } } @Override diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 5b1ea8b81c80..d8a8c8b0ee2a 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -1,13 +1,7 @@ package: "com.android.media.flags" container: "system" -flag { - name: "enable_rlp_callbacks_in_media_router2" - is_exported: true - namespace: "media_solutions" - description: "Make RouteListingPreference getter and callbacks public in MediaRouter2." - bug: "281067101" -} +# Flags are ordered alphabetically by name. flag { name: "adjust_volume_for_foreground_app_playing_audio_without_media_session" @@ -17,6 +11,13 @@ flag { } flag { + name: "enable_audio_input_device_routing_and_volume_control" + namespace: "media_better_together" + description: "Allows audio input devices routing and volume control via system settings." + bug: "355684672" +} + +flag { name: "enable_audio_policies_device_and_bluetooth_controller" is_exported: true namespace: "media_solutions" @@ -25,64 +26,70 @@ flag { } flag { - name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling" - namespace: "media_solutions" - description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller." - bug: "293743975" + name: "enable_built_in_speaker_route_suitability_statuses" + is_exported: true + namespace: "media_solutions" + description: "Make MediaRoute2Info provide information about routes suitability for transfer." + bug: "279555229" } flag { - name: "enable_waiting_state_for_system_session_creation_request" + name: "enable_cross_user_routing_in_media_router2" + is_exported: true namespace: "media_solutions" - description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id." - bug: "307723189" + description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users." + bug: "288580225" } flag { - name: "enable_new_media_route_2_info_types" - is_exported: true - namespace: "media_solutions" - description: "Enables the following type constants in MediaRoute2Info: CAR, COMPUTER, GAME_CONSOLE, SMARTPHONE, SMARTWATCH, TABLET, TABLET_DOCKED. Note that this doesn't gate any behavior. It only guards some API int symbols." - bug: "301713440" + name: "enable_full_scan_with_media_content_control" + namespace: "media_better_together" + description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground." + bug: "352401364" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { - name: "enable_new_wired_media_route_2_info_types" + name: "enable_get_transferable_routes" is_exported: true - namespace: "media_tv" - description: "Enables the following type constant in MediaRoute2Info: LINE_ANALOG, LINE_DIGITAL, AUX_LINE" - bug: "375691732" + namespace: "media_solutions" + description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API." + bug: "323154573" } flag { - name: "enable_privileged_routing_for_media_routing_control" - is_exported: true - namespace: "media_solutions" - description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders." - bug: "305919655" + name: "enable_mirroring_in_media_router_2" + namespace: "media_better_together" + description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes." + bug: "362507305" } flag { - name: "enable_cross_user_routing_in_media_router2" - is_exported: true + name: "enable_mr2_service_non_main_bg_thread" namespace: "media_solutions" - description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users." - bug: "288580225" + description: "Enables the use of a background thread in the media routing framework, instead of using the main thread." + bug: "310145678" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { - name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name" + name: "enable_new_media_route_2_info_types" + is_exported: true namespace: "media_solutions" - description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos." - bug: "314324170" + description: "Enables the following type constants in MediaRoute2Info: CAR, COMPUTER, GAME_CONSOLE, SMARTPHONE, SMARTWATCH, TABLET, TABLET_DOCKED. Note that this doesn't gate any behavior. It only guards some API int symbols." + bug: "301713440" } flag { - name: "enable_built_in_speaker_route_suitability_statuses" - is_exported: true - namespace: "media_solutions" - description: "Make MediaRoute2Info provide information about routes suitability for transfer." - bug: "279555229" + name: "enable_new_wired_media_route_2_info_types" + is_exported: true + namespace: "media_tv" + description: "Enables the following type constant in MediaRoute2Info: LINE_ANALOG, LINE_DIGITAL, AUX_LINE" + bug: "375691732" } flag { @@ -94,11 +101,10 @@ flag { } flag { - name: "enable_get_transferable_routes" - is_exported: true + name: "enable_null_session_in_media_browser_service" namespace: "media_solutions" - description: "Exposes RoutingController#getTransferableRoutes() (previously hidden) to the public API." - bug: "323154573" + description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers." + bug: "185136506" } flag { @@ -109,60 +115,56 @@ flag { } flag { - name: "enable_mr2_service_non_main_bg_thread" + name: "enable_prevention_of_manager_scans_when_no_apps_scan" namespace: "media_solutions" - description: "Enables the use of a background thread in the media routing framework, instead of using the main thread." - bug: "310145678" + description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning." + bug: "319604673" metadata { purpose: PURPOSE_BUGFIX } } flag { - name: "enable_screen_off_scanning" + name: "enable_privileged_routing_for_media_routing_control" is_exported: true namespace: "media_solutions" - description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off." - bug: "281072508" + description: "Allow access to privileged routing capabilities to MEDIA_ROUTING_CONTROL holders." + bug: "305919655" } flag { - name: "enable_null_session_in_media_browser_service" + name: "enable_rlp_callbacks_in_media_router2" + is_exported: true namespace: "media_solutions" - description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers." - bug: "185136506" + description: "Make RouteListingPreference getter and callbacks public in MediaRouter2." + bug: "281067101" } flag { - name: "enable_prevention_of_manager_scans_when_no_apps_scan" + name: "enable_screen_off_scanning" + is_exported: true namespace: "media_solutions" - description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning." - bug: "319604673" - metadata { - purpose: PURPOSE_BUGFIX - } + description: "Enable new MediaRouter2 API to enable watch companion apps to scan while the phone screen is off." + bug: "281072508" } flag { - name: "enable_full_scan_with_media_content_control" - namespace: "media_better_together" - description: "Allows holders of the MEDIA_CONTENT_CONTROL permission to scan for routes while not in the foreground." - bug: "352401364" - metadata { - purpose: PURPOSE_BUGFIX - } + name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name" + namespace: "media_solutions" + description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos." + bug: "314324170" } flag { - name: "enable_audio_input_device_routing_and_volume_control" - namespace: "media_better_together" - description: "Allows audio input devices routing and volume control via system settings." - bug: "355684672" + name: "enable_waiting_state_for_system_session_creation_request" + namespace: "media_solutions" + description: "Introduces a waiting state for the session creation request and prevents it from early failing when the selectedRoute from the bluetooth stack doesn't match the pending request route id." + bug: "307723189" } flag { - name: "enable_mirroring_in_media_router_2" - namespace: "media_better_together" - description: "Enables support for mirroring routes in the MediaRouter2 framework, allowing Output Switcher to offer mirroring routes." - bug: "362507305" + name: "fallback_to_default_handling_when_media_session_has_fixed_volume_handling" + namespace: "media_solutions" + description: "Fallbacks to the default handling for volume adjustment when media session has fixed volume handling and its app is in the foreground and setting a media controller." + bug: "293743975" } diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java index 5c11def43209..273f21e01f86 100644 --- a/media/java/android/media/quality/AmbientBacklightEvent.java +++ b/media/java/android/media/quality/AmbientBacklightEvent.java @@ -40,7 +40,7 @@ public final class AmbientBacklightEvent implements Parcelable { @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED, AMBIENT_BACKLIGHT_EVENT_METADATA, AMBIENT_BACKLIGHT_EVENT_INTERRUPTED}) - public @interface AmbientBacklightEventTypes {} + public @interface Type {} /** * Event type for ambient backlight events. The ambient backlight is enabled. @@ -69,9 +69,9 @@ public final class AmbientBacklightEvent implements Parcelable { private final AmbientBacklightMetadata mMetadata; /** - * Constructor of AmbientBacklightEvent. + * Constructs AmbientBacklightEvent. */ - public AmbientBacklightEvent(int eventType, + public AmbientBacklightEvent(@Type int eventType, @Nullable AmbientBacklightMetadata metadata) { mEventType = eventType; mMetadata = metadata; @@ -85,6 +85,7 @@ public final class AmbientBacklightEvent implements Parcelable { /** * Gets event type. */ + @Type public int getEventType() { return mEventType; } diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java index 9c11f9a3e560..5cea10d2de43 100644 --- a/media/java/android/media/quality/AmbientBacklightMetadata.java +++ b/media/java/android/media/quality/AmbientBacklightMetadata.java @@ -29,6 +29,9 @@ import java.util.Arrays; /** * Metadata of ambient backlight. + * + * <p>A metadata instance is sent from ambient backlight hardware in a {@link AmbientBacklightEvent} + * with {@link AmbientBacklightEvent#AMBIENT_BACKLIGHT_EVENT_METADATA}. * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) @@ -44,10 +47,15 @@ public final class AmbientBacklightMetadata implements Parcelable { private final int[] mZonesColors; /** - * Constructor of AmbientBacklightMetadata. + * Constructs AmbientBacklightMetadata. */ - public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm, - int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber, + public AmbientBacklightMetadata( + @NonNull String packageName, + @AmbientBacklightSettings.CompressAlgorithm int compressAlgorithm, + @AmbientBacklightSettings.Source int source, + @PixelFormat.Format int colorFormat, + int horizontalZonesNumber, + int verticalZonesNumber, @NonNull int[] zonesColors) { mPackageName = packageName; mCompressAlgorithm = compressAlgorithm; @@ -69,7 +77,7 @@ public final class AmbientBacklightMetadata implements Parcelable { } /** - * Gets package name. + * Gets package name of the metadata. * @hide */ @NonNull @@ -102,7 +110,9 @@ public final class AmbientBacklightMetadata implements Parcelable { } /** - * Gets the number of lights in each horizontal zone. + * Gets the number of horizontal color zones. + * + * <p>A color zone is a group of lights that always display the same color. */ @IntRange(from = 0) public int getHorizontalZonesNumber() { @@ -110,7 +120,9 @@ public final class AmbientBacklightMetadata implements Parcelable { } /** - * Gets the number of lights in each vertical zone. + * Gets the number of vertical color zones. + * + * <p>A color zone is a group of lights that always display the same color. */ @IntRange(from = 0) public int getVerticalZonesNumber() { @@ -118,10 +130,11 @@ public final class AmbientBacklightMetadata implements Parcelable { } /** + * Gets color data of vertical color zones. * @hide */ @NonNull - public int[] getZonesColors() { + public int[] getVerticalZonesColors() { return mZonesColors; } diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java index 4ed7bc79fdca..d904cf728da7 100644 --- a/media/java/android/media/quality/AmbientBacklightSettings.java +++ b/media/java/android/media/quality/AmbientBacklightSettings.java @@ -30,7 +30,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Settings for ambient backlight. + * Settings to configure ambient backlight hardware. * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) @@ -124,8 +124,13 @@ public final class AmbientBacklightSettings implements Parcelable { /** * Constructs AmbientBacklightSettings. */ - public AmbientBacklightSettings(int source, int maxFps, int colorFormat, - int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted, + public AmbientBacklightSettings( + @Source int source, + int maxFps, + @PixelFormat.Format int colorFormat, + int horizontalZonesNumber, + int verticalZonesNumber, + boolean isLetterboxOmitted, int threshold) { mSource = source; mMaxFps = maxFps; @@ -171,7 +176,9 @@ public final class AmbientBacklightSettings implements Parcelable { } /** - * Gets the number of lights in each horizontal zone. + * Gets the number of horizontal color zones. + * + * <p>A color zone is a group of lights that always display the same color. */ @IntRange(from = 0) public int getHorizontalZonesNumber() { @@ -179,7 +186,9 @@ public final class AmbientBacklightSettings implements Parcelable { } /** - * Gets the number of lights in each vertical zone. + * Gets the number of vertical color zones. + * + * <p>A color zone is a group of lights that always display the same color. */ @IntRange(from = 0) public int getVerticalZonesNumber() { @@ -187,7 +196,11 @@ public final class AmbientBacklightSettings implements Parcelable { } /** - * Returns {@code true} if letter box is omitted; {@code false} otherwise. + * Returns {@code true} if the black portion of the screen in letter box mode is omitted; + * {@code false} otherwise. + * + * <p>Letter-box is a technique to keep the original aspect ratio when displayed on a screen + * with different aspect ratio. Black bars are added to the top and bottom. * @hide */ public boolean isLetterboxOmitted() { @@ -195,6 +208,10 @@ public final class AmbientBacklightSettings implements Parcelable { } /** + * Gets the detection threshold of the ambient light. + * + * <p>If the color of a color zone is changed by the difference is smaller than the threshold, + * the change is ignored. * @hide */ public int getThreshold() { diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl index 250d59b7c2d7..aaedf21999f7 100644 --- a/media/java/android/media/quality/IMediaQualityManager.aidl +++ b/media/java/android/media/quality/IMediaQualityManager.aidl @@ -42,10 +42,12 @@ interface IMediaQualityManager { SoundProfile createSoundProfile(in SoundProfile pp); void updateSoundProfile(in String id, in SoundProfile pp); void removeSoundProfile(in String id); - SoundProfile getSoundProfileById(in String id); + SoundProfile getSoundProfile(in int type, in String name); List<SoundProfile> getSoundProfilesByPackage(in String packageName); List<SoundProfile> getAvailableSoundProfiles(); List<String> getSoundProfilePackageNames(); + List<String> getSoundProfileAllowList(); + void setSoundProfileAllowList(in List<String> packages); void registerPictureProfileCallback(in IPictureProfileCallback cb); void registerSoundProfileCallback(in ISoundProfileCallback cb); diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/ISoundProfileCallback.aidl index 72d1524198fd..9043757316bc 100644 --- a/media/java/android/media/quality/ISoundProfileCallback.aidl +++ b/media/java/android/media/quality/ISoundProfileCallback.aidl @@ -17,6 +17,7 @@ package android.media.quality; +import android.media.quality.ParamCapability; import android.media.quality.SoundProfile; /** @@ -24,7 +25,9 @@ import android.media.quality.SoundProfile; * @hide */ oneway interface ISoundProfileCallback { - void onSoundProfileAdded(in long id, in SoundProfile p); - void onSoundProfileUpdated(in long id, in SoundProfile p); - void onSoundProfileRemoved(in long id, in SoundProfile p); + void onSoundProfileAdded(in String id, in SoundProfile p); + void onSoundProfileUpdated(in String id, in SoundProfile p); + void onSoundProfileRemoved(in String id, in SoundProfile p); + void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps); + void onError(in int err); } diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java index 4d4526cf9925..dcf497122053 100644 --- a/media/java/android/media/quality/MediaQualityManager.java +++ b/media/java/android/media/quality/MediaQualityManager.java @@ -111,7 +111,7 @@ public final class MediaQualityManager { }; ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() { @Override - public void onSoundProfileAdded(long profileId, SoundProfile profile) { + public void onSoundProfileAdded(String profileId, SoundProfile profile) { synchronized (mLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { // TODO: filter callback record @@ -120,7 +120,7 @@ public final class MediaQualityManager { } } @Override - public void onSoundProfileUpdated(long profileId, SoundProfile profile) { + public void onSoundProfileUpdated(String profileId, SoundProfile profile) { synchronized (mLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { // TODO: filter callback record @@ -129,7 +129,7 @@ public final class MediaQualityManager { } } @Override - public void onSoundProfileRemoved(long profileId, SoundProfile profile) { + public void onSoundProfileRemoved(String profileId, SoundProfile profile) { synchronized (mLock) { for (SoundProfileCallbackRecord record : mSpCallbackRecords) { // TODO: filter callback record @@ -137,6 +137,24 @@ public final class MediaQualityManager { } } } + @Override + public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) { + synchronized (mLock) { + for (SoundProfileCallbackRecord record : mSpCallbackRecords) { + // TODO: filter callback record + record.postParamCapabilitiesChanged(profileId, caps); + } + } + } + @Override + public void onError(int err) { + synchronized (mLock) { + for (SoundProfileCallbackRecord record : mSpCallbackRecords) { + // TODO: filter callback record + record.postError(err); + } + } + } }; IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() { @Override @@ -331,14 +349,17 @@ public final class MediaQualityManager { /** - * Gets sound profile by given profile ID. - * @return the corresponding sound profile if available; {@code null} if the ID doesn't - * exist or the profile is not accessible to the caller. + * Gets sound profile by given profile type and name. + * + * @return the corresponding sound profile if available; {@code null} if the name doesn't + * exist. * @hide */ - public SoundProfile getSoundProfileById(String profileId) { + @Nullable + public SoundProfile getSoundProfile( + @SoundProfile.ProfileType int type, @NonNull String name) { try { - return mService.getSoundProfileById(profileId); + return mService.getSoundProfile(type, name); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -349,8 +370,9 @@ public final class MediaQualityManager { * @SystemApi gets profiles that available to the given package * @hide */ + @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) - public List<SoundProfile> getSoundProfilesByPackage(String packageName) { + public List<SoundProfile> getSoundProfilesByPackage(@NonNull String packageName) { try { return mService.getSoundProfilesByPackage(packageName); } catch (RemoteException e) { @@ -362,6 +384,7 @@ public final class MediaQualityManager { * Gets profiles that available to the caller package * @hide */ + @NonNull public List<SoundProfile> getAvailableSoundProfiles() { try { return mService.getAvailableSoundProfiles(); @@ -371,9 +394,12 @@ public final class MediaQualityManager { } /** - * @SystemApi all stored sound profiles of all packages + * @SystemApi Gets all package names whose sound profiles are available. + * + * @see #getSoundProfilesByPackage(String) * @hide */ + @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) public List<String> getSoundProfilePackageNames() { try { @@ -387,12 +413,13 @@ public final class MediaQualityManager { /** * Creates a sound profile and store it in the system. * - * @return the stored profile with an assigned profile ID. + * <p>If the profile is created successfully, + * {@link SoundProfileCallback#onSoundProfileAdded(long, SoundProfile)} is invoked. * @hide */ - public SoundProfile createSoundProfile(SoundProfile sp) { + public void createSoundProfile(@NonNull SoundProfile sp) { try { - return mService.createSoundProfile(sp); + mService.createSoundProfile(sp); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -403,7 +430,7 @@ public final class MediaQualityManager { * Updates an existing sound profile and store it in the system. * @hide */ - public void updateSoundProfile(String profileId, SoundProfile sp) { + public void updateSoundProfile(@NonNull String profileId, @NonNull SoundProfile sp) { try { mService.updateSoundProfile(profileId, sp); } catch (RemoteException e) { @@ -416,7 +443,7 @@ public final class MediaQualityManager { * Removes a sound profile from the system. * @hide */ - public void removeSoundProfile(String profileId) { + public void removeSoundProfile(@NonNull String profileId) { try { mService.removeSoundProfile(profileId); } catch (RemoteException e) { @@ -468,6 +495,36 @@ public final class MediaQualityManager { } /** + * Gets the allowlist of packages that can create and removed sound profiles + * + * @see #createSoundProfile(SoundProfile) + * @see #removeSoundProfile(String) + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) + @NonNull + public List<String> getSoundProfileAllowList() { + try { + return mService.getSoundProfileAllowList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets the allowlist of packages that can create and removed sound profiles + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) + public void setSoundProfileAllowList(@NonNull List<String> packageNames) { + try { + mService.setSoundProfileAllowList(packageNames); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise. * @hide */ @@ -567,6 +624,7 @@ public final class MediaQualityManager { /** * Registers a {@link AmbientBacklightCallback}. + * @hide */ public void registerAmbientBacklightCallback( @NonNull @CallbackExecutor Executor executor, @@ -580,6 +638,7 @@ public final class MediaQualityManager { /** * Unregisters the existing {@link AmbientBacklightCallback}. + * @hide */ public void unregisterAmbientBacklightCallback( @NonNull final AmbientBacklightCallback callback) { @@ -600,6 +659,7 @@ public final class MediaQualityManager { * Set the ambient backlight settings. * * @param settings The settings to use for the backlight detector. + * @hide */ public void setAmbientBacklightSettings( @NonNull AmbientBacklightSettings settings) { @@ -615,6 +675,7 @@ public final class MediaQualityManager { * Enables or disables the ambient backlight detection. * * @param enabled {@code true} to enable, {@code false} to disable. + * @hide */ public void setAmbientBacklightEnabled(boolean enabled) { try { @@ -698,7 +759,7 @@ public final class MediaQualityManager { return mCallback; } - public void postSoundProfileAdded(final long id, SoundProfile profile) { + public void postSoundProfileAdded(final String id, SoundProfile profile) { mExecutor.execute(new Runnable() { @Override @@ -708,7 +769,7 @@ public final class MediaQualityManager { }); } - public void postSoundProfileUpdated(final long id, SoundProfile profile) { + public void postSoundProfileUpdated(final String id, SoundProfile profile) { mExecutor.execute(new Runnable() { @Override public void run() { @@ -717,7 +778,7 @@ public final class MediaQualityManager { }); } - public void postSoundProfileRemoved(final long id, SoundProfile profile) { + public void postSoundProfileRemoved(final String id, SoundProfile profile) { mExecutor.execute(new Runnable() { @Override public void run() { @@ -725,6 +786,24 @@ public final class MediaQualityManager { } }); } + + public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + mCallback.onParamCapabilitiesChanged(id, caps); + } + }); + } + + public void postError(int error) { + mExecutor.execute(new Runnable() { + @Override + public void run() { + mCallback.onError(error); + } + }); + } } private static final class AmbientBacklightCallbackRecord { @@ -801,12 +880,13 @@ public final class MediaQualityManager { * This is invoked when parameter capabilities has been changed due to status changes of the * content. * - * @param profileId the ID of the profile used by the media content. + * @param profileId the ID of the profile used by the media content. {@code null} if there + * is no associated profile * @param updatedCaps the updated capabilities. * @hide */ public void onParamCapabilitiesChanged( - @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) { + @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) { } } @@ -816,29 +896,64 @@ public final class MediaQualityManager { */ public abstract static class SoundProfileCallback { /** + * This is invoked when a sound profile has been added. + * + * @param profileId the ID of the profile. + * @param profile the newly added profile. * @hide */ - public void onSoundProfileAdded(long id, SoundProfile profile) { + public void onSoundProfileAdded( + @NonNull String profileId, @NonNull SoundProfile profile) { } + /** + * This is invoked when a sound profile has been updated. + * + * @param profileId the ID of the profile. + * @param profile the profile with updated info. * @hide */ - public void onSoundProfileUpdated(long id, SoundProfile profile) { + public void onSoundProfileUpdated( + @NonNull String profileId, @NonNull SoundProfile profile) { } + /** + * This is invoked when a sound profile has been removed. + * + * @param profileId the ID of the profile. + * @param profile the removed profile. * @hide */ - public void onSoundProfileRemoved(long id, SoundProfile profile) { + public void onSoundProfileRemoved( + @NonNull String profileId, @NonNull SoundProfile profile) { } + /** + * This is invoked when an issue has occurred. + * + * @param errorCode the error code * @hide */ - public void onError(int errorCode) { + public void onError(@SoundProfile.ErrorCode int errorCode) { + } + + /** + * This is invoked when parameter capabilities has been changed due to status changes of the + * content. + * + * @param profileId the ID of the profile used by the media content. {@code null} if there + * is no associated profile + * @param updatedCaps the updated capabilities. + * @hide + */ + public void onParamCapabilitiesChanged( + @Nullable String profileId, @NonNull List<ParamCapability> updatedCaps) { } } /** * Callback used to monitor status of ambient backlight. + * @hide */ public abstract static class AmbientBacklightCallback { /** diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java index 20d117bf15cf..de93afe4316f 100644 --- a/media/java/android/media/quality/SoundProfile.java +++ b/media/java/android/media/quality/SoundProfile.java @@ -17,54 +17,119 @@ package android.media.quality; import android.annotation.FlaggedApi; +import android.annotation.IntDef; +import android.media.tv.TvInputInfo; import android.media.tv.flags.Flags; -import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.os.PersistableBundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresPermission; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** + * Profile for sound quality. * @hide */ @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW) public class SoundProfile implements Parcelable { @Nullable - private Long mId; + private String mId; + private final int mType; @NonNull private final String mName; @Nullable private final String mInputId; - @Nullable + @NonNull private final String mPackageName; @NonNull - private final Bundle mParams; + private final PersistableBundle mParams; - protected SoundProfile(Parcel in) { - if (in.readByte() == 0) { - mId = null; - } else { - mId = in.readLong(); - } + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "TYPE_", value = { + TYPE_SYSTEM, + TYPE_APPLICATION}) + public @interface ProfileType {} + + /** + * System profile type. + * + * <p>A profile of system type is managed by the system, and readable to the package returned by + * {@link #getPackageName()}. + */ + public static final int TYPE_SYSTEM = 1; + /** + * Application profile type. + * + * <p>A profile of application type is managed by the package returned by + * {@link #getPackageName()}. + */ + public static final int TYPE_APPLICATION = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = false, prefix = "ERROR_", value = { + ERROR_UNKNOWN, + ERROR_NO_PERMISSION, + ERROR_DUPLICATE, + ERROR_INVALID_ARGUMENT, + ERROR_NOT_ALLOWLISTED + }) + public @interface ErrorCode {} + + /** + * Error code for unknown errors. + */ + public static final int ERROR_UNKNOWN = 0; + + /** + * Error code for missing necessary permission to handle the profiles. + */ + public static final int ERROR_NO_PERMISSION = 1; + + /** + * Error code for creating a profile with existing profile type and name. + * + * @see #getProfileType() + * @see #getName() + */ + public static final int ERROR_DUPLICATE = 2; + + /** + * Error code for invalid argument. + */ + public static final int ERROR_INVALID_ARGUMENT = 3; + + /** + * Error code for the case when an operation requires an allowlist but the caller is not in the + * list. + * + * @see MediaQualityManager#getSoundProfileAllowList() + */ + public static final int ERROR_NOT_ALLOWLISTED = 4; + + protected SoundProfile(@NonNull Parcel in) { + mId = in.readString(); + mType = in.readInt(); mName = in.readString(); mInputId = in.readString(); mPackageName = in.readString(); - mParams = in.readBundle(); + mParams = in.readPersistableBundle(); } @Override - public void writeToParcel(Parcel dest, int flags) { - if (mId == null) { - dest.writeByte((byte) 0); - } else { - dest.writeByte((byte) 1); - dest.writeLong(mId); - } + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mId); + dest.writeInt(mType); dest.writeString(mName); dest.writeString(mInputId); dest.writeString(mPackageName); - dest.writeBundle(mParams); + dest.writePersistableBundle(mParams); } @Override @@ -72,6 +137,7 @@ public class SoundProfile implements Parcelable { return 0; } + @NonNull public static final Creator<SoundProfile> CREATOR = new Creator<SoundProfile>() { @Override public SoundProfile createFromParcel(Parcel in) { @@ -91,93 +157,164 @@ public class SoundProfile implements Parcelable { * @hide */ public SoundProfile( - @Nullable Long id, + @Nullable String id, + int type, @NonNull String name, @Nullable String inputId, - @Nullable String packageName, - @NonNull Bundle params) { + @NonNull String packageName, + @NonNull PersistableBundle params) { this.mId = id; + this.mType = type; this.mName = name; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name); this.mInputId = inputId; this.mPackageName = packageName; this.mParams = params; } + /** + * Gets profile ID. + * + * <p>A profile ID is a globally unique ID generated and assigned by the system. For profile + * objects retrieved from system (e.g {@link MediaQualityManager#getAvailableSoundProfiles()}) + * this profile ID is non-null; For profiles built locally with {@link Builder}, it's + * {@code null}. + * + * @return the unique profile ID; {@code null} if the profile is built locally with + * {@link Builder}. + */ @Nullable - public Long getProfileId() { + public String getProfileId() { return mId; } + /** + * Only used by system to assign the ID. + * @hide + */ + public void setProfileId(String id) { + mId = id; + } + + /** + * Gets profile type. + */ + @ProfileType + public int getProfileType() { + return mType; + } + + /** + * Gets the profile name. + */ @NonNull public String getName() { return mName; } + /** + * Gets the input ID if the profile is for a TV input. + * + * @return the corresponding TV input ID; {@code null} if the profile is not associated with a + * TV input. + * + * @see TvInputInfo#getId() + */ @Nullable public String getInputId() { return mInputId; } + /** + * Gets the package name of this profile. + * + * <p>The package name defines the user of a profile. Only this specific package and system app + * can access to this profile. + * + * @return the package name; {@code null} if the profile is built locally using + * {@link Builder} and the package is not set. + */ @Nullable public String getPackageName() { return mPackageName; } + + /** + * Gets the parameters of this profile. + * + * <p>The keys of commonly used parameters can be found in + * {@link MediaQualityContract.SoundQuality}. + */ @NonNull - public Bundle getParameters() { - return new Bundle(mParams); + public PersistableBundle getParameters() { + return new PersistableBundle(mParams); } /** * A builder for {@link SoundProfile} + * @hide */ public static class Builder { @Nullable - private Long mId; + private String mId; + private int mType = TYPE_APPLICATION; @NonNull private String mName; @Nullable private String mInputId; - @Nullable + @NonNull private String mPackageName; @NonNull - private Bundle mParams; + private PersistableBundle mParams; /** * Creates a new Builder. - * - * @hide */ public Builder(@NonNull String name) { mName = name; - com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name); } /** - * Copy constructor. - * - * @hide + * Copy constructor of builder. */ public Builder(@NonNull SoundProfile p) { mId = null; // ID needs to be reset + mType = p.getProfileType(); mName = p.getName(); mPackageName = p.getPackageName(); mInputId = p.getInputId(); + mParams = p.getParameters(); } /** - * Sets profile ID. - * @hide using by MediaQualityService + * Only used by system to assign the ID. + * @hide */ @NonNull - public Builder setProfileId(@Nullable Long id) { + public Builder setProfileId(@Nullable String id) { mId = id; return this; } /** + * Sets profile type. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) + @NonNull + public Builder setProfileType(@ProfileType int value) { + mType = value; + return this; + } + + /** * Sets input ID. + * + * @see SoundProfile#getInputId() + * + * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) @NonNull public Builder setInputId(@NonNull String value) { mInputId = value; @@ -186,7 +323,12 @@ public class SoundProfile implements Parcelable { /** * Sets package name of the profile. + * + * @see SoundProfile#getPackageName() + * + * @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE) @NonNull public Builder setPackageName(@NonNull String value) { mPackageName = value; @@ -195,12 +337,15 @@ public class SoundProfile implements Parcelable { /** * Sets profile parameters. + * + * @see SoundProfile#getParameters() */ @NonNull - public Builder setParameters(@NonNull Bundle params) { - mParams = new Bundle(params); + public Builder setParameters(@NonNull PersistableBundle params) { + mParams = new PersistableBundle(params); return this; } + /** * Builds the instance. */ @@ -209,6 +354,7 @@ public class SoundProfile implements Parcelable { SoundProfile o = new SoundProfile( mId, + mType, mName, mInputId, mPackageName, diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index 23dd9b7aee37..4180710534c3 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -45,8 +45,10 @@ cc_library_shared { header_libs: [ "jni_headers", + "native_headers", "libhwui_internal_headers", ], + export_header_lib_headers: ["native_headers"], static_libs: [ "libarect", diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt index 79a0607bbd1c..f587660cae5b 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -57,6 +57,7 @@ package android.nfc { @FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension { method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference(); + method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public int forceRoutingTableCommit(); method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList(); method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public android.nfc.RoutingStatus getRoutingStatus(); method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public java.util.List<android.nfc.NfcRoutingTableEntry> getRoutingTable(); @@ -73,6 +74,9 @@ package android.nfc { method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization(); method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback); + field public static final int COMMIT_ROUTING_STATUS_FAILED = 3; // 0x3 + field public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6; // 0x6 + field public static final int COMMIT_ROUTING_STATUS_OK = 0; // 0x0 field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0 field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1 field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_EE = 3; // 0x3 @@ -96,6 +100,7 @@ package android.nfc { method public void onEnableFinished(int); method public void onEnableRequested(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onEnableStarted(); + method public void onExtractOemPackages(@NonNull android.nfc.NdefMessage, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>); method public void onGetOemAppSearchIntent(@NonNull java.util.List<java.lang.String>, @NonNull java.util.function.Consumer<android.content.Intent>); method public void onHceEventReceived(int); method public void onLaunchHceAppChooserActivity(@NonNull String, @NonNull java.util.List<android.nfc.cardemulation.ApduServiceInfo>, @NonNull android.content.ComponentName, @NonNull String); @@ -106,7 +111,7 @@ package android.nfc { method public void onReaderOptionChanged(boolean); method public void onRfDiscoveryStarted(boolean); method public void onRfFieldActivated(boolean); - method public void onRoutingChanged(); + method public void onRoutingChanged(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public void onRoutingTableFull(); method public void onStateUpdated(int); method public void onTagConnected(boolean); diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index 40fd0683f465..31514a09adad 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -120,4 +120,5 @@ interface INfcAdapter boolean isTagPresent(); List<Entry> getRoutingTableEntryList(); void indicateDataMigration(boolean inProgress, String pkg); + int commitRouting(); } diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl index fb793b024288..1a21c0bae413 100644 --- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl +++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl @@ -41,7 +41,7 @@ interface INfcOemExtensionCallback { void onEnableFinished(int status); void onDisableFinished(int status); void onTagDispatch(in ResultReceiver isSkipped); - void onRoutingChanged(); + void onRoutingChanged(in ResultReceiver isSkipped); void onHceEventReceived(int action); void onReaderOptionChanged(boolean enabled); void onCardEmulationActivated(boolean isActivated); @@ -54,4 +54,5 @@ interface INfcOemExtensionCallback { void onLaunchHceTapAgainActivity(in ApduServiceInfo service, in String category); void onRoutingTableFull(); void onLogEventNotified(in OemLogItems item); + void onExtractOemPackages(in NdefMessage message, in ResultReceiver packageReceiver); } diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java index fd131b8ef55d..326ca6449c53 100644 --- a/nfc/java/android/nfc/NfcOemExtension.java +++ b/nfc/java/android/nfc/NfcOemExtension.java @@ -194,6 +194,30 @@ public final class NfcOemExtension { public @interface StatusCode {} /** + * Routing commit succeeded. + */ + public static final int COMMIT_ROUTING_STATUS_OK = 0; + /** + * Routing commit failed. + */ + public static final int COMMIT_ROUTING_STATUS_FAILED = 3; + /** + * Routing commit failed due to the update is in progress. + */ + public static final int COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS = 6; + + /** + * Status codes returned when calling {@link #forceRoutingTableCommit()} + * @hide + */ + @IntDef(prefix = "COMMIT_ROUTING_STATUS_", value = { + COMMIT_ROUTING_STATUS_OK, + COMMIT_ROUTING_STATUS_FAILED, + COMMIT_ROUTING_STATUS_FAILED_UPDATE_IN_PROGRESS, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CommitRoutingStatusCode {} + /** * Interface for Oem extensions for NFC. */ public interface Callback { @@ -286,8 +310,12 @@ public final class NfcOemExtension { /** * Notifies routing configuration is changed. + * @param isCommitRoutingSkipped The {@link Consumer} to be + * completed. If routing commit should be skipped, + * the {@link Consumer#accept(Object)} should be called with + * {@link Boolean#TRUE}, otherwise call with {@link Boolean#FALSE}. */ - void onRoutingChanged(); + void onRoutingChanged(@NonNull Consumer<Boolean> isCommitRoutingSkipped); /** * API to activate start stop cpu boost on hce event. @@ -404,6 +432,19 @@ public final class NfcOemExtension { * @param item the log items that contains log information of NFC event. */ void onLogEventNotified(@NonNull OemLogItems item); + + /** + * Callback to to extract OEM defined packages from given NDEF message when + * a NFC tag is detected. These are used to handle NFC tags encoded with a + * proprietary format for storing app name (Android native app format). + * + * @param message NDEF message containing OEM package names + * @param packageConsumer The {@link Consumer} to be completed. + * The {@link Consumer#accept(Object)} should be called with + * the list of package names. + */ + void onExtractOemPackages(@NonNull NdefMessage message, + @NonNull Consumer<List<String>> packageConsumer); } @@ -740,6 +781,18 @@ public final class NfcOemExtension { return result; } + /** + * API to force a routing table commit. + * @return a {@link StatusCode} to indicate if commit routing succeeded or not + */ + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION) + @CommitRoutingStatusCode + public int forceRoutingTableCommit() { + return NfcAdapter.callServiceReturn( + () -> NfcAdapter.sService.commitRouting(), COMMIT_ROUTING_STATUS_FAILED); + } + private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub { @Override @@ -843,9 +896,10 @@ public final class NfcOemExtension { new ReceiverWrapper<>(isSkipped), cb::onTagDispatch, ex)); } @Override - public void onRoutingChanged() throws RemoteException { + public void onRoutingChanged(ResultReceiver isSkipped) throws RemoteException { mCallbackMap.forEach((cb, ex) -> - handleVoidCallback(null, (Object input) -> cb.onRoutingChanged(), ex)); + handleVoidCallback( + new ReceiverWrapper<>(isSkipped), cb::onRoutingChanged, ex)); } @Override public void onHceEventReceived(int action) throws RemoteException { @@ -924,6 +978,15 @@ public final class NfcOemExtension { handleVoidCallback(item, cb::onLogEventNotified, ex)); } + @Override + public void onExtractOemPackages(NdefMessage message, ResultReceiver packageConsumer) + throws RemoteException { + mCallbackMap.forEach((cb, ex) -> + handleVoid2ArgCallback(message, + new ReceiverWrapper<>(packageConsumer), + cb::onExtractOemPackages, ex)); + } + private <T> void handleVoidCallback( T input, Consumer<T> callbackMethod, Executor executor) { synchronized (mLock) { @@ -1034,8 +1097,14 @@ public final class NfcOemExtension { Bundle bundle = new Bundle(); bundle.putParcelable("intent", (Intent) result); mResultReceiver.send(0, bundle); + } else if (result instanceof List<?> list) { + if (list.stream().allMatch(String.class::isInstance)) { + Bundle bundle = new Bundle(); + bundle.putStringArray("packageNames", + list.stream().map(pkg -> (String) pkg).toArray(String[]::new)); + mResultReceiver.send(0, bundle); + } } - } @Override diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a4b8821383e0..123f82393679 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1748,6 +1748,13 @@ flag { } flag { + name: "notification_shade_blur" + namespace: "systemui" + description: "Enables the new blur effect on the Notification Shade." + bug: "370555223" +} + +flag { name: "ensure_enr_views_visibility" namespace: "systemui" description: "Ensures public and private visibilities" @@ -1790,4 +1797,4 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2ee9ddb0e453..01ec4d026646 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2875,7 +2875,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { - mKeyguardTransitions.startKeyguardTransition(showing, aodShowing); + startKeyguardTransition(showing, aodShowing); } else { try { @@ -3019,7 +3019,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, final int keyguardFlag = flags; mUiBgExecutor.execute(() -> { if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { - mKeyguardTransitions.startKeyguardTransition( + startKeyguardTransition( false /* keyguardShowing */, false /* aodShowing */); return; } @@ -3035,6 +3035,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } }; + private void startKeyguardTransition(boolean keyguardShowing, boolean aodShowing) { + mKeyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing); + } + private final Runnable mHideAnimationFinishedRunnable = () -> { Log.e(TAG, "mHideAnimationFinishedRunnable#run"); mHideAnimationRunning = false; @@ -3490,8 +3494,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mSurfaceBehindRemoteAnimationRequested = true; if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) { - mKeyguardTransitions.startKeyguardTransition( - false /* keyguardShowing */, false /* aodShowing */); + startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */); return; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 2914cb9fdfdc..a137d6cf91ec 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -122,10 +122,7 @@ constructor( if (visible) { if (enableNewKeyguardShellTransitions) { - keyguardTransitions.startKeyguardTransition( - false /* keyguardShowing */, - false, /* aodShowing */ - ) + startKeyguardTransition(false, /* keyguardShowing */ false /* aodShowing */) isKeyguardGoingAway = true return } @@ -233,7 +230,7 @@ constructor( "aodVisible=$aodVisible).", ) if (enableNewKeyguardShellTransitions) { - keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible) + startKeyguardTransition(lockscreenShowing, aodVisible) } else { activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible) } @@ -241,6 +238,10 @@ constructor( this.isAodVisible = aodVisible } + private fun startKeyguardTransition(keyguardShowing: Boolean, aodShowing: Boolean) { + keyguardTransitions.startKeyguardTransition(keyguardShowing, aodShowing) + } + private fun endKeyguardGoingAwayAnimation() { if (!isKeyguardGoingAway) { Log.d( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt index 13ac321473f4..f3d513940bcf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt @@ -113,34 +113,38 @@ constructor( } private val showIcon = - canShowIcon - .flatMapLatest { canShow -> - if (!canShow) { - flowOf(false) - } else { - combine( - shouldShowIconForOosAfterHysteresis, - interactor.connectionState, - interactor.isWifiActive, - airplaneModeRepository.isAirplaneMode, - ) { showForOos, connectionState, isWifiActive, isAirplaneMode -> - if (isWifiActive || isAirplaneMode) { - false - } else { - showForOos || - connectionState == SatelliteConnectionState.On || - connectionState == SatelliteConnectionState.Connected + if (interactor.isOpportunisticSatelliteIconEnabled) { + canShowIcon + .flatMapLatest { canShow -> + if (!canShow) { + flowOf(false) + } else { + combine( + shouldShowIconForOosAfterHysteresis, + interactor.connectionState, + interactor.isWifiActive, + airplaneModeRepository.isAirplaneMode, + ) { showForOos, connectionState, isWifiActive, isAirplaneMode -> + if (isWifiActive || isAirplaneMode) { + false + } else { + showForOos || + connectionState == SatelliteConnectionState.On || + connectionState == SatelliteConnectionState.Connected + } } } } + .distinctUntilChanged() + .logDiffsForTable( + tableLog, + columnPrefix = "vm", + columnName = COL_VISIBLE, + initialValue = false, + ) + } else { + flowOf(false) } - .distinctUntilChanged() - .logDiffsForTable( - tableLog, - columnPrefix = "vm", - columnName = COL_VISIBLE, - initialValue = false, - ) .stateIn(scope, SharingStarted.WhileSubscribed(), false) override val icon: StateFlow<Icon?> = diff --git a/services/backup/flags.aconfig b/services/backup/flags.aconfig index d53f949753d8..fcb7934f7ca0 100644 --- a/services/backup/flags.aconfig +++ b/services/backup/flags.aconfig @@ -60,3 +60,12 @@ flag { bug: "331749778" is_fixed_read_only: true } + +flag { + name: "enable_restricted_mode_changes" + namespace: "onboarding" + description: "Enables the new framework behavior of not putting apps in restricted mode for " + "B&R operations in certain cases." + bug: "376661510" + is_fixed_read_only: true +} diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 466d477992b3..5de2fb30ac78 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -43,6 +43,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AlarmManager; import android.app.AppGlobals; +import android.app.ApplicationThreadConstants; import android.app.IActivityManager; import android.app.IBackupAgent; import android.app.PendingIntent; @@ -59,6 +60,9 @@ import android.app.backup.IBackupObserver; import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.ISelectBackupTransportCallback; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -298,6 +302,15 @@ public class UserBackupManagerService { private static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; private static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; + /** + * Enables the OS making a decision on whether backup restricted mode should be used for apps + * that haven't explicitly opted in or out. See + * {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE} for details. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA) + public static final long OS_DECIDES_BACKUP_RESTRICTED_MODE = 376661510; + // Time delay for initialization operations that can be delayed so as not to consume too much // CPU on bring-up and increase time-to-UI. private static final long INITIALIZATION_DELAY_MILLIS = 3000; @@ -352,6 +365,9 @@ public class UserBackupManagerService { // Backups that we haven't started yet. Keys are package names. private final HashMap<String, BackupRequest> mPendingBackups = new HashMap<>(); + private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>(); + private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>(); + // locking around the pending-backup management private final Object mQueueLock = new Object(); @@ -523,7 +539,8 @@ public class UserBackupManagerService { @VisibleForTesting UserBackupManagerService(Context context, PackageManager packageManager, LifecycleOperationStorage operationStorage, TransportManager transportManager, - BackupHandler backupHandler, BackupManagerConstants backupManagerConstants) { + BackupHandler backupHandler, BackupManagerConstants backupManagerConstants, + IActivityManager activityManager, ActivityManagerInternal activityManagerInternal) { mContext = context; mUserId = 0; @@ -534,6 +551,8 @@ public class UserBackupManagerService { mFullBackupQueue = new ArrayList<>(); mBackupHandler = backupHandler; mConstants = backupManagerConstants; + mActivityManager = activityManager; + mActivityManagerInternal = activityManagerInternal; mBaseStateDir = null; mDataDir = null; @@ -543,13 +562,11 @@ public class UserBackupManagerService { mRunInitReceiver = null; mRunInitIntent = null; mAgentTimeoutParameters = null; - mActivityManagerInternal = null; mAlarmManager = null; mWakelock = null; mBackupPreferences = null; mBackupPasswordManager = null; mPackageManagerBinder = null; - mActivityManager = null; mBackupManagerBinder = null; mScheduledBackupEligibility = null; } @@ -1651,9 +1668,11 @@ public class UserBackupManagerService { synchronized (mAgentConnectLock) { mConnecting = true; mConnectedAgent = null; + boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode, + app.packageName); try { if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId, - backupDestination)) { + backupDestination, useRestrictedMode)) { Slog.d(TAG, addUserIdToLogMessage(mUserId, "awaiting agent for " + app)); // success; wait for the agent to arrive @@ -3103,6 +3122,91 @@ public class UserBackupManagerService { } } + /** + * Marks the given set of packages as packages that should not be put into restricted mode if + * they are started for the given {@link BackupAnnotations.OperationType}. + */ + public void setNoRestrictedModePackages(Set<String> packageNames, + @BackupAnnotations.OperationType int opType) { + if (opType == BackupAnnotations.OperationType.BACKUP) { + mBackupNoRestrictedModePackages.clear(); + mBackupNoRestrictedModePackages.addAll(packageNames); + } else if (opType == BackupAnnotations.OperationType.RESTORE) { + mRestoreNoRestrictedModePackages.clear(); + mRestoreNoRestrictedModePackages.addAll(packageNames); + } else { + throw new IllegalArgumentException("opType must be BACKUP or RESTORE"); + } + } + + /** + * Clears the list of packages that should not be put into restricted mode for either backup or + * restore. + */ + public void clearNoRestrictedModePackages() { + mBackupNoRestrictedModePackages.clear(); + mRestoreNoRestrictedModePackages.clear(); + } + + /** + * If the app has specified {@link PackageManager#PROPERTY_USE_RESTRICTED_BACKUP_MODE}, then + * its value is returned. If it hasn't and it targets an SDK below + * {@link Build.VERSION_CODES#BAKLAVA} then returns true. If it targets a newer SDK, then + * returns the decision made by the {@link android.app.backup.BackupTransport}. + * + * <p>When this method is called, we should have already asked the transport and cached its + * response in {@link #mBackupNoRestrictedModePackages} or + * {@link #mRestoreNoRestrictedModePackages} so this method will immediately return without + * any IPC to the transport. + */ + private boolean shouldUseRestrictedBackupModeForPackage( + @BackupAnnotations.OperationType int mode, String packageName) { + if (!Flags.enableRestrictedModeChanges()) { + return true; + } + + // Key/Value apps are never put in restricted mode. + if (mode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL + || mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) { + return false; + } + + try { + PackageManager.Property property = mPackageManager.getPropertyAsUser( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, + packageName, /* className= */ null, + mUserId); + if (property.isBoolean()) { + // If the package has explicitly specified, we won't ask the transport. + return property.getBoolean(); + } else { + Slog.w(TAG, PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE + + "must be a boolean."); + } + } catch (NameNotFoundException e) { + // This is expected when the package has not defined the property in its manifest. + } + + // The package has not specified the property. The behavior depends on the package's + // targetSdk. + // <36 gets the old behavior of always using restricted mode. + if (!CompatChanges.isChangeEnabled(OS_DECIDES_BACKUP_RESTRICTED_MODE, packageName, + UserHandle.of(mUserId))) { + return true; + } + + // Apps targeting >=36 get the behavior decided by the transport. + // By this point, we should have asked the transport and cached its decision. + if ((mode == ApplicationThreadConstants.BACKUP_MODE_FULL + && mBackupNoRestrictedModePackages.contains(packageName)) + || (mode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL + && mRestoreNoRestrictedModePackages.contains(packageName))) { + Slog.d(TAG, "Transport requested no restricted mode for: " + packageName); + return false; + } + return true; + } + private boolean startConfirmationUi(int token, String action) { try { Intent confIntent = new Intent(action); diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index cca166b0939c..be9cdc8692cb 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -16,6 +16,8 @@ package com.android.server.backup.fullbackup; +import static android.app.backup.BackupAnnotations.OperationType.BACKUP; + import static com.android.server.backup.BackupManagerService.DEBUG; import static com.android.server.backup.BackupManagerService.DEBUG_SCHEDULING; import static com.android.server.backup.BackupManagerService.MORE_DEBUG; @@ -34,6 +36,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.ArraySet; import android.util.EventLog; import android.util.Log; import android.util.Slog; @@ -388,6 +391,10 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } } + // We ask the transport which packages should not be put in restricted mode and cache + // the result in UBMS to be used later when the apps are started for backup. + setNoRestrictedModePackages(transport, mPackages); + // Set up to send data to the transport final int N = mPackages.size(); int chunkSizeInBytes = 8 * 1024; // 8KB @@ -694,6 +701,9 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba mUserBackupManagerService.scheduleNextFullBackupJob(backoff); } + // Clear this to avoid using the memory until reboot. + mUserBackupManagerService.clearNoRestrictedModePackages(); + Slog.i(TAG, "Full data backup pass finished."); mUserBackupManagerService.getWakelock().release(); } @@ -722,6 +732,21 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba } } + private void setNoRestrictedModePackages(BackupTransportClient transport, + List<PackageInfo> packages) { + try { + Set<String> packageNames = new ArraySet<>(); + for (int i = 0; i < packages.size(); i++) { + packageNames.add(packages.get(i).packageName); + } + packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames, + BACKUP); + mUserBackupManagerService.setNoRestrictedModePackages(packageNames, BACKUP); + } catch (RemoteException e) { + Slog.i(TAG, "Failed to retrieve no restricted mode packages from transport"); + } + } + // Run the backup and pipe it back to the given socket -- expects to run on // a standalone thread. The runner owns this half of the pipe, and closes // it to indicate EOD to the other end. diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index e536876f6cc3..5ee51a5aa189 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -53,6 +53,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; +import android.util.ArraySet; import android.util.EventLog; import android.util.Slog; @@ -482,6 +483,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { return; } + // We ask the transport which packages should not be put in restricted mode and cache + // the result in UBMS to be used later when the apps are started for restore. + setNoRestrictedModePackages(transport, packages); + RestoreDescription desc = transport.nextRestorePackage(); if (desc == null) { Slog.e(TAG, "No restore metadata available; halting"); @@ -1358,6 +1363,9 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { // Clear any ongoing session timeout. backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT); + // Clear this to avoid using the memory until reboot. + backupManagerService.clearNoRestrictedModePackages(); + // If we have a PM token, we must under all circumstances be sure to // handshake when we've finished. if (mPmToken > 0) { @@ -1819,4 +1827,20 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { return packageInfo; } + + @VisibleForTesting + void setNoRestrictedModePackages(BackupTransportClient transport, + PackageInfo[] packages) { + try { + Set<String> packageNames = new ArraySet<>(); + for (int i = 0; i < packages.length; i++) { + packageNames.add(packages[i].packageName); + } + packageNames = transport.getPackagesThatShouldNotUseRestrictedMode(packageNames, + RESTORE); + backupManagerService.setNoRestrictedModePackages(packageNames, RESTORE); + } catch (RemoteException e) { + Slog.i(TAG, "Failed to retrieve restricted mode packages from transport"); + } + } } diff --git a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java index daf3415229ea..373811fef802 100644 --- a/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java +++ b/services/backup/java/com/android/server/backup/transport/BackupTransportClient.java @@ -17,6 +17,7 @@ package com.android.server.backup.transport; import android.annotation.Nullable; +import android.app.backup.BackupAnnotations; import android.app.backup.BackupTransport; import android.app.backup.IBackupManagerMonitor; import android.app.backup.RestoreDescription; @@ -26,6 +27,7 @@ import android.content.pm.PackageInfo; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.ArraySet; import android.util.Slog; import com.android.internal.backup.IBackupTransport; @@ -375,6 +377,26 @@ public class BackupTransportClient { } /** + * See + * {@link IBackupTransport#getPackagesThatShouldNotUseRestrictedMode(List, int, AndroidFuture)}. + */ + public Set<String> getPackagesThatShouldNotUseRestrictedMode(Set<String> packageNames, + @BackupAnnotations.OperationType + int operationType) throws RemoteException { + AndroidFuture<List<String>> resultFuture = mTransportFutures.newFuture(); + mTransportBinder.getPackagesThatShouldNotUseRestrictedMode(List.copyOf(packageNames), + operationType, + resultFuture); + List<String> resultList = getFutureResult(resultFuture); + Set<String> set = new ArraySet<>(); + if (resultList == null) { + return set; + } + set.addAll(resultList); + return set; + } + + /** * Allows the {@link TransportConnection} to notify this client * if the underlying transport has become unusable. If that happens * we want to cancel all active futures or callbacks. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index dfddc089e4a4..d880bce921aa 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -478,7 +478,6 @@ import com.android.server.wm.WindowProcessController; import dalvik.annotation.optimization.NeverCompile; import dalvik.system.VMRuntime; - import libcore.util.EmptyArray; import java.io.File; @@ -4493,16 +4492,11 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, "Unattached app died before backup, skipping"); final int userId = app.userId; final String packageName = app.info.packageName; - mHandler.post(new Runnable() { - @Override - public void run() { - try { - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentDisconnectedForUser(userId, packageName); - } catch (RemoteException e) { - // Can't happen; the backup manager is local - } + mHandler.post(() -> { + try { + getBackupManager().agentDisconnectedForUser(userId, packageName); + } catch (RemoteException e) { + // Can't happen; the backup manager is local } }); } @@ -4673,7 +4667,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (backupTarget != null && backupTarget.appInfo.packageName.equals(processName)) { isRestrictedBackupMode = backupTarget.appInfo.uid >= FIRST_APPLICATION_UID && ((backupTarget.backupMode == BackupRecord.RESTORE_FULL) - || (backupTarget.backupMode == BackupRecord.BACKUP_FULL)); + || (backupTarget.backupMode == BackupRecord.BACKUP_FULL)) + && backupTarget.useRestrictedMode; } final ActiveInstrumentation instr = app.getActiveInstrumentation(); @@ -13499,16 +13494,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (backupTarget != null && pid == backupTarget.app.getPid()) { if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG_CLEANUP, "App " + backupTarget.appInfo + " died during backup"); - mHandler.post(new Runnable() { - @Override - public void run() { - try { - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentDisconnectedForUser(app.userId, app.info.packageName); - } catch (RemoteException e) { - // can't happen; backup manager is local - } + mHandler.post(() -> { + try { + getBackupManager().agentDisconnectedForUser(app.userId, app.info.packageName); + } catch (RemoteException e) { + // can't happen; backup manager is local } }); } @@ -14011,7 +14001,7 @@ public class ActivityManagerService extends IActivityManager.Stub // instantiated. The backup agent will invoke backupAgentCreated() on the // activity manager to announce its creation. public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId, - @BackupDestination int backupDestination) { + @BackupDestination int backupDestination, boolean useRestrictedMode) { long startTimeNs = SystemClock.uptimeNanos(); if (DEBUG_BACKUP) { Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode @@ -14096,7 +14086,8 @@ public class ActivityManagerService extends IActivityManager.Stub + app.packageName + ": " + e); } - BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination); + BackupRecord r = new BackupRecord(app, backupMode, targetUserId, backupDestination, + useRestrictedMode); ComponentName hostingName = (backupMode == ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL || backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE) @@ -14122,8 +14113,9 @@ public class ActivityManagerService extends IActivityManager.Stub // process, etc, then mark it as being in full backup so that certain calls to the // process can be blocked. This is not reset to false anywhere because we kill the // process after the full backup is done and the ProcessRecord will vaporize anyway. - if (UserHandle.isApp(app.uid) && - backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL) { + if (UserHandle.isApp(app.uid) + && backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL + && r.useRestrictedMode) { proc.setInFullBackup(true); } r.app = proc; @@ -14221,9 +14213,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long oldIdent = Binder.clearCallingIdentity(); try { - IBackupManager bm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - bm.agentConnectedForUser(userId, agentPackageName, agent); + getBackupManager().agentConnectedForUser(userId, agentPackageName, agent); } catch (RemoteException e) { // can't happen; the backup manager service is local } catch (Exception e) { @@ -18013,14 +18003,6 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid, int userId) { - // For the simplification, we don't support USER_ALL nor USER_CURRENT here. - if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) { - throw new IllegalArgumentException("Unsupported userId"); - } - - mUserController.handleIncomingUser(pid, uid, userId, true, - ALLOW_NON_FULL, "addStartInfoTimestampSystem", null); - addStartInfoTimestampInternal(key, timestampNs, userId, uid); } @@ -19353,4 +19335,8 @@ public class ActivityManagerService extends IActivityManager.Stub } return token; } + + private IBackupManager getBackupManager() { + return IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE)); + } } diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java index 0b056d7883bf..64cc6f0e66e3 100644 --- a/services/core/java/com/android/server/am/BackupRecord.java +++ b/services/core/java/com/android/server/am/BackupRecord.java @@ -32,15 +32,18 @@ final class BackupRecord { final int userId; // user for which backup is performed final int backupMode; // full backup / incremental / restore @BackupDestination final int backupDestination; // see BackupAnnotations#BackupDestination + final boolean useRestrictedMode; // whether the app should be put into restricted backup mode ProcessRecord app; // where this agent is running or null // ----- Implementation ----- - BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination) { + BackupRecord(ApplicationInfo _appInfo, int _backupMode, int _userId, int _backupDestination, + boolean _useRestrictedMode) { appInfo = _appInfo; backupMode = _backupMode; userId = _userId; backupDestination = _backupDestination; + useRestrictedMode = _useRestrictedMode; } public String toString() { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index b51db137f293..98f738c38d63 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -351,7 +351,8 @@ class ProcessRecord implements WindowProcessListener { private String[] mIsolatedEntryPointArgs; /** - * Process is currently hosting a backup agent for backup or restore. + * Process is currently hosting a backup agent for backup or restore. Note that this is only set + * when the process is put into restricted backup mode. */ @GuardedBy("mService") private boolean mInFullBackup; diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 364497491785..14d3fbc22372 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -393,6 +393,8 @@ final class ProcessServiceRecord { adj = ProcessList.PERCEPTIBLE_APP_ADJ; } else if (adj < ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ; + } else if (Flags.addModifyRawOomAdjServiceLevel() && adj < ProcessList.SERVICE_ADJ) { + adj = ProcessList.SERVICE_ADJ; } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) { adj = ProcessList.CACHED_APP_MIN_ADJ; } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) { diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index 711b163ea424..c59c40fc9cd8 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -260,3 +260,13 @@ flag { description: "Use PROCESS_CAPABILITY_CPU_TIME to control unfreeze state." bug: "370817323" } + +flag { + name: "add_modify_raw_oom_adj_service_level" + namespace: "backstage_power" + description: "Add a SERVICE_ADJ level to the modifyRawOomAdj method" + bug: "374810368" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 6ba356990cac..5f716605e8cd 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -9224,6 +9224,9 @@ public class AudioService extends IAudioService.Stub return; } + // index values sent to APM are in the stream type SDK range, not *10 + int indexMinVolCurve = MIN_STREAM_VOLUME[mStreamType]; + int indexMaxVolCurve = MAX_STREAM_VOLUME[mStreamType]; synchronized (this) { if (mStreamType == AudioSystem.STREAM_VOICE_CALL) { if (MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] @@ -9234,11 +9237,15 @@ public class AudioService extends IAudioService.Stub if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) { // SCO devices have a different min index mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10; + indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]; + indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]; mIndexStepFactor = 1.f; } else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) { // For non SCO devices the stream state does not change the min index if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) { mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10; + indexMinVolCurve = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]; + indexMaxVolCurve = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO]; } else { mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10; } @@ -9259,7 +9266,7 @@ public class AudioService extends IAudioService.Stub } final int status = AudioSystem.initStreamVolume( - mStreamType, mIndexMin / 10, mIndexMax / 10); + mStreamType, indexMinVolCurve, indexMaxVolCurve); sVolumeLogger.enqueue(new EventLogger.StringEvent( "updateIndexFactors() stream:" + mStreamType + " index min/max:" + mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:" diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index b63b07f0453e..e92b5188652b 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -20,6 +20,7 @@ import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE; import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS; import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME; import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER; +import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME; import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED; import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME; import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER; @@ -444,7 +445,7 @@ public final class PlaybackActivityMonitor } if (DEBUG) { - Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)", + Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)", portId, AudioPlaybackConfiguration.playerStateToString(event), extras)); } @@ -1381,6 +1382,9 @@ public final class PlaybackActivityMonitor if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) { builder.append("volumeShaper "); } + if ((mEventValue & MUTED_BY_PORT_VOLUME) != 0) { + builder.append("portVolume "); + } } return builder.toString(); default: diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 586d59492f57..e7ea868ca04f 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -4,6 +4,14 @@ container: "system" # Important: Flags must be accessed through DisplayManagerFlags. flag { + name: "is_always_on_available_api" + namespace: "display_manager" + description: "Allows querying of AOD availability" + bug: "324046664" + is_fixed_read_only: true +} + +flag { name: "enable_port_in_display_layout" namespace: "display_manager" description: "Allows refering to displays by port in display layout" diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java index c5c8a5ea9d82..1f8a2007a9ff 100644 --- a/services/core/java/com/android/server/media/quality/MediaQualityService.java +++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java @@ -262,7 +262,7 @@ public class MediaQualityService extends SystemService { // TODO: implement } @Override - public SoundProfile getSoundProfileById(String id) { + public SoundProfile getSoundProfile(int type, String id) { return null; } @Override @@ -313,6 +313,15 @@ public class MediaQualityService extends SystemService { } @Override + public List<String> getSoundProfileAllowList() { + return new ArrayList<>(); + } + + @Override + public void setSoundProfileAllowList(List<String> packages) { + } + + @Override public boolean isSupported() { return false; } diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 9f4b9f1c1f24..6d54be84d5e5 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -58,6 +58,7 @@ import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.provider.DeviceConfig; import android.stats.storage.StorageEnums; +import android.text.TextUtils; import android.util.IntArray; import android.util.Log; import android.util.Pair; @@ -355,7 +356,8 @@ public final class BroadcastHelper { @Nullable int[] userIds, @Nullable int[] instantUserIds, @Nullable SparseArray<int[]> broadcastAllowList, - @NonNull AndroidPackage pkg) { + @NonNull AndroidPackage pkg, + @NonNull String[] sharedUidPackages) { final boolean isForWholeApp = componentNames.contains(packageName); if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) { sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames, @@ -374,20 +376,36 @@ public final class BroadcastHelper { exportedComponentNames.removeAll(notExportedComponentNames); if (!notExportedComponentNames.isEmpty()) { - // Limit sending of the PACKAGE_CHANGED broadcast to only the system and the - // application itself when the component is not exported. + // Limit sending of the PACKAGE_CHANGED broadcast to only the system, the application + // itself and applications with the same UID when the component is not exported. // First, send the PACKAGE_CHANGED broadcast to the system. - sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, - notExportedComponentNames, packageUid, reason, userIds, instantUserIds, - broadcastAllowList, "android" /* targetPackageName */, - new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}); + if (!TextUtils.equals(packageName, "android")) { + sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, + notExportedComponentNames, packageUid, reason, userIds, instantUserIds, + broadcastAllowList, "android" /* targetPackageName */, + new String[]{ + PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}); + } // Second, send the PACKAGE_CHANGED broadcast to the application itself. sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, notExportedComponentNames, packageUid, reason, userIds, instantUserIds, broadcastAllowList, packageName /* targetPackageName */, null /* requiredPermissions */); + + // Third, send the PACKAGE_CHANGED broadcast to the applications with the same UID. + for (int i = 0; i < sharedUidPackages.length; i++) { + final String sharedPackage = sharedUidPackages[i]; + if (TextUtils.equals(packageName, sharedPackage)) { + continue; + } + sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, + notExportedComponentNames, packageUid, reason, userIds, instantUserIds, + broadcastAllowList, sharedPackage /* targetPackageName */, + null /* requiredPermissions */); + } + } if (!exportedComponentNames.isEmpty()) { @@ -936,7 +954,8 @@ public final class BroadcastHelper { isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds); mHandler.post(() -> sendPackageChangedBroadcastInternal( packageName, dontKillApp, componentNames, packageUid, reason, userIds, - instantUserIds, broadcastAllowList, setting.getPkg())); + instantUserIds, broadcastAllowList, setting.getPkg(), + snapshot.getSharedUserPackagesForPackage(packageName, userId))); mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames, packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler); } diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java index 745665bab5b3..527d68049537 100644 --- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java +++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java @@ -17,51 +17,240 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; +import static android.os.Process.SYSTEM_UID; +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ResolveInfo; import android.content.pm.SharedLibraryInfo; +import android.content.pm.dependencyinstaller.DependencyInstallerCallback; +import android.content.pm.dependencyinstaller.IDependencyInstallerCallback; +import android.content.pm.dependencyinstaller.IDependencyInstallerService; import android.content.pm.parsing.PackageLite; +import android.os.Handler; import android.os.OutcomeReceiver; +import android.os.Process; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.AndroidFuture; +import com.android.internal.infra.ServiceConnector; import java.util.List; +import java.util.concurrent.TimeUnit; /** * Helper class to interact with SDK Dependency Installer service. */ public class InstallDependencyHelper { + private static final String TAG = InstallDependencyHelper.class.getSimpleName(); + private static final boolean DEBUG = true; + private static final String ACTION_INSTALL_DEPENDENCY = + "android.intent.action.INSTALL_DEPENDENCY"; + // The maximum amount of time to wait before the system unbinds from the verifier. + private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6); + private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1); + private final SharedLibrariesImpl mSharedLibraries; + private final Context mContext; + private final Object mRemoteServiceLock = new Object(); + + @GuardedBy("mRemoteServiceLock") + private ServiceConnector<IDependencyInstallerService> mRemoteService = null; - InstallDependencyHelper(SharedLibrariesImpl sharedLibraries) { + InstallDependencyHelper(Context context, SharedLibrariesImpl sharedLibraries) { + mContext = context; mSharedLibraries = sharedLibraries; } - void resolveLibraryDependenciesIfNeeded(PackageLite pkg, - OutcomeReceiver<Void, PackageManagerException> callback) { - final List<SharedLibraryInfo> missing; + void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId, + Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) { + CallOnceProxy callback = new CallOnceProxy(handler, origCallback); try { - missing = mSharedLibraries.collectMissingSharedLibraryInfos(pkg); + resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback); } catch (PackageManagerException e) { callback.onError(e); - return; + } catch (Exception e) { + onError(callback, e.getMessage()); } + } + + + private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot, + int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException { + final List<SharedLibraryInfo> missing = + mSharedLibraries.collectMissingSharedLibraryInfos(pkg); if (missing.isEmpty()) { + if (DEBUG) { + Slog.i(TAG, "No missing dependency for " + pkg); + } // No need for dependency resolution. Move to installation directly. callback.onResult(null); return; } - try { - bindToDependencyInstaller(); - } catch (Exception e) { - PackageManagerException pe = new PackageManagerException( - INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage()); - callback.onError(pe); + if (!bindToDependencyInstallerIfNeeded(userId, handler, snapshot)) { + onError(callback, "Dependency Installer Service not found"); + return; + } + + IDependencyInstallerCallback serviceCallback = new IDependencyInstallerCallback.Stub() { + @Override + public void onAllDependenciesResolved(int[] sessionIds) throws RemoteException { + // TODO(b/372862145): Implement waiting for sessions to finish installation + callback.onResult(null); + } + + @Override + public void onFailureToResolveAllDependencies() throws RemoteException { + onError(callback, "Failed to resolve all dependencies automatically"); + } + }; + + boolean scheduleSuccess; + synchronized (mRemoteServiceLock) { + scheduleSuccess = mRemoteService.run(service -> { + service.onDependenciesRequired(missing, + new DependencyInstallerCallback(serviceCallback.asBinder())); + }); + } + if (!scheduleSuccess) { + onError(callback, "Failed to schedule job on Dependency Installer Service"); } } - private void bindToDependencyInstaller() { - throw new IllegalStateException("Failed to bind to Dependency Installer"); + private void onError(CallOnceProxy callback, String msg) { + PackageManagerException pe = new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, msg); + callback.onError(pe); } + private boolean bindToDependencyInstallerIfNeeded(int userId, Handler handler, + Computer snapshot) { + synchronized (mRemoteServiceLock) { + if (mRemoteService != null) { + if (DEBUG) { + Slog.i(TAG, "DependencyInstallerService already bound"); + } + return true; + } + } + + Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY); + // TODO(b/372862145): Use RoleManager to find the package name + List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal( + serviceIntent, /*resolvedType=*/ null, /*flags=*/0, + userId, SYSTEM_UID, Process.INVALID_PID, + /*includeInstantApps*/ false, /*resolveForStart*/ false); + + if (resolvedIntents.isEmpty()) { + return false; + } + + ResolveInfo resolveInfo = resolvedIntents.getFirst(); + ComponentName componentName = resolveInfo.getComponentInfo().getComponentName(); + serviceIntent.setComponent(componentName); + + ServiceConnector<IDependencyInstallerService> serviceConnector = + new ServiceConnector.Impl<IDependencyInstallerService>(mContext, serviceIntent, + Context.BIND_AUTO_CREATE, userId, + IDependencyInstallerService.Stub::asInterface) { + @Override + protected Handler getJobHandler() { + return handler; + } + + @Override + protected long getRequestTimeoutMs() { + return REQUEST_TIMEOUT_MILLIS; + } + + @Override + protected long getAutoDisconnectTimeoutMs() { + return UNBIND_TIMEOUT_MILLIS; + } + }; + + + synchronized (mRemoteServiceLock) { + // Some other thread managed to connect to the service first + if (mRemoteService != null) { + return true; + } + mRemoteService = serviceConnector; + mRemoteService.setServiceLifecycleCallbacks( + new ServiceConnector.ServiceLifecycleCallbacks<>() { + @Override + public void onDisconnected(@NonNull IDependencyInstallerService service) { + Slog.w(TAG, + "DependencyInstallerService " + componentName + " is disconnected"); + destroy(); + } + + @Override + public void onBinderDied() { + Slog.w(TAG, "DependencyInstallerService " + componentName + " has died"); + destroy(); + } + + private void destroy() { + synchronized (mRemoteServiceLock) { + if (mRemoteService != null) { + mRemoteService.unbind(); + mRemoteService = null; + } + } + } + + }); + AndroidFuture<IDependencyInstallerService> unusedFuture = mRemoteService.connect(); + } + return true; + } + + /** + * Ensure we call one of the outcomes only once, on the right handler. + * + * Repeated calls will be no-op. + */ + private static class CallOnceProxy implements OutcomeReceiver<Void, PackageManagerException> { + private final Handler mHandler; + private final OutcomeReceiver<Void, PackageManagerException> mCallback; + @GuardedBy("this") + private boolean mCalled = false; + + CallOnceProxy(Handler handler, OutcomeReceiver<Void, PackageManagerException> callback) { + mHandler = handler; + mCallback = callback; + } + + @Override + public void onResult(Void result) { + synchronized (this) { + if (!mCalled) { + mHandler.post(() -> { + mCallback.onResult(null); + }); + mCalled = true; + } + } + } + + @Override + public void onError(@NonNull PackageManagerException error) { + synchronized (this) { + if (!mCalled) { + mHandler.post(() -> { + mCallback.onError(error); + }); + mCalled = true; + } + } + } + } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index eb70748918b6..9b44f93467ed 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -347,7 +347,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements synchronized (mVerificationPolicyPerUser) { mVerificationPolicyPerUser.put(USER_SYSTEM, DEFAULT_VERIFICATION_POLICY); } - mInstallDependencyHelper = new InstallDependencyHelper( + mInstallDependencyHelper = new InstallDependencyHelper(mContext, mPm.mInjector.getSharedLibrariesImpl()); LocalServices.getService(SystemServiceManager.class).startService( diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index e156b31c19e1..505b7e6205df 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -3428,8 +3428,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private void resolveLibraryDependenciesIfNeeded() { synchronized (mLock) { - // TODO(b/372862145): Callback should be called on a handler passed as parameter mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite, + mPm.snapshotComputer(), userId, mHandler, new OutcomeReceiver<>() { @Override diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index fc54f6864db0..17d7a14d9129 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -1017,10 +1017,11 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable boolean isSdkOrStatic = libraryType.equals(LIBRARY_TYPE_SDK) || libraryType.equals(LIBRARY_TYPE_STATIC); if (isSdkOrStatic && outMissingSharedLibraryInfos != null) { - // TODO(b/372862145): Pass the CertDigest too // If Dependency Installation is supported, try that instead of failing. + final List<String> libCertDigests = Arrays.asList(requiredCertDigests[i]); SharedLibraryInfo missingLibrary = new SharedLibraryInfo( - libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE + libName, libVersion, SharedLibraryInfo.TYPE_SDK_PACKAGE, + libCertDigests ); outMissingSharedLibraryInfos.add(missingLibrary); } else { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java index 1be5cef28244..acd34e323dc2 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java @@ -28,6 +28,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -60,6 +61,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.List; @AppModeFull @AppModeNonSdkSandbox @@ -124,7 +126,8 @@ public class BroadcastHelperTest { @Test public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission() throws Exception { - changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */); + changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */, + new String[0] /* sharedPackages */); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mMockActivityManagerInternal).broadcastIntentWithCallback( @@ -140,7 +143,8 @@ public class BroadcastHelperTest { @Test public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself() throws Exception { - changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */); + changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */, + new String[0] /* sharedPackages */); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null), @@ -150,9 +154,45 @@ public class BroadcastHelperTest { assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME); } + @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES) + @Test + public void changeNonExportedComponent_sendPackageChangedBroadcastToSharedUserIdApplications() + throws Exception { + changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */, + new String[]{"shared.package"} /* sharedPackages */); + + ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class); + ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass( + String[].class); + verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback( + captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(), + eq(null), eq(null), eq(null)); + List<Intent> intents = captorIntent.getAllValues(); + List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues(); + assertNotNull(intents); + assertThat(intents.size()).isEqualTo(3); + + final Intent intent1 = intents.get(0); + final String[] requiredPermission1 = requiredPermissions.get(0); + assertThat(intent1.getPackage()).isEqualTo("android"); + assertThat(requiredPermission1).isEqualTo( + new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}); + + final Intent intent2 = intents.get(1); + final String[] requiredPermission2 = requiredPermissions.get(1); + assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME); + assertThat(requiredPermission2).isNull(); + + final Intent intent3 = intents.get(2); + final String[] requiredPermission3 = requiredPermissions.get(2); + assertThat(intent3.getPackage()).isEqualTo("shared.package"); + assertThat(requiredPermission3).isNull(); + } + @Test public void changeExportedComponent_sendPackageChangedBroadcastToAll() throws Exception { - changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */); + changeComponentAndSendPackageChangedBroadcast(true /* changeExportedComponent */, + new String[0] /* sharedPackages */); ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class); verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null), @@ -162,11 +202,14 @@ public class BroadcastHelperTest { assertNull(intent.getPackage()); } - private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent) { + private void changeComponentAndSendPackageChangedBroadcast(boolean changeExportedComponent, + String[] sharedPackages) { when(mMockSnapshot.getPackageStateInternal(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME), anyInt())).thenReturn(mMockPackageStateInternal); when(mMockSnapshot.isInstantAppInternal(any(), anyInt(), anyInt())).thenReturn(false); when(mMockSnapshot.getVisibilityAllowLists(any(), any())).thenReturn(null); + when(mMockSnapshot.getSharedUserPackagesForPackage(eq(PACKAGE_CHANGED_TEST_PACKAGE_NAME), + anyInt())).thenReturn(sharedPackages); when(mMockPackageStateInternal.getPkg()).thenReturn(mMockAndroidPackageInternal); when(mMockParsedActivity.getClassName()).thenReturn( diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index dcbc23410fdb..5a872ea04bcc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -47,10 +47,8 @@ import static com.android.server.am.Flags.FLAG_AVOID_RESOLVING_TYPE; import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK; import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE; import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK; - import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -80,6 +78,7 @@ import android.Manifest; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.ApplicationThreadConstants; import android.app.BackgroundStartPrivileges; import android.app.BroadcastOptions; import android.app.ForegroundServiceDelegationOptions; @@ -87,6 +86,7 @@ import android.app.IUidObserver; import android.app.Notification; import android.app.NotificationChannel; import android.app.SyncNotedAppOp; +import android.app.backup.BackupAnnotations; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -111,6 +111,7 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.UserHandle; import android.permission.IPermissionManager; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -133,6 +134,7 @@ import com.android.server.am.ProcessList.IsolatedUidRange; import com.android.server.am.ProcessList.IsolatedUidRangeAllocator; import com.android.server.am.UidObserverController.ChangeRecord; import com.android.server.appop.AppOpsService; +import com.android.server.job.JobSchedulerInternal; import com.android.server.notification.NotificationManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerService; @@ -228,6 +230,7 @@ public class ActivityManagerServiceTest { @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private ActivityTaskManagerInternal mActivityTaskManagerInternal; @Mock private NotificationManagerInternal mNotificationManagerInternal; + @Mock private JobSchedulerInternal mJobSchedulerInternal; @Mock private ContentResolver mContentResolver; private TestInjector mInjector; @@ -249,6 +252,7 @@ public class ActivityManagerServiceTest { LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal); LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInternal); LocalServices.addService(NotificationManagerInternal.class, mNotificationManagerInternal); + LocalServices.addService(JobSchedulerInternal.class, mJobSchedulerInternal); doReturn(new ComponentName("", "")).when(mPackageManagerInternal) .getSystemUiServiceComponent(); @@ -308,6 +312,7 @@ public class ActivityManagerServiceTest { LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class); LocalServices.removeServiceForTest(NotificationManagerInternal.class); + LocalServices.removeServiceForTest(JobSchedulerInternal.class); if (mMockingSession != null) { mMockingSession.finishMocking(); @@ -1548,6 +1553,50 @@ public class ActivityManagerServiceTest { eq(notificationId), anyInt()); } + @SuppressWarnings("GuardedBy") + @Test + public void bindBackupAgent_fullBackup_shouldUseRestrictedMode_setsInFullBackup() + throws Exception { + ActivityManagerService spyAms = spy(mAms); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = TEST_PACKAGE; + applicationInfo.processName = TEST_PACKAGE; + applicationInfo.uid = TEST_UID; + doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE), + anyLong(), anyInt()); + ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID); + doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID)); + + spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL, + UserHandle.USER_SYSTEM, + BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */ + true); + + assertThat(appRec.isInFullBackup()).isTrue(); + } + + @SuppressWarnings("GuardedBy") + @Test + public void bindBackupAgent_fullBackup_shouldNotUseRestrictedMode_doesNotSetInFullBackup() + throws Exception { + ActivityManagerService spyAms = spy(mAms); + ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.packageName = TEST_PACKAGE; + applicationInfo.processName = TEST_PACKAGE; + applicationInfo.uid = TEST_UID; + doReturn(applicationInfo).when(mPackageManager).getApplicationInfo(eq(TEST_PACKAGE), + anyLong(), anyInt()); + ProcessRecord appRec = new ProcessRecord(mAms, applicationInfo, TAG, TEST_UID); + doReturn(appRec).when(spyAms).getProcessRecordLocked(eq(TEST_PACKAGE), eq(TEST_UID)); + + spyAms.bindBackupAgent(TEST_PACKAGE, ApplicationThreadConstants.BACKUP_MODE_FULL, + UserHandle.USER_SYSTEM, + BackupAnnotations.BackupDestination.CLOUD, /* shouldUseRestrictedMode= */ + false); + + assertThat(appRec.isInFullBackup()).isFalse(); + } + private static class TestHandler extends Handler { private static final long WAIT_FOR_MSG_TIMEOUT_MS = 4000; // 4 sec private static final long WAIT_FOR_MSG_INTERVAL_MS = 400; // 0.4 sec diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index f82a86092064..a9569b4096ff 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -1427,6 +1427,31 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoOne_Service_NotPerceptible_AboveClient() { + ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + ProcessRecord service = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, + MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); + bindService(app, client, null, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class)); + bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); + mProcessStateController.setRunningRemoteAnimation(client, true); + mProcessStateController.updateHasAboveClientLocked(app.mServices); + setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE); + updateOomAdj(client, app, service); + + final int expectedAdj; + if (Flags.addModifyRawOomAdjServiceLevel()) { + expectedAdj = SERVICE_ADJ; + } else { + expectedAdj = CACHED_APP_MIN_ADJ; + } + assertEquals(expectedAdj, app.mState.getSetAdj()); + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_DoOne_Service_NotVisible() { ProcessRecord app = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); @@ -2906,7 +2931,7 @@ public class MockingOomAdjusterTests { // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and // verify that its OOM adjustment level is unaffected. bindService(service, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); - app.mServices.updateHasAboveClientLocked(); + mProcessStateController.updateHasAboveClientLocked(app.mServices); assertTrue(app.mServices.hasAboveClient()); updateOomAdj(app); @@ -2928,7 +2953,7 @@ public class MockingOomAdjusterTests { // Simulate binding to a service in the same process using BIND_ABOVE_CLIENT and // verify that its OOM adjustment level is unaffected. bindService(app, app, null, null, Context.BIND_ABOVE_CLIENT, mock(IBinder.class)); - app.mServices.updateHasAboveClientLocked(); + mProcessStateController.updateHasAboveClientLocked(app.mServices); assertFalse(app.mServices.hasAboveClient()); updateOomAdj(app); @@ -2983,7 +3008,7 @@ public class MockingOomAdjusterTests { // Since sr.app is null, this service cannot be in the same process as the // client so we expect the BIND_ABOVE_CLIENT adjustment to take effect. - app.mServices.updateHasAboveClientLocked(); + mProcessStateController.updateHasAboveClientLocked(app.mServices); updateOomAdj(app); assertTrue(app.mServices.hasAboveClient()); assertNotEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj()); @@ -3306,7 +3331,7 @@ public class MockingOomAdjusterTests { if (Flags.pushGlobalStateToOomadjuster()) { mProcessStateController.setBackupTarget(app, app.userId); } else { - BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0); + BackupRecord backupTarget = new BackupRecord(null, 0, 0, 0, true); backupTarget.app = app; doReturn(backupTarget).when(mService.mBackupTargets).get(anyInt()); } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java index 65286d9aabc7..07f2188d30eb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -18,9 +18,7 @@ package com.android.server.backup; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; - import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -32,20 +30,27 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; +import android.app.ApplicationThreadConstants; +import android.app.IActivityManager; import android.app.backup.BackupAgent; -import android.app.backup.BackupAnnotations; import android.app.backup.BackupAnnotations.BackupDestination; +import android.app.backup.BackupAnnotations.OperationType; import android.app.backup.BackupRestoreEventLogger.DataTypeResult; import android.app.backup.IBackupManagerMonitor; import android.app.backup.IBackupObserver; import android.app.job.JobInfo; import android.app.job.JobScheduler; +import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Handler; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.testing.TestableContext; import android.util.FeatureFlagUtils; @@ -68,7 +73,9 @@ import com.google.common.collect.ImmutableSet; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -77,8 +84,12 @@ import org.mockito.quality.Strictness; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.function.IntConsumer; +import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; +import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; + @Presubmit @RunWith(AndroidJUnit4.class) public class UserBackupManagerServiceTest { @@ -88,6 +99,11 @@ public class UserBackupManagerServiceTest { private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 100; @UserIdInt private static final int USER_ID = 0; + @Rule + public TestRule compatChangeRule = new PlatformCompatChangeRule(); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock IBackupManagerMonitor mBackupManagerMonitor; @Mock IBackupObserver mBackupObserver; @Mock PackageManager mPackageManager; @@ -99,10 +115,14 @@ public class UserBackupManagerServiceTest { @Mock JobScheduler mJobScheduler; @Mock BackupHandler mBackupHandler; @Mock BackupManagerMonitorEventSender mBackupManagerMonitorEventSender; + @Mock IActivityManager mActivityManager; + @Mock + ActivityManagerInternal mActivityManagerInternal; private TestableContext mContext; private MockitoSession mSession; private TestBackupService mService; + private ApplicationInfo mTestPackageApplicationInfo; @Before public void setUp() throws Exception { @@ -120,12 +140,14 @@ public class UserBackupManagerServiceTest { mContext.getTestablePermissions().setPermission(android.Manifest.permission.BACKUP, PackageManager.PERMISSION_GRANTED); - mService = new TestBackupService(mContext, mPackageManager, mOperationStorage, - mTransportManager, mBackupHandler); + mService = new TestBackupService(); mService.setEnabled(true); mService.setSetupComplete(true); mService.enqueueFullBackup("com.test.backup.app", /* lastBackedUp= */ 0); - } + + mTestPackageApplicationInfo = new ApplicationInfo(); + mTestPackageApplicationInfo.packageName = TEST_PACKAGE; + } @After public void tearDown() { @@ -298,9 +320,160 @@ public class UserBackupManagerServiceTest { new DataTypeResult(/* dataType */ "type_2")); mService.reportDelayedRestoreResult(TEST_PACKAGE, results); - verify(mBackupManagerMonitorEventSender).sendAgentLoggingResults( - eq(packageInfo), eq(results), eq(BackupAnnotations.OperationType.RESTORE)); + eq(packageInfo), eq(results), eq(OperationType.RESTORE)); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode() + throws Exception { + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_keyValueBackup_shouldNotUseRestrictedMode() + throws Exception { + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_INCREMENTAL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_keyValueRestore_shouldNotUseRestrictedMode() + throws Exception { + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_RESTORE, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + // Make sure we never hit the code that checks the property. + verify(mPackageManager, never()).getPropertyAsUser(any(), any(), any(), anyInt()); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_packageOptedIn_shouldUseRestrictedMode() + throws Exception { + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ true, + TEST_PACKAGE, /* className= */ null)); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + public void bindToAgentSynchronous_packageOptedOut_shouldNotUseRestrictedMode() + throws Exception { + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenReturn(new PackageManager.Property( + PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE, /* value= */ false, + TEST_PACKAGE, /* className= */ null)); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @DisableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_targetSdkBelowB_shouldUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_targetSdkB_notInList_shouldUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + mService.clearNoRestrictedModePackages(); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(true)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_forRestore_targetSdkB_inList_shouldNotUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.RESTORE); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES) + @EnableCompatChanges({UserBackupManagerService.OS_DECIDES_BACKUP_RESTRICTED_MODE}) + public void bindToAgentSynchronous_forBackup_targetSdkB_inList_shouldNotUseRestrictedMode() + throws Exception { + // Mock that the app has not explicitly set the property. + when(mPackageManager.getPropertyAsUser( + eq(PackageManager.PROPERTY_USE_RESTRICTED_BACKUP_MODE), + eq(TEST_PACKAGE), any(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException() + ); + mService.setNoRestrictedModePackages(Set.of(TEST_PACKAGE), OperationType.BACKUP); + + mService.bindToAgentSynchronous(mTestPackageApplicationInfo, + ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD); + + verify(mActivityManager).bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(), anyInt(), + /* useRestrictedMode= */ eq(false)); } private static PackageInfo getPackageInfo(String packageName) { @@ -316,11 +489,9 @@ public class UserBackupManagerServiceTest { private volatile Thread mWorkerThread = null; - TestBackupService(Context context, PackageManager packageManager, - LifecycleOperationStorage operationStorage, TransportManager transportManager, - BackupHandler backupHandler) { - super(context, packageManager, operationStorage, transportManager, backupHandler, - createConstants(context)); + TestBackupService() { + super(mContext, mPackageManager, mOperationStorage, mTransportManager, mBackupHandler, + createConstants(mContext), mActivityManager, mActivityManagerInternal); } private static BackupManagerConstants createConstants(Context context) { diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java index 94742537ed1a..331057398949 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/fullbackup/PerformFullTransportBackupTaskTest.java @@ -18,34 +18,95 @@ package com.android.server.backup.fullbackup; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.when; +import android.app.backup.BackupAnnotations; +import android.app.backup.BackupTransport; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; import androidx.test.runner.AndroidJUnit4; +import com.android.server.backup.BackupAgentTimeoutParameters; +import com.android.server.backup.OperationStorage; import com.android.server.backup.TransportManager; import com.android.server.backup.UserBackupManagerService; +import com.android.server.backup.transport.BackupTransportClient; +import com.android.server.backup.transport.TransportConnection; +import com.android.server.backup.utils.BackupEligibilityRules; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + @Presubmit @RunWith(AndroidJUnit4.class) public class PerformFullTransportBackupTaskTest { + private static final String TEST_PACKAGE_1 = "package1"; + private static final String TEST_PACKAGE_2 = "package2"; + + @Mock + BackupAgentTimeoutParameters mBackupAgentTimeoutParameters; + @Mock + BackupEligibilityRules mBackupEligibilityRules; @Mock UserBackupManagerService mBackupManagerService; @Mock + BackupTransportClient mBackupTransportClient; + @Mock + CountDownLatch mLatch; + @Mock + OperationStorage mOperationStorage; + @Mock + PackageManager mPackageManager; + @Mock + TransportConnection mTransportConnection; + @Mock TransportManager mTransportManager; + @Mock + UserBackupManagerService.BackupWakeLock mWakeLock; + + private final List<String> mEligiblePackages = new ArrayList<>(); + + private PerformFullTransportBackupTask mTask; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + when(mBackupManagerService.getPackageManager()).thenReturn(mPackageManager); + when(mBackupManagerService.getQueueLock()).thenReturn("something!"); + when(mBackupManagerService.isEnabled()).thenReturn(true); + when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock); + when(mBackupManagerService.isSetupComplete()).thenReturn(true); + when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn( + mBackupAgentTimeoutParameters); when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager); + when(mTransportManager.getCurrentTransportClient(any())).thenReturn(mTransportConnection); + when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransportClient); + when(mTransportConnection.connect(any())).thenReturn(mBackupTransportClient); + when(mBackupTransportClient.performFullBackup(any(), any(), anyInt())).thenReturn( + BackupTransport.TRANSPORT_ERROR); + when(mBackupEligibilityRules.appIsEligibleForBackup( + argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn( + true); + when(mBackupEligibilityRules.appGetsFullBackup( + argThat(app -> mEligiblePackages.contains(app.packageName)))).thenReturn( + true); } @Test @@ -70,4 +131,49 @@ public class PerformFullTransportBackupTaskTest { /* backupEligibilityRules */ null); }); } + + @Test + public void run_setsAndClearsNoRestrictedModePackages() throws Exception { + mockPackageEligibleForFullBackup(TEST_PACKAGE_1); + mockPackageEligibleForFullBackup(TEST_PACKAGE_2); + createTask(new String[] {TEST_PACKAGE_1, TEST_PACKAGE_2}); + when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(), + anyInt())).thenReturn(Set.of("package1")); + + mTask.run(); + + InOrder inOrder = inOrder(mBackupManagerService); + inOrder.verify(mBackupManagerService).setNoRestrictedModePackages( + eq(Set.of("package1")), + eq(BackupAnnotations.OperationType.BACKUP)); + inOrder.verify(mBackupManagerService).clearNoRestrictedModePackages(); + } + + private void createTask(String[] packageNames) { + mTask = PerformFullTransportBackupTask + .newWithCurrentTransport( + mBackupManagerService, + mOperationStorage, + /* observer */ null, + /* whichPackages */ packageNames, + /* updateSchedule */ false, + /* runningJob */ null, + mLatch, + /* backupObserver */ null, + /* monitor */ null, + /* userInitiated */ false, + /* caller */ null, + mBackupEligibilityRules); + } + + private void mockPackageEligibleForFullBackup(String packageName) throws Exception { + mEligiblePackages.add(packageName); + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.packageName = packageName; + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; + packageInfo.applicationInfo = appInfo; + when(mPackageManager.getPackageInfoAsUser(eq(packageName), anyInt(), anyInt())).thenReturn( + packageInfo); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java index 414532b88e22..055adf68ee0f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java @@ -23,8 +23,10 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.backup.BackupAnnotations; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupTransport; @@ -91,6 +93,8 @@ public class PerformUnifiedRestoreTaskTest { private UserBackupManagerService mBackupManagerService; @Mock private TransportConnection mTransportConnection; + @Mock + private BackupTransportClient mBackupTransportClient; private Set<String> mExcludedkeys = new HashSet<>(); private Map<String, String> mBackupData = new HashMap<>(); @@ -151,6 +155,23 @@ public class PerformUnifiedRestoreTaskTest { } @Test + public void setNoRestrictedModePackages_callsTransportAndSetsValue() throws Exception { + PackageInfo packageInfo1 = new PackageInfo(); + packageInfo1.packageName = "package1"; + PackageInfo packageInfo2 = new PackageInfo(); + packageInfo2.packageName = "package2"; + when(mBackupTransportClient.getPackagesThatShouldNotUseRestrictedMode(any(), + anyInt())).thenReturn(Set.of("package1")); + + mRestoreTask.setNoRestrictedModePackages(mBackupTransportClient, + new PackageInfo[]{packageInfo1, packageInfo2}); + + verify(mBackupManagerService).setNoRestrictedModePackages( + eq(Set.of("package1")), + eq(BackupAnnotations.OperationType.RESTORE)); + } + + @Test public void testFilterExcludedKeys() throws Exception { when(mBackupManagerService.getExcludedRestoreKeys(eq(PACKAGE_NAME))) .thenReturn(mExcludedkeys); diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java index 2d7d46f83c47..13e32078f609 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/transport/BackupTransportClientTest.java @@ -19,7 +19,14 @@ package com.android.server.backup.transport; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; - +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +import android.app.backup.BackupAnnotations.OperationType; import android.app.backup.BackupTransport; import android.app.backup.IBackupManagerMonitor; import android.app.backup.RestoreDescription; @@ -38,15 +45,31 @@ import com.android.internal.backup.IBackupTransport; import com.android.internal.backup.ITransportStatusCallback; import com.android.internal.infra.AndroidFuture; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.util.List; +import java.util.Set; @Presubmit @RunWith(AndroidJUnit4.class) public class BackupTransportClientTest { + @Mock + IBackupTransport mMockBackupTransport; + + private BackupTransportClient mMockingTransportClient; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mMockingTransportClient = new BackupTransportClient( + mMockBackupTransport); + } + private static class TestFuturesFakeTransportBinder extends FakeTransportBinderBase { public final Object mLock = new Object(); @@ -128,6 +151,70 @@ public class BackupTransportClientTest { thread.join(); } + @Test + public void getPackagesThatShouldNotUseRestrictedMode_passesSetAsListToBinder() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2")); + + mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of("package1", "package2"), + OperationType.BACKUP); + + verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode( + argThat(list -> Set.copyOf(list).equals(Set.of("package1", "package2"))), + eq(OperationType.BACKUP), any()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_forRestore_callsBinderForRestore() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(null); + + mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of(), + OperationType.RESTORE); + + verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), + eq(OperationType.RESTORE), any()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_forBackup_callsBinderForBackup() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(null); + + mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of(), + OperationType.BACKUP); + + verify(mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), + eq(OperationType.BACKUP), any()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_nullResult_returnsEmptySet() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(null); + + Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of(), + OperationType.BACKUP); + + assertThat(result).isEqualTo(Set.of()); + } + + @Test + public void getPackagesThatShouldNotUseRestrictedMode_returnsResultAsSet() + throws Exception { + mockGetPackagesThatShouldNotUseRestrictedModeReturn(List.of("package1", "package2")); + + Set<String> result = mMockingTransportClient.getPackagesThatShouldNotUseRestrictedMode( + Set.of("package1", "package2"), + OperationType.BACKUP); + + assertThat(result).isEqualTo(Set.of("package1", "package2")); + } + private static class TestCallbacksFakeTransportBinder extends FakeTransportBinderBase { public final Object mLock = new Object(); @@ -158,7 +245,6 @@ public class BackupTransportClientTest { assertThat(status).isEqualTo(123); } - @Test public void testFinishBackup_completesLater_returnsStatus() throws Exception { TestCallbacksFakeTransportBinder binder = new TestCallbacksFakeTransportBinder(); @@ -211,6 +297,14 @@ public class BackupTransportClientTest { thread.join(); } + private void mockGetPackagesThatShouldNotUseRestrictedModeReturn(List<String> returnList) + throws Exception { + doAnswer( + i -> ((AndroidFuture<List<String>>) i.getArguments()[2]).complete(returnList)).when( + mMockBackupTransport).getPackagesThatShouldNotUseRestrictedMode(any(), anyInt(), + any()); + } + // Convenience layer so we only need to fake specific methods useful for each test case. private static class FakeTransportBinderBase implements IBackupTransport { @Override public void name(AndroidFuture<String> f) throws RemoteException {} @@ -258,6 +352,10 @@ public class BackupTransportClientTest { @Override public void getBackupManagerMonitor(AndroidFuture<IBackupManagerMonitor> resultFuture) throws RemoteException {} + @Override + public void getPackagesThatShouldNotUseRestrictedMode(List<String> packageNames, + int operationType, AndroidFuture<List<String>> resultFuture) + throws RemoteException {} @Override public IBinder asBinder() { return null; } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java index f6c644e3d4d4..20ac0781e2ed 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import android.content.Context; import android.content.pm.SharedLibraryInfo; import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; @@ -34,6 +35,8 @@ import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.os.FileUtils; +import android.os.Handler; +import android.os.Looper; import android.os.OutcomeReceiver; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; @@ -71,13 +74,17 @@ public class InstallDependencyHelperTest { private static final String PUSH_FILE_DIR = "/data/local/tmp/tests/smockingservicestest/pm/"; private static final String TEST_APP_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk"; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + @Mock private SharedLibrariesImpl mSharedLibraries; + @Mock private Context mContext; + @Mock private Computer mComputer; private InstallDependencyHelper mInstallDependencyHelper; @Before public void setUp() { MockitoAnnotations.initMocks(this); - mInstallDependencyHelper = new InstallDependencyHelper(mSharedLibraries); + mInstallDependencyHelper = new InstallDependencyHelper(mContext, mSharedLibraries); } @Test @@ -88,7 +95,8 @@ public class InstallDependencyHelperTest { PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2); CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false); - mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback); + mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer, + 0, mHandler, callback); callback.assertFailure(); assertThat(callback.error).hasMessageThat().contains("xyz"); @@ -104,11 +112,12 @@ public class InstallDependencyHelperTest { .thenReturn(missingDependency); CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false); - mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback); + mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer, + 0, mHandler, callback); callback.assertFailure(); assertThat(callback.error).hasMessageThat().contains( - "Failed to bind to Dependency Installer"); + "Dependency Installer Service not found"); } @@ -121,7 +130,8 @@ public class InstallDependencyHelperTest { .thenReturn(missingDependency); CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true); - mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, callback); + mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer, + 0, mHandler, callback); callback.assertSuccess(); } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index f68ae2c7e249..74a907f8ae22 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -309,7 +309,7 @@ public class SharedConnectivityManager { */ @TestApi @NonNull - @FlaggedApi("com.android.wifi.flags.shared_connectivity_broadcast_receiver_test_api") + @SuppressLint("UnflaggedApi") // Exempt: Test API for already shipped feature public BroadcastReceiver getBroadcastReceiver() { return mBroadcastReceiver; } |