Merge "Suppress InvalidPatternSyntax issue instead of demoting" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index ce3e985..b54022b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -64,6 +64,7 @@
":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.tracing.flags-aconfig-java{.generated_srcjars}",
":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+ ":android.webkit.flags-aconfig-java{.generated_srcjars}",
]
filegroup {
@@ -762,3 +763,19 @@
aconfig_declarations: "android.appwidget.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// WebView
+aconfig_declarations {
+ name: "android.webkit.flags-aconfig",
+ package: "android.webkit",
+ srcs: [
+ "core/java/android/webkit/*.aconfig",
+ "services/core/java/com/android/server/webkit/*.aconfig",
+ ],
+}
+
+java_aconfig_library {
+ name: "android.webkit.flags-aconfig-java",
+ aconfig_declarations: "android.webkit.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index c42c7ca..2ace602 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -128,6 +128,7 @@
final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl.Array mOutControls = new InsetsSourceControl.Array();
+ final Bundle mOutBundle = new Bundle();
final IWindow mWindow;
final View mView;
final WindowManager.LayoutParams mParams;
@@ -136,7 +137,7 @@
final SurfaceControl mOutSurfaceControl;
final IntSupplier mViewVisibility;
-
+ int mRelayoutSeq;
int mFlags;
RelayoutRunner(Activity activity, IWindow window, IntSupplier visibilitySupplier) {
@@ -152,10 +153,11 @@
void runBenchmark(BenchmarkState state) throws RemoteException {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
while (state.keepRunning()) {
+ mRelayoutSeq++;
session.relayout(mWindow, mParams, mWidth, mHeight,
- mViewVisibility.getAsInt(), mFlags, 0 /* seq */, 0 /* lastSyncSeqId */,
+ mViewVisibility.getAsInt(), mFlags, mRelayoutSeq, 0 /* lastSyncSeqId */,
mOutFrames, mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState,
- mOutControls, new Bundle());
+ mOutControls, mOutBundle);
}
}
}
diff --git a/api/Android.bp b/api/Android.bp
index d6c14fb..2b1cfcb 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -404,3 +404,49 @@
"ApiDocs.bp",
"StubLibraries.bp",
]
+
+genrule_defaults {
+ name: "flag-api-mapping-generation-defaults",
+ cmd: "$(location extract-flagged-apis) $(in) $(out)",
+ tools: ["extract-flagged-apis"],
+}
+
+genrule {
+ name: "flag-api-mapping-PublicApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-current.txt"],
+ out: ["flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
+
+genrule {
+ name: "flag-api-mapping-SystemApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-system-current.txt"],
+ out: ["system_flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
+
+genrule {
+ name: "flag-api-mapping-ModuleLibApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-module-lib-current.txt"],
+ out: ["module_lib_flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
+
+genrule {
+ name: "flag-api-mapping-SystemServerApi",
+ defaults: ["flag-api-mapping-generation-defaults"],
+ srcs: [":frameworks-base-api-system-server-current.txt"],
+ out: ["system_server_flag_api_map.textproto"],
+ dist: {
+ targets: ["droid"],
+ },
+}
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 948e64f..9ffb704 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -25,7 +25,7 @@
var cb = ApiFile.parseApi(listOf(File(args[0])))
val flagToApi = mutableMapOf<String, MutableList<String>>()
cb.getPackages()
- .allTopLevelClasses()
+ .allClasses()
.filter { it.methods().size > 0 }
.forEach {
for (method in it.methods()) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 207abb2..812d4cd6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12781,6 +12781,7 @@
method public boolean isPackageSuspended();
method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
method public abstract boolean isSafeMode();
+ method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
@@ -36759,6 +36760,7 @@
field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
field public static final String ACTION_APP_SEARCH_SETTINGS = "android.settings.APP_SEARCH_SETTINGS";
field public static final String ACTION_APP_USAGE_SETTINGS = "android.settings.action.APP_USAGE_SETTINGS";
+ field @FlaggedApi("android.app.modes_api") public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
field public static final String ACTION_AUTO_ROTATE_SETTINGS = "android.settings.AUTO_ROTATE_SETTINGS";
field public static final String ACTION_BATTERY_SAVER_SETTINGS = "android.settings.BATTERY_SAVER_SETTINGS";
field public static final String ACTION_BIOMETRIC_ENROLL = "android.settings.BIOMETRIC_ENROLL";
@@ -36846,6 +36848,7 @@
field public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
field public static final String EXTRA_APP_PACKAGE = "android.provider.extra.APP_PACKAGE";
field public static final String EXTRA_AUTHORITIES = "authorities";
+ field @FlaggedApi("android.app.modes_api") public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
field public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED = "android.settings.extra.battery_saver_mode_enabled";
field public static final String EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED = "android.provider.extra.BIOMETRIC_AUTHENTICATORS_ALLOWED";
field public static final String EXTRA_CHANNEL_FILTER_LIST = "android.provider.extra.CHANNEL_FILTER_LIST";
@@ -45159,6 +45162,7 @@
method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
+ method @FlaggedApi("com.android.internal.telephony.flags.work_profile_api_split") @NonNull public android.telephony.SubscriptionManager createForAllUserProfiles();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
method @Deprecated public static android.telephony.SubscriptionManager from(android.content.Context);
method public java.util.List<android.telephony.SubscriptionInfo> getAccessibleSubscriptionInfoList();
@@ -54183,7 +54187,7 @@
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
- method public boolean isGranularScrollingSupported();
+ method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported();
method public boolean isHeading();
method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
@@ -54233,7 +54237,7 @@
method public void setError(CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
- method public void setGranularScrollingSupported(boolean);
+ method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean);
method public void setHeading(boolean);
method public void setHintText(CharSequence);
method public void setImportantForAccessibility(boolean);
@@ -54288,7 +54292,7 @@
field public static final String ACTION_ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT = "android.view.accessibility.action.ARGUMENT_PRESS_AND_HOLD_DURATION_MILLIS_INT";
field public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
field public static final String ACTION_ARGUMENT_ROW_INT = "android.view.accessibility.action.ARGUMENT_ROW_INT";
- field public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
+ field @FlaggedApi("android.view.accessibility.granular_scrolling") public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT = "android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
field public static final String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
field public static final String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
field public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
@@ -54391,10 +54395,9 @@
public static final class AccessibilityNodeInfo.CollectionInfo {
ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean);
ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int);
- ctor public AccessibilityNodeInfo.CollectionInfo(int, int, boolean, int, int, int);
method public int getColumnCount();
- method public int getImportantForAccessibilityItemCount();
- method public int getItemCount();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getImportantForAccessibilityItemCount();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") public int getItemCount();
method public int getRowCount();
method public int getSelectionMode();
method public boolean isHierarchical();
@@ -54403,18 +54406,18 @@
field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
field public static final int SELECTION_MODE_NONE = 0; // 0x0
field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
- field public static final int UNDEFINED = -1; // 0xffffffff
+ field @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final int UNDEFINED = -1; // 0xffffffff
}
- public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
- ctor public AccessibilityNodeInfo.CollectionInfo.Builder();
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
- method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
+ @FlaggedApi("android.view.accessibility.collection_info_item_counts") public static final class AccessibilityNodeInfo.CollectionInfo.Builder {
+ ctor @FlaggedApi("android.view.accessibility.collection_info_item_counts") public AccessibilityNodeInfo.CollectionInfo.Builder();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo build();
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setColumnCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setHierarchical(boolean);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setImportantForAccessibilityItemCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setItemCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setRowCount(int);
+ method @FlaggedApi("android.view.accessibility.collection_info_item_counts") @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionInfo.Builder setSelectionMode(int);
}
public static final class AccessibilityNodeInfo.CollectionItemInfo {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 32d252e..275fe77 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3868,8 +3868,9 @@
public class PackageInstaller {
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3883,12 +3884,20 @@
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_ID = "android.content.pm.extra.UNARCHIVE_ID";
field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+ field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
field public static final int LOCATION_DATA_APP = 0; // 0x0
field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0
field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1
field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; // 0x4
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; // 0x5
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2; // 0x2
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3; // 0x3
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1; // 0x1
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_GENERIC_ERROR = 100; // 0x64
+ field @FlaggedApi("android.content.pm.archiving") public static final int UNARCHIVAL_OK = 0; // 0x0
}
public static class PackageInstaller.InstallInfo {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 02eaf0b..8af1216 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -232,6 +232,7 @@
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.org.conscrypt.TrustedCertificateStore;
import com.android.server.am.MemInfoDumpProto;
+import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
import dalvik.system.AppSpecializationHooks;
@@ -3713,7 +3714,13 @@
final ArrayList<ResultInfo> list = new ArrayList<>();
list.add(new ResultInfo(id, requestCode, resultCode, data));
final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread);
- clientTransaction.addCallback(ActivityResultItem.obtain(activityToken, list));
+ final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
+ activityToken, list);
+ if (Flags.bundleClientTransactionFlag()) {
+ clientTransaction.addTransactionItem(activityResultItem);
+ } else {
+ clientTransaction.addCallback(activityResultItem);
+ }
try {
mAppThread.scheduleTransaction(clientTransaction);
} catch (RemoteException e) {
@@ -4492,16 +4499,26 @@
private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.token,
+ final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
- /* dontReport */ false, /* autoEnteringPip */ false));
+ /* dontReport */ false, /* autoEnteringPip */ false);
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(pauseActivityItem);
+ } else {
+ transaction.setLifecycleStateRequest(pauseActivityItem);
+ }
executeTransaction(transaction);
}
private void scheduleResume(ActivityClientRecord r) {
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(r.token,
- /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
+ final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(r.token,
+ /* isForward */ false, /* shouldSendCompatFakeFocus */ false);
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(resumeActivityItem);
+ } else {
+ transaction.setLifecycleStateRequest(resumeActivityItem);
+ }
executeTransaction(transaction);
}
@@ -6092,8 +6109,13 @@
TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
// Schedule the transaction.
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
- transaction.addCallback(activityRelaunchItem);
- transaction.setLifecycleStateRequest(lifecycleRequest);
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(activityRelaunchItem);
+ transaction.addTransactionItem(lifecycleRequest);
+ } else {
+ transaction.addCallback(activityRelaunchItem);
+ transaction.setLifecycleStateRequest(lifecycleRequest);
+ }
executeTransaction(transaction);
}
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ca6d8df..a4c3bb8 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -80,6 +80,8 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VersionedPackage;
import android.content.pm.dex.ArtManager;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.res.ApkAssets;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
@@ -144,6 +146,7 @@
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.function.Function;
/** @hide */
public class ApplicationPackageManager extends PackageManager {
@@ -4024,4 +4027,34 @@
throw e.rethrowFromSystemServer();
}
}
+
+ @Override
+ public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+ Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null");
+ Objects.requireNonNull(parserFunction, "parserFunction cannot be null");
+ try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) {
+ return parserFunction.apply(xmlResourceParser);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to get the android manifest parser", e);
+ throw e;
+ }
+ }
+
+ private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath)
+ throws IOException {
+ ApkAssets apkAssets = null;
+ try {
+ apkAssets = ApkAssets.loadFromPath(apkFilePath);
+ return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
+ } finally {
+ if (apkAssets != null) {
+ try {
+ apkAssets.close();
+ } catch (Throwable ignored) {
+ Log.w(TAG, "Failed to close apkAssets", ignored);
+ }
+ }
+ }
+ }
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 56d0d1f..6c9c14f 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -51,6 +51,7 @@
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.UserHandle;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Adjustment;
import android.service.notification.Condition;
@@ -1253,7 +1254,8 @@
* <p>
* If this method returns true, calls to
* {@link #updateAutomaticZenRule(String, AutomaticZenRule)} may fail and apps should defer
- * rule management to system settings/uis.
+ * rule management to system settings/uis via
+ * {@link Settings#ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
*/
@FlaggedApi(Flags.FLAG_MODES_API)
public boolean areAutomaticZenRulesUserManaged() {
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index cc56a1c..d8448dc 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -72,6 +72,7 @@
per-file *Zen* = file:/packages/SystemUI/OWNERS
per-file *StatusBar* = file:/packages/SystemUI/OWNERS
per-file *UiModeManager* = file:/packages/SystemUI/OWNERS
+per-file notification.aconfig = file:/packages/SystemUI/OWNERS
# PackageManager
per-file ApplicationPackageManager.java = file:/services/core/java/com/android/server/pm/OWNERS
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index bbd07b8..35ce102 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -48,3 +48,10 @@
description: "Update DumpSys to include information about migrated APIs in DPE"
bug: "304999634"
}
+
+flag {
+ name: "quiet_mode_credential_bug_fix"
+ namespace: "enterprise"
+ description: "Guards a bugfix that ends the credential input flow if the managed user has not stopped."
+ bug: "293441361"
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 7c34cde..5e55268 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -75,7 +75,6 @@
/**
* Adds a message to the end of the sequence of transaction items.
* @param item A single message that can contain a client activity/window request/callback.
- * TODO(b/260873529): replace both {@link #addCallback} and {@link #setLifecycleStateRequest}.
*/
public void addTransactionItem(@NonNull ClientTransactionItem item) {
if (mTransactionItems == null) {
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index dfbccb4..475c6fb 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -235,7 +235,9 @@
* Configuration - ActivityResult - Configuration - ActivityResult
* index 1 will be returned, because ActivityResult request on position 1 will be the last
* request that moves activity to the RESUMED state where it will eventually end.
+ * @deprecated to be removed with {@link TransactionExecutor#executeCallbacks}.
*/
+ @Deprecated
static int lastCallbackRequestingState(@NonNull ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null || callbacks.isEmpty()
diff --git a/core/java/android/app/time/TEST_MAPPING b/core/java/android/app/time/TEST_MAPPING
index 49a4467..47a152a 100644
--- a/core/java/android/app/time/TEST_MAPPING
+++ b/core/java/android/app/time/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.app."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "FrameworksServicesTests",
"options": [
diff --git a/core/java/android/app/timedetector/TEST_MAPPING b/core/java/android/app/timedetector/TEST_MAPPING
index c050a55..9517fb9 100644
--- a/core/java/android/app/timedetector/TEST_MAPPING
+++ b/core/java/android/app/timedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.app."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "FrameworksServicesTests",
"options": [
diff --git a/core/java/android/app/timezonedetector/TEST_MAPPING b/core/java/android/app/timezonedetector/TEST_MAPPING
index 46656d1..fd41b86 100644
--- a/core/java/android/app/timezonedetector/TEST_MAPPING
+++ b/core/java/android/app/timezonedetector/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.app."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "FrameworksServicesTests",
"options": [
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
index 016d97f..aa32392 100644
--- a/core/java/android/app/usage/ParcelableUsageEventList.java
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -48,13 +48,16 @@
private List<Event> mList;
- public ParcelableUsageEventList(List<Event> list) {
+ public ParcelableUsageEventList(@NonNull List<Event> list) {
+ if (list == null) {
+ throw new IllegalArgumentException("Empty list");
+ }
mList = list;
}
private ParcelableUsageEventList(Parcel in) {
final int N = in.readInt();
- mList = new ArrayList<>();
+ mList = new ArrayList<>(N);
if (DEBUG) Log.d(TAG, "Retrieving " + N + " items");
if (N <= 0) {
return;
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 1eb452c..6c7eba0 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -24,9 +24,11 @@
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -35,6 +37,7 @@
* from which to read {@link android.app.usage.UsageEvents.Event} objects.
*/
public final class UsageEvents implements Parcelable {
+ private static final String TAG = "UsageEvents";
/** @hide */
public static final String INSTANT_APP_PACKAGE_NAME = "android.instant_app";
@@ -786,10 +789,20 @@
}
private void readUsageEventsFromParcelWithParceledList(Parcel in) {
+ mEventCount = in.readInt();
mIndex = in.readInt();
- mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(),
- ParcelableUsageEventList.class).getList();
- mEventCount = mEventsToWrite.size();
+ ParcelableUsageEventList slice = in.readParcelable(getClass().getClassLoader(),
+ ParcelableUsageEventList.class);
+ if (slice != null) {
+ mEventsToWrite = slice.getList();
+ } else {
+ mEventsToWrite = new ArrayList<>();
+ }
+
+ if (mEventCount != mEventsToWrite.size()) {
+ Log.w(TAG, "Partial usage event list received: " + mEventCount + " != "
+ + mEventsToWrite.size());
+ }
}
private void readUsageEventsFromParcelWithBlob(Parcel in) {
@@ -1065,6 +1078,7 @@
}
private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) {
+ dest.writeInt(mEventCount);
dest.writeInt(mIndex);
dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags);
}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 59ed045..1f25fd0 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -16,6 +16,7 @@
package android.content.pm;
+import android.app.PendingIntent;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstallerCallback;
@@ -82,7 +83,7 @@
void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
- void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
+ void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)")
void installPackageArchived(in ArchivedPackageParcel archivedPackageParcel,
@@ -90,4 +91,6 @@
in IntentSender statusReceiver,
String installerPackageName, in UserHandle userHandle);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+ void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e9a2aaa..4f0bfc7 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -387,6 +387,24 @@
"android.content.pm.extra.UNARCHIVE_ALL_USERS";
/**
+ * Current status of an unarchive operation. Will be one of
+ * {@link #UNARCHIVAL_OK}, {@link #UNARCHIVAL_ERROR_USER_ACTION_NEEDED},
+ * {@link #UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE}, {@link #UNARCHIVAL_ERROR_NO_CONNECTIVITY},
+ * {@link #UNARCHIVAL_GENERIC_ERROR}, {@link #UNARCHIVAL_ERROR_INSTALLER_DISABLED} or
+ * {@link #UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED}.
+ *
+ * <p> If the status is not {@link #UNARCHIVAL_OK}, then {@link Intent#EXTRA_INTENT} will be set
+ * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a
+ * failure dialog.
+ *
+ * @see #requestUnarchive
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final String EXTRA_UNARCHIVE_STATUS = "android.content.pm.extra.UNARCHIVE_STATUS";
+
+ /**
* A list of warnings that occurred during installation.
*
* @hide
@@ -652,6 +670,102 @@
@Retention(RetentionPolicy.SOURCE)
public @interface UserActionReason {}
+ /**
+ * The unarchival is possible and will commence.
+ *
+ * <p> Note that this does not mean that the unarchival has completed. This status should be
+ * sent before any longer asynchronous action (e.g. app download) is started.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_OK = 0;
+
+ /**
+ * The user needs to interact with the installer to enable the installation.
+ *
+ * <p> An example use case for this could be that the user needs to login to allow the
+ * download for a paid app.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_USER_ACTION_NEEDED = 1;
+
+ /**
+ * Not enough storage to unarchive the application.
+ *
+ * <p> The installer can optionally provide a {@code userActionIntent} for a space-clearing
+ * dialog. If no action is provided, then a generic intent
+ * {@link android.os.storage.StorageManager#ACTION_MANAGE_STORAGE} is started instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE = 2;
+
+ /**
+ * The device is not connected to the internet
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_NO_CONNECTIVITY = 3;
+
+ /**
+ * The installer responsible for the unarchival is disabled.
+ *
+ * <p> Should only be used by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4;
+
+ /**
+ * The installer responsible for the unarchival has been uninstalled
+ *
+ * <p> Should only be used by the system.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5;
+
+ /**
+ * Generic error: The app cannot be unarchived.
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public static final int UNARCHIVAL_GENERIC_ERROR = 100;
+
+ /**
+ * The set of error types that can be set for
+ * {@link #reportUnarchivalStatus(int, int, PendingIntent)}.
+ *
+ * @hide
+ */
+ @IntDef(value = {
+ UNARCHIVAL_OK,
+ UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+ UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+ UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+ UNARCHIVAL_ERROR_INSTALLER_DISABLED,
+ UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED,
+ UNARCHIVAL_GENERIC_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UnarchivalStatus {}
+
+
/** Default set of checksums - includes all available checksums.
* @see Session#requestChecksums */
private static final int DEFAULT_CHECKSUMS =
@@ -2238,8 +2352,8 @@
* Requests to archive a package which is currently installed.
*
* <p> During the archival process, the apps APKs and cache are removed from the device while
- * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be
- * restored again through their responsible installer.
+ * the user data is kept. Through the {@link #requestUnarchive} call, apps
+ * can be restored again through their responsible installer.
*
* <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
* will be displayed to users with UI treatment to highlight that said apps are archived. If
@@ -2278,6 +2392,10 @@
* <p> The installation will happen asynchronously and can be observed through
* {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
*
+ * @param statusReceiver Callback used to notify whether the installer has accepted the
+ * unarchival request or an error has occurred. The status update will be
+ * sent though {@link EXTRA_UNARCHIVE_STATUS}. Only one status will be
+ * sent.
* @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
* visible to the caller or if the package has no
* installer on the device anymore to unarchive it.
@@ -2290,10 +2408,10 @@
Manifest.permission.REQUEST_INSTALL_PACKAGES})
@SystemApi
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public void requestUnarchive(@NonNull String packageName)
+ public void requestUnarchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
throws IOException, PackageManager.NameNotFoundException {
try {
- mInstaller.requestUnarchive(packageName, mInstallerPackageName,
+ mInstaller.requestUnarchive(packageName, mInstallerPackageName, statusReceiver,
new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
@@ -2303,6 +2421,39 @@
}
}
+ /**
+ * Reports the status of an unarchival to the system.
+ *
+ * @param unarchiveId the ID provided by the system as part of the
+ * intent.action.UNARCHIVE broadcast with EXTRA_UNARCHIVE_ID.
+ * @param status is used for the system to provide the user with necessary
+ * follow-up steps or errors.
+ * @param requiredStorageBytes If the error is UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE this field
+ * should be set to specify how many additional bytes of storage
+ * are required to unarchive the app.
+ * @param userActionIntent Optional intent to start a follow up action required to
+ * facilitate the unarchival flow (e.g. user needs to log in).
+ * @throws PackageManager.NameNotFoundException if no unarchival with {@code unarchiveId} exists
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.INSTALL_PACKAGES,
+ Manifest.permission.REQUEST_INSTALL_PACKAGES})
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ARCHIVING)
+ public void reportUnarchivalStatus(int unarchiveId, @UnarchivalStatus int status,
+ long requiredStorageBytes, @Nullable PendingIntent userActionIntent)
+ throws PackageManager.NameNotFoundException {
+ try {
+ mInstaller.reportUnarchivalStatus(unarchiveId, status, requiredStorageBytes,
+ userActionIntent, new UserHandle(mUserId));
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// (b/239722738) This class serves as a bridge between the PackageLite class, which
// is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
// This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
@@ -2566,6 +2717,8 @@
public int developmentInstallFlags = 0;
/** {@hide} */
public int unarchiveId = -1;
+ /** {@hide} */
+ public IntentSender unarchiveIntentSender;
private final ArrayMap<String, Integer> mPermissionStates;
@@ -2618,6 +2771,7 @@
applicationEnabledSettingPersistent = source.readBoolean();
developmentInstallFlags = source.readInt();
unarchiveId = source.readInt();
+ unarchiveIntentSender = source.readParcelable(null, IntentSender.class);
}
/** {@hide} */
@@ -2652,6 +2806,7 @@
ret.applicationEnabledSettingPersistent = applicationEnabledSettingPersistent;
ret.developmentInstallFlags = developmentInstallFlags;
ret.unarchiveId = unarchiveId;
+ ret.unarchiveIntentSender = unarchiveIntentSender;
return ret;
}
@@ -3364,6 +3519,7 @@
applicationEnabledSettingPersistent);
pw.printHexPair("developmentInstallFlags", developmentInstallFlags);
pw.printPair("unarchiveId", unarchiveId);
+ pw.printPair("unarchiveIntentSender", unarchiveIntentSender);
pw.println();
}
@@ -3408,6 +3564,7 @@
dest.writeBoolean(applicationEnabledSettingPersistent);
dest.writeInt(developmentInstallFlags);
dest.writeInt(unarchiveId);
+ dest.writeParcelable(unarchiveIntentSender, flags);
}
public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index c3b3423..fe31c9d 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -34,6 +34,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.annotation.XmlRes;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -96,6 +97,7 @@
import dalvik.system.VMRuntime;
import java.io.File;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.cert.Certificate;
@@ -108,6 +110,7 @@
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* Class for retrieving various kinds of information related to the application
@@ -11426,4 +11429,60 @@
throw new UnsupportedOperationException(
"unregisterPackageMonitorCallback not implemented in subclass");
}
+
+ /**
+ * Retrieve AndroidManifest.xml information for the given application apk path.
+ *
+ * <p>Example:
+ *
+ * <pre><code>
+ * Bundle result;
+ * try {
+ * result = getContext().getPackageManager().parseAndroidManifest(apkFilePath,
+ * xmlResourceParser -> {
+ * Bundle bundle = new Bundle();
+ * // Search the start tag
+ * int type;
+ * while ((type = xmlResourceParser.next()) != XmlPullParser.START_TAG
+ * && type != XmlPullParser.END_DOCUMENT) {
+ * }
+ * if (type != XmlPullParser.START_TAG) {
+ * return bundle;
+ * }
+ *
+ * // Start to read the tags and attributes from the xmlResourceParser
+ * if (!xmlResourceParser.getName().equals("manifest")) {
+ * return bundle;
+ * }
+ * String packageName = xmlResourceParser.getAttributeValue(null, "package");
+ * bundle.putString("package", packageName);
+ *
+ * // Continue to read the tags and attributes from the xmlResourceParser
+ *
+ * return bundle;
+ * });
+ * } catch (IOException e) {
+ * }
+ * </code></pre>
+ *
+ * Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml
+ * information by the XmlResourceParser object. After leaving the parserFunction, the
+ * XmlResourceParser object will be closed.
+ *
+ * @param apkFilePath The path of an application apk file.
+ * @param parserFunction The parserFunction will be invoked with the XmlResourceParser object
+ * after getting the AndroidManifest.xml of an application package.
+ *
+ * @return Returns the result of the {@link Function#apply(Object)}.
+ *
+ * @throws IOException if the AndroidManifest.xml of an application package cannot be
+ * read or accessed.
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO)
+ @WorkerThread
+ public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+ @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
+ throw new UnsupportedOperationException(
+ "parseAndroidManifest not implemented in subclass");
+ }
}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index f817fb8..1100731 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -82,8 +82,8 @@
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
/**
- * Debuggable builds will throw an BinderProxyMapSizeException if the number of
- * map entries exceeds:
+ * We will throw a BinderProxyMapSizeException if the number of map entries
+ * exceeds:
*/
private static final int CRASH_AT_SIZE = 25_000;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 33c15d77..ff6ec29 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -18,6 +18,7 @@
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
@@ -36,6 +37,7 @@
import android.app.AppOpsManager;
import android.app.Application;
import android.app.AutomaticZenRule;
+import android.app.Flags;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.SearchManager;
@@ -1904,6 +1906,36 @@
= "android.settings.ACTION_CONDITION_PROVIDER_SETTINGS";
/**
+ * Activity Action: Shows the settings page for an {@link AutomaticZenRule} mode.
+ * <p>
+ * Users can change the behavior of the mode when it's activated and access the owning app's
+ * additional configuration screen, where triggering criteria can be modified (see
+ * {@link AutomaticZenRule#setConfigurationActivity(ComponentName)}).
+ * <p>
+ * A matching Activity will only be found if
+ * {@link NotificationManager#areAutomaticZenRulesUserManaged()} is true.
+ * <p>
+ * Input: Intent's data URI set with an application name, using the "package" schema (like
+ * "package:com.my.app").
+ * Input: The id of the rule, provided in {@link #EXTRA_AUTOMATIC_ZEN_RULE_ID}.
+ * <p>
+ * Output: Nothing.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+ = "android.settings.AUTOMATIC_ZEN_RULE_SETTINGS";
+
+ /**
+ * Activity Extra: The String id of the {@link AutomaticZenRule mode} settings to display.
+ * <p>
+ * This must be passed as an extra field to the {@link #ACTION_AUTOMATIC_ZEN_RULE_SETTINGS}.
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final String EXTRA_AUTOMATIC_ZEN_RULE_ID
+ = "android.provider.extra.AUTOMATIC_ZEN_RULE_ID";
+
+ /**
* Activity Action: Show settings for video captioning.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
diff --git a/core/java/android/service/timezone/TEST_MAPPING b/core/java/android/service/timezone/TEST_MAPPING
index 21a8eab..e5910ea 100644
--- a/core/java/android/service/timezone/TEST_MAPPING
+++ b/core/java/android/service/timezone/TEST_MAPPING
@@ -1,6 +1,5 @@
{
- // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
- "postsubmit": [
+ "presubmit": [
{
"name": "FrameworksTimeCoreTests",
"options": [
@@ -8,7 +7,10 @@
"include-filter": "android.service."
}
]
- },
+ }
+ ],
+ // TODO(b/182461754): Change to "presubmit" when go/test-mapping-slo-guide allows.
+ "postsubmit": [
{
"name": "CtsLocationTimeZoneManagerHostTest"
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 43bfe13..53aed49 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -23,7 +23,7 @@
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.annotation.Hide;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -752,6 +752,7 @@
* {@link #isGranularScrollingSupported()} to check if granular scrolling is supported.
* </p>
*/
+ @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
public static final String ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT =
"android.view.accessibility.action.ARGUMENT_SCROLL_AMOUNT_FLOAT";
@@ -2608,6 +2609,7 @@
* @return True if all scroll actions that could support
* {@link #ACTION_ARGUMENT_SCROLL_AMOUNT_FLOAT} have done so, false otherwise.
*/
+ @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
public boolean isGranularScrollingSupported() {
return getBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING);
}
@@ -2626,6 +2628,7 @@
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
+ @FlaggedApi(Flags.FLAG_GRANULAR_SCROLLING)
public void setGranularScrollingSupported(boolean granularScrollingSupported) {
setBooleanProperty(BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING,
granularScrollingSupported);
@@ -6119,6 +6122,7 @@
* This should be used for {@code mItemCount} and
* {@code mImportantForAccessibilityItemCount} when values for those fields are not known.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public static final int UNDEFINED = -1;
private int mRowCount;
@@ -6229,8 +6233,8 @@
* the item count is not known.
* @param importantForAccessibilityItemCount The count of the collection's views considered
* important for accessibility.
+ * @hide
*/
- @Hide
public CollectionInfo(int rowCount, int columnCount, boolean hierarchical,
int selectionMode, int itemCount, int importantForAccessibilityItemCount) {
mRowCount = rowCount;
@@ -6287,6 +6291,7 @@
*
* @return The count of items, which may be {@code UNDEFINED} if the count is not known.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public int getItemCount() {
return mItemCount;
}
@@ -6297,6 +6302,7 @@
* @return The count of items important for accessibility, which may be {@code UNDEFINED}
* if the count is not known.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public int getImportantForAccessibilityItemCount() {
return mImportantForAccessibilityItemCount;
}
@@ -6323,6 +6329,7 @@
* The builder for CollectionInfo.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public static final class Builder {
private int mRowCount = 0;
private int mColumnCount = 0;
@@ -6334,6 +6341,7 @@
/**
* Creates a new Builder.
*/
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public Builder() {
}
@@ -6343,6 +6351,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setRowCount(int rowCount) {
mRowCount = rowCount;
return this;
@@ -6354,6 +6363,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setColumnCount(int columnCount) {
mColumnCount = columnCount;
return this;
@@ -6364,6 +6374,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setHierarchical(boolean hierarchical) {
mHierarchical = hierarchical;
return this;
@@ -6375,6 +6386,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setSelectionMode(int selectionMode) {
mSelectionMode = selectionMode;
return this;
@@ -6389,6 +6401,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setItemCount(int itemCount) {
mItemCount = itemCount;
return this;
@@ -6401,6 +6414,7 @@
* @return This builder.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo.Builder setImportantForAccessibilityItemCount(
int importantForAccessibilityItemCount) {
mImportantForAccessibilityItemCount = importantForAccessibilityItemCount;
@@ -6411,6 +6425,7 @@
* Creates a new {@link CollectionInfo} instance.
*/
@NonNull
+ @FlaggedApi(Flags.FLAG_COLLECTION_INFO_ITEM_COUNTS)
public CollectionInfo build() {
CollectionInfo collectionInfo = new CollectionInfo(mRowCount, mColumnCount,
mHierarchical);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 950fa4b..c337cb4 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -17,6 +17,13 @@
}
flag {
+ namespace: "accessibility"
+ name: "collection_info_item_counts"
+ description: "Fields for total items and the number of important for accessibility items in a collection"
+ bug: "302376158"
+}
+
+flag {
name: "deduplicate_accessibility_warning_dialog"
namespace: "accessibility"
description: "Removes duplicate definition of the accessibility warning dialog."
@@ -39,6 +46,13 @@
flag {
namespace: "accessibility"
+ name: "granular_scrolling"
+ description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
+ bug: "302376158"
+}
+
+flag {
+ namespace: "accessibility"
name: "update_always_on_a11y_service"
description: "Updates the Always-On A11yService state when the user changes the enablement of the shortcut."
bug: "298869916"
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index b600b22..933cc49 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -9,6 +9,16 @@
bug: "260873529"
}
+# Using a fixed read only flag because there are ClientTransaction scheduling before
+# WindowManagerService creation.
+flag {
+ namespace: "windowing_sdk"
+ name: "bundle_client_transaction_flag"
+ description: "To bundle multiple ClientTransactionItems into one ClientTransaction"
+ bug: "260873529"
+ is_fixed_read_only: true
+}
+
flag {
namespace: "windowing_sdk"
name: "activity_embedding_overlay_presentation_flag"
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index e3bb1fe..7be27be 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -54,18 +54,6 @@
*/
public static final class NotificationFlags {
- /**
- * FOR DEVELOPMENT / TESTING ONLY!!!
- * Forcibly demote *ALL* FSI notifications as if no apps have the app op permission.
- * NOTE: enabling this implies SHOW_STICKY_HUN_FOR_DENIED_FSI in SystemUI
- */
- public static final Flag FSI_FORCE_DEMOTE =
- devFlag("persist.sysui.notification.fsi_force_demote");
-
- /** Gating the feature which shows FSI-denied notifications as Sticky HUNs */
- public static final Flag SHOW_STICKY_HUN_FOR_DENIED_FSI =
- releasedFlag("persist.sysui.notification.show_sticky_hun_for_denied_fsi");
-
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 5c1d91f..5b68e8e 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -184,11 +184,11 @@
void NativeInputEventReceiver::setFdEvents(int events) {
if (mFdEvents != events) {
mFdEvents = events;
- int fd = mInputConsumer.getChannel()->getFd();
+ auto&& fd = mInputConsumer.getChannel()->getFd();
if (events) {
- mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
+ mMessageQueue->getLooper()->addFd(fd.get(), 0, events, this, nullptr);
} else {
- mMessageQueue->getLooper()->removeFd(fd);
+ mMessageQueue->getLooper()->removeFd(fd.get());
}
}
}
diff --git a/core/jni/android_view_InputEventSender.cpp b/core/jni/android_view_InputEventSender.cpp
index 833952d..6bdf821 100644
--- a/core/jni/android_view_InputEventSender.cpp
+++ b/core/jni/android_view_InputEventSender.cpp
@@ -102,8 +102,8 @@
}
status_t NativeInputEventSender::initialize() {
- int receiveFd = mInputPublisher.getChannel()->getFd();
- mMessageQueue->getLooper()->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, this, NULL);
+ auto&& receiveFd = mInputPublisher.getChannel()->getFd();
+ mMessageQueue->getLooper()->addFd(receiveFd.get(), 0, ALOOPER_EVENT_INPUT, this, NULL);
return OK;
}
@@ -112,7 +112,7 @@
LOG(DEBUG) << "channel '" << getInputChannelName() << "' ~ Disposing input event sender.";
}
- mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd());
+ mMessageQueue->getLooper()->removeFd(mInputPublisher.getChannel()->getFd().get());
}
status_t NativeInputEventSender::sendKeyEvent(uint32_t seq, const KeyEvent* event) {
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4b02257..2327b20 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -42,6 +42,7 @@
import android.app.PictureInPictureParams;
import android.app.ResourcesManager;
import android.app.servertransaction.ActivityConfigurationChangeItem;
+import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
@@ -73,6 +74,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.content.ReferrerIntent;
+import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -227,7 +229,8 @@
try {
// Send process level config change.
ClientTransaction transaction = newTransaction(activityThread);
- transaction.addCallback(ConfigurationChangeItem.obtain(newConfig, DEVICE_ID_INVALID));
+ addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
+ newConfig, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -243,7 +246,7 @@
newConfig.seq++;
newConfig.smallestScreenWidthDp++;
transaction = newTransaction(activityThread);
- transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), newConfig));
appThread.scheduleTransaction(transaction);
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -444,16 +447,16 @@
activity.mTestLatch = new CountDownLatch(1);
ClientTransaction transaction = newTransaction(activityThread);
- transaction.addCallback(ConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
processConfigLandscape, DEVICE_ID_INVALID));
appThread.scheduleTransaction(transaction);
transaction = newTransaction(activityThread);
- transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigLandscape));
- transaction.addCallback(ConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ConfigurationChangeItem.obtain(
processConfigPortrait, DEVICE_ID_INVALID));
- transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+ addClientTransactionItem(transaction, ActivityConfigurationChangeItem.obtain(
activity.getActivityToken(), activityConfigPortrait));
appThread.scheduleTransaction(transaction);
@@ -840,8 +843,8 @@
false /* shouldSendCompatFakeFocus*/);
final ClientTransaction transaction = newTransaction(activity);
- transaction.addCallback(callbackItem);
- transaction.setLifecycleStateRequest(resumeStateRequest);
+ addClientTransactionItem(transaction, callbackItem);
+ addClientTransactionItem(transaction, resumeStateRequest);
return transaction;
}
@@ -853,7 +856,7 @@
false /* shouldSendCompatFakeFocus */);
final ClientTransaction transaction = newTransaction(activity);
- transaction.setLifecycleStateRequest(resumeStateRequest);
+ addClientTransactionItem(transaction, resumeStateRequest);
return transaction;
}
@@ -864,7 +867,7 @@
activity.getActivityToken(), 0 /* configChanges */);
final ClientTransaction transaction = newTransaction(activity);
- transaction.setLifecycleStateRequest(stopStateRequest);
+ addClientTransactionItem(transaction, stopStateRequest);
return transaction;
}
@@ -876,7 +879,7 @@
activity.getActivityToken(), config);
final ClientTransaction transaction = newTransaction(activity);
- transaction.addCallback(item);
+ addClientTransactionItem(transaction, item);
return transaction;
}
@@ -888,7 +891,7 @@
resume);
final ClientTransaction transaction = newTransaction(activity);
- transaction.addCallback(item);
+ addClientTransactionItem(transaction, item);
return transaction;
}
@@ -903,6 +906,17 @@
return ClientTransaction.obtain(activityThread.getApplicationThread());
}
+ private static void addClientTransactionItem(@NonNull ClientTransaction transaction,
+ @NonNull ClientTransactionItem item) {
+ if (Flags.bundleClientTransactionFlag()) {
+ transaction.addTransactionItem(item);
+ } else if (item.isActivityLifecycleItem()) {
+ transaction.setLifecycleStateRequest((ActivityLifecycleItem) item);
+ } else {
+ transaction.addCallback(item);
+ }
+ }
+
// Test activity
public static class TestActivity extends Activity {
static final String PIP_REQUESTED_OVERRIDE_ENTER = "pip_requested_override_enter";
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
index 2ec58d4..5a202c5 100644
--- a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
@@ -43,11 +44,29 @@
@LargeTest
public class ParcelableUsageEventListTest {
private static final int SMALL_TEST_EVENT_COUNT = 100;
- private static final int LARGE_TEST_EVENT_COUNT = 10000;
+ private static final int LARGE_TEST_EVENT_COUNT = 30000;
private Random mRandom = new Random();
@Test
+ public void testNullList() throws Exception {
+ Parcel parcel = Parcel.obtain();
+ try {
+ parcel.writeParcelable(new ParcelableUsageEventList(null), 0);
+ fail("Expected IllegalArgumentException with null list.");
+ } catch (IllegalArgumentException expected) {
+ // Expected.
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+ @Test
+ public void testEmptyList() throws Exception {
+ testParcelableUsageEventList(0);
+ }
+
+ @Test
public void testSmallList() throws Exception {
testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT);
}
@@ -58,15 +77,15 @@
}
private void testParcelableUsageEventList(int eventCount) throws Exception {
- List<Event> smallList = new ArrayList<>();
+ List<Event> eventList = new ArrayList<>();
for (int i = 0; i < eventCount; i++) {
- smallList.add(generateUsageEvent());
+ eventList.add(generateUsageEvent());
}
ParcelableUsageEventList slice;
Parcel parcel = Parcel.obtain();
try {
- parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0);
+ parcel.writeParcelable(new ParcelableUsageEventList(eventList), 0);
parcel.setDataPosition(0);
slice = parcel.readParcelable(getClass().getClassLoader(),
ParcelableUsageEventList.class);
@@ -79,7 +98,7 @@
assertEquals(eventCount, slice.getList().size());
for (int i = 0; i < eventCount; i++) {
- compareUsageEvent(smallList.get(i), slice.getList().get(i));
+ compareUsageEvent(eventList.get(i), slice.getList().get(i));
}
}
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
index 4533db6..3abdb6f 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/IntentKtx.kt
@@ -37,17 +37,17 @@
RequestInfo::class.java
)
-val Intent.getCredentialProviderDataList: List<ProviderData>
+val Intent.getCredentialProviderDataList: List<GetCredentialProviderData>
get() = this.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
GetCredentialProviderData::class.java
- ) ?: emptyList()
+ ) ?.filterIsInstance<GetCredentialProviderData>() ?: emptyList()
-val Intent.createCredentialProviderDataList: List<ProviderData>
+val Intent.createCredentialProviderDataList: List<CreateCredentialProviderData>
get() = this.extras?.getParcelableArrayList(
ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
CreateCredentialProviderData::class.java
- ) ?: emptyList()
+ ) ?.filterIsInstance<CreateCredentialProviderData>() ?: emptyList()
val Intent.resultReceiver: ResultReceiver?
get() = this.getParcelableExtra(
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
index ee45fbb..d4bca2a 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/mapper/RequestGetMapper.kt
@@ -18,7 +18,6 @@
import android.content.Intent
import android.credentials.ui.Entry
-import android.credentials.ui.GetCredentialProviderData
import androidx.credentials.provider.PasswordCredentialEntry
import com.android.credentialmanager.factory.fromSlice
import com.android.credentialmanager.ktx.getCredentialProviderDataList
@@ -32,12 +31,10 @@
fun Intent.toGet(): Request.Get {
val credentialEntries = mutableListOf<Pair<String, Entry>>()
for (providerData in getCredentialProviderDataList) {
- if (providerData is GetCredentialProviderData) {
- for (credentialEntry in providerData.credentialEntries) {
- credentialEntries.add(
- Pair(providerData.providerFlattenedComponentName, credentialEntry)
- )
- }
+ for (credentialEntry in providerData.credentialEntries) {
+ credentialEntries.add(
+ Pair(providerData.providerFlattenedComponentName, credentialEntry)
+ )
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2c15fc6..8412cba 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -249,6 +249,8 @@
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
Settings.Secure.STYLUS_BUTTONS_ENABLED,
Settings.Secure.STYLUS_HANDWRITING_ENABLED,
- Settings.Secure.DEFAULT_NOTE_TASK_PROFILE
+ Settings.Secure.DEFAULT_NOTE_TASK_PROFILE,
+ Settings.Secure.CREDENTIAL_SERVICE,
+ Settings.Secure.CREDENTIAL_SERVICE_PRIMARY
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 71c2ddc..9197554 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -19,11 +19,13 @@
import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COMMA_SEPARATED_COMPONENT_LIST_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.COMPONENT_NAME_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.JSON_OBJECT_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.LOCALE_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
@@ -62,7 +64,6 @@
VALIDATORS.put(Secure.ADAPTIVE_CHARGING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
VALIDATORS.put(
Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
new InclusiveFloatRangeValidator(1.0f, Float.MAX_VALUE));
@@ -398,5 +399,8 @@
VALIDATORS.put(Secure.STYLUS_HANDWRITING_ENABLED,
new DiscreteValueValidator(new String[] {"-1", "0", "1"}));
VALIDATORS.put(Secure.DEFAULT_NOTE_TASK_PROFILE, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.CREDENTIAL_SERVICE, CREDENTIAL_SERVICE_VALIDATOR);
+ VALIDATORS.put(Secure.CREDENTIAL_SERVICE_PRIMARY, NULLABLE_COMPONENT_NAME_VALIDATOR);
+ VALIDATORS.put(Secure.AUTOFILL_SERVICE, AUTOFILL_SERVICE_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
index 49012b0..a8a659e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java
@@ -235,4 +235,30 @@
}
}
};
+
+ static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ if (value == null || value.equals("")) {
+ return true;
+ }
+
+ return COLON_SEPARATED_COMPONENT_LIST_VALIDATOR.validate(value);
+ }
+ };
+
+ static final Validator AUTOFILL_SERVICE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ if (value == null || value.equals("")) {
+ return true;
+ }
+
+ if (value.equals("credential-provider")) {
+ return true;
+ }
+
+ return NULLABLE_COMPONENT_NAME_VALIDATOR.validate(value);
+ }
+ };
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index f1b53ed..efed8c3 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -851,8 +851,6 @@
Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
Settings.Secure.UI_TRANSLATION_ENABLED,
- Settings.Secure.CREDENTIAL_SERVICE,
- Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
Settings.Secure.DND_CONFIGS_MIGRATED,
Settings.Secure.NAVIGATION_MODE_RESTORE);
diff --git a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
index 865f431..3b3bf8ca 100644
--- a/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/settings/validators/SettingsValidatorsTest.java
@@ -340,6 +340,60 @@
failIfOffendersPresent(offenders, "Settings.Secure");
}
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfNull() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(null));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfEmpty() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(""));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfSingleComponentName() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+ "android.credentials/android.credentials.Test"));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsTrueIfMultipleComponentName() {
+ assertTrue(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate(
+ "android.credentials/android.credentials.Test"
+ + ":android.credentials/.Test2"));
+ }
+
+ @Test
+ public void testCredentialServiceValidator_returnsFalseIfInvalidComponentName() {
+ assertFalse(SettingsValidators.CREDENTIAL_SERVICE_VALIDATOR.validate("test"));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfNull() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(null));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfEmpty() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(""));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfPlaceholder() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("credential-provider"));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsTrueIfSingleComponentName() {
+ assertTrue(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate(
+ "android.credentials/android.credentials.Test"));
+ }
+
+ @Test
+ public void testAutofillServiceValidator_returnsFalseIfInvalidComponentName() {
+ assertFalse(SettingsValidators.AUTOFILL_SERVICE_VALIDATOR.validate("test"));
+ }
+
private void failIfOffendersPresent(String offenders, String settingsType) {
if (offenders.length() > 0) {
fail("All " + settingsType + " settings that are backed up have to have a non-null"
diff --git a/packages/SystemUI/communal/layout/Android.bp b/packages/SystemUI/communal/layout/Android.bp
deleted file mode 100644
index 88dad66..0000000
--- a/packages/SystemUI/communal/layout/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_library {
- name: "CommunalLayoutLib",
- srcs: [
- "src/**/*.kt",
- ],
- static_libs: [
- "androidx.arch.core_core-runtime",
- "androidx.compose.animation_animation-graphics",
- "androidx.compose.runtime_runtime",
- "androidx.compose.material3_material3",
- "jsr330",
- "kotlinx-coroutines-android",
- "kotlinx-coroutines-core",
- ],
- manifest: "AndroidManifest.xml",
- kotlincflags: ["-Xjvm-default=all"],
-}
diff --git a/packages/SystemUI/communal/layout/AndroidManifest.xml b/packages/SystemUI/communal/layout/AndroidManifest.xml
deleted file mode 100644
index 141be07..0000000
--- a/packages/SystemUI/communal/layout/AndroidManifest.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2023 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest package="com.android.systemui.communal.layout" />
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
deleted file mode 100644
index 91fe33c..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/CommunalLayoutEngine.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.layout
-
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-
-/** Computes the arrangement of cards. */
-class CommunalLayoutEngine {
- companion object {
- /**
- * Determines the size that each card should be rendered in, and distributes the cards into
- * columns.
- *
- * Returns a nested list where the outer list contains columns, and the inner list contains
- * cards in each column.
- *
- * Currently treats the first supported size as the size to be rendered in, ignoring other
- * supported sizes.
- *
- * Cards are ordered by priority, from highest to lowest.
- */
- fun distributeCardsIntoColumns(
- cards: List<CommunalGridLayoutCard>,
- ): List<List<CommunalGridLayoutCardInfo>> {
- val result = ArrayList<ArrayList<CommunalGridLayoutCardInfo>>()
-
- var capacityOfLastColumn = 0
- val sorted = cards.sortedByDescending { it.priority }
- for (card in sorted) {
- val cardSize = card.supportedSizes.first()
- if (capacityOfLastColumn >= cardSize.value) {
- // Card fits in last column
- capacityOfLastColumn -= cardSize.value
- } else {
- // Create a new column
- result.add(arrayListOf())
- capacityOfLastColumn = CommunalGridLayoutCard.Size.FULL.value - cardSize.value
- }
-
- result.last().add(CommunalGridLayoutCardInfo(card, cardSize))
- }
-
- return result
- }
- }
-
- /**
- * A data class that wraps around a [CommunalGridLayoutCard] and also contains the size that the
- * card should be rendered in.
- */
- data class CommunalGridLayoutCardInfo(
- val card: CommunalGridLayoutCard,
- val size: CommunalGridLayoutCard.Size,
- )
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
deleted file mode 100644
index 33024f7..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/CommunalGridLayout.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.layout.ui.compose
-
-import android.util.SizeF
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.systemui.communal.layout.CommunalLayoutEngine
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutConfig
-
-/**
- * An arrangement of cards with a horizontal scroll, where each card is displayed in the right size
- * and follows a specific order based on its priority, ensuring a seamless layout without any gaps.
- */
-@Composable
-fun CommunalGridLayout(
- modifier: Modifier,
- layoutConfig: CommunalGridLayoutConfig,
- communalCards: List<CommunalGridLayoutCard>,
-) {
- val columns = CommunalLayoutEngine.distributeCardsIntoColumns(communalCards)
- LazyRow(
- modifier = modifier.height(layoutConfig.gridHeight),
- horizontalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
- ) {
- for (column in columns) {
- item {
- Column(
- modifier = Modifier.width(layoutConfig.cardWidth),
- verticalArrangement = Arrangement.spacedBy(layoutConfig.gridGutter),
- ) {
- for (cardInfo in column) {
- Row(
- modifier = Modifier.height(layoutConfig.cardHeight(cardInfo.size)),
- ) {
- cardInfo.card.Content(
- modifier = Modifier.fillMaxSize(),
- size =
- SizeF(
- layoutConfig.cardWidth.value,
- layoutConfig.cardHeight(cardInfo.size).value,
- ),
- )
- }
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
deleted file mode 100644
index 4b2a156..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutCard.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.layout.ui.compose.config
-
-import android.util.SizeF
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-
-/** A card that hosts content to be rendered in the communal grid layout. */
-abstract class CommunalGridLayoutCard {
- /**
- * Content to be hosted by the card.
- *
- * To host non-Compose views, see
- * https://developer.android.com/jetpack/compose/migrate/interoperability-apis/views-in-compose.
- *
- * @param size The size given to the card. Content of the card should fill all this space, given
- * that margins and paddings have been taken care of by the layout.
- */
- @Composable abstract fun Content(modifier: Modifier, size: SizeF)
-
- /**
- * Sizes supported by the card.
- *
- * If multiple sizes are available, they should be ranked in order of preference, from most to
- * least preferred.
- */
- abstract val supportedSizes: List<Size>
-
- /**
- * Priority of the content hosted by the card.
- *
- * The value of priority is relative to other cards. Cards with a higher priority are generally
- * ordered first.
- */
- open val priority: Int = 0
-
- /**
- * Size of the card.
- *
- * @param value A numeric value that represents the size. Must be less than or equal to
- * [Size.FULL].
- */
- enum class Size(val value: Int) {
- /** The card takes up full height of the grid layout. */
- FULL(value = 6),
-
- /** The card takes up half of the vertical space of the grid layout. */
- HALF(value = 3),
-
- /** The card takes up a third of the vertical space of the grid layout. */
- THIRD(value = 2),
- }
-}
diff --git a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt b/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
deleted file mode 100644
index 143df83..0000000
--- a/packages/SystemUI/communal/layout/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfig.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.times
-
-/**
- * Configurations of the communal grid layout.
- *
- * The communal grid layout follows Material Design's responsive layout grid (see
- * https://m2.material.io/design/layout/responsive-layout-grid.html), in which the layout is divided
- * up by columns and gutters, and each card occupies one or multiple columns.
- */
-data class CommunalGridLayoutConfig(
- /**
- * Size in dp of each grid column.
- *
- * Every card occupies one or more grid columns, which means that the width of each card is
- * influenced by the size of the grid columns.
- */
- val gridColumnSize: Dp,
-
- /**
- * Size in dp of each grid gutter.
- *
- * A gutter is the space between columns that helps separate content. This is, therefore, also
- * the size of the gaps between cards, both horizontally and vertically.
- */
- val gridGutter: Dp,
-
- /**
- * Height in dp of the grid layout.
- *
- * Cards with a full size take up the entire height of the grid layout.
- */
- val gridHeight: Dp,
-
- /**
- * Number of grid columns that each card occupies.
- *
- * It's important to note that all the cards take up the same number of grid columns, or in
- * simpler terms, they all have the same width.
- */
- val gridColumnsPerCard: Int,
-) {
- /**
- * Width in dp of each card.
- *
- * It's important to note that all the cards take up the same number of grid columns, or in
- * simpler terms, they all have the same width.
- */
- val cardWidth = gridColumnSize * gridColumnsPerCard + gridGutter * (gridColumnsPerCard - 1)
-
- /** Returns the height of a card in dp, based on its size. */
- fun cardHeight(cardSize: CommunalGridLayoutCard.Size): Dp {
- return when (cardSize) {
- CommunalGridLayoutCard.Size.FULL -> cardHeightBy(denominator = 1)
- CommunalGridLayoutCard.Size.HALF -> cardHeightBy(denominator = 2)
- CommunalGridLayoutCard.Size.THIRD -> cardHeightBy(denominator = 3)
- }
- }
-
- /** Returns the height of a card in dp when the layout is evenly divided by [denominator]. */
- private fun cardHeightBy(denominator: Int): Dp {
- return (gridHeight - (denominator - 1) * gridGutter) / denominator
- }
-}
diff --git a/packages/SystemUI/communal/layout/tests/Android.bp b/packages/SystemUI/communal/layout/tests/Android.bp
deleted file mode 100644
index 9a05504..0000000
--- a/packages/SystemUI/communal/layout/tests/Android.bp
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
-}
-
-android_test {
- name: "CommunalLayoutLibTests",
- srcs: [
- "**/*.kt",
- ],
- static_libs: [
- "CommunalLayoutLib",
- "androidx.test.runner",
- "androidx.test.rules",
- "androidx.test.ext.junit",
- "frameworks-base-testutils",
- "junit",
- "kotlinx_coroutines_test",
- "mockito-target-extended-minus-junit4",
- "platform-test-annotations",
- "testables",
- "truth",
- ],
- libs: [
- "android.test.mock",
- "android.test.base",
- "android.test.runner",
- ],
- jni_libs: [
- "libdexmakerjvmtiagent",
- "libstaticjvmtiagent",
- ],
- manifest: "AndroidManifest.xml",
-}
diff --git a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml b/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
deleted file mode 100644
index b19007c..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<manifest
- xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.systemui.communal.layout.tests">
-
- <application android:debuggable="true" android:largeHeap="true">
- <uses-library android:name="android.test.mock" />
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="android.testing.TestableInstrumentation"
- android:targetPackage="com.android.systemui.communal.layout.tests"
- android:label="Tests for CommunalLayoutLib">
- </instrumentation>
-
-</manifest>
diff --git a/packages/SystemUI/communal/layout/tests/AndroidTest.xml b/packages/SystemUI/communal/layout/tests/AndroidTest.xml
deleted file mode 100644
index 1352b23..0000000
--- a/packages/SystemUI/communal/layout/tests/AndroidTest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration description="Runs tests for CommunalLayoutLib">
-
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="install-arg" value="-t" />
- <option name="test-file-name" value="CommunalLayoutLibTests.apk" />
- </target_preparer>
-
- <option name="test-suite-tag" value="apct" />
- <option name="test-suite-tag" value="framework-base-presubmit" />
- <option name="test-tag" value="CommunalLayoutLibTests" />
-
- <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
- <option name="package" value="com.android.systemui.communal.layout.tests" />
- <option name="runner" value="android.testing.TestableInstrumentation" />
- <option name="hidden-api-checks" value="false"/>
- </test>
-
-</configuration>
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
deleted file mode 100644
index 50b7c5f..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/CommunalLayoutEngineTest.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package com.android.systemui.communal.layout
-
-import android.util.SizeF
-import androidx.compose.material3.Card
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.communal.layout.ui.compose.config.CommunalGridLayoutCard
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalLayoutEngineTest {
- @Test
- fun distribution_fullLayout() {
- val cards =
- listOf(
- generateCard(CommunalGridLayoutCard.Size.FULL),
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- )
- val expected =
- listOf(
- listOf(
- CommunalGridLayoutCard.Size.FULL,
- ),
- listOf(
- CommunalGridLayoutCard.Size.HALF,
- CommunalGridLayoutCard.Size.HALF,
- ),
- listOf(
- CommunalGridLayoutCard.Size.THIRD,
- CommunalGridLayoutCard.Size.THIRD,
- CommunalGridLayoutCard.Size.THIRD,
- ),
- )
-
- assertDistributionBySize(cards, expected)
- }
-
- @Test
- fun distribution_layoutWithGaps() {
- val cards =
- listOf(
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- generateCard(CommunalGridLayoutCard.Size.HALF),
- generateCard(CommunalGridLayoutCard.Size.FULL),
- generateCard(CommunalGridLayoutCard.Size.THIRD),
- )
- val expected =
- listOf(
- listOf(
- CommunalGridLayoutCard.Size.HALF,
- CommunalGridLayoutCard.Size.THIRD,
- ),
- listOf(
- CommunalGridLayoutCard.Size.HALF,
- ),
- listOf(
- CommunalGridLayoutCard.Size.FULL,
- ),
- listOf(
- CommunalGridLayoutCard.Size.THIRD,
- ),
- )
-
- assertDistributionBySize(cards, expected)
- }
-
- @Test
- fun distribution_sortByPriority() {
- val cards =
- listOf(
- generateCard(priority = 2),
- generateCard(priority = 7),
- generateCard(priority = 10),
- generateCard(priority = 1),
- generateCard(priority = 5),
- )
- val expected =
- listOf(
- listOf(10, 7),
- listOf(5, 2),
- listOf(1),
- )
-
- assertDistributionByPriority(cards, expected)
- }
-
- private fun assertDistributionBySize(
- cards: List<CommunalGridLayoutCard>,
- expected: List<List<CommunalGridLayoutCard.Size>>,
- ) {
- val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
- for (c in expected.indices) {
- for (r in expected[c].indices) {
- assertThat(result[c][r].size).isEqualTo(expected[c][r])
- }
- }
- }
-
- private fun assertDistributionByPriority(
- cards: List<CommunalGridLayoutCard>,
- expected: List<List<Int>>,
- ) {
- val result = CommunalLayoutEngine.distributeCardsIntoColumns(cards)
-
- for (c in expected.indices) {
- for (r in expected[c].indices) {
- assertThat(result[c][r].card.priority).isEqualTo(expected[c][r])
- }
- }
- }
-
- private fun generateCard(size: CommunalGridLayoutCard.Size): CommunalGridLayoutCard {
- return object : CommunalGridLayoutCard() {
- override val supportedSizes = listOf(size)
-
- @Composable
- override fun Content(modifier: Modifier, size: SizeF) {
- Card(modifier = modifier, content = {})
- }
- }
- }
-
- private fun generateCard(priority: Int): CommunalGridLayoutCard {
- return object : CommunalGridLayoutCard() {
- override val supportedSizes = listOf(Size.HALF)
- override val priority = priority
-
- @Composable
- override fun Content(modifier: Modifier, size: SizeF) {
- Card(modifier = modifier, content = {})
- }
- }
- }
-}
diff --git a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt b/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
deleted file mode 100644
index 946eeec..0000000
--- a/packages/SystemUI/communal/layout/tests/src/com/android/systemui/communal/layout/ui/compose/config/CommunalGridLayoutConfigTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.android.systemui.communal.layout.ui.compose.config
-
-import androidx.compose.ui.unit.dp
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalGridLayoutConfigTest {
- @Test
- fun cardWidth() {
- Truth.assertThat(
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 3.dp,
- gridHeight = 17.dp,
- gridColumnsPerCard = 1,
- )
- .cardWidth
- )
- .isEqualTo(5.dp)
-
- Truth.assertThat(
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 3.dp,
- gridHeight = 17.dp,
- gridColumnsPerCard = 2,
- )
- .cardWidth
- )
- .isEqualTo(13.dp)
-
- Truth.assertThat(
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 3.dp,
- gridHeight = 17.dp,
- gridColumnsPerCard = 3,
- )
- .cardWidth
- )
- .isEqualTo(21.dp)
- }
-
- @Test
- fun cardHeight() {
- val config =
- CommunalGridLayoutConfig(
- gridColumnSize = 5.dp,
- gridGutter = 2.dp,
- gridHeight = 10.dp,
- gridColumnsPerCard = 3,
- )
-
- Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.FULL)).isEqualTo(10.dp)
- Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.HALF)).isEqualTo(4.dp)
- Truth.assertThat(config.cardHeight(CommunalGridLayoutCard.Size.THIRD)).isEqualTo(2.dp)
- }
-}
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 16c2437..796abf4b 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -31,7 +31,6 @@
],
static_libs: [
- "CommunalLayoutLib",
"SystemUI-core",
"PlatformComposeCore",
"PlatformComposeSceneTransitionLayout",
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 1fa55f5..54cb501 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -384,6 +384,11 @@
}
}
+ @Nullable
+ View getAodNotifIconContainer() {
+ return mAodIconContainer;
+ }
+
@Override
protected void onViewDetached() {
mClockRegistry.unregisterClockChangeListener(mClockChangedListener);
@@ -639,6 +644,7 @@
}
} else {
mNotificationIconAreaController.setupAodIcons(nic);
+ mAodIconContainer = nic;
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 87d937b..4fbf077 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -84,6 +84,7 @@
Dumpable {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@VisibleForTesting static final String TAG = "KeyguardStatusViewController";
+ private static final long STATUS_AREA_HEIGHT_ANIMATION_MILLIS = 133;
/**
* Duration to use for the animator when the keyguard status view alignment changes, and a
@@ -104,6 +105,10 @@
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ private final DozeParameters mDozeParameters;
+
+ private View mStatusArea = null;
+ private ValueAnimator mStatusAreaHeightAnimator = null;
private Boolean mSplitShadeEnabled = false;
private Boolean mStatusViewCentered = true;
@@ -123,6 +128,46 @@
}
};
+ private final View.OnLayoutChangeListener mStatusAreaLayoutChangeListener =
+ new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v,
+ int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom
+ ) {
+ if (!mDozeParameters.getAlwaysOn()) {
+ return;
+ }
+
+ int oldHeight = oldBottom - oldTop;
+ int diff = v.getHeight() - oldHeight;
+ if (diff == 0) {
+ return;
+ }
+
+ int startValue = -1 * diff;
+ long duration = STATUS_AREA_HEIGHT_ANIMATION_MILLIS;
+ if (mStatusAreaHeightAnimator != null
+ && mStatusAreaHeightAnimator.isRunning()) {
+ duration += mStatusAreaHeightAnimator.getDuration()
+ - mStatusAreaHeightAnimator.getCurrentPlayTime();
+ startValue += (int) mStatusAreaHeightAnimator.getAnimatedValue();
+ mStatusAreaHeightAnimator.cancel();
+ mStatusAreaHeightAnimator = null;
+ }
+
+ mStatusAreaHeightAnimator = ValueAnimator.ofInt(startValue, 0);
+ mStatusAreaHeightAnimator.setDuration(duration);
+ final View nic = mKeyguardClockSwitchController.getAodNotifIconContainer();
+ if (nic != null) {
+ mStatusAreaHeightAnimator.addUpdateListener(anim -> {
+ nic.setTranslationY((int) anim.getAnimatedValue());
+ });
+ }
+ mStatusAreaHeightAnimator.start();
+ }
+ };
+
@Inject
public KeyguardStatusViewController(
KeyguardStatusView keyguardStatusView,
@@ -144,6 +189,7 @@
mKeyguardClockSwitchController = keyguardClockSwitchController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mConfigurationController = configurationController;
+ mDozeParameters = dozeParameters;
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
logger.getBuffer());
@@ -218,12 +264,15 @@
@Override
protected void onViewAttached() {
+ mStatusArea = mView.findViewById(R.id.keyguard_status_area);
+ mStatusArea.addOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mConfigurationController.addCallback(mConfigurationListener);
}
@Override
protected void onViewDetached() {
+ mStatusArea.removeOnLayoutChangeListener(mStatusAreaLayoutChangeListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
mConfigurationController.removeCallback(mConfigurationListener);
}
@@ -293,9 +342,15 @@
/**
* Get the height of the keyguard status view without the notification icon area, as that's
* only visible on AOD.
+ *
+ * We internally animate height changes to the status area to prevent discontinuities in the
+ * doze animation introduced by the height suddenly changing due to smartpace.
*/
public int getLockscreenHeight() {
- return mView.getHeight() - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
+ int heightAnimValue = mStatusAreaHeightAnimator == null ? 0 :
+ (int) mStatusAreaHeightAnimator.getAnimatedValue();
+ return mView.getHeight() + heightAnimValue
+ - mKeyguardClockSwitchController.getNotificationIconAreaHeight();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
index 99529a1..9a50d83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryForegroundViewModel.kt
@@ -31,6 +31,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -49,12 +50,22 @@
transitionInteractor.startedKeyguardState.map { keyguardState ->
keyguardState == KeyguardState.AOD
}
+
+ private fun getColor(usingBackgroundProtection: Boolean): Int {
+ return if (usingBackgroundProtection) {
+ Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
+ } else {
+ Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+ }
+ }
+
private val color: Flow<Int> =
- configurationRepository.onAnyConfigurationChange
- .map { Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) }
- .onStart {
- emit(Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary))
- }
+ deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBgProtection ->
+ configurationRepository.onAnyConfigurationChange
+ .map { getColor(useBgProtection) }
+ .onStart { emit(getColor(useBgProtection)) }
+ }
+
private val useAodIconVariant: Flow<Boolean> =
combine(isShowingAod, deviceEntryUdfpsInteractor.isUdfpsSupported) {
isTransitionToAod,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 146715d..13fb42c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -35,6 +35,7 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.power.data.repository.FakePowerRepository;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -70,6 +71,7 @@
@Mock protected KeyguardClockSwitch mKeyguardClockSwitch;
@Mock protected FrameLayout mMediaHostContainer;
+ @Mock protected KeyguardStatusAreaView mKeyguardStatusAreaView;
@Before
public void setup() {
@@ -109,6 +111,8 @@
when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
+ when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
+ .thenReturn(mKeyguardStatusAreaView);
}
protected void givenViewAttached() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 948942f..9c3288b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static junit.framework.Assert.assertEquals;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -27,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.animation.AnimatorTestRule;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -40,6 +43,7 @@
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -51,6 +55,9 @@
@RunWith(AndroidTestingRunner.class)
public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
+ @Rule
+ public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
@Test
public void dozeTimeTick_updatesSlice() {
mController.dozeTimeTick();
@@ -230,4 +237,34 @@
throw new RuntimeException(e);
}
}
+
+ @Test
+ public void statusAreaHeightChange_animatesHeightOutputChange() {
+ // Init & Capture Layout Listener
+ mController.onInit();
+ mController.onViewAttached();
+
+ when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ ArgumentCaptor<View.OnLayoutChangeListener> captor =
+ ArgumentCaptor.forClass(View.OnLayoutChangeListener.class);
+ verify(mKeyguardStatusAreaView).addOnLayoutChangeListener(captor.capture());
+ View.OnLayoutChangeListener listener = captor.getValue();
+
+ // Setup and validate initial height
+ when(mKeyguardStatusView.getHeight()).thenReturn(200);
+ when(mKeyguardClockSwitchController.getNotificationIconAreaHeight()).thenReturn(10);
+ assertEquals(190, mController.getLockscreenHeight());
+
+ // Trigger Change and validate value unchanged immediately
+ when(mKeyguardStatusAreaView.getHeight()).thenReturn(100);
+ when(mKeyguardStatusView.getHeight()).thenReturn(300); // Include child height
+ listener.onLayoutChange(mKeyguardStatusAreaView,
+ /* new layout */ 100, 300, 200, 400,
+ /* old layout */ 100, 300, 200, 300);
+ assertEquals(190, mController.getLockscreenHeight());
+
+ // Complete animation, validate height increased
+ mAnimatorTestRule.advanceTimeBy(200);
+ assertEquals(290, mController.getLockscreenHeight());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index a7e7dd0..2b51ac5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,6 +19,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.core.animation.doOnEnd
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.doOnEnd
@@ -30,6 +31,7 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
+@FlakyTest(bugId = 302149604)
class AnimatorTestRuleOrderTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule()
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bae06347..c2a1b6b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4021,11 +4021,8 @@
Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e);
return false;
}
- final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags
- .SHOW_STICKY_HUN_FOR_DENIED_FSI);
return checkUseFullScreenIntentPermission(attributionSource, applicationInfo,
- showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */);
+ false /* forDataDelivery */);
}
@Override
@@ -7274,28 +7271,12 @@
notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
if (notification.fullScreenIntent != null) {
- final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
- if (forceDemoteFsiToStickyHun) {
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
+ final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
+ attributionSource, ai, true /* forDataDelivery */);
+ if (!canUseFullScreenIntent) {
makeStickyHun(notification, pkg, userId);
- } else {
- final AttributionSource attributionSource =
- new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
- final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags
- .SHOW_STICKY_HUN_FOR_DENIED_FSI);
- final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
- attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */,
- true /* forDataDelivery */);
- if (!canUseFullScreenIntent) {
- if (showStickyHunIfDenied) {
- makeStickyHun(notification, pkg, userId);
- } else {
- notification.fullScreenIntent = null;
- Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
- + "USE_FULL_SCREEN_INTENT permission");
- }
- }
}
}
@@ -7402,27 +7383,20 @@
}
private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
- @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission,
+ @NonNull ApplicationInfo applicationInfo,
boolean forDataDelivery) {
if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
return true;
}
- if (isAppOpPermission) {
- final int permissionResult;
- if (forDataDelivery) {
- permissionResult = mPermissionManager.checkPermissionForDataDelivery(
- permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
- } else {
- permissionResult = mPermissionManager.checkPermissionForPreflight(
- permission.USE_FULL_SCREEN_INTENT, attributionSource);
- }
- return permissionResult == PermissionManager.PERMISSION_GRANTED;
+ final int permissionResult;
+ if (forDataDelivery) {
+ permissionResult = mPermissionManager.checkPermissionForDataDelivery(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
} else {
- final int permissionResult = getContext().checkPermission(
- permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(),
- attributionSource.getUid());
- return permissionResult == PERMISSION_GRANTED;
+ permissionResult = mPermissionManager.checkPermissionForPreflight(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource);
}
+ return permissionResult == PermissionManager.PERMISSION_GRANTED;
}
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index d2e980b..9a6ea2c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -530,16 +530,13 @@
this.timeout_millis = p.r.getSbn().getNotification().getTimeoutAfter();
this.is_non_dismissible = NotificationRecordLogger.isNonDismissible(p.r);
- final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
- .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
final boolean hasFullScreenIntent =
p.r.getSbn().getNotification().fullScreenIntent != null;
final boolean hasFsiRequestedButDeniedFlag = (p.r.getSbn().getNotification().flags
& Notification.FLAG_FSI_REQUESTED_BUT_DENIED) != 0;
- this.fsi_state = NotificationRecordLogger.getFsiState(isStickyHunFlagEnabled,
+ this.fsi_state = NotificationRecordLogger.getFsiState(
hasFullScreenIntent, hasFsiRequestedButDeniedFlag, eventType);
this.is_locked = p.r.isLocked();
@@ -587,13 +584,10 @@
* @return FrameworkStatsLog enum of the state of the full screen intent posted with this
* notification.
*/
- static int getFsiState(boolean isStickyHunFlagEnabled,
- boolean hasFullScreenIntent,
+ static int getFsiState(boolean hasFullScreenIntent,
boolean hasFsiRequestedButDeniedFlag,
NotificationReportedEvent eventType) {
-
- if (!isStickyHunFlagEnabled
- || eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
+ if (eventType == NotificationReportedEvent.NOTIFICATION_UPDATED) {
// Zeroes in protos take zero bandwidth, but non-zero numbers take bandwidth,
// so we should log 0 when possible.
return 0;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 783e9bb..252664a 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2143,10 +2143,7 @@
* @return State of the full screen intent permission for this package.
*/
@VisibleForTesting
- int getFsiState(String pkg, int uid, boolean requestedFSIPermission, boolean isFlagEnabled) {
- if (!isFlagEnabled) {
- return 0;
- }
+ int getFsiState(String pkg, int uid, boolean requestedFSIPermission) {
if (!requestedFSIPermission) {
return PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
}
@@ -2167,10 +2164,8 @@
* the user.
*/
@VisibleForTesting
- boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags,
- boolean isStickyHunFlagEnabled) {
- if (!isStickyHunFlagEnabled
- || fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
+ boolean isFsiPermissionUserSet(String pkg, int uid, int fsiState, int currentPermissionFlags) {
+ if (fsiState == PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED) {
return false;
}
return (currentPermissionFlags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
@@ -2213,22 +2208,18 @@
pkgsWithPermissionsToHandle.remove(key);
}
- final boolean isStickyHunFlagEnabled = SystemUiSystemPropertiesFlags.getResolver()
- .isEnabled(NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
final boolean requestedFSIPermission = mPermissionHelper.hasRequestedPermission(
android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg, r.uid);
- final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission,
- isStickyHunFlagEnabled);
+ final int fsiState = getFsiState(r.pkg, r.uid, requestedFSIPermission);
final int currentPermissionFlags = mPm.getPermissionFlags(
android.Manifest.permission.USE_FULL_SCREEN_INTENT, r.pkg,
UserHandle.getUserHandleForUid(r.uid));
final boolean fsiIsUserSet =
- isFsiPermissionUserSet(r.pkg, r.uid, fsiState, currentPermissionFlags,
- isStickyHunFlagEnabled);
+ isFsiPermissionUserSet(r.pkg, r.uid, fsiState,
+ currentPermissionFlags);
events.add(FrameworkStatsLog.buildStatsEvent(
PACKAGE_NOTIFICATION_PREFERENCES,
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 79cd2a0..92d469c 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -259,6 +259,19 @@
*/
boolean shouldFilterApplicationIncludingUninstalled(@Nullable PackageStateInternal ps,
int callingUid, int userId);
+
+ /**
+ * Different from
+ * {@link #shouldFilterApplicationIncludingUninstalled(PackageStateInternal, int, int)}, the
+ * function returns {@code true} if:
+ * <ul>
+ * <li>The target package is not archived.
+ * <li>The package cannot be found in the device or has been uninstalled in the current user.
+ * </ul>
+ */
+ boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+ @Nullable PackageStateInternal ps,
+ int callingUid, int userId);
/**
* Different from {@link #shouldFilterApplication(SharedUserSetting, int, int)}, the function
* returns {@code true} if packages with the same shared user are all uninstalled in the current
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 11a6d1b..e5c4ccc 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -2455,7 +2455,8 @@
*/
public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, @Nullable ComponentName component,
- @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+ @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall,
+ boolean filterArchived) {
if (Process.isSdkSandboxUid(callingUid)) {
int clientAppUid = Process.getAppUidForSdkSandboxUid(callingUid);
// SDK sandbox should be able to see it's client app
@@ -2469,14 +2470,20 @@
}
final String instantAppPkgName = getInstantAppPackageName(callingUid);
final boolean callerIsInstantApp = instantAppPkgName != null;
+ final boolean packageArchivedForUser = ps != null && PackageArchiver.isArchived(
+ ps.getUserStateOrDefault(userId));
// Don't treat hiddenUntilInstalled as an uninstalled state, phone app needs to access
// these hidden application details to customize carrier apps. Also, allowing the system
// caller accessing to application across users.
if (ps == null
|| (filterUninstall
- && !isSystemOrRootOrShell(callingUid)
- && !ps.isHiddenUntilInstalled()
- && !ps.getUserStateOrDefault(userId).isInstalled())) {
+ && !isSystemOrRootOrShell(callingUid)
+ && !ps.isHiddenUntilInstalled()
+ && !ps.getUserStateOrDefault(userId).isInstalled()
+ // Archived packages behave like uninstalled packages. So if filterUninstall is
+ // set to true, we dismiss filtering some uninstalled package only if it is
+ // archived and filterArchived is set as false.
+ && (!packageArchivedForUser || filterArchived))) {
// If caller is instant app or sdk sandbox and ps is null, pretend the application
// exists, but, needs to be filtered
return (callerIsInstantApp || filterUninstall || Process.isSdkSandboxUid(callingUid));
@@ -2524,7 +2531,20 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
+ */
+ public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
+ int callingUid, @Nullable ComponentName component,
+ @PackageManager.ComponentType int componentType, int userId, boolean filterUninstall) {
+ return shouldFilterApplication(
+ ps, callingUid, component, componentType, userId, filterUninstall,
+ true /* filterArchived */);
+ }
+
+ /**
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplication(@Nullable PackageStateInternal ps,
int callingUid, @Nullable ComponentName component,
@@ -2534,7 +2554,8 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplication(
@Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2543,7 +2564,8 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplication(@NonNull SharedUserSetting sus,
int callingUid, int userId) {
@@ -2558,7 +2580,8 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplicationIncludingUninstalled(
@Nullable PackageStateInternal ps, int callingUid, int userId) {
@@ -2567,7 +2590,19 @@
}
/**
- * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean)
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
+ */
+ public final boolean shouldFilterApplicationIncludingUninstalledNotArchived(
+ @Nullable PackageStateInternal ps, int callingUid, int userId) {
+ return shouldFilterApplication(
+ ps, callingUid, null, TYPE_UNKNOWN, userId, true /* filterUninstall */,
+ false /* filterArchived */);
+ }
+
+ /**
+ * @see #shouldFilterApplication(PackageStateInternal, int, ComponentName, int, int, boolean,
+ * boolean)
*/
public final boolean shouldFilterApplicationIncludingUninstalled(
@NonNull SharedUserSetting sus, int callingUid, int userId) {
@@ -5015,7 +5050,7 @@
String installerPackageName = installSource.mInstallerPackageName;
if (installerPackageName != null) {
final PackageStateInternal ps = mSettings.getPackage(installerPackageName);
- if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid,
+ if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
UserHandle.getUserId(callingUid))) {
installerPackageName = null;
}
@@ -5033,7 +5068,8 @@
return InstallSource.EMPTY;
}
- if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
+ if (ps == null || shouldFilterApplicationIncludingUninstalledNotArchived(ps, callingUid,
+ userId)) {
return null;
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 65c6329..3b3d79e 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -440,7 +440,7 @@
if (outInfo != null) {
// Remember which users are affected, before the installed states are modified
outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
- ? ps.queryInstalledUsers(allUserHandles, /* installed= */true)
+ ? ps.queryUsersInstalledOrHasData(allUserHandles)
: new int[]{userId};
}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index eff6157..d2a4c27 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -37,6 +37,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedActivityParcel;
import android.content.pm.ArchivedPackageParcel;
import android.content.pm.LauncherActivityInfo;
@@ -140,6 +141,8 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"archiveApp");
+ verifyUninstallPermissions();
+
CompletableFuture<ArchiveState> archiveStateFuture;
try {
archiveStateFuture = createArchiveState(packageName, userId);
@@ -182,6 +185,7 @@
throws PackageManager.NameNotFoundException {
PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
Binder.getCallingUid(), userId);
+ verifyNotSystemApp(ps.getFlags());
String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
verifyInstaller(responsibleInstallerPackage, userId);
verifyOptOutStatus(packageName,
@@ -316,6 +320,13 @@
return intentReceivers != null && !intentReceivers.getList().isEmpty();
}
+ private void verifyNotSystemApp(int flags) throws PackageManager.NameNotFoundException {
+ if ((flags & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+ (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+ throw new PackageManager.NameNotFoundException("System apps cannot be archived.");
+ }
+ }
+
/**
* Returns true if the app is archivable.
*/
@@ -337,6 +348,11 @@
throw new ParcelableException(e);
}
+ if ((ps.getFlags() & ApplicationInfo.FLAG_SYSTEM) != 0 || (
+ (ps.getFlags() & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+ return false;
+ }
+
if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) {
return false;
}
@@ -372,9 +388,11 @@
void requestUnarchive(
@NonNull String packageName,
@NonNull String callerPackageName,
+ @NonNull IntentSender statusReceiver,
@NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(statusReceiver);
Objects.requireNonNull(userHandle);
Computer snapshot = mPm.snapshotComputer();
@@ -385,6 +403,8 @@
}
snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
"unarchiveApp");
+ verifyInstallPermissions();
+
PackageStateInternal ps;
try {
ps = getPackageState(packageName, snapshot, binderUid, userId);
@@ -400,9 +420,12 @@
packageName)));
}
+ // TODO(b/305902395) Introduce a confirmation dialog if the requestor only holds
+ // REQUEST_INSTALL permission.
int draftSessionId;
try {
- draftSessionId = createDraftSession(packageName, installerPackage, userId);
+ draftSessionId = createDraftSession(packageName, installerPackage, statusReceiver,
+ userId);
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -415,11 +438,36 @@
() -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
}
- private int createDraftSession(String packageName, String installerPackage, int userId) {
+ private void verifyInstallPermissions() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.REQUEST_INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the com.android.permission.INSTALL_PACKAGES "
+ + "or com.android.permission.REQUEST_INSTALL_PACKAGES permission to request "
+ + "an unarchival.");
+ }
+ }
+
+ private void verifyUninstallPermissions() {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+ Manifest.permission.REQUEST_DELETE_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("You need the com.android.permission.DELETE_PACKAGES "
+ + "or com.android.permission.REQUEST_DELETE_PACKAGES permission to request "
+ + "an archival.");
+ }
+ }
+
+ private int createDraftSession(String packageName, String installerPackage,
+ IntentSender statusReceiver, int userId) {
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+ sessionParams.unarchiveIntentSender = statusReceiver;
+
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index af43a8b..c9663fc 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,8 +16,14 @@
package com.android.server.pm;
+import static android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVITY;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR;
+import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
@@ -37,6 +43,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PackageDeleteObserver;
+import android.app.PendingIntent;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -56,6 +63,7 @@
import android.content.pm.PackageInstaller.InstallConstraintsResult;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
+import android.content.pm.PackageInstaller.UnarchivalStatus;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
@@ -71,6 +79,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelableException;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
@@ -1630,8 +1639,10 @@
public void requestUnarchive(
@NonNull String packageName,
@NonNull String callerPackageName,
+ @NonNull IntentSender statusReceiver,
@NonNull UserHandle userHandle) {
- mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
+ mPackageArchiver.requestUnarchive(packageName, callerPackageName, statusReceiver,
+ userHandle);
}
@Override
@@ -1689,6 +1700,102 @@
}
}
+ // TODO(b/307299702) Implement error dialog and propagate userActionIntent.
+ @Override
+ public void reportUnarchivalStatus(
+ int unarchiveId,
+ @UnarchivalStatus int status,
+ long requiredStorageBytes,
+ @Nullable PendingIntent userActionIntent,
+ @NonNull UserHandle userHandle) {
+ verifyReportUnarchiveStatusInput(
+ status, requiredStorageBytes, userActionIntent, userHandle);
+
+ int userId = userHandle.getIdentifier();
+ int binderUid = Binder.getCallingUid();
+
+ synchronized (mSessions) {
+ PackageInstallerSession session = mSessions.get(unarchiveId);
+ if (session == null || session.userId != userId
+ || session.params.appPackageName == null) {
+ throw new ParcelableException(new PackageManager.NameNotFoundException(
+ TextUtils.formatSimple(
+ "No valid session with unarchival ID %s found for user %s.",
+ unarchiveId, userId)));
+ }
+
+ if (!isCallingUidOwner(session)) {
+ throw new SecurityException(TextUtils.formatSimple(
+ "The caller UID %s does not have access to the session with unarchiveId "
+ + "%d.",
+ binderUid, unarchiveId));
+ }
+
+ IntentSender unarchiveIntentSender = session.params.unarchiveIntentSender;
+ if (unarchiveIntentSender == null) {
+ throw new IllegalStateException(
+ TextUtils.formatSimple(
+ "Unarchival status for ID %s has already been set or a "
+ + "session has been created for it already by the "
+ + "caller.",
+ unarchiveId));
+ }
+
+ // Execute expensive calls outside the sync block.
+ mPm.mHandler.post(
+ () -> notifyUnarchivalListener(status, session.params.appPackageName,
+ unarchiveIntentSender));
+ session.params.unarchiveIntentSender = null;
+ if (status != UNARCHIVAL_OK) {
+ Binder.withCleanCallingIdentity(session::abandon);
+ }
+ }
+ }
+
+ private static void verifyReportUnarchiveStatusInput(int status, long requiredStorageBytes,
+ @Nullable PendingIntent userActionIntent,
+ @NonNull UserHandle userHandle) {
+ Objects.requireNonNull(userHandle);
+ if (status == UNARCHIVAL_ERROR_USER_ACTION_NEEDED) {
+ Objects.requireNonNull(userActionIntent);
+ }
+ if (status == UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes <= 0) {
+ throw new IllegalStateException(
+ "Insufficient storage error set, but requiredStorageBytes unspecified.");
+ }
+ if (status != UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE && requiredStorageBytes > 0) {
+ throw new IllegalStateException(
+ TextUtils.formatSimple("requiredStorageBytes set, but error is %s.", status)
+ );
+ }
+ if (!List.of(
+ UNARCHIVAL_OK,
+ UNARCHIVAL_ERROR_USER_ACTION_NEEDED,
+ UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE,
+ UNARCHIVAL_ERROR_NO_CONNECTIVITY,
+ UNARCHIVAL_GENERIC_ERROR).contains(status)) {
+ throw new IllegalStateException("Invalid status code passed " + status);
+ }
+ }
+
+ private void notifyUnarchivalListener(int status, String packageName,
+ IntentSender unarchiveIntentSender) {
+ final Intent fillIn = new Intent();
+ fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
+ fillIn.putExtra(PackageInstaller.EXTRA_UNARCHIVE_STATUS, status);
+ // TODO(b/307299702) Attach failure dialog with EXTRA_INTENT and requiredStorageBytes here.
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_DENIED);
+ try {
+ unarchiveIntentSender.sendIntent(mContext, 0, fillIn, /* onFinished= */ null,
+ /* handler= */ null, /* requiredPermission= */ null,
+ options.toBundle());
+ } catch (SendIntentException e) {
+ Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+ }
+ }
+
private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
int installerUid) {
int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 6f45d2b..f992bd8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4689,10 +4689,12 @@
final int translatedUserId =
translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+ final LocalIntentReceiver receiver = new LocalIntentReceiver();
try {
mInterface.getPackageInstaller().requestUnarchive(packageName,
- /* callerPackageName= */ "", new UserHandle(translatedUserId));
+ /* callerPackageName= */ "", receiver.getIntentSender(),
+ new UserHandle(translatedUserId));
} catch (Exception e) {
pw.println("Failure [" + e.getMessage() + "]");
return 1;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 72090f2..b50d0a0 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -779,6 +779,10 @@
return readUserState(userId).isInstalled();
}
+ boolean isArchived(int userId) {
+ return PackageArchiver.isArchived(readUserState(userId));
+ }
+
int getInstallReason(int userId) {
return readUserState(userId).getInstallReason();
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 4e14c90..8556317 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1592,6 +1592,19 @@
*/
private void showConfirmCredentialToDisableQuietMode(
@UserIdInt int userId, @Nullable IntentSender target) {
+ if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
+ // TODO (b/308121702) It may be brittle to rely on user states to check profile state
+ int state;
+ synchronized (mUserStates) {
+ state = mUserStates.get(userId, UserState.STATE_NONE);
+ }
+ if (state != UserState.STATE_NONE) {
+ Slog.i(LOG_TAG,
+ "showConfirmCredentialToDisableQuietMode() called too early, user " + userId
+ + " is still alive.");
+ return;
+ }
+ }
// otherwise, we show a profile challenge to trigger decryption of the user
final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
Context.KEYGUARD_SERVICE);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index ee46ce1..b3672ec 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -16,6 +16,8 @@
package com.android.server.webkit;
+import static android.webkit.Flags.updateServiceV2;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -51,7 +53,7 @@
private static final String TAG = "WebViewUpdateService";
private BroadcastReceiver mWebViewUpdatedReceiver;
- private WebViewUpdateServiceImpl mImpl;
+ private WebViewUpdateServiceInterface mImpl;
static final int PACKAGE_CHANGED = 0;
static final int PACKAGE_ADDED = 1;
@@ -60,7 +62,11 @@
public WebViewUpdateService(Context context) {
super(context);
- mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+ if (updateServiceV2()) {
+ mImpl = new WebViewUpdateServiceImpl2(context, SystemImpl.getInstance());
+ } else {
+ mImpl = new WebViewUpdateServiceImpl(context, SystemImpl.getInstance());
+ }
}
@Override
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 43d62aa..cfdef14 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -63,7 +63,7 @@
*
* @hide
*/
-class WebViewUpdateServiceImpl {
+class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
private static final String TAG = WebViewUpdateServiceImpl.class.getSimpleName();
private static class WebViewPackageMissingException extends Exception {
@@ -112,7 +112,8 @@
mSystemInterface = systemInterface;
}
- void packageStateChanged(String packageName, int changedState, int userId) {
+ @Override
+ public void packageStateChanged(String packageName, int changedState, int userId) {
// We don't early out here in different cases where we could potentially early-out (e.g. if
// we receive PACKAGE_CHANGED for another user than the system user) since that would
// complicate this logic further and open up for more edge cases.
@@ -163,7 +164,8 @@
}
}
- void prepareWebViewInSystemServer() {
+ @Override
+ public void prepareWebViewInSystemServer() {
mSystemInterface.notifyZygote(isMultiProcessEnabled());
try {
synchronized (mLock) {
@@ -210,7 +212,8 @@
mSystemInterface.ensureZygoteStarted();
}
- void handleNewUser(int userId) {
+ @Override
+ public void handleNewUser(int userId) {
// The system user is always started at boot, and by that point we have already run one
// round of the package-changing logic (through prepareWebViewInSystemServer()), so early
// out here.
@@ -218,7 +221,8 @@
handleUserChange();
}
- void handleUserRemoved(int userId) {
+ @Override
+ public void handleUserRemoved(int userId) {
handleUserChange();
}
@@ -232,14 +236,16 @@
updateCurrentWebViewPackage(null);
}
- void notifyRelroCreationCompleted() {
+ @Override
+ public void notifyRelroCreationCompleted() {
synchronized (mLock) {
mNumRelroCreationsFinished++;
checkIfRelrosDoneLocked();
}
}
- WebViewProviderResponse waitForAndGetProvider() {
+ @Override
+ public WebViewProviderResponse waitForAndGetProvider() {
PackageInfo webViewPackage = null;
final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
boolean webViewReady = false;
@@ -284,7 +290,8 @@
* replacing that provider it will not be in use directly, but will be used when the relros
* or the replacement are done).
*/
- String changeProviderAndSetting(String newProviderName) {
+ @Override
+ public String changeProviderAndSetting(String newProviderName) {
PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
if (newPackage == null) return "";
return newPackage.packageName;
@@ -367,7 +374,8 @@
/**
* Fetch only the currently valid WebView packages.
**/
- WebViewProviderInfo[] getValidWebViewPackages() {
+ @Override
+ public WebViewProviderInfo[] getValidWebViewPackages() {
ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
WebViewProviderInfo[] providers =
new WebViewProviderInfo[providersAndPackageInfos.length];
@@ -464,11 +472,13 @@
return true;
}
- WebViewProviderInfo[] getWebViewPackages() {
+ @Override
+ public WebViewProviderInfo[] getWebViewPackages() {
return mSystemInterface.getWebViewPackages();
}
- PackageInfo getCurrentWebViewPackage() {
+ @Override
+ public PackageInfo getCurrentWebViewPackage() {
synchronized (mLock) {
return mCurrentWebViewPackage;
}
@@ -620,7 +630,8 @@
return null;
}
- boolean isMultiProcessEnabled() {
+ @Override
+ public boolean isMultiProcessEnabled() {
int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
if (mSystemInterface.isMultiProcessDefaultEnabled()) {
// Multiprocess should be enabled unless the user has turned it off manually.
@@ -631,7 +642,8 @@
}
}
- void enableMultiProcess(boolean enable) {
+ @Override
+ public void enableMultiProcess(boolean enable) {
PackageInfo current = getCurrentWebViewPackage();
mSystemInterface.setMultiProcessSetting(mContext,
enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
@@ -644,7 +656,8 @@
/**
* Dump the state of this Service.
*/
- void dumpState(PrintWriter pw) {
+ @Override
+ public void dumpState(PrintWriter pw) {
pw.println("Current WebView Update Service state");
pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
new file mode 100644
index 0000000..e618c7e
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.webkit;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
+import android.os.AsyncTask;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.webkit.UserPackage;
+import android.webkit.WebViewFactory;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of the WebViewUpdateService.
+ * This class doesn't depend on the android system like the actual Service does and can be used
+ * directly by tests (as long as they implement a SystemInterface).
+ *
+ * This class keeps track of and prepares the current WebView implementation, and needs to keep
+ * track of a couple of different things such as what package is used as WebView implementation.
+ *
+ * The package-visible methods in this class are accessed from WebViewUpdateService either on the UI
+ * thread or on one of multiple Binder threads. The WebView preparation code shares state between
+ * threads meaning that code that chooses a new WebView implementation or checks which
+ * implementation is being used needs to hold a lock.
+ *
+ * The WebViewUpdateService can be accessed in a couple of different ways.
+ * 1. It is started from the SystemServer at boot - at that point we just initiate some state such
+ * as the WebView preparation class.
+ * 2. The SystemServer calls WebViewUpdateService.prepareWebViewInSystemServer. This happens at boot
+ * and the WebViewUpdateService should not have been accessed before this call. In this call we
+ * choose WebView implementation for the first time.
+ * 3. The update service listens for Intents related to package installs and removals. These intents
+ * are received and processed on the UI thread. Each intent can result in changing WebView
+ * implementation.
+ * 4. The update service can be reached through Binder calls which are handled on specific binder
+ * threads. These calls can be made from any process. Generally they are used for changing WebView
+ * implementation (from Settings), getting information about the current WebView implementation (for
+ * loading WebView into an app process), or notifying the service about Relro creation being
+ * completed.
+ *
+ * @hide
+ */
+class WebViewUpdateServiceImpl2 implements WebViewUpdateServiceInterface {
+ private static final String TAG = WebViewUpdateServiceImpl2.class.getSimpleName();
+
+ private static class WebViewPackageMissingException extends Exception {
+ WebViewPackageMissingException(String message) {
+ super(message);
+ }
+
+ WebViewPackageMissingException(Exception e) {
+ super(e);
+ }
+ }
+
+ private static final int WAIT_TIMEOUT_MS = 1000; // KEY_DISPATCHING_TIMEOUT is 5000.
+ private static final long NS_PER_MS = 1000000;
+
+ private static final int VALIDITY_OK = 0;
+ private static final int VALIDITY_INCORRECT_SDK_VERSION = 1;
+ private static final int VALIDITY_INCORRECT_VERSION_CODE = 2;
+ private static final int VALIDITY_INCORRECT_SIGNATURE = 3;
+ private static final int VALIDITY_NO_LIBRARY_FLAG = 4;
+
+ private static final int MULTIPROCESS_SETTING_ON_VALUE = Integer.MAX_VALUE;
+ private static final int MULTIPROCESS_SETTING_OFF_VALUE = Integer.MIN_VALUE;
+
+ private final SystemInterface mSystemInterface;
+ private final Context mContext;
+
+ private long mMinimumVersionCode = -1;
+
+ // Keeps track of the number of running relro creations
+ private int mNumRelroCreationsStarted = 0;
+ private int mNumRelroCreationsFinished = 0;
+ // Implies that we need to rerun relro creation because we are using an out-of-date package
+ private boolean mWebViewPackageDirty = false;
+ private boolean mAnyWebViewInstalled = false;
+
+ private static final int NUMBER_OF_RELROS_UNKNOWN = Integer.MAX_VALUE;
+
+ // The WebView package currently in use (or the one we are preparing).
+ private PackageInfo mCurrentWebViewPackage = null;
+
+ private final Object mLock = new Object();
+
+ WebViewUpdateServiceImpl2(Context context, SystemInterface systemInterface) {
+ mContext = context;
+ mSystemInterface = systemInterface;
+ }
+
+ @Override
+ public void packageStateChanged(String packageName, int changedState, int userId) {
+ // We don't early out here in different cases where we could potentially early-out (e.g. if
+ // we receive PACKAGE_CHANGED for another user than the system user) since that would
+ // complicate this logic further and open up for more edge cases.
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+ String webviewPackage = provider.packageName;
+
+ if (webviewPackage.equals(packageName)) {
+ boolean updateWebView = false;
+ boolean removedOrChangedOldPackage = false;
+ String oldProviderName = null;
+ PackageInfo newPackage = null;
+ synchronized (mLock) {
+ try {
+ newPackage = findPreferredWebViewPackage();
+ if (mCurrentWebViewPackage != null) {
+ oldProviderName = mCurrentWebViewPackage.packageName;
+ }
+ // Only trigger update actions if the updated package is the one
+ // that will be used, or the one that was in use before the
+ // update, or if we haven't seen a valid WebView package before.
+ updateWebView =
+ provider.packageName.equals(newPackage.packageName)
+ || provider.packageName.equals(oldProviderName)
+ || mCurrentWebViewPackage == null;
+ // We removed the old package if we received an intent to remove
+ // or replace the old package.
+ removedOrChangedOldPackage =
+ provider.packageName.equals(oldProviderName);
+ if (updateWebView) {
+ onWebViewProviderChanged(newPackage);
+ }
+ } catch (WebViewPackageMissingException e) {
+ mCurrentWebViewPackage = null;
+ Slog.e(TAG, "Could not find valid WebView package to create relro with "
+ + e);
+ }
+ }
+ if (updateWebView && !removedOrChangedOldPackage
+ && oldProviderName != null) {
+ // If the provider change is the result of adding or replacing a
+ // package that was not the previous provider then we must kill
+ // packages dependent on the old package ourselves. The framework
+ // only kills dependents of packages that are being removed.
+ mSystemInterface.killPackageDependents(oldProviderName);
+ }
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void prepareWebViewInSystemServer() {
+ mSystemInterface.notifyZygote(isMultiProcessEnabled());
+ try {
+ synchronized (mLock) {
+ mCurrentWebViewPackage = findPreferredWebViewPackage();
+ String userSetting = mSystemInterface.getUserChosenWebViewProvider(mContext);
+ if (userSetting != null
+ && !userSetting.equals(mCurrentWebViewPackage.packageName)) {
+ // Don't persist the user-chosen setting across boots if the package being
+ // chosen is not used (could be disabled or uninstalled) so that the user won't
+ // be surprised by the device switching to using a certain webview package,
+ // that was uninstalled/disabled a long time ago, if it is installed/enabled
+ // again.
+ mSystemInterface.updateUserSetting(mContext,
+ mCurrentWebViewPackage.packageName);
+ }
+ onWebViewProviderChanged(mCurrentWebViewPackage);
+ }
+ } catch (Throwable t) {
+ // Log and discard errors at this stage as we must not crash the system server.
+ Slog.e(TAG, "error preparing webview provider from system server", t);
+ }
+
+ if (getCurrentWebViewPackage() == null) {
+ // We didn't find a valid WebView implementation. Try explicitly re-enabling the
+ // fallback package for all users in case it was disabled, even if we already did the
+ // one-time migration before. If this actually changes the state, we will see the
+ // PackageManager broadcast shortly and try again.
+ WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages();
+ WebViewProviderInfo fallbackProvider = getFallbackProvider(webviewProviders);
+ if (fallbackProvider != null) {
+ Slog.w(TAG, "No valid provider, trying to enable " + fallbackProvider.packageName);
+ mSystemInterface.enablePackageForAllUsers(mContext, fallbackProvider.packageName,
+ true);
+ } else {
+ Slog.e(TAG, "No valid provider and no fallback available.");
+ }
+ }
+ }
+
+ private void startZygoteWhenReady() {
+ // Wait on a background thread for RELRO creation to be done. We ignore the return value
+ // because even if RELRO creation failed we still want to start the zygote.
+ waitForAndGetProvider();
+ mSystemInterface.ensureZygoteStarted();
+ }
+
+ @Override
+ public void handleNewUser(int userId) {
+ // The system user is always started at boot, and by that point we have already run one
+ // round of the package-changing logic (through prepareWebViewInSystemServer()), so early
+ // out here.
+ if (userId == UserHandle.USER_SYSTEM) return;
+ handleUserChange();
+ }
+
+ @Override
+ public void handleUserRemoved(int userId) {
+ handleUserChange();
+ }
+
+ /**
+ * Called when a user was added or removed to ensure WebView preparation is triggered.
+ * This has to be done since the WebView package we use depends on the enabled-state
+ * of packages for all users (so adding or removing a user might cause us to change package).
+ */
+ private void handleUserChange() {
+ // Potentially trigger package-changing logic.
+ updateCurrentWebViewPackage(null);
+ }
+
+ @Override
+ public void notifyRelroCreationCompleted() {
+ synchronized (mLock) {
+ mNumRelroCreationsFinished++;
+ checkIfRelrosDoneLocked();
+ }
+ }
+
+ @Override
+ public WebViewProviderResponse waitForAndGetProvider() {
+ PackageInfo webViewPackage = null;
+ final long timeoutTimeMs = System.nanoTime() / NS_PER_MS + WAIT_TIMEOUT_MS;
+ boolean webViewReady = false;
+ int webViewStatus = WebViewFactory.LIBLOAD_SUCCESS;
+ synchronized (mLock) {
+ webViewReady = webViewIsReadyLocked();
+ while (!webViewReady) {
+ final long timeNowMs = System.nanoTime() / NS_PER_MS;
+ if (timeNowMs >= timeoutTimeMs) break;
+ try {
+ mLock.wait(timeoutTimeMs - timeNowMs);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ webViewReady = webViewIsReadyLocked();
+ }
+ // Make sure we return the provider that was used to create the relro file
+ webViewPackage = mCurrentWebViewPackage;
+ if (webViewReady) {
+ // success
+ } else if (!mAnyWebViewInstalled) {
+ webViewStatus = WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ } else {
+ // Either the current relro creation isn't done yet, or the new relro creatioin
+ // hasn't kicked off yet (the last relro creation used an out-of-date WebView).
+ webViewStatus = WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
+ String timeoutError = "Timed out waiting for relro creation, relros started "
+ + mNumRelroCreationsStarted
+ + " relros finished " + mNumRelroCreationsFinished
+ + " package dirty? " + mWebViewPackageDirty;
+ Slog.e(TAG, timeoutError);
+ Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, timeoutError);
+ }
+ }
+ if (!webViewReady) Slog.w(TAG, "creating relro file timed out");
+ return new WebViewProviderResponse(webViewPackage, webViewStatus);
+ }
+
+ /**
+ * Change WebView provider and provider setting and kill packages using the old provider.
+ * Return the new provider (in case we are in the middle of creating relro files, or
+ * replacing that provider it will not be in use directly, but will be used when the relros
+ * or the replacement are done).
+ */
+ @Override
+ public String changeProviderAndSetting(String newProviderName) {
+ PackageInfo newPackage = updateCurrentWebViewPackage(newProviderName);
+ if (newPackage == null) return "";
+ return newPackage.packageName;
+ }
+
+ /**
+ * Update the current WebView package.
+ * @param newProviderName the package to switch to, null if no package has been explicitly
+ * chosen.
+ */
+ private PackageInfo updateCurrentWebViewPackage(@Nullable String newProviderName) {
+ PackageInfo oldPackage = null;
+ PackageInfo newPackage = null;
+ boolean providerChanged = false;
+ synchronized (mLock) {
+ oldPackage = mCurrentWebViewPackage;
+
+ if (newProviderName != null) {
+ mSystemInterface.updateUserSetting(mContext, newProviderName);
+ }
+
+ try {
+ newPackage = findPreferredWebViewPackage();
+ providerChanged = (oldPackage == null)
+ || !newPackage.packageName.equals(oldPackage.packageName);
+ } catch (WebViewPackageMissingException e) {
+ // If updated the Setting but don't have an installed WebView package, the
+ // Setting will be used when a package is available.
+ mCurrentWebViewPackage = null;
+ Slog.e(TAG, "Couldn't find WebView package to use " + e);
+ return null;
+ }
+ // Perform the provider change if we chose a new provider
+ if (providerChanged) {
+ onWebViewProviderChanged(newPackage);
+ }
+ }
+ // Kill apps using the old provider only if we changed provider
+ if (providerChanged && oldPackage != null) {
+ mSystemInterface.killPackageDependents(oldPackage.packageName);
+ }
+ // Return the new provider, this is not necessarily the one we were asked to switch to,
+ // but the persistent setting will now be pointing to the provider we were asked to
+ // switch to anyway.
+ return newPackage;
+ }
+
+ /**
+ * This is called when we change WebView provider, either when the current provider is
+ * updated or a new provider is chosen / takes precedence.
+ */
+ private void onWebViewProviderChanged(PackageInfo newPackage) {
+ synchronized (mLock) {
+ mAnyWebViewInstalled = true;
+ if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+ mCurrentWebViewPackage = newPackage;
+
+ // The relro creations might 'finish' (not start at all) before
+ // WebViewFactory.onWebViewProviderChanged which means we might not know the
+ // number of started creations before they finish.
+ mNumRelroCreationsStarted = NUMBER_OF_RELROS_UNKNOWN;
+ mNumRelroCreationsFinished = 0;
+ mNumRelroCreationsStarted =
+ mSystemInterface.onWebViewProviderChanged(newPackage);
+ // If the relro creations finish before we know the number of started creations
+ // we will have to do any cleanup/notifying here.
+ checkIfRelrosDoneLocked();
+ } else {
+ mWebViewPackageDirty = true;
+ }
+ }
+
+ // Once we've notified the system that the provider has changed and started RELRO creation,
+ // try to restart the zygote so that it will be ready when apps use it.
+ if (isMultiProcessEnabled()) {
+ AsyncTask.THREAD_POOL_EXECUTOR.execute(this::startZygoteWhenReady);
+ }
+ }
+
+ /**
+ * Fetch only the currently valid WebView packages.
+ **/
+ @Override
+ public WebViewProviderInfo[] getValidWebViewPackages() {
+ ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+ WebViewProviderInfo[] providers =
+ new WebViewProviderInfo[providersAndPackageInfos.length];
+ for (int n = 0; n < providersAndPackageInfos.length; n++) {
+ providers[n] = providersAndPackageInfos[n].provider;
+ }
+ return providers;
+ }
+
+ private static class ProviderAndPackageInfo {
+ public final WebViewProviderInfo provider;
+ public final PackageInfo packageInfo;
+
+ ProviderAndPackageInfo(WebViewProviderInfo provider, PackageInfo packageInfo) {
+ this.provider = provider;
+ this.packageInfo = packageInfo;
+ }
+ }
+
+ private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+ WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+ List<ProviderAndPackageInfo> providers = new ArrayList<>();
+ for (int n = 0; n < allProviders.length; n++) {
+ try {
+ PackageInfo packageInfo =
+ mSystemInterface.getPackageInfoForProvider(allProviders[n]);
+ if (validityResult(allProviders[n], packageInfo) == VALIDITY_OK) {
+ providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
+ }
+ } catch (NameNotFoundException e) {
+ // Don't add non-existent packages
+ }
+ }
+ return providers.toArray(new ProviderAndPackageInfo[providers.size()]);
+ }
+
+ /**
+ * Returns either the package info of the WebView provider determined in the following way:
+ * If the user has chosen a provider then use that if it is valid,
+ * otherwise use the first package in the webview priority list that is valid.
+ *
+ */
+ private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+ ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
+
+ String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
+
+ // If the user has chosen provider, use that (if it's installed and enabled for all
+ // users).
+ for (ProviderAndPackageInfo providerAndPackage : providers) {
+ if (providerAndPackage.provider.packageName.equals(userChosenProvider)) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
+ }
+ }
+
+ // User did not choose, or the choice failed; use the most stable provider that is
+ // installed and enabled for all users, and available by default (not through
+ // user choice).
+ for (ProviderAndPackageInfo providerAndPackage : providers) {
+ if (providerAndPackage.provider.availableByDefault) {
+ // userPackages can contain null objects.
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext,
+ providerAndPackage.provider);
+ if (isInstalledAndEnabledForAllUsers(userPackages)) {
+ return providerAndPackage.packageInfo;
+ }
+ }
+ }
+
+ // This should never happen during normal operation (only with modified system images).
+ mAnyWebViewInstalled = false;
+ throw new WebViewPackageMissingException("Could not find a loadable WebView package");
+ }
+
+ /**
+ * Return true iff {@param packageInfos} point to only installed and enabled packages.
+ * The given packages {@param packageInfos} should all be pointing to the same package, but each
+ * PackageInfo representing a different user's package.
+ */
+ private static boolean isInstalledAndEnabledForAllUsers(
+ List<UserPackage> userPackages) {
+ for (UserPackage userPackage : userPackages) {
+ if (!userPackage.isInstalledPackage() || !userPackage.isEnabledPackage()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public WebViewProviderInfo[] getWebViewPackages() {
+ return mSystemInterface.getWebViewPackages();
+ }
+
+ @Override
+ public PackageInfo getCurrentWebViewPackage() {
+ synchronized (mLock) {
+ return mCurrentWebViewPackage;
+ }
+ }
+
+ /**
+ * Returns whether WebView is ready and is not going to go through its preparation phase
+ * again directly.
+ */
+ private boolean webViewIsReadyLocked() {
+ return !mWebViewPackageDirty
+ && (mNumRelroCreationsStarted == mNumRelroCreationsFinished)
+ // The current package might be replaced though we haven't received an intent
+ // declaring this yet, the following flag makes anyone loading WebView to wait in
+ // this case.
+ && mAnyWebViewInstalled;
+ }
+
+ private void checkIfRelrosDoneLocked() {
+ if (mNumRelroCreationsStarted == mNumRelroCreationsFinished) {
+ if (mWebViewPackageDirty) {
+ mWebViewPackageDirty = false;
+ // If we have changed provider since we started the relro creation we need to
+ // redo the whole process using the new package instead.
+ try {
+ PackageInfo newPackage = findPreferredWebViewPackage();
+ onWebViewProviderChanged(newPackage);
+ } catch (WebViewPackageMissingException e) {
+ mCurrentWebViewPackage = null;
+ // If we can't find any valid WebView package we are now in a state where
+ // mAnyWebViewInstalled is false, so loading WebView will be blocked and we
+ // should simply wait until we receive an intent declaring a new package was
+ // installed.
+ }
+ } else {
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ private int validityResult(WebViewProviderInfo configInfo, PackageInfo packageInfo) {
+ // Ensure the provider targets this framework release (or a later one).
+ if (!UserPackage.hasCorrectTargetSdkVersion(packageInfo)) {
+ return VALIDITY_INCORRECT_SDK_VERSION;
+ }
+ if (!versionCodeGE(packageInfo.getLongVersionCode(), getMinimumVersionCode())
+ && !mSystemInterface.systemIsDebuggable()) {
+ // Webview providers may be downgraded arbitrarily low, prevent that by enforcing
+ // minimum version code. This check is only enforced for user builds.
+ return VALIDITY_INCORRECT_VERSION_CODE;
+ }
+ if (!providerHasValidSignature(configInfo, packageInfo, mSystemInterface)) {
+ return VALIDITY_INCORRECT_SIGNATURE;
+ }
+ if (WebViewFactory.getWebViewLibrary(packageInfo.applicationInfo) == null) {
+ return VALIDITY_NO_LIBRARY_FLAG;
+ }
+ return VALIDITY_OK;
+ }
+
+ /**
+ * Both versionCodes should be from a WebView provider package implemented by Chromium.
+ * VersionCodes from other kinds of packages won't make any sense in this method.
+ *
+ * An introduction to Chromium versionCode scheme:
+ * "BBBBPPPXX"
+ * BBBB: 4 digit branch number. It monotonically increases over time.
+ * PPP: patch number in the branch. It is padded with zeroes to the left. These three digits
+ * may change their meaning in the future.
+ * XX: Digits to differentiate different APK builds of the same source version.
+ *
+ * This method takes the "BBBB" of versionCodes and compare them.
+ *
+ * https://www.chromium.org/developers/version-numbers describes general Chromium versioning;
+ * https://source.chromium.org/chromium/chromium/src/+/master:build/util/android_chrome_version.py
+ * is the canonical source for how Chromium versionCodes are calculated.
+ *
+ * @return true if versionCode1 is higher than or equal to versionCode2.
+ */
+ private static boolean versionCodeGE(long versionCode1, long versionCode2) {
+ long v1 = versionCode1 / 100000;
+ long v2 = versionCode2 / 100000;
+
+ return v1 >= v2;
+ }
+
+ /**
+ * Gets the minimum version code allowed for a valid provider. It is the minimum versionCode
+ * of all available-by-default WebView provider packages. If there is no such WebView provider
+ * package on the system, then return -1, which means all positive versionCode WebView packages
+ * are accepted.
+ *
+ * Note that this is a private method that handles a variable (mMinimumVersionCode) which is
+ * shared between threads. Furthermore, this method does not hold mLock meaning that we must
+ * take extra care to ensure this method is thread-safe.
+ */
+ private long getMinimumVersionCode() {
+ if (mMinimumVersionCode > 0) {
+ return mMinimumVersionCode;
+ }
+
+ long minimumVersionCode = -1;
+ for (WebViewProviderInfo provider : mSystemInterface.getWebViewPackages()) {
+ if (provider.availableByDefault) {
+ try {
+ long versionCode =
+ mSystemInterface.getFactoryPackageVersion(provider.packageName);
+ if (minimumVersionCode < 0 || versionCode < minimumVersionCode) {
+ minimumVersionCode = versionCode;
+ }
+ } catch (NameNotFoundException e) {
+ // Safe to ignore.
+ }
+ }
+ }
+
+ mMinimumVersionCode = minimumVersionCode;
+ return mMinimumVersionCode;
+ }
+
+ private static boolean providerHasValidSignature(WebViewProviderInfo provider,
+ PackageInfo packageInfo, SystemInterface systemInterface) {
+ // Skip checking signatures on debuggable builds, for development purposes.
+ if (systemInterface.systemIsDebuggable()) return true;
+
+ // Allow system apps to be valid providers regardless of signature.
+ if (packageInfo.applicationInfo.isSystemApp()) return true;
+
+ // We don't support packages with multiple signatures.
+ if (packageInfo.signatures.length != 1) return false;
+
+ // If any of the declared signatures match the package signature, it's valid.
+ for (Signature signature : provider.signatures) {
+ if (signature.equals(packageInfo.signatures[0])) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the only fallback provider in the set of given packages, or null if there is none.
+ */
+ private static WebViewProviderInfo getFallbackProvider(WebViewProviderInfo[] webviewPackages) {
+ for (WebViewProviderInfo provider : webviewPackages) {
+ if (provider.isFallback) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isMultiProcessEnabled() {
+ int settingValue = mSystemInterface.getMultiProcessSetting(mContext);
+ if (mSystemInterface.isMultiProcessDefaultEnabled()) {
+ // Multiprocess should be enabled unless the user has turned it off manually.
+ return settingValue > MULTIPROCESS_SETTING_OFF_VALUE;
+ } else {
+ // Multiprocess should not be enabled, unless the user has turned it on manually.
+ return settingValue >= MULTIPROCESS_SETTING_ON_VALUE;
+ }
+ }
+
+ @Override
+ public void enableMultiProcess(boolean enable) {
+ PackageInfo current = getCurrentWebViewPackage();
+ mSystemInterface.setMultiProcessSetting(mContext,
+ enable ? MULTIPROCESS_SETTING_ON_VALUE : MULTIPROCESS_SETTING_OFF_VALUE);
+ mSystemInterface.notifyZygote(enable);
+ if (current != null) {
+ mSystemInterface.killPackageDependents(current.packageName);
+ }
+ }
+
+ /**
+ * Dump the state of this Service.
+ */
+ @Override
+ public void dumpState(PrintWriter pw) {
+ pw.println("Current WebView Update Service state");
+ pw.println(String.format(" Multiprocess enabled: %b", isMultiProcessEnabled()));
+ synchronized (mLock) {
+ if (mCurrentWebViewPackage == null) {
+ pw.println(" Current WebView package is null");
+ } else {
+ pw.println(String.format(" Current WebView package (name, version): (%s, %s)",
+ mCurrentWebViewPackage.packageName,
+ mCurrentWebViewPackage.versionName));
+ }
+ pw.println(String.format(" Minimum targetSdkVersion: %d",
+ UserPackage.MINIMUM_SUPPORTED_SDK));
+ pw.println(String.format(" Minimum WebView version code: %d",
+ mMinimumVersionCode));
+ pw.println(String.format(" Number of relros started: %d",
+ mNumRelroCreationsStarted));
+ pw.println(String.format(" Number of relros finished: %d",
+ mNumRelroCreationsFinished));
+ pw.println(String.format(" WebView package dirty: %b", mWebViewPackageDirty));
+ pw.println(String.format(" Any WebView package installed: %b",
+ mAnyWebViewInstalled));
+
+ try {
+ PackageInfo preferredWebViewPackage = findPreferredWebViewPackage();
+ pw.println(String.format(
+ " Preferred WebView package (name, version): (%s, %s)",
+ preferredWebViewPackage.packageName,
+ preferredWebViewPackage.versionName));
+ } catch (WebViewPackageMissingException e) {
+ pw.println(String.format(" Preferred WebView package: none"));
+ }
+
+ dumpAllPackageInformationLocked(pw);
+ }
+ }
+
+ private void dumpAllPackageInformationLocked(PrintWriter pw) {
+ WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
+ pw.println(" WebView packages:");
+ for (WebViewProviderInfo provider : allProviders) {
+ List<UserPackage> userPackages =
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider);
+ PackageInfo systemUserPackageInfo =
+ userPackages.get(UserHandle.USER_SYSTEM).getPackageInfo();
+ if (systemUserPackageInfo == null) {
+ pw.println(String.format(" %s is NOT installed.", provider.packageName));
+ continue;
+ }
+
+ int validity = validityResult(provider, systemUserPackageInfo);
+ String packageDetails = String.format(
+ "versionName: %s, versionCode: %d, targetSdkVersion: %d",
+ systemUserPackageInfo.versionName,
+ systemUserPackageInfo.getLongVersionCode(),
+ systemUserPackageInfo.applicationInfo.targetSdkVersion);
+ if (validity == VALIDITY_OK) {
+ boolean installedForAllUsers = isInstalledAndEnabledForAllUsers(
+ mSystemInterface.getPackageInfoForProviderAllUsers(mContext, provider));
+ pw.println(String.format(
+ " Valid package %s (%s) is %s installed/enabled for all users",
+ systemUserPackageInfo.packageName,
+ packageDetails,
+ installedForAllUsers ? "" : "NOT"));
+ } else {
+ pw.println(String.format(" Invalid package %s (%s), reason: %s",
+ systemUserPackageInfo.packageName,
+ packageDetails,
+ getInvalidityReason(validity)));
+ }
+ }
+ }
+
+ private static String getInvalidityReason(int invalidityReason) {
+ switch (invalidityReason) {
+ case VALIDITY_INCORRECT_SDK_VERSION:
+ return "SDK version too low";
+ case VALIDITY_INCORRECT_VERSION_CODE:
+ return "Version code too low";
+ case VALIDITY_INCORRECT_SIGNATURE:
+ return "Incorrect signature";
+ case VALIDITY_NO_LIBRARY_FLAG:
+ return "No WebView-library manifest flag";
+ default:
+ return "Unexcepted validity-reason";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
new file mode 100644
index 0000000..a9c3dc4
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceInterface.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.webkit;
+
+import android.content.pm.PackageInfo;
+import android.webkit.WebViewProviderInfo;
+import android.webkit.WebViewProviderResponse;
+
+import java.io.PrintWriter;
+
+interface WebViewUpdateServiceInterface {
+ void packageStateChanged(String packageName, int changedState, int userId);
+
+ void handleNewUser(int userId);
+
+ void handleUserRemoved(int userId);
+
+ WebViewProviderInfo[] getWebViewPackages();
+
+ void prepareWebViewInSystemServer();
+
+ void notifyRelroCreationCompleted();
+
+ WebViewProviderResponse waitForAndGetProvider();
+
+ String changeProviderAndSetting(String newProviderName);
+
+ WebViewProviderInfo[] getValidWebViewPackages();
+
+ PackageInfo getCurrentWebViewPackage();
+
+ boolean isMultiProcessEnabled();
+
+ void enableMultiProcess(boolean enable);
+
+ void dumpState(PrintWriter pw);
+}
diff --git a/services/core/java/com/android/server/webkit/flags.aconfig b/services/core/java/com/android/server/webkit/flags.aconfig
new file mode 100644
index 0000000..1411acc
--- /dev/null
+++ b/services/core/java/com/android/server/webkit/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.webkit"
+
+flag {
+ name: "update_service_v2"
+ namespace: "webview"
+ description: "Using a new version of the WebView update service"
+ bug: "308907090"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 287aaf9..8cc197c 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -46,7 +46,6 @@
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
-import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -88,7 +87,6 @@
/** If enabled the creator will not allow BAL on its behalf by default. */
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @Overridable
private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR =
296478951;
public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index d0d7f49..e6bbd52 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -101,10 +101,8 @@
mPolicy = displayContent.getDisplayPolicy();
final Resources r = mPolicy.getContext().getResources();
mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
- mTransientControlTarget = new ControlTarget(
- stateController, displayContent.mWmService.mH, "TransientControlTarget");
- mPermanentControlTarget = new ControlTarget(
- stateController, displayContent.mWmService.mH, "PermanentControlTarget");
+ mTransientControlTarget = new ControlTarget(displayContent, "TransientControlTarget");
+ mPermanentControlTarget = new ControlTarget(displayContent, "PermanentControlTarget");
}
/** Updates the target which can control system bars. */
@@ -699,24 +697,35 @@
}
}
- private static class ControlTarget implements InsetsControlTarget {
+ private static class ControlTarget implements InsetsControlTarget, Runnable {
+ private final Handler mHandler;
+ private final Object mGlobalLock;
private final InsetsState mState = new InsetsState();
- private final InsetsController mInsetsController;
private final InsetsStateController mStateController;
+ private final InsetsController mInsetsController;
private final String mName;
- ControlTarget(InsetsStateController stateController, Handler handler, String name) {
- mStateController = stateController;
- mInsetsController = new InsetsController(new Host(handler, name));
+ ControlTarget(DisplayContent displayContent, String name) {
+ mHandler = displayContent.mWmService.mH;
+ mGlobalLock = displayContent.mWmService.mGlobalLock;
+ mStateController = displayContent.getInsetsStateController();
+ mInsetsController = new InsetsController(new Host(mHandler, name));
mName = name;
}
@Override
public void notifyInsetsControlChanged() {
- mState.set(mStateController.getRawInsetsState(), true /* copySources */);
- mInsetsController.onStateChanged(mState);
- mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+ mHandler.post(this);
+ }
+
+ @Override
+ public void run() {
+ synchronized (mGlobalLock) {
+ mState.set(mStateController.getRawInsetsState(), true /* copySources */);
+ mInsetsController.onStateChanged(mState);
+ mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this));
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index b3a3650..89a70e5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -43,8 +43,6 @@
/* Start Available Flags */
- final boolean mSyncWindowConfigUpdateFlag = Flags.syncWindowConfigUpdateFlag();
-
final boolean mWindowStateResizeItemFlag = Flags.windowStateResizeItemFlag();
final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index a9f5b14..18a2acc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.ComponentName;
@@ -171,6 +172,12 @@
when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
when(mActivityManager.getLauncherLargeIconDensity()).thenReturn(100);
+ when(mContext.checkCallingOrSelfPermission(
+ eq(Manifest.permission.REQUEST_INSTALL_PACKAGES))).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ when(mContext.checkCallingOrSelfPermission(
+ eq(Manifest.permission.REQUEST_DELETE_PACKAGES))).thenReturn(
+ PackageManager.PERMISSION_DENIED);
when(mAppOpsManager.checkOp(
eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
@@ -386,7 +393,7 @@
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, "different",
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
@@ -404,7 +411,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
@@ -416,7 +423,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
@@ -428,7 +435,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s is not currently archived.", PACKAGE));
@@ -452,7 +459,7 @@
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
- UserHandle.CURRENT));
+ mIntentSender, UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("No installer found to unarchive app %s.", PACKAGE));
@@ -462,7 +469,8 @@
public void unarchiveApp_success() {
mUserState.setArchiveState(createArchiveState()).setInstalled(false);
- mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+ mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+ UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 7fb8b30..a47fbce 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -87,8 +87,6 @@
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
@@ -739,9 +737,6 @@
clearInvocations(mRankingHandler);
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
- mTestFlagResolver.setFlagOverride(FSI_FORCE_DEMOTE, false);
- mTestFlagResolver.setFlagOverride(SHOW_STICKY_HUN_FOR_DENIED_FSI, false);
-
var checker = mock(TestableNotificationManagerService.ComponentPermissionChecker.class);
mService.permissionChecker = checker;
when(checker.check(anyString(), anyInt(), anyInt(), anyBoolean()))
@@ -11472,14 +11467,12 @@
verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class));
}
- private void verifyStickyHun(Flag flag, int permissionState, boolean appRequested,
+ private void verifyStickyHun(int permissionState, boolean appRequested,
boolean isSticky) throws Exception {
when(mPermissionHelper.hasRequestedPermission(Manifest.permission.USE_FULL_SCREEN_INTENT,
PKG, mUserId)).thenReturn(appRequested);
- mTestFlagResolver.setFlagOverride(flag, true);
-
when(mPermissionManager.checkPermissionForDataDelivery(
eq(Manifest.permission.USE_FULL_SCREEN_INTENT), any(), any()))
.thenReturn(permissionState);
@@ -11503,8 +11496,7 @@
public void testFixNotification_flagEnableStickyHun_fsiPermissionHardDenied_showStickyHun()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
/* isSticky= */ true);
}
@@ -11512,16 +11504,14 @@
public void testFixNotification_flagEnableStickyHun_fsiPermissionSoftDenied_showStickyHun()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
/* isSticky= */ true);
}
@Test
public void testFixNotification_fsiPermissionSoftDenied_appNotRequest_noShowStickyHun()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, false,
/* isSticky= */ false);
}
@@ -11530,39 +11520,11 @@
public void testFixNotification_flagEnableStickyHun_fsiPermissionGranted_showFsi()
throws Exception {
- verifyStickyHun(/* flag= */ SHOW_STICKY_HUN_FOR_DENIED_FSI,
- /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
+ verifyStickyHun(/* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
/* isSticky= */ false);
}
@Test
- public void testFixNotification_flagForceStickyHun_fsiPermissionHardDenied_showStickyHun()
- throws Exception {
-
- verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_HARD_DENIED, true,
- /* isSticky= */ true);
- }
-
- @Test
- public void testFixNotification_flagForceStickyHun_fsiPermissionSoftDenied_showStickyHun()
- throws Exception {
-
- verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_SOFT_DENIED, true,
- /* isSticky= */ true);
- }
-
- @Test
- public void testFixNotification_flagForceStickyHun_fsiPermissionGranted_showStickyHun()
- throws Exception {
-
- verifyStickyHun(/* flag= */ FSI_FORCE_DEMOTE,
- /* permissionState= */ PermissionManager.PERMISSION_GRANTED, true,
- /* isSticky= */ true);
- }
-
- @Test
public void fixNotification_withFgsFlag_butIsNotFgs() throws Exception {
final ApplicationInfo applicationInfo = new ApplicationInfo();
when(mPackageManagerClient.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
index 5147a08..29848d0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java
@@ -171,19 +171,8 @@
}
@Test
- public void testGetFsiState_stickyHunFlagDisabled_zero() {
- final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ false,
- /* hasFullScreenIntent= */ true,
- /* hasFsiRequestedButDeniedFlag= */ true,
- /* eventType= */ NOTIFICATION_POSTED);
- assertEquals(0, fsiState);
- }
-
- @Test
public void testGetFsiState_isUpdate_zero() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ true,
/* hasFsiRequestedButDeniedFlag= */ true,
/* eventType= */ NOTIFICATION_UPDATED);
@@ -193,7 +182,6 @@
@Test
public void testGetFsiState_hasFsi_allowedEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ true,
/* hasFsiRequestedButDeniedFlag= */ false,
/* eventType= */ NOTIFICATION_POSTED);
@@ -203,7 +191,6 @@
@Test
public void testGetFsiState_fsiPermissionDenied_deniedEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ false,
/* hasFsiRequestedButDeniedFlag= */ true,
/* eventType= */ NOTIFICATION_POSTED);
@@ -213,7 +200,6 @@
@Test
public void testGetFsiState_noFsi_noFsiEnum() {
final int fsiState = NotificationRecordLogger.getFsiState(
- /* isStickyHunFlagEnabled= */ true,
/* hasFullScreenIntent= */ false,
/* hasFsiRequestedButDeniedFlag= */ false,
/* eventType= */ NOTIFICATION_POSTED);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c156e37..fe21103 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -5733,17 +5733,9 @@
}
@Test
- public void testGetFsiState_flagDisabled_zero() {
- final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission */ true, /* isFlagEnabled= */ false);
-
- assertEquals(0, fsiState);
- }
-
- @Test
public void testGetFsiState_appDidNotRequest_enumNotRequested() {
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission */ false, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission */ false);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED, fsiState);
}
@@ -5754,7 +5746,7 @@
.thenReturn(PermissionManager.PERMISSION_GRANTED);
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission= */ true, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission= */ true);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED, fsiState);
}
@@ -5765,7 +5757,7 @@
.thenReturn(PermissionManager.PERMISSION_SOFT_DENIED);
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission = */ true);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
}
@@ -5776,7 +5768,7 @@
.thenReturn(PermissionManager.PERMISSION_HARD_DENIED);
final int fsiState = mHelper.getFsiState("pkg", /* uid= */ 0,
- /* requestedFsiPermission = */ true, /* isFlagEnabled= */ true);
+ /* requestedFsiPermission = */ true);
assertEquals(PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED, fsiState);
}
@@ -5785,18 +5777,7 @@
public void testIsFsiPermissionUserSet_appDidNotRequest_false() {
final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
/* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED,
- /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ true);
-
- assertFalse(isUserSet);
- }
-
- @Test
- public void testIsFsiPermissionUserSet_flagDisabled_false() {
- final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
- /* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
- /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ false);
+ /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
assertFalse(isUserSet);
}
@@ -5805,8 +5786,7 @@
public void testIsFsiPermissionUserSet_userSet_true() {
final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
/* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
- /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ true);
+ /* currentPermissionFlags= */ PackageManager.FLAG_PERMISSION_USER_SET);
assertTrue(isUserSet);
}
@@ -5815,8 +5795,7 @@
public void testIsFsiPermissionUserSet_notUserSet_false() {
final boolean isUserSet = mHelper.isFsiPermissionUserSet("pkg", /* uid= */ 0,
/* fsiState = */ PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED,
- /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET,
- /* isStickyHunFlagEnabled= */ true);
+ /* currentPermissionFlags= */ ~PackageManager.FLAG_PERMISSION_USER_SET);
assertFalse(isUserSet);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 63de41f..4c56f33 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1953,6 +1953,13 @@
}
}
+ // Flags status.
+ pw.println("Flags:");
+ pw.println(" " + Flags.FLAG_USER_INTERACTION_TYPE_API
+ + ": " + Flags.userInteractionTypeApi());
+ pw.println(" " + Flags.FLAG_USE_PARCELED_LIST
+ + ": " + Flags.useParceledList());
+
final int[] userIds;
synchronized (mLock) {
final int userCount = mUserState.size();
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f8608b8..e12a815 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -23,6 +23,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.ColorInt;
import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -70,6 +71,7 @@
import com.android.internal.telephony.ISetOpportunisticDataCallback;
import com.android.internal.telephony.ISub;
import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.HandlerExecutor;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.Preconditions;
@@ -95,7 +97,22 @@
import java.util.stream.Collectors;
/**
- * Subscription manager provides the mobile subscription information.
+ * Subscription manager provides the mobile subscription information that are associated with the
+ * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+ * and below can see all subscriptions as it does today.
+ *
+ * <p>For example, if we have
+ * <ul>
+ * <li> Subscription 1 associated with personal profile.
+ * <li> Subscription 2 associated with work profile.
+ * </ul>
+ * Then for SDK 35+, if the caller identity is personal profile, then
+ * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission
+ * may be required as documented on the each API.
+ *
*/
@SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
@RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -1446,6 +1463,16 @@
}
}
+ /**
+ * {@code true} if the SubscriptionManager instance should see all subscriptions regardless its
+ * association with particular user profile.
+ *
+ * <p> It only applies to Android SDK 35(V) and above. For Android SDK 34(U) and below, the
+ * caller can see all subscription across user profiles as it does today today even if it's
+ * {@code false}.
+ */
+ private boolean mIsForAllUserProfiles = false;
+
/** @hide */
@UnsupportedAppUsage
public SubscriptionManager(Context context) {
@@ -1776,8 +1803,23 @@
}
/**
- * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
- * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
+ * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller
+ * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U)
+ * and below can see all subscriptions as it does today.
+ *
+ * <p>For example, if we have
+ * <ul>
+ * <li> Subscription 1 associated with personal profile.
+ * <li> Subscription 2 associated with work profile.
+ * </ul>
+ * Then for SDK 35+, if the caller identity is personal profile, then this will return
+ * subscription 1 only and vice versa.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert this instance to see all.
+ *
+ * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by
+ * {@link SubscriptionInfo#getSubscriptionId}.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see
@@ -1800,8 +1842,25 @@
* </ul>
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ // @RequiresPermission(TODO(b/308809058))
public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
- return getActiveSubscriptionInfoList(/* userVisibleonly */true);
+ List<SubscriptionInfo> activeList = null;
+
+ try {
+ ISub iSub = TelephonyManager.getSubscriptionService();
+ if (iSub != null) {
+ activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
+ mContext.getAttributionTag(), mIsForAllUserProfiles);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ if (activeList != null) {
+ activeList = activeList.stream().filter(subInfo -> isSubscriptionVisible(subInfo))
+ .collect(Collectors.toList());
+ }
+ return activeList;
}
/**
@@ -1835,6 +1894,26 @@
}
/**
+ * Convert this subscription manager instance into one that can see all subscriptions across
+ * user profiles.
+ *
+ * @return a SubscriptionManager that can see all subscriptions regardless its user profile
+ * association.
+ *
+ * @see #getActiveSubscriptionInfoList
+ * @see #getActiveSubscriptionInfoCount
+ * @see UserHandle
+ */
+ @FlaggedApi(Flags.FLAG_WORK_PROFILE_API_SPLIT)
+ // @RequiresPermission(TODO(b/308809058))
+ // The permission check for accessing all subscriptions will be enforced upon calling the
+ // individual APIs linked above.
+ @NonNull public SubscriptionManager createForAllUserProfiles() {
+ mIsForAllUserProfiles = true;
+ return this;
+ }
+
+ /**
* This is similar to {@link #getActiveSubscriptionInfoList()}, but if userVisibleOnly
* is true, it will filter out the hidden subscriptions.
*
@@ -1847,7 +1926,7 @@
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), true /*isForAllUserProfiles*/);
}
} catch (RemoteException ex) {
// ignore it
@@ -2002,13 +2081,19 @@
}
/**
- * Get the active subscription count.
+ * Get the active subscription count associated with the current caller user profile for
+ * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as
+ * it does today.
+ *
+ * <p>If the caller needs to see all subscriptions across user profiles,
+ * use {@link #createForAllUserProfiles} to convert this instance to see all.
*
* @return The current number of active subscriptions.
*
* @see #getActiveSubscriptionInfoList()
*/
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ // @RequiresPermission(TODO(b/308809058))
public int getActiveSubscriptionInfoCount() {
int result = 0;
@@ -2016,7 +2101,7 @@
ISub iSub = TelephonyManager.getSubscriptionService();
if (iSub != null) {
result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(),
- mContext.getAttributionTag());
+ mContext.getAttributionTag(), mIsForAllUserProfiles);
}
} catch (RemoteException ex) {
// ignore it
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index d2dbeb7..3f41d56 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -66,6 +66,8 @@
*
* @param callingPackage The package maing the call.
* @param callingFeatureId The feature in the package
+ * @param isForAllProfiles whether the caller intends to see all subscriptions regardless
+ * association.
* @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
* <ul>
* <li>
@@ -83,14 +85,17 @@
* </ul>
*/
List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage,
- String callingFeatureId);
+ String callingFeatureId, boolean isForAllProfiles);
/**
* @param callingPackage The package making the call.
* @param callingFeatureId The feature in the package.
+ * @param isForAllProfile whether the caller intends to see all subscriptions regardless
+ * association.
* @return the number of active subscriptions
*/
- int getActiveSubInfoCount(String callingPackage, String callingFeatureId);
+ int getActiveSubInfoCount(String callingPackage, String callingFeatureId,
+ boolean isForAllProfile);
/**
* @return the maximum number of subscriptions this device will support at any one time.
diff --git a/tests/InputScreenshotTest/Android.bp b/tests/InputScreenshotTest/Android.bp
index 15aaa46..83ced2c 100644
--- a/tests/InputScreenshotTest/Android.bp
+++ b/tests/InputScreenshotTest/Android.bp
@@ -7,12 +7,27 @@
default_applicable_licenses: ["frameworks_base_license"],
}
+filegroup {
+ name: "InputScreenshotTestRNGFiles",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ exclude_srcs: [
+ "src/android/input/screenshot/KeyboardLayoutPreviewAnsiScreenshotTest.kt",
+ "src/android/input/screenshot/KeyboardLayoutPreviewJisScreenshotTest.kt",
+ ],
+}
+
android_test {
name: "InputScreenshotTests",
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
+ exclude_srcs: [
+ "src/android/input/screenshot/package-info.java",
+ ],
platform_apis: true,
certificate: "platform",
static_libs: [
@@ -43,6 +58,7 @@
"hamcrest-library",
"kotlin-test",
"flag-junit",
+ "platform-parametric-runner-lib",
"platform-test-annotations",
"services.core.unboosted",
"testables",
diff --git a/tests/InputScreenshotTest/robotests/Android.bp b/tests/InputScreenshotTest/robotests/Android.bp
new file mode 100644
index 0000000..912f4b80
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/Android.bp
@@ -0,0 +1,71 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+ name: "InputRoboRNGTestsAssetsLib",
+ asset_dirs: ["assets"],
+ sdk_version: "current",
+ platform_apis: true,
+ manifest: "AndroidManifest.xml",
+ optimize: {
+ enabled: false,
+ },
+ use_resource_processor: true,
+}
+
+android_app {
+ name: "InputRoboApp",
+ srcs: [],
+ static_libs: [
+ "androidx.test.espresso.core",
+ "androidx.appcompat_appcompat",
+ "flag-junit",
+ "guava",
+ "InputRoboRNGTestsAssetsLib",
+ "platform-screenshot-diff-core",
+ "PlatformComposeSceneTransitionLayoutTestsUtils",
+ ],
+ manifest: "robo-manifest.xml",
+ aaptflags: [
+ "--extra-packages",
+ "com.android.input.screenshot",
+ ],
+ dont_merge_manifests: true,
+ platform_apis: true,
+ system_ext_specific: true,
+ certificate: "platform",
+ privileged: true,
+ resource_dirs: [],
+ kotlincflags: ["-Xjvm-default=all"],
+
+ plugins: ["dagger2-compiler"],
+ use_resource_processor: true,
+}
+
+android_robolectric_test {
+ name: "InputRoboRNGTests",
+ srcs: [
+ ":InputScreenshotTestRNGFiles",
+ ":flag-junit",
+ ":platform-test-screenshot-rules",
+ ],
+ // Do not add any new libraries here, they should be added to SystemUIGoogleRobo above.
+ static_libs: [
+ "androidx.compose.runtime_runtime",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
+ "platform-parametric-runner-lib",
+ "uiautomator-helpers",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth",
+ ],
+ upstream: true,
+ java_resource_dirs: ["config"],
+ instrumentation_for: "InputRoboApp",
+}
diff --git a/tests/InputScreenshotTest/robotests/AndroidManifest.xml b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
new file mode 100644
index 0000000..5689311
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.input.screenshot">
+ <uses-sdk android:minSdkVersion="21"/>
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+</manifest>
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
new file mode 100644
index 0000000..baf204a
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_landscape_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
new file mode 100644
index 0000000..deb3cee
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/phone/light_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
new file mode 100644
index 0000000..34e25f7
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/assets/tablet/dark_portrait_layout-preview.png
Binary files differ
diff --git a/tests/InputScreenshotTest/robotests/config/robolectric.properties b/tests/InputScreenshotTest/robotests/config/robolectric.properties
new file mode 100644
index 0000000..83d7549
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/config/robolectric.properties
@@ -0,0 +1,15 @@
+# Copyright (C) 2022 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/tests/InputScreenshotTest/robotests/robo-manifest.xml b/tests/InputScreenshotTest/robotests/robo-manifest.xml
new file mode 100644
index 0000000..e86f58e
--- /dev/null
+++ b/tests/InputScreenshotTest/robotests/robo-manifest.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--- Include all the namespaces we will ever need anywhere, because this is the source the manifest merger uses for namespaces -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.android.input.screenshot"
+ coreApp="true">
+ <application>
+ <activity
+ android:name="androidx.activity.ComponentActivity"
+ android:exported="true">
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
index c2c3d55..75dab41 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/InputScreenshotTestRule.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.graphics.Bitmap
+import android.os.Build
import androidx.activity.ComponentActivity
import androidx.compose.foundation.Image
import androidx.compose.ui.platform.ViewRootForTest
@@ -49,15 +50,17 @@
)
)
private val composeRule = createAndroidComposeRule<ComponentActivity>()
- private val delegateRule =
- RuleChain.outerRule(colorsRule)
- .around(deviceEmulationRule)
+ private val roboRule =
+ RuleChain.outerRule(deviceEmulationRule)
.around(screenshotRule)
.around(composeRule)
+ private val delegateRule = RuleChain.outerRule(colorsRule).around(roboRule)
private val matcher = UnitTestBitmapMatcher
+ private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
override fun apply(base: Statement, description: Description): Statement {
- return delegateRule.apply(base, description)
+ val ruleToApply = if (isRobolectric) roboRule else delegateRule
+ return ruleToApply.apply(base, description)
}
/**
@@ -84,4 +87,4 @@
val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
}
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
index 8ae6dfd..ab7bb4e 100644
--- a/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/KeyboardLayoutPreviewIsoScreenshotTest.kt
@@ -26,14 +26,15 @@
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
import platform.test.screenshot.DeviceEmulationSpec
/** A screenshot test for Keyboard layout preview for Iso physical layout. */
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
class KeyboardLayoutPreviewIsoScreenshotTest(emulationSpec: DeviceEmulationSpec) {
companion object {
- @Parameterized.Parameters(name = "{0}")
+ @Parameters(name = "{0}")
@JvmStatic
fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletMinimal
}
@@ -55,4 +56,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
new file mode 100644
index 0000000..4b5a56d
--- /dev/null
+++ b/tests/InputScreenshotTest/src/android/input/screenshot/package-info.java
@@ -0,0 +1,4 @@
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+package com.android.input.screenshot;
+
+import org.robolectric.annotation.GraphicsMode;