Merge "Fix modeOnCanceled for LOCKSCREEN <=> AOD transitions" into main
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index eb5842b..45719a7 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 36137
+# Bug template url: https://b.corp.google.com/issues/new?component=36137&template=198919
+
alexbuy@google.com
patb@google.com
schfan@google.com
\ No newline at end of file
diff --git a/api/Android.bp b/api/Android.bp
index 8e06366..093ee4b 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -298,6 +298,28 @@
"org.xmlpull",
]
+// These are libs from framework-internal-utils that are required (i.e. being referenced)
+// from framework-non-updatable-sources. Add more here when there's a need.
+// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+// dependencies gets bigger.
+android_non_updatable_stubs_libs = [
+ "android.hardware.cas-V1.2-java",
+ "android.hardware.health-V1.0-java-constants",
+ "android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V2.0-java",
+ "android.hardware.tv.input-V1.0-java-constants",
+ "android.hardware.usb-V1.0-java-constants",
+ "android.hardware.usb-V1.1-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
+ "android.hardware.vibrator-V1.3-java",
+ "framework-protos",
+]
+
+java_defaults {
+ name: "android-non-updatable-stubs-libs-defaults",
+ libs: android_non_updatable_stubs_libs,
+}
+
// Defaults for all stubs that include the non-updatable framework. These defaults do not include
// module symbols, so will not compile correctly on their own. Users must add module APIs to the
// classpath (or sources) somehow.
@@ -329,18 +351,7 @@
// from framework-non-updatable-sources. Add more here when there's a need.
// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
// dependencies gets bigger.
- libs: [
- "android.hardware.cas-V1.2-java",
- "android.hardware.health-V1.0-java-constants",
- "android.hardware.thermal-V1.0-java-constants",
- "android.hardware.thermal-V2.0-java",
- "android.hardware.tv.input-V1.0-java-constants",
- "android.hardware.usb-V1.0-java-constants",
- "android.hardware.usb-V1.1-java-constants",
- "android.hardware.usb.gadget-V1.0-java",
- "android.hardware.vibrator-V1.3-java",
- "framework-protos",
- ],
+ libs: android_non_updatable_stubs_libs,
flags: [
"--error NoSettingsProvider",
"--error UnhiddenSystemApi",
diff --git a/cmds/telecom/Android.bp b/cmds/telecom/Android.bp
index be02710..494d2ae3 100644
--- a/cmds/telecom/Android.bp
+++ b/cmds/telecom/Android.bp
@@ -21,5 +21,8 @@
java_binary {
name: "telecom",
wrapper: "telecom.sh",
- srcs: ["**/*.java"],
+ srcs: [
+ ":telecom-shell-commands-src",
+ "**/*.java",
+ ],
}
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 1488e14cf..50af5a7 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -17,30 +17,22 @@
package com.android.commands.telecom;
import android.app.ActivityThread;
-import android.content.ComponentName;
import android.content.Context;
-import android.net.Uri;
-import android.os.IUserManager;
import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.sysprop.TelephonyProperties;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import com.android.internal.os.BaseCommand;
import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.TelecomShellCommand;
-import java.io.PrintStream;
-import java.util.Arrays;
-import java.util.stream.Collectors;
+import java.io.FileDescriptor;
-public final class Telecom extends BaseCommand {
+/**
+ * @deprecated Use {@code com.android.server.telecom.TelecomShellCommand} instead and execute the
+ * shell command using {@code adb shell cmd telecom...}. This is only here for backwards
+ * compatibility reasons.
+ */
+@Deprecated
+public final class Telecom {
/**
* Command-line entry point.
@@ -52,458 +44,11 @@
// TODO: Do it in zygote and RuntimeInit. b/148897549
ActivityThread.initializeMainlineModules();
- (new Telecom()).run(args);
- }
- private static final String CALLING_PACKAGE = Telecom.class.getPackageName();
- private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
- private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
- private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
- private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
- "set-user-selected-outgoing-phone-account";
- private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
- private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
- private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
- private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP =
- "add-or-remove-call-companion-app";
- private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT =
- "set-phone-acct-suggestion-component";
- private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
- private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service";
- private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
- private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
- private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
- private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
- private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS =
- "cleanup-orphan-phone-accounts";
- private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
- private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
- "is-non-ui-in-call-service-bound";
-
- /**
- * Change the system dialer package name if a package name was specified,
- * Example: adb shell telecom set-system-dialer <PACKAGE>
- *
- * Restore it to the default if if argument is "default" or no argument is passed.
- * Example: adb shell telecom set-system-dialer default
- */
- private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
- private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
- private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
- private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
- private static final String COMMAND_GET_SIM_CONFIG = "get-sim-config";
- private static final String COMMAND_GET_MAX_PHONES = "get-max-phones";
- private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER =
- "set-test-emergency-phone-account-package-filter";
- /**
- * Command used to emit a distinct "mark" in the logs.
- */
- private static final String COMMAND_LOG_MARK = "log-mark";
-
- private ComponentName mComponent;
- private String mAccountId;
- private ITelecomService mTelecomService;
- private TelephonyManager mTelephonyManager;
- private IUserManager mUserManager;
-
- @Override
- public void onShowUsage(PrintStream out) {
- out.println("usage: telecom [subcommand] [options]\n"
- + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
- + "usage: telecom register-sim-phone-account [-e] <COMPONENT> <ID> <USER_SN>"
- + " <LABEL>: registers a PhoneAccount with CAPABILITY_SIM_SUBSCRIPTION"
- + " and optionally CAPABILITY_PLACE_EMERGENCY_CALLS if \"-e\" is provided\n"
- + "usage: telecom set-user-selected-outgoing-phone-account [-e] <COMPONENT> <ID> "
- + "<USER_SN>\n"
- + "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
- + "usage: telecom set-test-call-screening-app <PACKAGE>\n"
- + "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n"
- + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n"
- + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>"
- + " <LABEL> <ADDRESS>\n"
- + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom set-call-diagnostic-service <PACKAGE>\n"
- + "usage: telecom set-default-dialer <PACKAGE>\n"
- + "usage: telecom get-default-dialer\n"
- + "usage: telecom get-system-dialer\n"
- + "usage: telecom wait-on-handlers\n"
- + "usage: telecom set-sim-count <COUNT>\n"
- + "usage: telecom get-sim-config\n"
- + "usage: telecom get-max-phones\n"
- + "usage: telecom stop-block-suppression: Stop suppressing the blocked number"
- + " provider after a call to emergency services.\n"
- + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have"
- + " gotten wedged in Telecom.\n"
- + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that"
- + " no longer have a valid UserHandle or accounts that no longer belongs to an"
- + " installed package.\n"
- + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n"
- + "\n"
- + "telecom set-phone-account-enabled: Enables the given phone account, if it has"
- + " already been registered with Telecom.\n"
- + "\n"
- + "telecom set-phone-account-disabled: Disables the given phone account, if it"
- + " has already been registered with telecom.\n"
- + "\n"
- + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n"
- + "telecom set-default-dialer: Sets the override default dialer to the given"
- + " component; this will override whatever the dialer role is set to.\n"
- + "\n"
- + "telecom get-default-dialer: Displays the current default dialer.\n"
- + "\n"
- + "telecom get-system-dialer: Displays the current system dialer.\n"
- + "telecom set-system-dialer: Set the override system dialer to the given"
- + " component. To remove the override, send \"default\"\n"
- + "\n"
- + "telecom wait-on-handlers: Wait until all handlers finish their work.\n"
- + "\n"
- + "telecom set-sim-count: Set num SIMs (2 for DSDS, 1 for single SIM."
- + " This may restart the device.\n"
- + "\n"
- + "telecom get-sim-config: Get the mSIM config string. \"DSDS\" for DSDS mode,"
- + " or \"\" for single SIM\n"
- + "\n"
- + "telecom get-max-phones: Get the max supported phones from the modem.\n"
- + "telecom set-test-emergency-phone-account-package-filter <PACKAGE>: sets a"
- + " package name that will be used for test emergency calls. To clear,"
- + " send an empty package name. Real emergency calls will still be placed"
- + " over Telephony.\n"
- + "telecom log-mark <MESSAGE>: emits a message into the telecom logs. Useful for "
- + "testers to indicate where in the logs various test steps take place.\n"
- + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular "
- + "non-ui-InCallService in InCallController to determine if it is bound \n"
- );
- }
-
- @Override
- public void onRun() throws Exception {
- mTelecomService = ITelecomService.Stub.asInterface(
- ServiceManager.getService(Context.TELECOM_SERVICE));
- if (mTelecomService == null) {
- Log.w(this, "onRun: Can't access telecom manager.");
- showError("Error: Could not access the Telecom Manager. Is the system running?");
- return;
- }
-
Looper.prepareMainLooper();
+ ITelecomService service = ITelecomService.Stub.asInterface(
+ ServiceManager.getService(Context.TELECOM_SERVICE));
Context context = ActivityThread.systemMain().getSystemContext();
- mTelephonyManager = context.getSystemService(TelephonyManager.class);
- if (mTelephonyManager == null) {
- Log.w(this, "onRun: Can't access telephony service.");
- showError("Error: Could not access the Telephony Service. Is the system running?");
- return;
- }
-
- mUserManager = IUserManager.Stub
- .asInterface(ServiceManager.getService(Context.USER_SERVICE));
- if (mUserManager == null) {
- Log.w(this, "onRun: Can't access user manager.");
- showError("Error: Could not access the User Manager. Is the system running?");
- return;
- }
- Log.i(this, "onRun: parsing command.");
- String command = nextArgRequired();
- switch (command) {
- case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
- runSetPhoneAccountEnabled(true);
- break;
- case COMMAND_SET_PHONE_ACCOUNT_DISABLED:
- runSetPhoneAccountEnabled(false);
- break;
- case COMMAND_REGISTER_PHONE_ACCOUNT:
- runRegisterPhoneAccount();
- break;
- case COMMAND_SET_TEST_CALL_REDIRECTION_APP:
- runSetTestCallRedirectionApp();
- break;
- case COMMAND_SET_TEST_CALL_SCREENING_APP:
- runSetTestCallScreeningApp();
- break;
- case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP:
- runAddOrRemoveCallCompanionApp();
- break;
- case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT:
- runSetTestPhoneAcctSuggestionComponent();
- break;
- case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE:
- runSetCallDiagnosticService();
- break;
- case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
- runRegisterSimPhoneAccount();
- break;
- case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
- runSetUserSelectedOutgoingPhoneAccount();
- break;
- case COMMAND_UNREGISTER_PHONE_ACCOUNT:
- runUnregisterPhoneAccount();
- break;
- case COMMAND_STOP_BLOCK_SUPPRESSION:
- runStopBlockSuppression();
- break;
- case COMMAND_CLEANUP_STUCK_CALLS:
- runCleanupStuckCalls();
- break;
- case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS:
- runCleanupOrphanPhoneAccounts();
- break;
- case COMMAND_RESET_CAR_MODE:
- runResetCarMode();
- break;
- case COMMAND_SET_DEFAULT_DIALER:
- runSetDefaultDialer();
- break;
- case COMMAND_GET_DEFAULT_DIALER:
- runGetDefaultDialer();
- break;
- case COMMAND_SET_SYSTEM_DIALER:
- runSetSystemDialer();
- break;
- case COMMAND_GET_SYSTEM_DIALER:
- runGetSystemDialer();
- break;
- case COMMAND_WAIT_ON_HANDLERS:
- runWaitOnHandler();
- break;
- case COMMAND_SET_SIM_COUNT:
- runSetSimCount();
- break;
- case COMMAND_GET_SIM_CONFIG:
- runGetSimConfig();
- break;
- case COMMAND_GET_MAX_PHONES:
- runGetMaxPhones();
- break;
- case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND:
- runIsNonUiInCallServiceBound();
- break;
- case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER:
- runSetEmergencyPhoneAccountPackageFilter();
- break;
- case COMMAND_LOG_MARK:
- runLogMark();
- break;
- default:
- Log.w(this, "onRun: unknown command: %s", command);
- throw new IllegalArgumentException ("unknown command '" + command + "'");
- }
- }
-
- private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final boolean success = mTelecomService.enablePhoneAccount(handle, enabled);
- if (success) {
- System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled."));
- } else {
- System.out.println("Error - is " + handle + " a valid PhoneAccount?");
- }
- }
-
- private void runRegisterPhoneAccount() throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final String label = nextArgRequired();
- PhoneAccount account = PhoneAccount.builder(handle, label)
- .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
- mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " registered.");
- }
-
- private void runRegisterSimPhoneAccount() throws RemoteException {
- boolean isEmergencyAccount = false;
- String opt;
- while ((opt = nextOption()) != null) {
- switch (opt) {
- case "-e": {
- isEmergencyAccount = true;
- break;
- }
- }
- }
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final String label = nextArgRequired();
- final String address = nextArgRequired();
- int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER
- | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
- | (isEmergencyAccount ? PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS : 0);
- PhoneAccount account = PhoneAccount.builder(
- handle, label)
- .setAddress(Uri.parse(address))
- .setSubscriptionAddress(Uri.parse(address))
- .setCapabilities(capabilities)
- .setShortDescription(label)
- .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
- .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
- .build();
- mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " registered.");
- }
-
- private void runSetTestCallRedirectionApp() throws RemoteException {
- final String packageName = nextArg();
- mTelecomService.setTestDefaultCallRedirectionApp(packageName);
- }
-
- private void runSetTestCallScreeningApp() throws RemoteException {
- final String packageName = nextArg();
- mTelecomService.setTestDefaultCallScreeningApp(packageName);
- }
-
- private void runAddOrRemoveCallCompanionApp() throws RemoteException {
- final String packageName = nextArgRequired();
- String isAdded = nextArgRequired();
- boolean isAddedBool = "1".equals(isAdded);
- mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool);
- }
-
- private void runSetCallDiagnosticService() throws RemoteException {
- String packageName = nextArg();
- if ("default".equals(packageName)) packageName = null;
- mTelecomService.setTestCallDiagnosticService(packageName);
- System.out.println("Success - " + packageName + " set as call diagnostic service.");
- }
-
- private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException {
- final String componentName = nextArg();
- mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
- }
-
- private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
- Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
- System.out.println("Success - " + handle + " set as default outgoing account.");
- }
-
- private void runUnregisterPhoneAccount() throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- mTelecomService.unregisterPhoneAccount(handle, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " unregistered.");
- }
-
- private void runStopBlockSuppression() throws RemoteException {
- mTelecomService.stopBlockSuppression();
- }
-
- private void runCleanupStuckCalls() throws RemoteException {
- mTelecomService.cleanupStuckCalls();
- }
-
- private void runCleanupOrphanPhoneAccounts() throws RemoteException {
- System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts()
- + " phone accounts.");
- }
-
- private void runResetCarMode() throws RemoteException {
- mTelecomService.resetCarMode();
- }
-
- private void runSetDefaultDialer() throws RemoteException {
- String packageName = nextArg();
- if ("default".equals(packageName)) packageName = null;
- mTelecomService.setTestDefaultDialer(packageName);
- System.out.println("Success - " + packageName + " set as override default dialer.");
- }
-
- private void runSetSystemDialer() throws RemoteException {
- final String flatComponentName = nextArg();
- final ComponentName componentName = (flatComponentName.equals("default")
- ? null : parseComponentName(flatComponentName));
- mTelecomService.setSystemDialer(componentName);
- System.out.println("Success - " + componentName + " set as override system dialer.");
- }
-
- private void runGetDefaultDialer() throws RemoteException {
- System.out.println(mTelecomService.getDefaultDialerPackage(CALLING_PACKAGE));
- }
-
- private void runGetSystemDialer() throws RemoteException {
- System.out.println(mTelecomService.getSystemDialerPackage(CALLING_PACKAGE));
- }
-
- private void runWaitOnHandler() throws RemoteException {
-
- }
-
- private void runSetSimCount() throws RemoteException {
- if (!callerIsRoot()) {
- System.out.println("set-sim-count requires adb root");
- return;
- }
- int numSims = Integer.parseInt(nextArgRequired());
- System.out.println("Setting sim count to " + numSims + ". Device may reboot");
- mTelephonyManager.switchMultiSimConfig(numSims);
- }
-
- /**
- * prints out whether a particular non-ui InCallServices is bound in a call
- */
- public void runIsNonUiInCallServiceBound() throws RemoteException {
- if (TextUtils.isEmpty(mArgs.peekNextArg())) {
- System.out.println("No Argument passed. Please pass a <PACKAGE_NAME> to lookup.");
- } else {
- System.out.println(
- String.valueOf(mTelecomService.isNonUiInCallServiceBound(nextArg())));
- }
- }
-
- /**
- * Prints the mSIM config to the console.
- * "DSDS" for a phone in DSDS mode
- * "" (empty string) for a phone in SS mode
- */
- private void runGetSimConfig() throws RemoteException {
- System.out.println(TelephonyProperties.multi_sim_config().orElse(""));
- }
-
- private void runGetMaxPhones() throws RemoteException {
- // how many logical modems can be potentially active simultaneously
- System.out.println(mTelephonyManager.getSupportedModemCount());
- }
-
- private void runSetEmergencyPhoneAccountPackageFilter() throws RemoteException {
- String packageName = mArgs.getNextArg();
- if (TextUtils.isEmpty(packageName)) {
- mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(null);
- System.out.println("Success - filter cleared");
- } else {
- mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(packageName);
- System.out.println("Success = filter set to " + packageName);
- }
-
- }
-
- private void runLogMark() throws RemoteException {
- String message = Arrays.stream(mArgs.peekRemainingArgs()).collect(Collectors.joining(" "));
- mTelecomService.requestLogMark(message);
- }
-
- private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
- if (TextUtils.isEmpty(mArgs.peekNextArg())) {
- return null;
- }
- final ComponentName component = parseComponentName(nextArgRequired());
- final String accountId = nextArgRequired();
- final String userSnInStr = nextArgRequired();
- UserHandle userHandle;
- try {
- final int userSn = Integer.parseInt(userSnInStr);
- userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
- } catch (NumberFormatException ex) {
- Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
- throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
- }
- return new PhoneAccountHandle(component, accountId, userHandle);
- }
-
- private boolean callerIsRoot() {
- return Process.ROOT_UID == Process.myUid();
- }
-
- private ComponentName parseComponentName(String component) {
- ComponentName cn = ComponentName.unflattenFromString(component);
- if (cn == null) {
- throw new IllegalArgumentException ("Invalid component " + component);
- }
- return cn;
+ new TelecomShellCommand(service, context).exec(null, FileDescriptor.in,
+ FileDescriptor.out, FileDescriptor.err, args);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 42ac6b7..b9ad92b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5395,7 +5395,7 @@
public final class AutomaticZenRule implements android.os.Parcelable {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
- ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
+ ctor @Deprecated public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
method public int describeContents();
method public android.net.Uri getConditionId();
@@ -6674,9 +6674,9 @@
method @Deprecated public android.app.Notification.Builder addPerson(String);
method @NonNull public android.app.Notification.Builder addPerson(android.app.Person);
method @NonNull public android.app.Notification build();
- method public android.widget.RemoteViews createBigContentView();
- method public android.widget.RemoteViews createContentView();
- method public android.widget.RemoteViews createHeadsUpContentView();
+ method @Deprecated public android.widget.RemoteViews createBigContentView();
+ method @Deprecated public android.widget.RemoteViews createContentView();
+ method @Deprecated public android.widget.RemoteViews createHeadsUpContentView();
method @NonNull public android.app.Notification.Builder extend(android.app.Notification.Extender);
method public android.os.Bundle getExtras();
method @Deprecated public android.app.Notification getNotification();
@@ -9608,7 +9608,7 @@
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
- method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
+ method @FlaggedApi("android.appwidget.flags.generated_previews") public boolean setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -15708,7 +15708,7 @@
method public boolean clipRect(int, int, int, int);
method @FlaggedApi("com.android.graphics.hwui.flags.clip_shader") public void clipShader(@NonNull android.graphics.Shader);
method public void concat(@Nullable android.graphics.Matrix);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat(@Nullable android.graphics.Matrix44);
method public void disableZ();
method public void drawARGB(int, int, int, int);
method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16361,7 +16361,7 @@
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
@@ -16370,7 +16370,7 @@
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
}
@@ -41153,19 +41153,19 @@
method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public final void migrateNotificationFilter(int, @Nullable java.util.List<java.lang.String>);
method public android.os.IBinder onBind(android.content.Intent);
- method public void onInterruptionFilterChanged(int);
- method public void onListenerConnected();
- method public void onListenerDisconnected();
- method public void onListenerHintsChanged(int);
- method public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
- method public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
- method public void onSilentStatusBarIconsVisibilityChanged(boolean);
+ method @UiThread public void onInterruptionFilterChanged(int);
+ method @UiThread public void onListenerConnected();
+ method @UiThread public void onListenerDisconnected();
+ method @UiThread public void onListenerHintsChanged(int);
+ method @UiThread public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
+ method @UiThread public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
+ method @UiThread public void onSilentStatusBarIconsVisibilityChanged(boolean);
method public final void requestInterruptionFilter(int);
method public final void requestListenerHints(int);
method public static void requestRebind(android.content.ComponentName);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d190c62..b73f199 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,7 +70,7 @@
field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE";
- field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_TRUSTED_SERVICE = "android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE";
+ field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE";
field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
@@ -403,7 +403,6 @@
field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
- field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION";
field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
@@ -2223,10 +2222,9 @@
}
public static final class Feature.Builder {
- ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle);
+ ctor public Feature.Builder(int);
method @NonNull public android.app.ondeviceintelligence.Feature build();
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
@@ -2238,7 +2236,7 @@
ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
method public int describeContents();
method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus();
+ method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2251,27 +2249,16 @@
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable {
- ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException;
- ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException;
- method public int describeContents();
- method @NonNull public java.io.FileInputStream getFileInputStream();
- method @NonNull public String getFilePartKey();
- method @NonNull public android.os.PersistableBundle getFilePartParams();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR;
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
@@ -2305,6 +2292,10 @@
field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
}
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+ method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+ }
+
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
ctor public ProcessingSignal();
method public void sendSignal(@NonNull android.os.PersistableBundle);
@@ -2315,8 +2306,18 @@
method public void onSignalReceived(@NonNull android.os.PersistableBundle);
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> {
- method public void onNewContent(@NonNull T);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
+ method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
+ ctor public TokenInfo(long, @NonNull android.os.PersistableBundle);
+ ctor public TokenInfo(long);
+ method public int describeContents();
+ method public long getCount();
+ method @NonNull public android.os.PersistableBundle getInfoParams();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR;
}
}
@@ -3759,7 +3760,6 @@
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
- field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String FACE_SERVICE = "face";
field public static final String FONT_SERVICE = "font";
field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding";
@@ -5137,15 +5137,6 @@
}
-package android.hardware.face {
-
- @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) public void authenticateInBackground(@Nullable java.util.concurrent.Executor, @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject, @Nullable android.os.CancellationSignal, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(anyOf={"android.permission.USE_BIOMETRIC_INTERNAL", android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION}) public boolean hasEnrolledTemplates();
- }
-
-}
-
package android.hardware.hdmi {
public abstract class HdmiClient {
@@ -11537,7 +11528,7 @@
method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public int getPermissionFlags(@NonNull String, @NonNull String, @NonNull String);
@@ -12891,7 +12882,7 @@
}
public abstract class NotificationListenerService extends android.app.Service {
- method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
+ method @UiThread public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
}
public static class NotificationListenerService.Ranking {
@@ -12963,12 +12954,14 @@
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
ctor public OnDeviceIntelligenceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
- method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onInferenceServiceConnected();
+ method public abstract void onInferenceServiceDisconnected();
+ method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
@@ -12985,17 +12978,18 @@
field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service {
- ctor public OnDeviceTrustedInferenceService();
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
+ ctor public OnDeviceSandboxedInferenceService();
method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
+ method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+ method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
+ method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
- field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
}
}
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1923641..1e72a06 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -511,9 +511,9 @@
InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String) parameter #0:
Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
@@ -571,7 +571,7 @@
Missing nullability on parameter `args` in method `dump`
MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
Missing nullability on parameter `base` in method `attachBaseContext`
-MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String):
+MissingNullability: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String):
Missing nullability on method `openFileInput` return
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
Missing nullability on parameter `intent` in method `onUnbind`
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index f6ec370..5e2397d 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -173,8 +173,8 @@
* interrupt the user (e.g. via sound & vibration) while this rule
* is active.
* @param enabled Whether the rule is enabled.
- * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
- * ZenPolicy, int, boolean)}.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
@Deprecated
public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
@@ -206,8 +206,10 @@
* while this rule is active. This overrides the global policy while this rule is
* action ({@link Condition#STATE_TRUE}).
* @param enabled Whether the rule is enabled.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
- // TODO (b/309088420): deprecate this constructor in favor of the builder
+ @Deprecated
public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
@Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
@Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
@@ -368,6 +370,9 @@
/**
* Sets the zen policy.
+ *
+ * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+ * a {@code null} value here means the previous policy is retained.
*/
public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
@@ -390,7 +395,12 @@
* Sets the configuration activity - an activity that handles
* {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
* about this rule and/or allows them to configure it. This is required to be non-null for rules
- * that are not backed by {@link android.service.notification.ConditionProviderService}.
+ * that are not backed by a {@link android.service.notification.ConditionProviderService}.
+ *
+ * <p>This is exclusive with the {@code owner} supplied in the constructor; rules where a
+ * configuration activity is set will not use the
+ * {@link android.service.notification.ConditionProviderService} supplied there to determine
+ * whether the rule should be active.
*/
public void setConfigurationActivity(@Nullable ComponentName componentName) {
this.configurationActivity = getTrimmedComponentName(componentName);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1129f9d..79cb09d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6068,11 +6068,19 @@
}
/**
- * Construct a RemoteViews for the final 1U notification layout. In order:
- * 1. Custom contentView from the caller
- * 2. Style's proposed content view
- * 3. Standard template view
+ * Construct a RemoteViews representing the standard notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createContentView() {
return createContentView(false /* increasedheight */ );
}
@@ -6181,8 +6189,19 @@
}
/**
- * Construct a RemoteViews for the final big notification layout.
+ * Construct a RemoteViews representing the expanded notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createBigContentView() {
RemoteViews result = null;
if (useExistingRemoteView(mN.bigContentView)) {
@@ -6315,8 +6334,19 @@
}
/**
- * Construct a RemoteViews for the final heads-up notification layout.
+ * Construct a RemoteViews representing the heads up notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createHeadsUpContentView() {
return createHeadsUpContentView(false /* useIncreasedHeight */);
}
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 283e21a..b82a1e3 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -270,13 +270,16 @@
* Integer extra for {@link #ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED} containing the state of
* the {@link AutomaticZenRule}.
*
- * <p>
- * The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
- * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
- * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
- * </p>
+ * <p>The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
+ * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
+ * {@link #AUTOMATIC_RULE_STATUS_ACTIVATED}, {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED}, or
+ * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
+ *
+ * <p>Note that the {@link #AUTOMATIC_RULE_STATUS_ACTIVATED} and
+ * {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED} statuses are only sent to packages targeting
+ * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above; apps targeting a lower SDK version
+ * will be sent {@link #AUTOMATIC_RULE_STATUS_UNKNOWN} in their place instead.
*/
- // TODO (b/309101513): Add new status types to javadoc
public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
"android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";
@@ -370,11 +373,15 @@
= "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
/**
- * Intent that is broadcast when the state of getNotificationPolicy() changes.
+ * Intent that is broadcast when the state of {@link #getNotificationPolicy()} changes.
*
* <p>This broadcast is only sent to registered receivers and (starting from
* {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
* Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+ *
+ * <p>Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, most calls to
+ * {@link #setNotificationPolicy(Policy)} will update the app's implicit rule policy instead of
+ * the global policy, so this broadcast will be sent much less frequently.
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -1378,12 +1385,16 @@
/**
* Updates the given zen rule.
*
- * <p>
- * Throws a SecurityException if policy access is not granted to this package.
+ * <p>Before {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, updating a rule that is not backed
+ * up by a {@link android.service.notification.ConditionProviderService} will deactivate it if
+ * it was previously active. Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this
+ * will only happen if the rule's definition is actually changing.
+ *
+ * <p>Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
- * <p>
- * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ * <p>Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ *
* @param id The id of the rule to update
* @param automaticZenRule the rule to update.
* @return Whether the rule was successfully updated.
@@ -1744,9 +1755,11 @@
/**
* Gets the current user-specified default notification policy.
*
- * <p>
+ * <p>For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) this method will return the policy associated
+ * to their implicit {@link AutomaticZenRule} instead, if it exists. See
+ * {@link #setNotificationPolicy(Policy)}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public Policy getNotificationPolicy() {
INotificationManager service = getService();
try {
@@ -1757,15 +1770,20 @@
}
/**
- * Sets the current notification policy.
+ * Sets the current notification policy (which applies when {@link #setInterruptionFilter} is
+ * called with the {@link #INTERRUPTION_FILTER_PRIORITY} value).
*
- * <p>
- * Only available if policy access is granted to this package.
- * See {@link #isNotificationPolicyAccessGranted}.
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global notification policy.
+ * Calling this method will instead create or update an {@link AutomaticZenRule} associated to
+ * the app, using a {@link ZenPolicy} corresponding to the {@link Policy} supplied here, and
+ * which will be activated/deactivated by calls to {@link #setInterruptionFilter(int)}.
+ *
+ * <p>Only available if policy access is granted to this package. See
+ * {@link #isNotificationPolicyAccessGranted}.
*
* @param policy The new desired policy.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
setNotificationPolicy(policy, /* fromUser= */ false);
}
@@ -2786,11 +2804,17 @@
* The interruption filter defines which notifications are allowed to
* interrupt the user (e.g. via sound & vibration) and is applied
* globally.
- * <p>
- * Only available if policy access is granted to this package. See
+ *
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an {@link AutomaticZenRule}
+ * associated to the app, using a {@link ZenPolicy} that corresponds to the {@link Policy}
+ * supplied to {@link #setNotificationPolicy(Policy)} (or the global policy when one wasn't
+ * provided).
+ *
+ * <p> Only available if policy access is granted to this package. See
* {@link #isNotificationPolicyAccessGranted}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index fb1b17b..89199ca 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,7 @@
package android.app.assist;
+import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
+import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
import android.annotation.FlaggedApi;
@@ -20,14 +22,17 @@
import android.os.BadParcelableException;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PooledStringReader;
import android.os.PooledStringWriter;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.service.autofill.FillRequest;
import android.service.credentials.CredentialProviderService;
@@ -37,6 +42,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.View;
import android.view.View.AutofillImportance;
import android.view.ViewRootImpl;
@@ -652,6 +658,9 @@
@Nullable OutcomeReceiver<GetCredentialResponse, GetCredentialException>
mGetCredentialCallback;
+ @Nullable ResultReceiver mGetCredentialResultReceiver;
+
+
AutofillValue mAutofillValue;
CharSequence[] mAutofillOptions;
boolean mSanitized;
@@ -916,6 +925,7 @@
mExtras = in.readBundle();
}
mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR);
+ mGetCredentialResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
}
/**
@@ -1153,6 +1163,7 @@
out.writeBundle(mExtras);
}
out.writeTypedObject(mGetCredentialRequest, flags);
+ out.writeTypedObject(mGetCredentialResultReceiver, flags);
return flags;
}
@@ -1295,9 +1306,8 @@
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public OutcomeReceiver<GetCredentialResponse,
- GetCredentialException> getCredentialManagerCallback() {
- return mGetCredentialCallback;
+ public ResultReceiver getCredentialManagerCallback() {
+ return mGetCredentialResultReceiver;
}
/**
@@ -1894,6 +1904,7 @@
final AssistStructure mAssist;
final ViewNode mNode;
final boolean mAsync;
+ private Handler mHandler;
/**
* Used to instantiate a builder for a stand-alone {@link ViewNode} which is not associated
@@ -2271,6 +2282,56 @@
option.getCandidateQueryData()
.putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids);
}
+ setUpResultReceiver(callback);
+ }
+
+ private void setUpResultReceiver(
+ OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+ final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+ Slog.d(TAG, "onReceiveResult from Credential Manager");
+ GetCredentialResponse getCredentialResponse =
+ resultData.getParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ GetCredentialResponse.class);
+
+ callback.onResult(getCredentialResponse);
+ } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+ String[] exception = resultData.getStringArray(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
+ if (exception != null && exception.length >= 2) {
+ Slog.w(TAG, "Credman bottom sheet from pinned "
+ + "entry failed with: + " + exception[0] + " , "
+ + exception[1]);
+ callback.onError(new GetCredentialException(
+ exception[0], exception[1]));
+ }
+ } else {
+ Slog.d(TAG, "Unknown resultCode from credential "
+ + "manager bottom sheet: " + resultCode);
+ }
+ }
+ };
+ ResultReceiver ipcFriendlyResultReceiver =
+ toIpcFriendlyResultReceiver(resultReceiver);
+ mNode.mGetCredentialResultReceiver = ipcFriendlyResultReceiver;
+ }
+
+ private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
}
@Override
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 5107354..4a38c92 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -199,24 +199,13 @@
private long mBuilderFieldsSet = 0L;
- public Builder(
- int id,
- int type,
- int variant,
- @NonNull PersistableBundle featureParams) {
+ /**
+ * Provides a builder instance to create a feature for given id.
+ * @param id the unique identifier for the feature.
+ */
+ public Builder(int id) {
mId = id;
- mType = type;
- mVariant = variant;
- mFeatureParams = featureParams;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mFeatureParams);
- }
-
- public @NonNull Builder setId(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mId = value;
- return this;
+ mFeatureParams = new PersistableBundle();
}
public @NonNull Builder setName(@NonNull String value) {
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index 92f3513..f3cbd26 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -41,7 +41,7 @@
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public final class FeatureDetails implements Parcelable {
@Status
- private final int mStatus;
+ private final int mFeatureStatus;
@NonNull
private final PersistableBundle mFeatureDetailParams;
@@ -73,21 +73,21 @@
}
public FeatureDetails(
- @Status int status,
+ @Status int featureStatus,
@NonNull PersistableBundle featureDetailParams) {
- this.mStatus = status;
+ this.mFeatureStatus = featureStatus;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = featureDetailParams;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mFeatureDetailParams);
}
public FeatureDetails(
- @Status int status) {
- this.mStatus = status;
+ @Status int featureStatus) {
+ this.mFeatureStatus = featureStatus;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = new PersistableBundle();
}
@@ -95,8 +95,8 @@
/**
* Returns an integer value associated with the feature status.
*/
- public @Status int getStatus() {
- return mStatus;
+ public @Status int getFeatureStatus() {
+ return mFeatureStatus;
}
@@ -111,7 +111,7 @@
public String toString() {
return MessageFormat.format("FeatureDetails '{' status = {0}, "
+ "persistableBundle = {1} '}'",
- mStatus,
+ mFeatureStatus,
mFeatureDetailParams);
}
@@ -121,21 +121,21 @@
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
FeatureDetails that = (FeatureDetails) o;
- return mStatus == that.mStatus
+ return mFeatureStatus == that.mFeatureStatus
&& java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
}
@Override
public int hashCode() {
int _hash = 1;
- _hash = 31 * _hash + mStatus;
+ _hash = 31 * _hash + mFeatureStatus;
_hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
return _hash;
}
@Override
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- dest.writeInt(mStatus);
+ dest.writeInt(mFeatureStatus);
dest.writeTypedObject(mFeatureDetailParams, flags);
}
@@ -151,9 +151,9 @@
PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
PersistableBundle.CREATOR);
- this.mStatus = status;
+ this.mFeatureStatus = status;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = persistableBundle;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mFeatureDetailParams);
diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java
deleted file mode 100644
index e9fb5f2..0000000
--- a/core/java/android/app/ondeviceintelligence/FilePart.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-
-import android.annotation.NonNull;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Represents file data with an associated file descriptor sent to and received from remote
- * processing. The interface ensures that the underlying file-descriptor is always opened in
- * read-only mode.
- *
- * @hide
- */
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-@SystemApi
-public final class FilePart implements Parcelable {
- private final String mPartKey;
- private final PersistableBundle mPartParams;
- private final ParcelFileDescriptor mParcelFileDescriptor;
-
- private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull ParcelFileDescriptor parcelFileDescriptor) {
- Objects.requireNonNull(partKey);
- Objects.requireNonNull(partParams);
- this.mPartKey = partKey;
- this.mPartParams = partParams;
- this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor);
- }
-
- /**
- * Create a file part using a filePath and any additional params.
- */
- public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull String filePath)
- throws FileNotFoundException {
- this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open(
- new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY)));
- }
-
- /**
- * Create a file part using a file input stream and any additional params.
- * It is the caller's responsibility to close the stream. It is safe to do so as soon as this
- * call returns.
- */
- public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull FileInputStream fileInputStream)
- throws IOException {
- this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD()));
- }
-
- /**
- * Returns a FileInputStream for the associated File.
- * Caller must close the associated stream when done reading from it.
- *
- * @return the FileInputStream associated with the FilePart.
- */
- @NonNull
- public FileInputStream getFileInputStream() {
- return new FileInputStream(mParcelFileDescriptor.getFileDescriptor());
- }
-
- /**
- * Returns the unique key associated with the part. Each Part key added to a content object
- * should be ensured to be unique.
- */
- @NonNull
- public String getFilePartKey() {
- return mPartKey;
- }
-
- /**
- * Returns the params associated with Part.
- */
- @NonNull
- public PersistableBundle getFilePartParams() {
- return mPartParams;
- }
-
-
- @Override
- public int describeContents() {
- return CONTENTS_FILE_DESCRIPTOR;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString8(getFilePartKey());
- dest.writePersistableBundle(getFilePartParams());
- mParcelFileDescriptor.writeToParcel(dest, flags
- | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's
- // copy of the Pfd is closed as soon as the Binder call succeeds.
- }
-
- @NonNull
- public static final Creator<FilePart> CREATOR = new Creator<>() {
- @Override
- public FilePart createFromParcel(Parcel in) {
- return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR),
- in.readParcelable(
- getClass().getClassLoader(), ParcelFileDescriptor.class));
- }
-
- @Override
- public FilePart[] newArray(int size) {
- return new FilePart[size];
- }
- };
-}
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index b925f48..360a809 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -31,7 +31,7 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
- import android.app.ondeviceintelligence.ITokenCountCallback;
+ import android.app.ondeviceintelligence.ITokenInfoCallback;
/**
@@ -56,8 +56,8 @@
void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal signal,
- in ITokenCountCallback tokenCountcallback) = 6;
+ void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal,
+ in ITokenInfoCallback tokenInfocallback) = 6;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 9848e1d..0adf305 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -3,6 +3,7 @@
import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.RemoteCallback;
/**
* Interface for a IResponseCallback for receiving response from on-device intelligence service.
@@ -12,4 +13,5 @@
interface IResponseCallback {
void onSuccess(in Content result) = 1;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+ void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index a680574..132e53e 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -4,6 +4,7 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.RemoteCallback;
/**
@@ -15,4 +16,5 @@
void onNewContent(in Content result) = 1;
void onSuccess(in Content result) = 2;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+ void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
deleted file mode 100644
index b724e03..0000000
--- a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.app.ondeviceintelligence;
-
-import android.os.PersistableBundle;
-
-/**
- * Interface for receiving the token count of a request for a given features.
- *
- * @hide
- */
-interface ITokenCountCallback {
- void onSuccess(long tokenCount) = 1;
- void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
-}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
new file mode 100644
index 0000000..9219a89
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.app.ondeviceintelligence.TokenInfo;
+
+/**
+ * Interface for receiving the token info of a request for a given feature.
+ *
+ * @hide
+ */
+interface ITokenInfoCallback {
+ void onSuccess(in TokenInfo tokenInfo) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 4d8e0d5..d195c4d 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -26,8 +26,10 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -37,6 +39,8 @@
import androidx.annotation.IntDef;
+import com.android.internal.R;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -60,7 +64,16 @@
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public class OnDeviceIntelligenceManager {
+ /**
+ * @hide
+ */
public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+
+ /**
+ * @hide
+ */
+ public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
+ "AugmentRequestContentBundleKey";
private final Context mContext;
private final IOnDeviceIntelligenceManager mService;
@@ -82,8 +95,6 @@
public void getVersion(
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull LongConsumer versionConsumer) {
- // TODO explore modifying this method into getServicePackageDetails and return both
- // version and package name of the remote service implementing this.
try {
RemoteCallback callback = new RemoteCallback(result -> {
if (result == null) {
@@ -100,6 +111,23 @@
}
}
+
+ /**
+ * Get package name configured for providing the remote implementation for this system service.
+ */
+ @Nullable
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public String getRemoteServicePackageName() {
+ String serviceConfigValue = mContext.getResources().getString(
+ R.string.config_defaultOnDeviceSandboxedInferenceService);
+ ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
+ if (componentName != null) {
+ return componentName.getPackageName();
+ }
+
+ return null;
+ }
+
/**
* Asynchronously get feature for a given id.
*
@@ -273,29 +301,29 @@
}
/**
- * The methods computes the token-count for a given request payload using the provided Feature
- * details.
+ * The methods computes the token related information for a given request payload using the
+ * provided {@link Feature}.
*
* @param feature feature associated with the request.
* @param request request that contains the content data and associated params.
- * @param outcomeReceiver callback to populate the token count or exception in case of
+ * @param outcomeReceiver callback to populate the token info or exception in case of
* failure.
* @param cancellationSignal signal to invoke cancellation on the operation in the remote
* implementation.
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void requestTokenCount(@NonNull Feature feature, @NonNull Content request,
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Long,
+ @NonNull OutcomeReceiver<TokenInfo,
OnDeviceIntelligenceManagerException> outcomeReceiver) {
try {
- ITokenCountCallback callback = new ITokenCountCallback.Stub() {
+ ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
- public void onSuccess(long tokenCount) {
+ public void onSuccess(TokenInfo tokenInfo) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> outcomeReceiver.onResult(tokenCount)));
+ () -> outcomeReceiver.onResult(tokenInfo)));
}
@Override
@@ -314,7 +342,7 @@
cancellationSignal.setRemote(transport);
}
- mService.requestTokenCount(feature, request, transport, callback);
+ mService.requestTokenInfo(feature, request, transport, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -328,33 +356,31 @@
* was a
* failure.
*
- * @param feature feature associated with the request.
- * @param request request that contains the Content data and
- * associated params.
- * @param requestType type of request being sent for processing the content.
- * @param responseOutcomeReceiver callback to populate the response content and
- * associated
- * params.
- * @param processingSignal signal to invoke custom actions in the
- * remote implementation.
- * @param cancellationSignal signal to invoke cancellation or
- * @param callbackExecutor executor to run the callback on.
+ * @param feature feature associated with the request.
+ * @param request request that contains the Content data and
+ * associated params.
+ * @param requestType type of request being sent for processing the content.
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
+ * remote implementation.
+ * @param callbackExecutor executor to run the callback on.
+ * @param responseCallback callback to populate the response content and
+ * associated params.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequest(@NonNull Feature feature, @NonNull Content request,
+ public void processRequest(@NonNull Feature feature, @Nullable Content request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Content,
- OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) {
+ @NonNull ProcessingOutcomeReceiver responseCallback) {
try {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
public void onSuccess(Content result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result));
+ callbackExecutor.execute(() -> responseCallback.onResult(result));
});
}
@@ -362,12 +388,24 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseOutcomeReceiver.onError(
+ () -> responseCallback.onError(
new OnDeviceIntelligenceManagerProcessingException(
errorCode, errorMessage, errorParams))));
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> responseCallback.onDataAugmentRequest(content, result -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
+ callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
+ })));
+ }
};
+
IProcessingSignal transport = null;
if (processingSignal != null) {
transport = ProcessingSignal.createTransport();
@@ -389,46 +427,48 @@
}
/**
- * Variation of {@link #processRequest} that asynchronously processes a request in a streaming
+ * Variation of {@link #processRequest} that asynchronously processes a request in a
+ * streaming
* fashion, where new content is pushed to caller in chunks via the
- * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete,
- * the service should call {@link StreamingResponseReceiver#onResult} and can optionally
- * populate the complete {@link Response}'s Content as part of the callback when the final
- * {@link Response} contains an enhanced aggregation of the Contents already streamed.
+ * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
+ * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
+ * populate the complete the full response {@link Content} as part of the callback in cases
+ * when the final response contains an enhanced aggregation of the Contents already
+ * streamed.
*
* @param feature feature associated with the request.
* @param request request that contains the Content data and associated
* params.
* @param requestType type of request being sent for processing the content.
- * @param processingSignal signal to invoke other custom actions in the
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
* remote implementation.
- * @param cancellationSignal signal to invoke cancellation
- * @param streamingResponseReceiver streaming callback to populate the response content and
+ * @param streamingResponseCallback streaming callback to populate the response content and
* associated params.
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request,
+ public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) {
+ @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
try {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
public void onNewContent(Content result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseReceiver.onNewContent(result));
+ () -> streamingResponseCallback.onNewContent(result));
});
}
@Override
public void onSuccess(Content result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result));
+ callbackExecutor.execute(
+ () -> streamingResponseCallback.onResult(result));
});
}
@@ -437,11 +477,26 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseReceiver.onError(
+ () -> streamingResponseCallback.onError(
new OnDeviceIntelligenceManagerProcessingException(
errorCode, errorMessage, errorParams)));
});
}
+
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> streamingResponseCallback.onDataAugmentRequest(content,
+ contentResponse -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ contentResponse);
+ callbackExecutor.execute(
+ () -> contentCallback.sendResult(bundle));
+ })));
+ }
};
IProcessingSignal transport = null;
@@ -468,7 +523,8 @@
public static final int REQUEST_TYPE_INFERENCE = 0;
/**
- * Prepares the remote implementation environment for e.g.loading inference runtime etc.which
+ * Prepares the remote implementation environment for e.g.loading inference runtime etc
+ * .which
* are time consuming beforehand to remove overhead and allow quick processing of requests
* thereof.
*/
@@ -485,7 +541,8 @@
REQUEST_TYPE_PREPARE,
REQUEST_TYPE_EMBEDDINGS
}, open = true)
- @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
+ ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface RequestType {
}
@@ -501,17 +558,32 @@
*/
public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
+ /**
+ * Error code to be used for on device intelligence manager failures.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface OnDeviceIntelligenceManagerErrorCode {
+ }
+
private final int mErrorCode;
private final PersistableBundle errorParams;
- public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage,
+ public OnDeviceIntelligenceManagerException(
+ @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
@NonNull PersistableBundle errorParams) {
super(errorMessage);
this.mErrorCode = errorCode;
this.errorParams = errorParams;
}
- public OnDeviceIntelligenceManagerException(int errorCode,
+ public OnDeviceIntelligenceManagerException(
+ @OnDeviceIntelligenceManagerErrorCode int errorCode,
@NonNull PersistableBundle errorParams) {
this.mErrorCode = errorCode;
this.errorParams = errorParams;
@@ -573,11 +645,15 @@
/** Inference suspended so that higher-priority inference can run. */
public static final int PROCESSING_ERROR_SUSPENDED = 13;
- /** Underlying processing encountered an internal error, like a violated precondition. */
+ /**
+ * Underlying processing encountered an internal error, like a violated precondition
+ * .
+ */
public static final int PROCESSING_ERROR_INTERNAL = 14;
/**
- * The processing was not able to be passed on to the remote implementation, as the service
+ * The processing was not able to be passed on to the remote implementation, as the
+ * service
* was unavailable.
*/
public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
new file mode 100644
index 0000000..b0b6e19
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.OutcomeReceiver;
+
+import java.util.function.Consumer;
+
+/**
+ * Response Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial {@link Content} that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingOutcomeReceiver extends
+ OutcomeReceiver<Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+ /**
+ * Callback to be invoked in cases where the remote service needs to perform retrieval or
+ * transformation operations based on a partially processed request, in order to augment the
+ * final response, by using the additional context sent via this callback.
+ *
+ * @param content The content payload that should be used to augment ongoing request.
+ * @param contentConsumer The augmentation data that should be sent to remote
+ * service for further processing a request.
+ */
+ default void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentConsumer) {
+ contentConsumer.accept(null);
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
similarity index 71%
rename from core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
index ebcf61c..ac2b032 100644
--- a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
@@ -21,23 +21,19 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
/**
* Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in
- * chunks to provide a async processing behaviour to the caller.
+ * possibly in chunks to provide a async processing behaviour to the caller.
*
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamingResponseReceiver<R, T, E extends Throwable> extends
- OutcomeReceiver<R, E> {
+public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
/**
- * Callback to be invoked when a part of the response i.e. some {@link Content} is already
- * processed and
- * needs to be passed onto the caller.
+ * Callback that would be invoked when a part of the response i.e. some {@link Content} is
+ * already processed and needs to be passed onto the caller.
*/
- void onNewContent(@NonNull T content);
+ void onNewContent(@NonNull Content content);
}
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.aidl b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
new file mode 100644
index 0000000..2c19c1e
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+/**
+ * @hide
+ */
+parcelable TokenInfo;
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/core/java/android/app/ondeviceintelligence/TokenInfo.java
new file mode 100644
index 0000000..035cc4b
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * This class is used to provide a token count response for the
+ * {@link OnDeviceIntelligenceManager#requestTokenInfo} outcome receiver.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class TokenInfo implements Parcelable {
+ private final long mCount;
+ private final PersistableBundle mInfoParams;
+
+ /**
+ * Construct a token count using the count value and associated params.
+ */
+ public TokenInfo(long count, @NonNull PersistableBundle persistableBundle) {
+ this.mCount = count;
+ mInfoParams = persistableBundle;
+ }
+
+ /**
+ * Construct a token count using the count value.
+ */
+ public TokenInfo(long count) {
+ this.mCount = count;
+ this.mInfoParams = new PersistableBundle();
+ }
+
+ /**
+ * Returns the token count associated with a request payload.
+ */
+ public long getCount() {
+ return mCount;
+ }
+
+ /**
+ * Returns the params representing token info.
+ */
+ @NonNull
+ public PersistableBundle getInfoParams() {
+ return mInfoParams;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mCount);
+ dest.writePersistableBundle(mInfoParams);
+ }
+
+ public static final @NonNull Parcelable.Creator<TokenInfo> CREATOR
+ = new Parcelable.Creator<>() {
+ @Override
+ public TokenInfo[] newArray(int size) {
+ return new TokenInfo[size];
+ }
+
+ @Override
+ public TokenInfo createFromParcel(@NonNull Parcel in) {
+ return new TokenInfo(in.readLong(), in.readPersistableBundle());
+ }
+ };
+}
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eb82e1f..cda4d89 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1417,13 +1417,15 @@
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+ *
+ * @return true if the call was successful, false if it was rate-limited.
*/
@FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
- public void setWidgetPreview(@NonNull ComponentName provider,
+ public boolean setWidgetPreview(@NonNull ComponentName provider,
@AppWidgetProviderInfo.CategoryFlags int widgetCategories,
@NonNull RemoteViews preview) {
try {
- mService.setWidgetPreview(provider, widgetCategories, preview);
+ return mService.setWidgetPreview(provider, widgetCategories, preview);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 70d2c7a..c7e5d88 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5081,8 +5081,6 @@
* @see #getSystemService
* @see android.hardware.face.FaceManager
*/
- @FlaggedApi(android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
public static final String FACE_SERVICE = "face";
/**
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6bb9c33..41c1f17 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -697,8 +697,9 @@
public List<UserHandle> getProfiles() {
if (mUserManager.isManagedProfile()
|| (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
- && android.os.Flags.allowPrivateProfile()
- && mUserManager.isPrivateProfile())) {
+ && android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && mUserManager.isPrivateProfile())) {
// If it's a managed or private profile, only return the current profile.
final List result = new ArrayList(1);
result.add(android.os.Process.myUserHandle());
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ac80561..48a7cc9 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -184,3 +184,11 @@
description: "Enable Private Space telephony and SMS intent redirection to the main user"
bug: "325576602"
}
+
+flag {
+ name: "block_private_space_creation"
+ namespace: "profile_experiences"
+ description: "Allow blocking private space creation based on specific conditions"
+ bug: "290333800"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 4dcc517..a908456 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -163,14 +163,31 @@
}
/**
- * Update the URI relative filter groups for a package. All previously existing groups
- * will be cleared before the new groups will be applied.
+ * Update the URI relative filter groups for a package. The groups set using this API acts
+ * as an additional filtering layer during intent resolution. It does not replace any
+ * existing groups that have been added to the package's intent filters either using the
+ * {@link android.content.IntentFilter#addUriRelativeFilterGroup(UriRelativeFilterGroup)}
+ * API or defined in the manifest.
+ * <p>
+ * Groups can be indexed to any domain or can be indexed for all subdomains by prefixing the
+ * hostname with a wildcard (i.e. "*.example.com"). Priority will be first given to groups
+ * that are indexed to the specific subdomain of the intent's data URI followed by any groups
+ * indexed to wildcard subdomains. If the subdomain consists of more than one label, priority
+ * will decrease corresponding to the decreasing number of subdomain labels after the wildcard.
+ * For example "a.b.c.d" will match "*.b.c.d" before "*.c.d".
+ * <p>
+ * All previously existing groups set for a domain index using this API will be cleared when
+ * new groups are set.
*
* @param packageName The name of the package.
* @param domainToGroupsMap A map of domains to a list of {@link UriRelativeFilterGroup}s that
* should apply to them. Groups for each domain will replace any groups
- * provided for that domain in a prior call to this method. Groups will
+ * provided for that domain in a prior call to this method. To clear
+ * existing groups, set the list to null or a empty list. Groups will
* be evaluated in the order they are provided.
+ *
+ * @see UriRelativeFilterGroup
+ * @see android.content.IntentFilter
* @hide
*/
@SystemApi
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 08b9064..4cdaaddd 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -727,7 +727,7 @@
* Returns if camera privacy is enabled for a specific package.
*
* @param packageName The package to check
- * @return boolean sensor privacy state.
+ * @return boolean camera privacy state.
*
* @hide
*/
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 8165d44..3ba8be4 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -28,10 +28,3 @@
bug: "302735104"
}
-flag {
- name: "face_background_authentication"
- namespace: "biometrics_framework"
- description: "Feature flag for allowing face background authentication with USE_BACKGROUND_FACE_AUTHENTICATION."
- bug: "318584190"
-}
-
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 066c45f..210ce2b 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,23 +18,18 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.CryptoObject;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
@@ -42,9 +37,9 @@
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
@@ -54,21 +49,15 @@
import android.view.Surface;
import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* A class that coordinates access to the face authentication hardware.
- *
- * <p>Please use {@link BiometricPrompt} for face authentication unless the experience must be
- * customized for unique system-level utilities, like the lock screen or ambient background usage.
- *
* @hide
*/
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-@SystemApi
@SystemService(Context.FACE_SERVICE)
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
@@ -99,76 +88,81 @@
@Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
private CryptoObject mCryptoObject;
private Face mRemovalFace;
- private Executor mExecutor;
+ private Handler mHandler;
private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
@Override // binder call
public void onEnrollResult(Face face, int remaining) {
- mExecutor.execute(() -> sendEnrollResult(face, remaining));
+ mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mExecutor.execute(() -> sendAcquiredResult(acquireInfo, vendorCode));
+ mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
}
@Override // binder call
public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
- mExecutor.execute(() -> sendAuthenticatedSucceeded(face, userId, isStrongBiometric));
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
+ isStrongBiometric ? 1 : 0, face).sendToTarget();
}
@Override // binder call
public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mExecutor.execute(() -> sendFaceDetected(sensorId, userId, isStrongBiometric));
+ mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
+ .sendToTarget();
}
@Override // binder call
public void onAuthenticationFailed() {
- mExecutor.execute(() -> sendAuthenticatedFailed());
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mExecutor.execute(() -> sendErrorResult(error, vendorCode));
+ mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
}
@Override // binder call
public void onRemoved(Face face, int remaining) {
- mExecutor.execute(() -> {
- sendRemovedResult(face, remaining);
- if (remaining == 0) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
- UserHandle.USER_CURRENT);
- }
- });
+ mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+ if (remaining == 0) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
+ UserHandle.USER_CURRENT);
+ }
}
@Override
public void onFeatureSet(boolean success, int feature) {
- mExecutor.execute(() -> sendSetFeatureCompleted(success, feature));
+ mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
}
@Override
public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
- mExecutor.execute(() -> sendGetFeatureCompleted(success, features, featureState));
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = success;
+ args.arg2 = features;
+ args.arg3 = featureState;
+ mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
}
@Override
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mExecutor.execute(() -> sendChallengeGenerated(sensorId, userId, challenge));
+ mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
+ .sendToTarget();
}
@Override
public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
- mExecutor.execute(() -> sendAuthenticationFrame(frame));
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
}
@Override
public void onEnrollmentFrame(FaceEnrollFrame frame) {
- mExecutor.execute(() -> sendEnrollmentFrame(frame));
+ mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
}
};
@@ -181,7 +175,7 @@
if (mService == null) {
Slog.v(TAG, "FaceAuthenticationManagerService was null");
}
- mExecutor = context.getMainExecutor();
+ mHandler = new MyHandler(context);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -195,16 +189,18 @@
}
/**
- * Returns an {@link Executor} for the given {@link Handler} or the main {@link Executor} if
- * {@code handler} is {@code null}.
+ * Use the provided handler thread for events.
*/
- private @NonNull Executor createExecutorForHandlerIfNeeded(@Nullable Handler handler) {
- return handler != null ? new HandlerExecutor(handler) : mContext.getMainExecutor();
+ private void useHandler(Handler handler) {
+ if (handler != null) {
+ mHandler = new MyHandler(handler.getLooper());
+ } else if (mHandler.getLooper() != mContext.getMainLooper()) {
+ mHandler = new MyHandler(mContext.getMainLooper());
+ }
}
/**
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FaceAuthenticateOptions)}.
- * @hide
*/
@Deprecated
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -216,22 +212,17 @@
}
/**
- * Request authentication.
- *
- * <p>This call operates the face recognition hardware and starts capturing images.
+ * Request authentication. This call operates the face recognition hardware and starts capturing images.
* It terminates when
* {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
* {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
* which point the object is no longer valid. The operation can be canceled by using the
- * provided {@code cancel} object.
+ * provided cancel object.
*
- * @param crypto the cryptographic operations to use for authentication or {@code null} if
- * none required
- * @param cancel an object that can be used to cancel authentication or {@code null} if not
- * needed
+ * @param crypto object associated with the call or null if none required
+ * @param cancel an object that can be used to cancel authentication
* @param callback an object to receive authentication events
- * @param handler an optional handler to handle callback events or {@code null} to obtain main
- * {@link Executor} from {@link Context}
+ * @param handler an optional handler to handle callback events
* @param options additional options to customize this request
* @throws IllegalArgumentException if the crypto operation is not supported or is not backed
* by
@@ -244,14 +235,6 @@
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
@NonNull AuthenticationCallback callback, @Nullable Handler handler,
@NonNull FaceAuthenticateOptions options) {
- authenticate(crypto, cancel, callback, createExecutorForHandlerIfNeeded(handler),
- options, false /* allowBackgroundAuthentication */);
- }
-
- @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
- private void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
- @NonNull AuthenticationCallback callback, @NonNull Executor executor,
- @NonNull FaceAuthenticateOptions options, boolean allowBackgroundAuthentication) {
if (callback == null) {
throw new IllegalArgumentException("Must supply an authentication callback");
}
@@ -266,15 +249,13 @@
if (mService != null) {
try {
- mExecutor = executor;
+ useHandler(handler);
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
- final long authId = allowBackgroundAuthentication
- ? mService.authenticateInBackground(
- mToken, operationId, mServiceReceiver, options)
- : mService.authenticate(mToken, operationId, mServiceReceiver, options);
+ final long authId = mService.authenticate(
+ mToken, operationId, mServiceReceiver, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -292,67 +273,6 @@
}
/**
- * Request background face authentication.
- *
- * <p>This call operates the face recognition hardware and starts capturing images.
- * It terminates when
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded(
- * BiometricPrompt.AuthenticationResult)} is called, at which point the object is no longer
- * valid. The operation can be canceled by using the provided cancel object.
- *
- * <p>See {@link BiometricPrompt#authenticate} for more details. Please use
- * {@link BiometricPrompt} for face authentication unless the experience must be customized for
- * unique system-level utilities, like the lock screen or ambient background usage.
- *
- * @param executor the specified {@link Executor} to handle callback events; if {@code null},
- * the callback will be executed on the main {@link Executor}.
- * @param crypto the cryptographic operations to use for authentication or {@code null} if
- * none required.
- * @param cancel an object that can be used to cancel authentication or {@code null} if not
- * needed.
- * @param callback an object to receive authentication events.
- * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
- * by
- * <a href="{@docRoot}training/articles/keystore.html">Android
- * Keystore facility</a>.
- * @hide
- */
- @RequiresPermission(USE_BACKGROUND_FACE_AUTHENTICATION)
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
- public void authenticateInBackground(@Nullable Executor executor,
- @Nullable BiometricPrompt.CryptoObject crypto, @Nullable CancellationSignal cancel,
- @NonNull BiometricPrompt.AuthenticationCallback callback) {
- authenticate(crypto, cancel, new AuthenticationCallback() {
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- callback.onAuthenticationError(errorCode, errString);
- }
-
- @Override
- public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
- callback.onAuthenticationHelp(helpCode, helpString);
- }
-
- @Override
- public void onAuthenticationSucceeded(AuthenticationResult result) {
- callback.onAuthenticationSucceeded(
- new BiometricPrompt.AuthenticationResult(
- crypto,
- BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
- }
-
- @Override
- public void onAuthenticationFailed() {
- callback.onAuthenticationFailed();
- }
- }, executor == null ? mContext.getMainExecutor() : executor,
- new FaceAuthenticateOptions.Builder().build(),
- true /* allowBackgroundAuthentication */);
- }
-
- /**
* Uses the face hardware to detect for the presence of a face, without giving details about
* accept/reject/lockout.
* @hide
@@ -710,14 +630,12 @@
}
/**
- * Determine if there are enrolled {@link Face} templates.
+ * Determine if there is a face enrolled.
*
- * @return {@code true} if there are enrolled {@link Face} templates, {@code false} otherwise
+ * @return true if a face is enrolled, false otherwise
* @hide
*/
- @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledTemplates() {
return hasEnrolledTemplates(UserHandle.myUserId());
}
@@ -882,7 +800,7 @@
PowerManager.PARTIAL_WAKE_LOCK,
"faceLockoutResetCallback");
wakeLock.acquire();
- mExecutor.execute(() -> {
+ mHandler.post(() -> {
try {
callback.onLockoutReset(sensorId);
} finally {
@@ -1352,6 +1270,70 @@
}
}
+ private class MyHandler extends Handler {
+ private MyHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ private MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
+ switch (msg.what) {
+ case MSG_ENROLL_RESULT:
+ sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
+ break;
+ case MSG_ACQUIRED:
+ sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
+ break;
+ case MSG_AUTHENTICATION_SUCCEEDED:
+ sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+ msg.arg2 == 1 /* isStrongBiometric */);
+ break;
+ case MSG_AUTHENTICATION_FAILED:
+ sendAuthenticatedFailed();
+ break;
+ case MSG_ERROR:
+ sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
+ break;
+ case MSG_REMOVED:
+ sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
+ break;
+ case MSG_SET_FEATURE_COMPLETED:
+ sendSetFeatureCompleted((boolean) msg.obj /* success */,
+ msg.arg1 /* feature */);
+ break;
+ case MSG_GET_FEATURE_COMPLETED:
+ SomeArgs args = (SomeArgs) msg.obj;
+ sendGetFeatureCompleted((boolean) args.arg1 /* success */,
+ (int[]) args.arg2 /* features */,
+ (boolean[]) args.arg3 /* featureState */);
+ args.recycle();
+ break;
+ case MSG_CHALLENGE_GENERATED:
+ sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (long) msg.obj /* challenge */);
+ break;
+ case MSG_FACE_DETECTED:
+ sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (boolean) msg.obj /* isStrongBiometric */);
+ break;
+ case MSG_AUTHENTICATION_FRAME:
+ sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+ break;
+ case MSG_ENROLLMENT_FRAME:
+ sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ Trace.endSection();
+ }
+ }
+
private void sendSetFeatureCompleted(boolean success, int feature) {
if (mSetFeatureCallback == null) {
return;
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index b98c0cb..553d9f7 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -47,7 +47,7 @@
byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
// Retrieve static sensor properties for all face sensors
- @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
// Retrieve static sensor properties for the specified sensor
@@ -59,11 +59,6 @@
long authenticate(IBinder token, long operationId, IFaceServiceReceiver receiver,
in FaceAuthenticateOptions options);
- // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
- @EnforcePermission("USE_BACKGROUND_FACE_AUTHENTICATION")
- long authenticateInBackground(IBinder token, long operationId, IFaceServiceReceiver receiver,
- in FaceAuthenticateOptions options);
-
// Uses the face hardware to detect for the presence of a face, without giving details
// about accept/reject/lockout. A requestId is returned that can be used to cancel this
// operation.
@@ -138,7 +133,7 @@
void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge);
// Determine if a user has at least one enrolled face
- @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName);
// Return the LockoutTracker status for the specified user
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 800ba6d..23d6007 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -85,6 +85,7 @@
boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
+ boolean canAddPrivateProfile(int userId);
int getUserSerialNumber(int userId);
int getUserHandle(int userSerialNumber);
int getUserRestrictionSource(String restrictionKey, int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9757a10..fdaa0b4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2353,6 +2353,17 @@
public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7;
/**
+ * Indicates user operation failed because user is disabled on the device.
+ * @hide
+ */
+ public static final int USER_OPERATION_ERROR_DISABLED_USER = 8;
+ /**
+ * Indicates user operation failed because user is disabled on the device.
+ * @hide
+ */
+ public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9;
+
+ /**
* Result returned from various user operations.
*
* @hide
@@ -2366,7 +2377,9 @@
USER_OPERATION_ERROR_CURRENT_USER,
USER_OPERATION_ERROR_LOW_STORAGE,
USER_OPERATION_ERROR_MAX_USERS,
- USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS
+ USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS,
+ USER_OPERATION_ERROR_DISABLED_USER,
+ USER_OPERATION_ERROR_PRIVATE_PROFILE,
})
public @interface UserOperationResult {}
@@ -2563,6 +2576,17 @@
}
/**
+ * Returns whether the device supports Private Profile
+ * @hide
+ */
+ public static boolean isPrivateProfileEnabled() {
+ if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+ return !ActivityManager.isLowRamDeviceStatic();
+ }
+ return true;
+ }
+
+ /**
* Returns whether multiple admins are enabled on the device
* @hide
*/
@@ -3155,6 +3179,27 @@
}
/**
+ * Checks if it's possible to add a private profile to the context user
+ * @return whether the context user can add a private profile.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS},
+ conditional = true)
+ @UserHandleAware
+ public boolean canAddPrivateProfile() {
+ if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+ try {
+ return mService.canAddPrivateProfile(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns whether the context user has at least one restricted profile associated with it.
* @return whether the user has a restricted profile associated with it
* @hide
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index a14a2c7..555a120 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -70,6 +70,8 @@
private final boolean mDefaultKeyboardVibrationEnabled;
+ private final boolean mHasFixedKeyboardAmplitude;
+
/** @hide */
public VibrationConfig(@Nullable Resources resources) {
mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -87,6 +89,8 @@
com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
+ mHasFixedKeyboardAmplitude = loadFloat(resources,
+ com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude, -1) > 0;
mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -197,6 +201,14 @@
return mDefaultKeyboardVibrationEnabled;
}
+ /**
+ * Whether the device has a fixed amplitude for keyboard.
+ * @hide
+ */
+ public boolean hasFixedKeyboardAmplitude() {
+ return mHasFixedKeyboardAmplitude;
+ }
+
/** Get the default vibration intensity for given usage. */
@VibrationIntensity
public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 410f510..3c7692d 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1800,7 +1800,11 @@
*/
@SystemApi
@NonNull
- @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.GET_RUNTIME_PERMISSIONS
+ })
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
@NonNull String persistentDeviceId) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index eea6464..e26dc73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3583,6 +3583,12 @@
+ " and user:" + userHandle
+ " with index:" + index);
}
+ // Always make sure to close any pre-existing tracker before
+ // replacing it, to prevent memory leaks
+ var oldTracker = mGenerationTrackers.get(name);
+ if (oldTracker != null) {
+ oldTracker.destroy();
+ }
mGenerationTrackers.put(name, new GenerationTracker(name,
array, index, generation,
mGenerationTrackerErrorHandler));
@@ -3805,6 +3811,12 @@
+ " in package:" + cr.getPackageName()
+ " with index:" + index);
}
+ // Always make sure to close any pre-existing tracker before
+ // replacing it, to prevent memory leaks
+ var oldTracker = mGenerationTrackers.get(prefix);
+ if (oldTracker != null) {
+ oldTracker.destroy();
+ }
mGenerationTrackers.put(prefix,
new GenerationTracker(prefix, array, index, generation,
mGenerationTrackerErrorHandler));
@@ -19992,6 +20004,13 @@
@Readable
public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
"consistent_notification_blocking_enabled";
+
+ /**
+ * Whether the Auto Bedtime Mode experience is enabled.
+ *
+ * @hide
+ */
+ public static final String AUTO_BEDTIME_MODE = "auto_bedtime_mode";
}
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7658af5..bd9ab86 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -468,6 +469,7 @@
* object as well as its identifying information (tag and id) and source
* (package name).
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
@@ -481,6 +483,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications, including the newly posted one.
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
@@ -499,6 +502,7 @@
* and source (package name) used to post the {@link android.app.Notification} that
* was just removed.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn) {
// optional
}
@@ -520,6 +524,7 @@
* for active notifications.
*
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationRemoved(sbn);
}
@@ -541,6 +546,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
@NotificationCancelReason int reason) {
onNotificationRemoved(sbn, rankingMap);
@@ -552,6 +558,7 @@
*
* @hide
*/
+ @UiThread
@SystemApi
public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
@NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) {
@@ -563,6 +570,7 @@
* the notification manager. You are safe to call {@link #getActiveNotifications()}
* at this time.
*/
+ @UiThread
public void onListenerConnected() {
// optional
}
@@ -572,6 +580,7 @@
* notification manager.You will not receive any events after this call, and may only
* call {@link #requestRebind(ComponentName)} at this time.
*/
+ @UiThread
public void onListenerDisconnected() {
// optional
}
@@ -582,6 +591,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRankingUpdate(RankingMap rankingMap) {
// optional
}
@@ -592,6 +602,7 @@
*
* @param hints The current {@link #getCurrentListenerHints() listener hints}.
*/
+ @UiThread
public void onListenerHintsChanged(int hints) {
// optional
}
@@ -603,6 +614,7 @@
* @param hideSilentStatusIcons whether or not status bar icons should be hidden for silent
* notifications
*/
+ @UiThread
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
// optional
}
@@ -620,6 +632,7 @@
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelModified(String pkg, UserHandle user,
NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -638,6 +651,7 @@
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelGroupModified(String pkg, UserHandle user,
NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -650,6 +664,7 @@
* @param interruptionFilter The current
* {@link #getCurrentInterruptionFilter() interruption filter}.
*/
+ @UiThread
public void onInterruptionFilterChanged(int interruptionFilter) {
// optional
}
@@ -1197,6 +1212,11 @@
* <p>
* Listen for updates using {@link #onInterruptionFilterChanged(int)}.
*
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an
+ * {@link android.app.AutomaticZenRule} associated to the app.
+ *
* <p>The service should wait for the {@link #onListenerConnected()} event
* before performing this operation.
*
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index e44c69c..6dbff71 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -36,11 +36,13 @@
*/
oneway interface IOnDeviceIntelligenceService {
void getVersion(in RemoteCallback remoteCallback);
- void getFeature(in int featureId, in IFeatureCallback featureCallback);
- void listFeatures(in IListFeaturesCallback listFeaturesCallback);
- void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+ void getFeature(int callerUid, int featureId, in IFeatureCallback featureCallback);
+ void listFeatures(int callerUid, in IListFeaturesCallback listFeaturesCallback);
+ void getFeatureDetails(int callerUid, in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
- void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+ void requestFeatureDownload(int callerUid, in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
void registerRemoteServices(in IRemoteProcessingService remoteProcessingService);
+ void notifyInferenceServiceConnected();
+ void notifyInferenceServiceDisconnected();
}
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
similarity index 74%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
rename to core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index e3fda04..73257ed 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -18,7 +18,7 @@
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.Feature;
@@ -29,18 +29,18 @@
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
/**
- * Interface for a concrete implementation to provide on device trusted inference.
+ * Interface for a concrete implementation to provide on-device sandboxed inference.
*
* @hide
*/
-oneway interface IOnDeviceTrustedInferenceService {
+oneway interface IOnDeviceSandboxedInferenceService {
void registerRemoteStorageService(in IRemoteStorageService storageService);
- void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
- in ITokenCountCallback tokenCountCallback);
- void processRequest(in Feature feature, in Content request, in int requestType,
+ void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ in ITokenInfoCallback tokenInfoCallback);
+ void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IResponseCallback callback);
- void processRequestStreaming(in Feature feature, in Content request, in int requestType,
+ void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IStreamingResponseCallback callback);
void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 46ba25d..fce3689 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -65,7 +65,7 @@
/**
* Abstract base class for performing setup for on-device inference and providing file access to
- * the isolated counter part {@link OnDeviceTrustedInferenceService}.
+ * the isolated counter part {@link OnDeviceSandboxedInferenceService}.
*
* <p> A service that provides configuration and model files relevant to performing inference on
* device. The system's default OnDeviceIntelligenceService implementation is configured in
@@ -110,6 +110,8 @@
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ // TODO(326052028) : Move the remote method calls to an app handler from the binder
+ // thread.
return new IOnDeviceIntelligenceService.Stub() {
/** {@inheritDoc} */
@Override
@@ -123,38 +125,40 @@
}
@Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback) {
+ public void listFeatures(int callerUid,
+ IListFeaturesCallback listFeaturesCallback) {
Objects.requireNonNull(listFeaturesCallback);
- OnDeviceIntelligenceService.this.onListFeatures(
+ OnDeviceIntelligenceService.this.onListFeatures(callerUid,
wrapListFeaturesCallback(listFeaturesCallback));
}
@Override
- public void getFeature(int id, IFeatureCallback featureCallback) {
+ public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
Objects.requireNonNull(featureCallback);
- OnDeviceIntelligenceService.this.onGetFeature(id,
- wrapFeatureCallback(featureCallback));
+ OnDeviceIntelligenceService.this.onGetFeature(callerUid,
+ id, wrapFeatureCallback(featureCallback));
}
@Override
- public void getFeatureDetails(Feature feature,
+ public void getFeatureDetails(int callerUid, Feature feature,
IFeatureDetailsCallback featureDetailsCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(featureDetailsCallback);
- OnDeviceIntelligenceService.this.onGetFeatureDetails(feature,
- wrapFeatureDetailsCallback(featureDetailsCallback));
+ OnDeviceIntelligenceService.this.onGetFeatureDetails(callerUid,
+ feature, wrapFeatureDetailsCallback(featureDetailsCallback));
}
@Override
- public void requestFeatureDownload(Feature feature,
+ public void requestFeatureDownload(int callerUid, Feature feature,
ICancellationSignal cancellationSignal,
IDownloadCallback downloadCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(downloadCallback);
- OnDeviceIntelligenceService.this.onDownloadFeature(feature,
+ OnDeviceIntelligenceService.this.onDownloadFeature(callerUid,
+ feature,
CancellationSignal.fromTransport(cancellationSignal),
wrapDownloadCallback(downloadCallback));
}
@@ -188,12 +192,38 @@
IRemoteProcessingService remoteProcessingService) {
mRemoteProcessingService = remoteProcessingService;
}
+
+ @Override
+ public void notifyInferenceServiceConnected() {
+ OnDeviceIntelligenceService.this.onInferenceServiceConnected();
+ }
+
+ @Override
+ public void notifyInferenceServiceDisconnected() {
+ OnDeviceIntelligenceService.this.onInferenceServiceDisconnected();
+ }
};
}
Slog.w(TAG, "Incorrect service interface, returning null.");
return null;
}
+
+ /**
+ * Invoked when a new instance of the remote inference service is created.
+ * This method should be used as a signal to perform any initialization operations, for e.g. by
+ * invoking the {@link #updateProcessingState} method to initialize the remote processing
+ * service.
+ */
+ public abstract void onInferenceServiceConnected();
+
+
+ /**
+ * Invoked when an instance of the remote inference service is disconnected.
+ */
+ public abstract void onInferenceServiceDisconnected();
+
+
/**
* Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
* service if there is a state change to be performed.
@@ -391,6 +421,7 @@
* Request download for feature that is requested and listen to download progress updates. If
* the download completes successfully, success callback should be populated.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature the feature for which files need to be downlaoded.
* process.
* @param cancellationSignal signal to attach a listener to, and receive cancellation signals
@@ -398,7 +429,7 @@
* @param downloadCallback callback to populate download updates for clients to listen on..
*/
public abstract void onDownloadFeature(
- @NonNull Feature feature,
+ int callerUid, @NonNull Feature feature,
@Nullable CancellationSignal cancellationSignal,
@NonNull DownloadCallback downloadCallback);
@@ -407,20 +438,22 @@
* implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
* details the client is looking for.
*
- * @param feature the feature for which status needs to be known.
- * @param featureStatusCallback callback to populate the resulting feature status.
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature the feature for which status needs to be known.
+ * @param featureDetailsCallback callback to populate the resulting feature status.
*/
- public abstract void onGetFeatureDetails(@NonNull Feature feature,
+ public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback);
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
/**
* Get feature using the provided identifier to the remote implementation.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param featureCallback callback to populate the features list.
*/
- public abstract void onGetFeature(int featureId,
+ public abstract void onGetFeature(int callerUid, int featureId,
@NonNull OutcomeReceiver<Feature,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
@@ -428,9 +461,10 @@
* List all features which are available in the remote implementation. The implementation might
* choose to provide only a certain list of features based on the caller.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param listFeaturesCallback callback to populate the features list.
*/
- public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>,
+ public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
/**
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
similarity index 73%
rename from core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
rename to core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 8600197..7f7f9c2 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -16,6 +16,7 @@
package android.service.ondeviceintelligence;
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
import android.annotation.CallbackExecutor;
@@ -23,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.ondeviceintelligence.Content;
@@ -30,14 +32,18 @@
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.StreamingResponseReceiver;
+import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.TokenInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -75,8 +81,8 @@
*
* <pre>
* {@literal
- * <service android:name=".SampleTrustedInferenceService"
- * android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE"
+ * <service android:name=".SampleSandboxedInferenceService"
+ * android:permission="android.permission.BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"
* android:isolatedProcess="true">
* </service>}
* </pre>
@@ -85,18 +91,18 @@
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public abstract class OnDeviceTrustedInferenceService extends Service {
- private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName();
+public abstract class OnDeviceSandboxedInferenceService extends Service {
+ private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
/**
* The {@link Intent} that must be declared as handled by the service. To be supported, the
* service must also require the
- * {@link android.Manifest.permission#BIND_ON_DEVICE_TRUSTED_INFERENCE_SERVICE}
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
* permission so that other applications can not abuse it.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
- "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
private IRemoteStorageService mRemoteStorageService;
@@ -107,7 +113,7 @@
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return new IOnDeviceTrustedInferenceService.Stub() {
+ return new IOnDeviceSandboxedInferenceService.Stub() {
@Override
public void registerRemoteStorageService(IRemoteStorageService storageService) {
Objects.requireNonNull(storageService);
@@ -115,50 +121,48 @@
}
@Override
- public void requestTokenCount(Feature feature, Content request,
+ public void requestTokenInfo(int callerUid, Feature feature, Content request,
ICancellationSignal cancellationSignal,
- ITokenCountCallback tokenCountCallback) {
+ ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(tokenCountCallback);
- OnDeviceTrustedInferenceService.this.onCountTokens(feature,
+ Objects.requireNonNull(tokenInfoCallback);
+ OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(callerUid,
+ feature,
request,
CancellationSignal.fromTransport(cancellationSignal),
- wrapTokenCountCallback(tokenCountCallback));
+ wrapTokenInfoCallback(tokenInfoCallback));
}
@Override
- public void processRequestStreaming(Feature feature, Content request,
+ public void processRequestStreaming(int callerUid, Feature feature, Content request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IStreamingResponseCallback callback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(callback);
- OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature,
+ OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(callerUid,
+ feature,
request,
requestType,
CancellationSignal.fromTransport(cancellationSignal),
ProcessingSignal.fromTransport(processingSignal),
- wrapStreamingResponseCallback(callback)
- );
+ wrapStreamingResponseCallback(callback));
}
@Override
- public void processRequest(Feature feature, Content request,
+ public void processRequest(int callerUid, Feature feature, Content request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IResponseCallback callback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(callback);
-
- OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request,
- requestType, CancellationSignal.fromTransport(cancellationSignal),
+ OnDeviceSandboxedInferenceService.this.onProcessRequest(callerUid, feature,
+ request, requestType,
+ CancellationSignal.fromTransport(cancellationSignal),
ProcessingSignal.fromTransport(processingSignal),
- wrapResponseCallback(callback)
- );
+ wrapResponseCallback(callback));
}
@Override
@@ -167,7 +171,7 @@
Objects.requireNonNull(processingState);
Objects.requireNonNull(callback);
- OnDeviceTrustedInferenceService.this.onUpdateProcessingState(processingState,
+ OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(processingState,
wrapOutcomeReceiver(callback)
);
}
@@ -178,35 +182,37 @@
}
/**
- * Invoked when caller wants to obtain a count of number of tokens present in the passed in
- * Request associated with the provided feature.
+ * Invoked when caller wants to obtain token info related to the payload in the passed
+ * content, associated with the provided feature.
* The expectation from the implementation is that when processing is complete, it
- * should provide the token count in the {@link OutcomeReceiver#onResult}.
+ * should provide the token info in the {@link OutcomeReceiver#onResult}.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param cancellationSignal Cancellation Signal to receive cancellation events from client and
* configure a listener to.
- * @param callback callback to populate failure and full response for the provided
+ * @param callback callback to populate failure or the token info for the provided
* request.
*/
@NonNull
- public abstract void onCountTokens(
- @NonNull Feature feature,
+ public abstract void onTokenInfoRequest(
+ int callerUid, @NonNull Feature feature,
@NonNull Content request,
@Nullable CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<Long,
+ @NonNull OutcomeReceiver<TokenInfo,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in a
* streaming manner. The expectation from the implementation is that when processing the
* request,
- * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously
+ * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
* provide partial Content results for the caller to utilize. Optionally the implementation can
- * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon
+ * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
* processing completion.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param requestType identifier representing the type of request.
@@ -218,13 +224,12 @@
*/
@NonNull
public abstract void onProcessRequestStreaming(
- @NonNull Feature feature,
- @NonNull Content request,
+ int callerUid, @NonNull Feature feature,
+ @Nullable Content request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull StreamedProcessingOutcomeReceiver callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -233,6 +238,7 @@
* should
* provide the complete response in the {@link OutcomeReceiver#onResult}.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param requestType identifier representing the type of request.
@@ -244,13 +250,12 @@
*/
@NonNull
public abstract void onProcessRequest(
- @NonNull Feature feature,
- @NonNull Content request,
+ int callerUid, @NonNull Feature feature,
+ @Nullable Content request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull ProcessingOutcomeReceiver callback);
/**
@@ -335,6 +340,26 @@
}
}
+
+ /**
+ * Returns the {@link Executor} to use for incoming IPC from request sender into your service
+ * implementation. For e.g. see
+ * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+ * Consumer)} where we use the executor to populate the consumer.
+ * <p>
+ * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
+ * provide the executor you want to use for incoming IPC.
+ *
+ * @return the {@link Executor} to use for incoming IPC from {@link OnDeviceIntelligenceManager}
+ * to {@link OnDeviceSandboxedInferenceService}.
+ */
+ @SuppressLint("OnNameExpected")
+ @NonNull
+ public Executor getCallbackExecutor() {
+ return new HandlerExecutor(Handler.createAsync(getMainLooper()));
+ }
+
+
private RemoteCallback wrapResultReceiverAsReadOnly(
@NonNull Consumer<Map<String, FileInputStream>> resultConsumer,
@NonNull Executor executor) {
@@ -355,10 +380,9 @@
});
}
- private OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback(
+ private ProcessingOutcomeReceiver wrapResponseCallback(
IResponseCallback callback) {
- return new OutcomeReceiver<>() {
+ return new ProcessingOutcomeReceiver() {
@Override
public void onResult(@androidx.annotation.NonNull Content response) {
try {
@@ -378,13 +402,23 @@
Slog.e(TAG, "Error sending result: " + e);
}
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
};
}
- private StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback(
+ private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
IStreamingResponseCallback callback) {
- return new StreamingResponseReceiver<>() {
+ return new StreamedProcessingOutcomeReceiver() {
@Override
public void onNewContent(@androidx.annotation.NonNull Content content) {
try {
@@ -413,17 +447,43 @@
Slog.e(TAG, "Error sending result: " + e);
}
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
};
}
- private OutcomeReceiver<Long,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback(
- ITokenCountCallback tokenCountCallback) {
+ private RemoteCallback wrapRemoteCallback(
+ @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+ return new RemoteCallback(
+ result -> {
+ if (result != null) {
+ getCallbackExecutor().execute(() -> contentCallback.accept(
+ result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ Content.class)));
+ } else {
+ getCallbackExecutor().execute(
+ () -> contentCallback.accept(null));
+ }
+ });
+ }
+
+ private OutcomeReceiver<TokenInfo,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
- public void onResult(Long tokenCount) {
+ public void onResult(TokenInfo tokenInfo) {
try {
- tokenCountCallback.onSuccess(tokenCount);
+ tokenInfoCallback.onSuccess(tokenInfo);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -433,7 +493,7 @@
public void onError(
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
try {
- tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
} catch (RemoteException e) {
Slog.e(TAG, "Error sending failure: " + e);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 67a3978..0ae3e59 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1775,9 +1775,9 @@
@CriticalNative
private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
@CriticalNative
- private static native float nativeGetXOffset(long nativePtr);
+ private static native float nativeGetRawXOffset(long nativePtr);
@CriticalNative
- private static native float nativeGetYOffset(long nativePtr);
+ private static native float nativeGetRawYOffset(long nativePtr);
@CriticalNative
private static native float nativeGetXPrecision(long nativePtr);
@CriticalNative
@@ -3745,7 +3745,7 @@
nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
- nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+ nativeGetRawXOffset(mNativePtr), nativeGetRawYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr),
nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d29963c..a6f380d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -33760,7 +33760,7 @@
@FlaggedApi(FLAG_VIEW_VELOCITY_API)
public float getFrameContentVelocity() {
if (viewVelocityApi()) {
- return mFrameContentVelocity;
+ return (mFrameContentVelocity < 0f) ? 0f : mFrameContentVelocity;
}
return 0;
}
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 131fca7..1efd375 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -315,7 +315,8 @@
/**
* Add to this view's child count. This increases the current child count by
* <var>num</var> children beyond what was last set by {@link #setChildCount}
- * or {@link #addChildCount}. The index at which the new child starts in the child
+ * or {@link #addChildCount}. The index at which the new
+ * child starts in the child
* array is returned.
*
* @param num The number of new children to add.
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index fe4ac4e..a2d8d80 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -4299,7 +4299,7 @@
}
if (mode == MODE_NORMAL) {
- mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel);
+ mApplication = parcel.readTypedObject(ApplicationInfo.CREATOR);
mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
mLayoutId = parcel.readInt();
mViewId = parcel.readInt();
@@ -6805,7 +6805,7 @@
mBitmapCache.writeBitmapsToParcel(dest, flags);
mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
- mApplication.writeToParcel(dest, flags);
+ dest.writeTypedObject(mApplication, flags);
if (mIsRoot || mIdealSize == null) {
dest.writeInt(0);
} else {
@@ -6893,7 +6893,8 @@
* @hide
*/
public boolean hasSameAppInfo(ApplicationInfo info) {
- return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+ return mApplication == null || mApplication.packageName.equals(info.packageName)
+ && mApplication.uid == info.uid;
}
/**
@@ -7672,8 +7673,7 @@
byte[] instruction;
final List<byte[]> instructions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
- instruction = new byte[in.readInt()];
- in.readByteArray(instruction);
+ instruction = in.readBlob();
instructions.add(instruction);
}
return new DrawInstructions(instructions);
@@ -7688,8 +7688,7 @@
final List<byte[]> instructions = drawInstructions.mInstructions;
dest.writeInt(instructions.size());
for (byte[] instruction : instructions) {
- dest.writeInt(instruction.length);
- dest.writeByteArray(instruction);
+ dest.writeBlob(instruction);
}
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7dcbbea..78f06b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -2655,7 +2655,8 @@
private boolean privateSpaceEnabled() {
return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+ && android.multiuser.Flags.allowResolverSheetForPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
/**
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
index 93fe37c..360fcaf 100644
--- a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -75,7 +75,8 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!(android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.showSetScreenLockDialog())) {
+ && android.multiuser.Flags.showSetScreenLockDialog()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures())) {
finish();
return;
}
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 4ef0a1b..97f8084 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -77,6 +77,7 @@
}
if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& !userManager.isManagedProfile(mUserId)) {
Log.e(TAG, "Unlaunchable activity for target package " + targetPackageName
+ " called for a non-managed-profile " + mUserId);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 85bdbb9..e55cdef 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,11 +80,10 @@
in Bundle extras, in IntentSender resultIntent);
boolean isRequestPinAppWidgetSupported();
oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
- void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+ boolean setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
in RemoteViews preview);
@nullable RemoteViews getWidgetPreview(in String callingPackage,
in ComponentName providerComponent, in int profileId, in int widgetCategory);
void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
-
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index bd806bf..91678c7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -560,6 +560,19 @@
*/
public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
+
+ /*
+ * (long) The reset interval for generated preview API calls.
+ */
+ public static final String GENERATED_PREVIEW_API_RESET_INTERVAL_MS =
+ "generated_preview_api_reset_interval_ms";
+
+ /*
+ * (int) The max number of generated preview API calls per reset interval.
+ */
+ public static final String GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL =
+ "generated_preview_api_max_calls_per_interval";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 30de546..2096ba4 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -55,37 +55,38 @@
* A service for the ProtoLog logging system.
*/
public class LegacyProtoLogImpl implements IProtoLog {
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
-
private static final int BUFFER_CAPACITY = 1024 * 1024;
private static final int PER_CHUNK_SIZE = 1024;
private static final String TAG = "ProtoLog";
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
static final String PROTOLOG_VERSION = "2.0.0";
- private static final int DEFAULT_PER_CHUNK_SIZE = 0;
private final File mLogFile;
private final String mLegacyViewerConfigFilename;
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
private final int mPerChunkSize;
private boolean mProtoLogEnabled;
private boolean mProtoLogEnabledLockFree;
private final Object mProtoLogEnabledLock = new Object();
- public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename) {
+ public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
+ TreeMap<String, IProtoLogGroup> logGroups) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
- LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize) {
+ LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
+ TreeMap<String, IProtoLogGroup> logGroups) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
+ this.mLogGroups = logGroups;
}
/**
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 53062d8..4ead82f 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -22,9 +22,9 @@
import static perfetto.protos.PerfettoTrace.InternedString.STR;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.BOOLEAN_PARAMS;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.DOUBLE_PARAMS;
-import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.MESSAGE_ID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.SINT64_PARAMS;
+import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STR_PARAM_IIDS;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.GROUPS;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.Group.ID;
@@ -70,6 +70,8 @@
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData;
@@ -78,7 +80,6 @@
* A service for the ProtoLog logging system.
*/
public class PerfettoProtoLogImpl implements IProtoLog {
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
private static final String LOG_TAG = "ProtoLog";
private final AtomicInteger mTracingInstances = new AtomicInteger();
@@ -89,8 +90,12 @@
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
- public PerfettoProtoLogImpl(String viewerConfigFilePath) {
+ private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
+
+ public PerfettoProtoLogImpl(String viewerConfigFilePath,
+ TreeMap<String, IProtoLogGroup> logGroups) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -98,23 +103,28 @@
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- });
+ }, logGroups);
}
- public PerfettoProtoLogImpl(ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+ public PerfettoProtoLogImpl(
+ ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ TreeMap<String, IProtoLogGroup> logGroups
+ ) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups);
}
@VisibleForTesting
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- ProtoLogViewerConfigReader viewerConfigReader
+ ProtoLogViewerConfigReader viewerConfigReader,
+ TreeMap<String, IProtoLogGroup> logGroups
) {
Producer.init(InitArguments.DEFAULTS);
mDataSource.register(DataSourceParams.DEFAULTS);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
+ this.mLogGroups = logGroups;
}
/**
@@ -128,7 +138,8 @@
long tsNanos = SystemClock.elapsedRealtimeNanos();
try {
- logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos);
+ mBackgroundLoggingService.submit(() ->
+ logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos));
if (group.isLogToLogcat()) {
logToLogcat(group.getTag(), level, messageHash, messageString, args);
}
@@ -456,40 +467,6 @@
}
/**
- * Responds to a shell command.
- */
- public int onShellCommand(ShellCommand shell) {
- PrintWriter pw = shell.getOutPrintWriter();
- String cmd = shell.getNextArg();
- if (cmd == null) {
- return unknownCommand(pw);
- }
- ArrayList<String> args = new ArrayList<>();
- String arg;
- while ((arg = shell.getNextArg()) != null) {
- args.add(arg);
- }
- final ILogger logger = (msg) -> logAndPrintln(pw, msg);
- String[] groups = args.toArray(new String[args.size()]);
- switch (cmd) {
- case "enable-text":
- return this.startLoggingToLogcat(groups, logger);
- case "disable-text":
- return this.stopLoggingToLogcat(groups, logger);
- default:
- return unknownCommand(pw);
- }
- }
-
- private int unknownCommand(PrintWriter pw) {
- pw.println("Unknown command");
- pw.println("Window manager logging options:");
- pw.println(" enable-text [group...]: Enable logcat logging for given groups");
- pw.println(" disable-text [group...]: Disable logcat logging for given groups");
- return -1;
- }
-
- /**
* Returns {@code true} iff logging to proto is enabled.
*/
public boolean isProtoEnabled() {
@@ -548,6 +525,49 @@
return 0;
}
+ /**
+ * Responds to a shell command.
+ */
+ public int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArg();
+ if (cmd == null) {
+ return unknownCommand(pw);
+ }
+ ArrayList<String> args = new ArrayList<>();
+ String arg;
+ while ((arg = shell.getNextArg()) != null) {
+ args.add(arg);
+ }
+ final ILogger logger = (msg) -> logAndPrintln(pw, msg);
+ String[] groups = args.toArray(new String[0]);
+ switch (cmd) {
+ case "start", "stop" -> {
+ pw.println("Command not supported. "
+ + "Please start and stop ProtoLog tracing with Perfetto.");
+ return -1;
+ }
+ case "enable-text" -> {
+ mViewerConfigReader.loadViewerConfig(logger);
+ return setTextLogging(true, logger, groups);
+ }
+ case "disable-text" -> {
+ return setTextLogging(false, logger, groups);
+ }
+ default -> {
+ return unknownCommand(pw);
+ }
+ }
+ }
+
+ private int unknownCommand(PrintWriter pw) {
+ pw.println("Unknown command");
+ pw.println("Window manager logging options:");
+ pw.println(" enable-text [group...]: Enable logcat logging for given groups");
+ pw.println(" disable-text [group...]: Disable logcat logging for given groups");
+ return -1;
+ }
+
static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Slog.i(LOG_TAG, msg);
if (pw != null) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 8965385..487ae814 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -18,6 +18,7 @@
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
+import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LOG_GROUPS;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
import android.annotation.Nullable;
@@ -28,6 +29,8 @@
import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLogToolInjected;
+import java.util.TreeMap;
+
/**
* A service for the ProtoLog logging system.
*/
@@ -43,6 +46,9 @@
@ProtoLogToolInjected(LEGACY_OUTPUT_FILE_PATH)
private static String sLegacyOutputFilePath;
+ @ProtoLogToolInjected(LOG_GROUPS)
+ private static TreeMap<String, IProtoLogGroup> sLogGroups;
+
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
@Nullable String messageString,
@@ -99,11 +105,10 @@
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance =
- new PerfettoProtoLogImpl(sViewerConfigPath);
+ sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sLogGroups);
} else {
- sServiceInstance =
- new LegacyProtoLogImpl(sLegacyOutputFilePath, sLegacyViewerConfigPath);
+ sServiceInstance = new LegacyProtoLogImpl(
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups);
}
}
return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 3c206ac..ae3d448 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -4,6 +4,7 @@
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import android.util.ArrayMap;
import android.util.proto.ProtoInputStream;
import com.android.internal.protolog.common.ILogger;
@@ -57,6 +58,7 @@
}
private void doLoadViewerConfig() throws IOException {
+ mLogMessageMap = new ArrayMap<>();
final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
index ffd0d76..17c82d7 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -22,7 +22,9 @@
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ProtoLogToolInjected {
- enum Value { VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH }
+ enum Value {
+ VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH, LOG_GROUPS
+ }
Value value();
}
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 23adb8f7..1a86363 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -411,8 +411,8 @@
jniThrowNullPointerException(env, "pointerCoords");
return;
}
- pointerCoordsToNative(env, pointerCoordsObj,
- event->getXOffset(), event->getYOffset(), &rawPointerCoords[i]);
+ pointerCoordsToNative(env, pointerCoordsObj, event->getRawXOffset(), event->getRawYOffset(),
+ &rawPointerCoords[i]);
env->DeleteLocalRef(pointerCoordsObj);
}
@@ -735,14 +735,14 @@
return event->offsetLocation(deltaX, deltaY);
}
-static jfloat android_view_MotionEvent_nativeGetXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getXOffset();
+ return event->getRawXOffset();
}
-static jfloat android_view_MotionEvent_nativeGetYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getYOffset();
+ return event->getRawYOffset();
}
static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
@@ -871,8 +871,8 @@
{"nativeGetClassification", "(J)I",
(void*)android_view_MotionEvent_nativeGetClassification},
{"nativeOffsetLocation", "(JFF)V", (void*)android_view_MotionEvent_nativeOffsetLocation},
- {"nativeGetXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetXOffset},
- {"nativeGetYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetYOffset},
+ {"nativeGetRawXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawXOffset},
+ {"nativeGetRawYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawYOffset},
{"nativeGetXPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetXPrecision},
{"nativeGetYPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetYPrecision},
{"nativeGetXCursorPosition", "(J)F",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 52bad21..1acdc75 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6772,13 +6772,6 @@
<permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
android:protectionLevel="signature" />
- <!-- Allows privileged apps to access the background face authentication.
- @SystemApi
- @FlaggedApi("android.hardware.biometrics.face_background_authentication")
- @hide -->
- <permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
android:protectionLevel="signature" />
@@ -7955,12 +7948,12 @@
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows an app to bind the on-device trusted service.
+ <!-- @SystemApi Allows an app to bind the on-device sandboxed service.
<p>Protection level: signature|privileged
@hide
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
-->
- <permission android:name="android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE"
+ <permission android:name="android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE"
android:protectionLevel="signature"/>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index df8158d..6e804c0 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -146,7 +146,7 @@
<item name="windowAnimationStyle">@style/Animation.InputMethod</item>
<item name="imeFullscreenBackground">?colorBackground</item>
<item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
- <item name="windowSwipeToDismiss">false</item>
+ <item name="windowSwipeToDismiss">true</item>
</style>
<!-- DeviceDefault theme for dialog windows and activities. In contrast to Material, the
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4f20fce..9d902c9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4673,8 +4673,8 @@
<!-- The component name for the default system on-device intelligence service, -->
<string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string>
- <!-- The component name for the default system on-device trusted inference service. -->
- <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string>
+ <!-- The component name for the default system on-device sandboxed inference service. -->
+ <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
wearable sensing. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59066eb..238772f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6434,4 +6434,6 @@
<string name="satellite_notification_open_message">Open Messages</string>
<!-- Invoke Satellite setting activity of Settings -->
<string name="satellite_notification_how_it_works">How it works</string>
+ <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
+ <string name="unarchival_session_app_label">Pending...</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 150951f..4b71654 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3916,7 +3916,7 @@
<java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
<java-symbol type="string" name="config_defaultWearableSensingService" />
<java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
- <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" />
+ <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
@@ -5373,4 +5373,6 @@
<java-symbol type="string" name="config_defaultContextualSearchKey" />
<java-symbol type="string" name="config_defaultContextualSearchEnabled" />
<java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
+
+ <java-symbol type="string" name="unarchival_session_app_label" />
</resources>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 15bb66b..8d9fad9 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -90,7 +90,7 @@
private static final long TEST_HD_FREQUENCY_VALUE = 95_300;
private static final long TEST_HD_STATION_ID_EXT_VALUE = 0x100000001L
| (TEST_HD_FREQUENCY_VALUE << 36);
- private static final long TEST_HD_LOCATION_VALUE = 0x89CC8E06CCB9ECL;
+ private static final long TEST_HD_LOCATION_VALUE = 0x4E647007665CF6L;
private static final long TEST_VENDOR_ID_VALUE = 9_901;
private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 37a499a..6cc5485 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -110,8 +110,6 @@
Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
Paths.get("/data/misc/wmtrace/wm_log.winscope"),
- Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"),
- Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
};
private Handler mHandler;
@@ -257,6 +255,38 @@
assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
}
+ @LargeTest
+ @Test
+ public void noPreDumpData_then_fullWithUsePreDumpFlag_ignoresFlag() throws Exception {
+ startPreDumpedUiTraces();
+
+ mBrm.preDumpUiData();
+ waitTillDumpstateExitedOrTimeout();
+
+ // Simulate lost of pre-dumped data.
+ // For example it can happen in this scenario:
+ // 1. Pre-dump data
+ // 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK)
+ // 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK)
+ removeFilesIfNeeded(UI_TRACES_PREDUMPED);
+
+ // Start bugreport with "use predump" flag. Because the pre-dumped data is not available
+ // the flag will be ignored and data will be dumped as in normal flow.
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ }
+
@Test
public void simultaneousBugreportsNotAllowed() throws Exception {
// Start bugreport #1
@@ -506,9 +536,6 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
"cmd window tracing start"
);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "service call SurfaceFlinger 1025 i32 1"
- );
}
private static void stopPreDumpedUiTraces() {
@@ -611,6 +638,14 @@
return files;
}
+ private static void removeFilesIfNeeded(Path[] paths) throws Exception {
+ for (Path path : paths) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "rm -f " + path.toString()
+ );
+ }
+ }
+
private static ParcelFileDescriptor parcelFd(File file) throws Exception {
return ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 34f5841..3a872b5 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -18,7 +18,6 @@
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
import static com.google.common.truth.Truth.assertThat;
@@ -36,15 +35,12 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
-import android.hardware.biometrics.BiometricPrompt;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import com.android.internal.R;
@@ -62,7 +58,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -83,8 +78,6 @@
@Mock
private FaceManager.AuthenticationCallback mAuthCallback;
@Mock
- private BiometricPrompt.AuthenticationCallback mBioAuthCallback;
- @Mock
private FaceManager.EnrollmentCallback mEnrollmentCallback;
@Mock
private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@@ -98,16 +91,13 @@
private TestLooper mLooper;
private Handler mHandler;
private FaceManager mFaceManager;
- private Executor mExecutor;
@Before
public void setUp() throws Exception {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- mExecutor = new HandlerExecutor(mHandler);
when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
- when(mContext.getMainExecutor()).thenReturn(mExecutor);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -169,19 +159,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- public void authenticateInBackground_errorWhenUnavailable() throws Exception {
- when(mService.authenticateInBackground(any(), anyLong(), any(), any()))
- .thenThrow(new RemoteException());
-
- mFaceManager.authenticateInBackground(mExecutor, null, new CancellationSignal(),
- mBioAuthCallback);
- mLooper.dispatchAll();
-
- verify(mBioAuthCallback).onAuthenticationError(eq(FACE_ERROR_HW_UNAVAILABLE), any());
- }
-
- @Test
public void enrollment_errorWhenFaceEnrollmentExists() throws RemoteException {
when(mResources.getInteger(R.integer.config_faceMaxTemplatesPerUser)).thenReturn(1);
when(mService.getEnrolledFaces(anyInt(), anyInt(), anyString()))
diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java
index 128c54b..d437f7b 100644
--- a/core/tests/coretests/src/android/view/ViewVelocityTest.java
+++ b/core/tests/coretests/src/android/view/ViewVelocityTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static junit.framework.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -23,6 +25,7 @@
import android.app.Activity;
import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.SmallTest;
@@ -64,6 +67,7 @@
@UiThreadTest
@Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
public void frameRateChangesWhenContentMoves() {
mMovingView.offsetLeftAndRight(100);
float frameRate = mViewRoot.getPreferredFrameRate();
@@ -72,11 +76,13 @@
@UiThreadTest
@Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
public void firstFrameNoMovement() {
assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
}
@Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
public void touchBoostDisable() throws Throwable {
mActivityRule.runOnUiThread(() -> {
long now = SystemClock.uptimeMillis();
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b209c7c..cb8754a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -1162,7 +1162,8 @@
@Test
public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markPrivateProfileUserAvailable();
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> privateResolvedComponentInfos =
@@ -1183,7 +1184,8 @@
@Test
public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
ResolverActivity.ENABLE_TABBED_VIEW = false;
markPrivateProfileUserAvailable();
markWorkProfileUserAvailable();
@@ -1205,7 +1207,8 @@
@Test
public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markPrivateProfileUserAvailable();
markWorkProfileUserAvailable();
Intent sendIntent = createSendImageIntent();
@@ -1228,7 +1231,8 @@
@Test
public void testPrivateProfile_triggerFromWorkProfile(){
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markPrivateProfileUserAvailable();
markWorkProfileUserAvailable();
Intent sendIntent = createSendImageIntent();
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 051e73f..0f12438 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -455,7 +455,7 @@
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
<permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
- <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
+ <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
<!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index b6ce9b6..f9ac02a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -789,10 +789,8 @@
* @param m The 4x4 matrix to preconcatenate with the current matrix
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public void concat44(@Nullable Matrix44 m) {
- if (m != null) {
- nConcat(mNativeCanvasWrapper, m.mBackingArray);
- }
+ public void concat(@Nullable Matrix44 m) {
+ if (m != null) nConcat(mNativeCanvasWrapper, m.mBackingArray);
}
/**
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index 7cc0eb7..a99e201 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.graphics.hwui.flags.Flags;
@@ -98,11 +99,11 @@
/**
* Gets the value at the matrix's row and column.
*
- * @param row An integer from 0 to 4 indicating the row of the value to get
- * @param col An integer from 0 to 4 indicating the column of the value to get
+ * @param row An integer from 0 to 3 indicating the row of the value to get
+ * @param col An integer from 0 to 3 indicating the column of the value to get
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public float get(int row, int col) {
+ public float get(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
return mBackingArray[row * 4 + col];
}
@@ -112,12 +113,13 @@
/**
* Sets the value at the matrix's row and column to the provided value.
*
- * @param row An integer from 0 to 4 indicating the row of the value to change
- * @param col An integer from 0 to 4 indicating the column of the value to change
+ * @param row An integer from 0 to 3 indicating the row of the value to change
+ * @param col An integer from 0 to 3 indicating the column of the value to change
* @param val The value the element at the specified index will be set to
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public void set(int row, int col, float val) {
+ public void set(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col,
+ float val) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
mBackingArray[row * 4 + col] = val;
} else {
diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
index 9482645..294b1f0 100644
--- a/libs/WindowManager/Shell/res/drawable/circular_progress.xml
+++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
@@ -25,7 +25,7 @@
<shape
android:shape="ring"
android:thickness="3dp"
- android:innerRadius="17dp"
+ android:innerRadius="14dp"
android:useLevel="true">
</shape>
</rotate>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
index 02b7075..e5fe1b54 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -15,12 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="48dp"
+ android:width="24dp"
+ android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
- android:fillColor="@android:color/white"
- android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" />
+ android:fillColor="@android:color/black"
+ android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/>
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
deleted file mode 100644
index 7c4f499..0000000
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
+++ /dev/null
@@ -1,25 +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.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="20dp"
- android:height="20dp"
- android:viewportWidth="20"
- android:viewportHeight="20">
- <path
- android:pathData="M15.701,14.583L18.567,17.5L17.425,18.733L14.525,15.833L12.442,17.917V12.5H17.917L15.701,14.583ZM15.833,5.833H17.5V7.5H15.833V5.833ZM17.5,4.167H15.833V2.567C16.75,2.567 17.5,3.333 17.5,4.167ZM12.5,2.5H14.167V4.167H12.5V2.5ZM15.833,9.167H17.5V10.833H15.833V9.167ZM7.5,17.5H5.833V15.833H7.5V17.5ZM4.167,7.5H2.5V5.833H4.167V7.5ZM4.167,2.567V4.167H2.5C2.5,3.333 3.333,2.567 4.167,2.567ZM4.167,14.167H2.5V12.5H4.167V14.167ZM7.5,4.167H5.833V2.5H7.5V4.167ZM10.833,4.167H9.167V2.5H10.833V4.167ZM10.833,17.5H9.167V15.833H10.833V17.5ZM4.167,10.833H2.5V9.167H4.167V10.833ZM4.167,17.567C3.25,17.567 2.5,16.667 2.5,15.833H4.167V17.567Z"
- android:fillColor="#1C1C14"/>
-</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index a5605a7..fa18e2b 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -80,11 +80,14 @@
<com.android.wm.shell.windowdecor.MaximizeButtonView
android:id="@+id/maximize_button_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="44dp"
+ android:layout_height="40dp"
android:layout_gravity="end"
+ android:layout_marginHorizontal="8dp"
+ android:paddingHorizontal="5dp"
+ android:paddingVertical="3dp"
android:clickable="true"
- android:focusable="true" />
+ android:focusable="true"/>
<ImageButton
android:id="@+id/close_window"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index c6f85a0..fca2fe4 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -134,15 +134,6 @@
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
-
- <Button
- android:id="@+id/select_button"
- android:contentDescription="@string/select_text"
- android:text="@string/select_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
- android:drawableTint="?androidprv:attr/materialColorOnSurface"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
</LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index e0057fe..296c8956 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -20,16 +20,16 @@
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:progressDrawable="@drawable/circular_progress"
- android:layout_width="40dp"
- android:layout_height="40dp"
+ android:layout_width="34dp"
+ android:layout_height="34dp"
android:indeterminate="false"
android:visibility="invisible"/>
<ImageButton
android:id="@+id/maximize_window"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:padding="9dp"
+ android:layout_width="34dp"
+ android:layout_height="34dp"
+ android:padding="5dp"
android:contentDescription="@string/maximize_button_text"
android:tint="?androidprv:attr/materialColorOnSurface"
android:background="?android:selectableItemBackgroundBorderless"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4d47ca9..139cde2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -983,7 +983,13 @@
// cache current min/max size
Point minSize = mPipBoundsState.getMinSize();
Point maxSize = mPipBoundsState.getMaxSize();
- mPipBoundsState.updateMinMaxSize(pictureInPictureParams.getAspectRatioFloat());
+ final float aspectRatioFloat;
+ if (pictureInPictureParams.hasSetAspectRatio()) {
+ aspectRatioFloat = pictureInPictureParams.getAspectRatioFloat();
+ } else {
+ aspectRatioFloat = mPipBoundsAlgorithm.getDefaultAspectRatio();
+ }
+ mPipBoundsState.updateMinMaxSize(aspectRatioFloat);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
// restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f4ccd68..500e6f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -442,12 +442,6 @@
mDesktopTasksController.requestSplit(decoration.mTaskInfo);
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
- } else if (id == R.id.select_button) {
- if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
- // TODO(b/278084491): dev option to enable display switching
- // remove when select is implemented
- mDesktopTasksController.moveToNextDisplay(mTaskId);
- }
} else if (id == R.id.maximize_window) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
decoration.closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index b37dd0d..3d0dd31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -35,7 +35,6 @@
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -53,6 +52,7 @@
*/
class HandleMenu {
private static final String TAG = "HandleMenu";
+ private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
private final Context mContext;
private final WindowDecoration mParentDecor;
private WindowDecoration.AdditionalWindow mHandleMenuWindow;
@@ -185,11 +185,9 @@
* Set up interactive elements & height of handle menu's more actions pill
*/
private void setupMoreActionsPill(View handleMenu) {
- final Button selectBtn = handleMenu.findViewById(R.id.select_button);
- selectBtn.setOnClickListener(mOnClickListener);
- final Button screenshotBtn = handleMenu.findViewById(R.id.screenshot_button);
- // TODO: Remove once implemented.
- screenshotBtn.setVisibility(View.GONE);
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
+ }
}
/**
@@ -305,12 +303,15 @@
* Determines handle menu height based on if windowing pill should be shown.
*/
private int getHandleMenuHeight(Resources resources) {
- int menuHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_height);
+ int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
if (!mShouldShowWindowingPill) {
menuHeight -= loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_windowing_pill_height);
}
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+ }
return menuHeight;
}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
new file mode 100644
index 0000000..4b008a7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
@@ -0,0 +1,51 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_libs_androidfw_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+cc_fuzz {
+ name: "resxmlparser_fuzzer",
+ srcs: [
+ "resxmlparser_fuzzer.cpp",
+ ],
+ host_supported: true,
+
+ static_libs: ["libgmock"],
+ target: {
+ android: {
+ shared_libs: [
+ "libandroidfw",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libandroidfw",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+ },
+ darwin: {
+ // libbinder is not supported on mac
+ enabled: false,
+ },
+ },
+
+ include_dirs: [
+ "system/incremental_delivery/incfs/util/include/",
+ ],
+
+ corpus: ["testdata/*"],
+ dictionary: "xmlparser_fuzzer.dict",
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
new file mode 100644
index 0000000..829a396
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <cstdint>
+#include <cstddef>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "androidfw/ResourceTypes.h"
+
+static void populateDynamicRefTableWithFuzzedData(
+ android::DynamicRefTable& table,
+ FuzzedDataProvider& fuzzedDataProvider) {
+
+ const size_t numMappings = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 5);
+ for (size_t i = 0; i < numMappings; ++i) {
+ const uint8_t packageId = fuzzedDataProvider.ConsumeIntegralInRange<uint8_t>(0x02, 0x7F);
+
+ // Generate a package name
+ std::string packageName;
+ size_t packageNameLength = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 128);
+ for (size_t j = 0; j < packageNameLength; ++j) {
+ // Consume characters only in the ASCII range (0x20 to 0x7E) to ensure valid UTF-8
+ char ch = fuzzedDataProvider.ConsumeIntegralInRange<char>(0x20, 0x7E);
+ packageName.push_back(ch);
+ }
+
+ // Convert std::string to String16 for compatibility
+ android::String16 androidPackageName(packageName.c_str(), packageName.length());
+
+ // Add the mapping to the table
+ table.addMapping(androidPackageName, packageId);
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ FuzzedDataProvider fuzzedDataProvider(data, size);
+
+ auto dynamic_ref_table = std::make_shared<android::DynamicRefTable>();
+
+ // Populate the DynamicRefTable with fuzzed data
+ populateDynamicRefTableWithFuzzedData(*dynamic_ref_table, fuzzedDataProvider);
+
+ auto tree = android::ResXMLTree(std::move(dynamic_ref_table));
+
+ std::vector<uint8_t> xmlData = fuzzedDataProvider.ConsumeRemainingBytes<uint8_t>();
+ if (tree.setTo(xmlData.data(), xmlData.size()) != android::NO_ERROR) {
+ return 0; // Exit early if unable to parse XML data
+ }
+
+ tree.restart();
+
+ size_t len = 0;
+ auto code = tree.next();
+ if (code == android::ResXMLParser::START_TAG) {
+ // Access element name
+ auto name = tree.getElementName(&len);
+
+ // Access attributes of the current element
+ for (size_t i = 0; i < tree.getAttributeCount(); i++) {
+ // Access attribute name
+ auto attrName = tree.getAttributeName(i, &len);
+ }
+ } else if (code == android::ResXMLParser::TEXT) {
+ const auto text = tree.getText(&len);
+ }
+ return 0; // Non-zero return values are reserved for future use.
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
new file mode 100644
index 0000000..417fec7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child id="1">
+ <subchild type="A">Content A</subchild>
+ <subchild type="B">Content B</subchild>
+ </child>
+ <child id="2" extra="data">
+ <subchild type="C">Content C</subchild>
+ </child>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
new file mode 100644
index 0000000..7e13db5
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child1>Value 1</child1>
+ <child2>Value 2</child2>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
new file mode 100644
index 0000000..90cdf35
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <!-- Example with special characters and CDATA -->
+ <data><![CDATA[Some <encoded> data & other "special" characters]]></data>
+ <message>Hello & Welcome!</message>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
new file mode 100644
index 0000000..745ded4
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
@@ -0,0 +1,11 @@
+root_tag=<root>
+child_tag=<child>
+end_child_tag=</child>
+id_attr=id="
+type_attr=type="
+cdata_start=<![CDATA[
+cdata_end=]]>
+ampersand_entity=&
+xml_header=<?xml version="1.0" encoding="UTF-8"?>
+comment_start=<!--
+comment_end= -->
diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.java b/location/java/android/location/provider/ForwardGeocodeRequest.java
index 8f227b1..89d14fb 100644
--- a/location/java/android/location/provider/ForwardGeocodeRequest.java
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.java
@@ -260,7 +260,11 @@
mCallingAttributionTag = null;
}
- /** Sets the attribution tag. */
+ /**
+ * Sets the attribution tag.
+ *
+ * @param attributionTag The attribution tag to associate with the request.
+ */
@NonNull
public Builder setCallingAttributionTag(@NonNull String attributionTag) {
mCallingAttributionTag = attributionTag;
diff --git a/location/java/android/location/provider/ReverseGeocodeRequest.java b/location/java/android/location/provider/ReverseGeocodeRequest.java
index 57c9047..2107707 100644
--- a/location/java/android/location/provider/ReverseGeocodeRequest.java
+++ b/location/java/android/location/provider/ReverseGeocodeRequest.java
@@ -207,7 +207,11 @@
mCallingAttributionTag = null;
}
- /** Sets the attribution tag. */
+ /**
+ * Sets the attribution tag.
+ *
+ * @param attributionTag The attribution tag to associate with the request.
+ */
@NonNull
public Builder setCallingAttributionTag(@NonNull String attributionTag) {
mCallingAttributionTag = attributionTag;
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 53699bc..0a22314 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -124,11 +124,11 @@
}
float AMotionEvent_getXOffset(const AInputEvent* motion_event) {
- return static_cast<const MotionEvent*>(motion_event)->getXOffset();
+ return static_cast<const MotionEvent*>(motion_event)->getRawXOffset();
}
float AMotionEvent_getYOffset(const AInputEvent* motion_event) {
- return static_cast<const MotionEvent*>(motion_event)->getYOffset();
+ return static_cast<const MotionEvent*>(motion_event)->getRawYOffset();
}
float AMotionEvent_getXPrecision(const AInputEvent* motion_event) {
diff --git a/nfc/Android.bp b/nfc/Android.bp
index b6bc40d..7dd16ba 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -38,6 +38,7 @@
name: "framework-nfc",
libs: [
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+ "framework-permission-s",
],
static_libs: [
"android.nfc.flags-aconfig-java",
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index c0db089..54f1421 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -194,13 +194,13 @@
package android.nfc.cardemulation {
public final class CardEmulation {
- method public boolean categoryAllowsForegroundPreference(String);
+ method @Deprecated public boolean categoryAllowsForegroundPreference(String);
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
- method public int getSelectionModeForCategory(String);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+ method @Deprecated public int getSelectionModeForCategory(String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index ea58504..e55f540 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -27,6 +27,7 @@
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -278,13 +279,22 @@
* @param category The category, e.g. {@link #CATEGORY_PAYMENT}
* @return whether AIDs in the category can be handled by a service
* specified by the foreground app.
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will be done in a AID
+ * category agnostic manner.
*/
@SuppressWarnings("NonUserGetterCalled")
+ @Deprecated
public boolean categoryAllowsForegroundPreference(String category) {
+ Context contextAsUser = mContext.createContextAsUser(
+ UserHandle.of(UserHandle.myUserId()), 0);
+ RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class);
+ if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+ return true;
+ }
if (CATEGORY_PAYMENT.equals(category)) {
boolean preferForeground = false;
- Context contextAsUser = mContext.createContextAsUser(
- UserHandle.of(UserHandle.myUserId()), 0);
try {
preferForeground = Settings.Secure.getInt(
contextAsUser.getContentResolver(),
@@ -309,7 +319,12 @@
* to pick a service if there is a conflict.
* @param category The category, for example {@link #CATEGORY_PAYMENT}
* @return the selection mode for the passed in category
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will be done in a AID
+ * category agnostic manner.
*/
+ @Deprecated
public int getSelectionModeForCategory(String category) {
if (CATEGORY_PAYMENT.equals(category)) {
boolean paymentRegistered = false;
@@ -792,8 +807,15 @@
* (e.g. eSE/eSE1, eSE2, etc.).
* 2. "OffHost" if the payment service does not specify secure element
* name.
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+ * A payment service will be selected automatically based on registered AIDs. In the case of
+ * multiple services that register for the same payment AID, the selection will be done on
+ * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Deprecated
@Nullable
public String getRouteDestinationForPreferredPaymentService() {
try {
@@ -836,8 +858,15 @@
* Returns a user-visible description of the preferred payment service.
*
* @return the preferred payment service description
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+ * A payment service will be selected automatically based on registered AIDs. In the case of
+ * multiple services that register for the same payment AID, the selection will be done on
+ * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Deprecated
@Nullable
public CharSequence getDescriptionForPreferredPaymentService() {
try {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 4e1f4ee..d7a2e36 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -21,14 +21,13 @@
import android.content.Context
import android.credentials.CredentialManager
import android.credentials.GetCredentialRequest
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialException
import android.credentials.GetCandidateCredentialsResponse
import android.credentials.GetCandidateCredentialsException
import android.credentials.CredentialOption
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.credentials.selection.ProviderData
+import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
@@ -123,13 +122,10 @@
// TODO(b/324635774): Use callback for validating. If the request is coming
// directly from the view, there should be a corresponding callback, otherwise
// we should fail fast,
- val getCredCallback = getCredManCallback(structure)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
return
- } else if (getCredCallback == null) {
- Log.i(TAG, "No credential manager callback found")
}
val credentialManager: CredentialManager =
getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
@@ -353,6 +349,7 @@
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
.setTitle(displayName)
+ icon.setTintBlendMode(BlendMode.DST)
sliceBuilder.setStartIcon(icon)
if (primaryEntry.credentialType ==
CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
@@ -526,42 +523,6 @@
TODO("Not yet implemented")
}
- private fun getCredManCallback(structure: AssistStructure): OutcomeReceiver<
- GetCredentialResponse, GetCredentialException>? {
- return traverseStructureForCallback(structure)
- }
-
- private fun traverseStructureForCallback(
- structure: AssistStructure
- ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
- val windowNodes: List<AssistStructure.WindowNode> =
- structure.run {
- (0 until windowNodeCount).map { getWindowNodeAt(it) }
- }
-
- windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
- return traverseNodeForCallback(windowNode.rootViewNode)
- }
- return null
- }
-
- private fun traverseNodeForCallback(
- viewNode: AssistStructure.ViewNode
- ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
- val children: List<AssistStructure.ViewNode> =
- viewNode.run {
- (0 until childCount).map { getChildAt(it) }
- }
-
- children.forEach { childNode: AssistStructure.ViewNode ->
- if (childNode.isFocused() && childNode.credentialManagerCallback != null) {
- return childNode.credentialManagerCallback
- }
- return traverseNodeForCallback(childNode)
- }
- return null
- }
-
private fun getCredManRequest(
structure: AssistStructure,
sessionId: Int,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index a7b5c36..e43b09e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -39,7 +39,6 @@
import com.android.credentialmanager.ui.theme.EntryShape
import kotlinx.coroutines.launch
-
/** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@@ -73,7 +72,7 @@
dragHandle = null,
// Never take over the full screen. We always want to leave some top scrim space
// for exiting and viewing the underlying app to help a user gain context.
- modifier = Modifier.padding(top = 56.dp),
+ modifier = Modifier.padding(top = 72.dp),
)
} else {
val scope = rememberCoroutineScope()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index c68ae8b..006a2d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.common.ui
+import android.credentials.flags.Flags
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets
@@ -63,7 +64,7 @@
modifier = Modifier.padding(
start = 24.dp,
end = 24.dp,
- bottom = 18.dp,
+ bottom = if (Flags.selectorUiImprovementsEnabled()) 8.dp else 18.dp,
top = if (topAppBar == null) 24.dp else 0.dp
).fillMaxWidth().wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
index 3ebdd20..ff421bc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
@@ -21,63 +21,20 @@
import android.content.Context
import android.util.Size
import android.widget.inline.InlinePresentationSpec
-import androidx.autofill.inline.common.TextViewStyle
-import androidx.autofill.inline.common.ViewStyle
-import androidx.autofill.inline.UiVersions
-import androidx.autofill.inline.UiVersions.Style
-import androidx.autofill.inline.v1.InlineSuggestionUi
-import androidx.core.content.ContextCompat
-import android.util.TypedValue
-import android.graphics.Typeface
-
class InlinePresentationsFactory {
companion object {
- private const val googleSansMediumFontFamily = "google-sans-medium"
- private const val googleSansTextFontFamily = "google-sans-text"
- // There is no min width required for now but this is needed for the spec builder
- private const val minInlineWidth = 5000
+ // There is no max width required for now but this is needed for the spec builder
+ private const val maxInlineWidth = 5000
fun modifyInlinePresentationSpec(context: Context,
originalSpec: InlinePresentationSpec): InlinePresentationSpec {
return InlinePresentationSpec.Builder(Size(originalSpec.minSize.width, originalSpec
.minSize.height),
- Size(minInlineWidth, originalSpec
+ Size(maxInlineWidth, originalSpec
.maxSize.height))
- .setStyle(UiVersions.newStylesBuilder().addStyle(getStyle(context)).build())
- .build()
- }
-
-
- fun getStyle(context: Context): Style {
- val textColorPrimary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_primary)
- val textColorSecondary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_secondary)
- val textColorBackground = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.inline_background)
- val chipHorizontalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.horizontal_chip_padding)
- val chipVerticalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.vertical_chip_padding)
- return InlineSuggestionUi.newStyleBuilder()
- .setChipStyle(
- ViewStyle.Builder().setPadding(chipHorizontalPadding,
- chipVerticalPadding,
- chipHorizontalPadding, chipVerticalPadding).build()
- )
- .setTitleStyle(
- TextViewStyle.Builder().setTextColor(textColorPrimary).setTextSize
- (TypedValue.COMPLEX_UNIT_DIP, 14F)
- .setTypeface(googleSansMediumFontFamily,
- Typeface.NORMAL).setBackgroundColor(textColorBackground)
- .build()
- )
- .setSubtitleStyle(TextViewStyle.Builder().setTextColor(textColorSecondary)
- .setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12F).setTypeface
- (googleSansTextFontFamily, Typeface.NORMAL).setBackgroundColor
- (textColorBackground).build())
+ .setStyle(originalSpec.getStyle())
.build()
}
}
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 98a5a67..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -54,6 +54,7 @@
"androidx.lifecycle_lifecycle-extensions",
"android.content.pm.flags-aconfig-java",
"android.os.flags-aconfig-java",
+ "android.multiuser.flags-aconfig-java",
],
lint: {
@@ -85,6 +86,7 @@
"androidx.lifecycle_lifecycle-extensions",
"android.content.pm.flags-aconfig-java",
"android.os.flags-aconfig-java",
+ "android.multiuser.flags-aconfig-java",
],
aaptflags: ["--product tablet"],
@@ -118,6 +120,7 @@
"androidx.lifecycle_lifecycle-extensions",
"android.content.pm.flags-aconfig-java",
"android.os.flags-aconfig-java",
+ "android.multiuser.flags-aconfig-java",
],
aaptflags: ["--product tv"],
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 221ca4f..8f5d07c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -166,6 +166,7 @@
messageBuilder.append(getString(
R.string.uninstall_application_text_current_user_clone_profile));
} else if (Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& customUserManager.isPrivateProfile()
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
messageBuilder.append(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
index 0fc1845..c6b6d36 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -235,7 +235,9 @@
messageString = context.getString(
R.string.uninstall_application_text_current_user_clone_profile
)
- } else if (Flags.allowPrivateProfile() && customUserManager!!.isPrivateProfile()) {
+ } else if (Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && customUserManager!!.isPrivateProfile()) {
// TODO(b/324244123): Get these Strings from a User Property API.
messageString = context.getString(
R.string.uninstall_application_text_current_user_private_profile
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 33086f98..2889ce2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1703,6 +1703,7 @@
public boolean isPrivateProfile() {
return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
}
@@ -1918,16 +1919,6 @@
}
};
- public static final AppFilter FILTER_PERSONAL_OR_PRIVATE = new AppFilter() {
- @Override
- public void init() {}
-
- @Override
- public boolean filterApp(AppEntry entry) {
- return entry.showInPersonalTab || entry.isPrivateProfile();
- }
- };
-
/**
* Displays a combined list with "downloaded" and "visible in launcher" apps only.
*/
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index da1fd55..0c7d6f0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -311,7 +311,7 @@
}
builder.apply {
- setSourceDevice(device, sourceAddrType)
+ setSourceDevice(device, addrType)
setSourceAdvertisingSid(sourceAdvertiserSid)
setBroadcastId(broadcastId)
setBroadcastName(broadcastName)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 5f026c4..e34c50e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -44,7 +44,6 @@
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
import android.annotation.TargetApi;
-import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
@@ -67,6 +66,7 @@
import com.android.settingslib.media.flags.Flags;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -81,11 +81,43 @@
/** InfoMediaManager provide interface to get InfoMediaDevice list. */
@RequiresApi(Build.VERSION_CODES.R)
-public abstract class InfoMediaManager extends MediaManager {
+public abstract class InfoMediaManager {
+ /** Callback for notifying device is added, removed and attributes changed. */
+ public interface MediaDeviceCallback {
- private static final String TAG = "InfoMediaManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+ /**
+ * Callback for notifying MediaDevice list is added.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListAdded(@NonNull List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying MediaDevice list is removed.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListRemoved(@NonNull List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying connected MediaDevice is changed.
+ *
+ * @param id the id of MediaDevice
+ */
+ void onConnectedDeviceChanged(@Nullable String id);
+
+ /**
+ * Callback for notifying that transferring is failed.
+ *
+ * @param reason the reason that the request has failed. Can be one of followings: {@link
+ * android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_REJECTED}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+ */
+ void onRequestFailed(int reason);
+ }
/** Checked exception that signals the specified package is not present in the system. */
public static class PackageNotAvailableException extends Exception {
@@ -94,19 +126,22 @@
}
}
+ private static final String TAG = "InfoMediaManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+ @NonNull protected final Context mContext;
@NonNull protected final String mPackageName;
+ private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
new ConcurrentHashMap<>();
/* package */ InfoMediaManager(
- Context context,
+ @NonNull Context context,
@NonNull String packageName,
- Notification notification,
- LocalBluetoothManager localBluetoothManager) {
- super(context, notification);
-
+ @NonNull LocalBluetoothManager localBluetoothManager) {
+ mContext = context;
mBluetoothManager = localBluetoothManager;
mPackageName = packageName;
}
@@ -115,7 +150,6 @@
public static InfoMediaManager createInstance(
Context context,
@Nullable String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
// The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -127,17 +161,14 @@
if (Flags.useMediaRouter2ForInfoMediaManager()) {
try {
- return new RouterInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
} catch (PackageNotAvailableException ex) {
// TODO: b/293578081 - Propagate this exception to callers for proper handling.
Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
- return new NoOpInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
}
} else {
- return new ManagerInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
}
}
@@ -239,6 +270,38 @@
return null;
}
+ protected final void registerCallback(MediaDeviceCallback callback) {
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ protected final void unregisterCallback(MediaDeviceCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ private void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListAdded(new ArrayList<>(devices));
+ }
+ }
+
+ private void dispatchConnectedDeviceChanged(String id) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onConnectedDeviceChanged(id);
+ }
+ }
+
+ protected void dispatchOnRequestFailed(int reason) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onRequestFailed(reason);
+ }
+ }
+
+ private Collection<MediaDeviceCallback> getCallbacks() {
+ return new CopyOnWriteArrayList<>(mCallbacks);
+ }
+
/**
* Get current device that played media.
* @return MediaDevice
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 5925492..63056b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -138,8 +138,7 @@
}
mInfoMediaManager =
- InfoMediaManager.createInstance(
- context, packageName, notification, mLocalBluetoothManager);
+ InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
}
/**
@@ -505,9 +504,9 @@
return new CopyOnWriteArrayList<>(mCallbacks);
}
- class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
+ class MediaDeviceCallback implements InfoMediaManager.MediaDeviceCallback {
@Override
- public void onDeviceListAdded(List<MediaDevice> devices) {
+ public void onDeviceListAdded(@NonNull List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
mMediaDevices.clear();
mMediaDevices.addAll(devices);
@@ -637,7 +636,7 @@
}
@Override
- public void onDeviceListRemoved(List<MediaDevice> devices) {
+ public void onDeviceListRemoved(@NonNull List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
mMediaDevices.removeAll(devices);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 453e807..c4fac35 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -54,9 +53,8 @@
/* package */ ManagerInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
mRouterManager = MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
deleted file mode 100644
index d562c8a..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2018 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.settingslib.media;
-
-import android.annotation.NonNull;
-import android.app.Notification;
-import android.content.Context;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * MediaManager provide interface to get MediaDevice list.
- */
-public abstract class MediaManager {
-
- protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
-
- protected Context mContext;
- protected Notification mNotification;
-
- MediaManager(Context context, Notification notification) {
- mContext = context;
- mNotification = notification;
- }
-
- protected void registerCallback(MediaDeviceCallback callback) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
- }
-
- protected void unregisterCallback(MediaDeviceCallback callback) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- }
- }
-
- protected void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onDeviceListAdded(new ArrayList<>(devices));
- }
- }
-
- protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onDeviceListRemoved(devices);
- }
- }
-
- protected void dispatchConnectedDeviceChanged(String id) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onConnectedDeviceChanged(id);
- }
- }
-
- protected void dispatchOnRequestFailed(int reason) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onRequestFailed(reason);
- }
- }
-
- private Collection<MediaDeviceCallback> getCallbacks() {
- return new CopyOnWriteArrayList<>(mCallbacks);
- }
-
- /**
- * Callback for notifying device is added, removed and attributes changed.
- */
- public interface MediaDeviceCallback {
-
- /**
- * Callback for notifying MediaDevice list is added.
- *
- * @param devices the MediaDevice list
- */
- void onDeviceListAdded(List<MediaDevice> devices);
-
- /**
- * Callback for notifying MediaDevice list is removed.
- *
- * @param devices the MediaDevice list
- */
- void onDeviceListRemoved(List<MediaDevice> devices);
-
- /**
- * Callback for notifying connected MediaDevice is changed.
- *
- * @param id the id of MediaDevice
- */
- void onConnectedDeviceChanged(String id);
-
- /**
- * Callback for notifying that transferring is failed.
- *
- * @param reason the reason that the request has failed. Can be one of followings:
- * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
- * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
- * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
- * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
- * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
- */
- void onRequestFailed(int reason);
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index ea4de39..ff4d4dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
@@ -42,9 +41,8 @@
NoOpInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
}
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index df03167..9c82cb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -17,7 +17,6 @@
package com.android.settingslib.media;
import android.annotation.SuppressLint;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
@@ -71,10 +70,9 @@
/* package */ RouterInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager)
throws PackageNotAvailableException {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
MediaRouter2 router = null;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 1ad7d49..fe83ffb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -319,7 +319,8 @@
@Test
public void testPrivateProfileFilterDisplaysCorrectApps() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mEntry.showInPersonalTab = true;
mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
@@ -334,7 +335,8 @@
@Test
public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mEntry.showInPersonalTab = false;
mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index c647cbb..f0185b9 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,22 +64,21 @@
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
- InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@@ -87,7 +86,7 @@
@RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index c159d5e..d85d253 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -91,7 +91,7 @@
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
- private MediaManager.MediaDeviceCallback mCallback;
+ private InfoMediaManager.MediaDeviceCallback mCallback;
@Mock
private MediaSessionManager mMediaSessionManager;
@Mock
@@ -109,8 +109,7 @@
doReturn(mMediaSessionManager).when(mContext).getSystemService(
Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
- new ManagerInfoMediaManager(
- mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
+ new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 9a7d4f1..693b7d0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -35,7 +36,6 @@
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import com.android.settingslib.bluetooth.A2dpProfile;
@@ -75,8 +75,6 @@
private static final String TEST_ADDRESS = "00:01:02:03:04:05";
@Mock
- private InfoMediaManager mInfoMediaManager;
- @Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
private LocalMediaManager.DeviceCallback mCallback;
@@ -87,8 +85,6 @@
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
- private MediaRouter2Manager mMediaRouter2Manager;
- @Mock
private MediaRoute2Info mRouteInfo1;
@Mock
private MediaRoute2Info mRouteInfo2;
@@ -97,6 +93,7 @@
private Context mContext;
private LocalMediaManager mLocalMediaManager;
+ private InfoMediaManager mInfoMediaManager;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private InfoMediaDevice mInfoMediaDevice1;
private InfoMediaDevice mInfoMediaDevice2;
@@ -116,10 +113,16 @@
when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+ // Need to call constructor to initialize final fields.
+ mInfoMediaManager = mock(
+ InfoMediaManager.class,
+ withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+
mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
- mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
- mInfoMediaManager, "com.test.packagename");
+ mLocalMediaManager =
+ new LocalMediaManager(
+ mContext, mLocalBluetoothManager, mInfoMediaManager, TEST_PACKAGE_NAME);
mLocalMediaManager.mAudioManager = mAudioManager;
}
@@ -146,7 +149,6 @@
mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
-
verify(mInfoMediaManager).connectToDevice(device);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
deleted file mode 100644
index c3237f0..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.media;
-
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.Collections;
-
-@RunWith(RobolectricTestRunner.class)
-public class MediaManagerTest {
-
- private static final String TEST_ID = "test_id";
-
- @Mock
- private MediaManager.MediaDeviceCallback mCallback;
- @Mock
- private MediaDevice mDevice;
-
- private MediaManager mMediaManager;
- private Context mContext;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
-
- when(mDevice.getId()).thenReturn(TEST_ID);
-
- mMediaManager = new MediaManager(mContext, null) {};
- }
-
- @Test
- public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchDeviceListAdded(Collections.emptyList());
-
- verify(mCallback).onDeviceListAdded(any());
- }
-
- @Test
- public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchDeviceListRemoved(Collections.emptyList());
-
- verify(mCallback).onDeviceListRemoved(Collections.emptyList());
- }
-
- @Test
- public void dispatchActiveDeviceChanged_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchConnectedDeviceChanged(TEST_ID);
-
- verify(mCallback).onConnectedDeviceChanged(TEST_ID);
- }
-
- @Test
- public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchOnRequestFailed(1);
-
- verify(mCallback).onRequestFailed(1);
- }
-
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 3043d54..8f8445d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -115,6 +115,7 @@
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+ Settings.Global.Wearable.AUTO_BEDTIME_MODE,
Settings.Global.FORCE_ENABLE_PSS_PROFILING,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c0a0760..6def40b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -452,6 +452,7 @@
VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.AUTO_BEDTIME_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0c02f56..eb2d13d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -566,9 +566,6 @@
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
- <!-- Permission required for CTS test - android.server.biometrics -->
- <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
-
<!-- Permissions required for CTS test - NotificationManagerTest -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index baa1397..d05d40d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,16 @@
}
flag {
+ name: "notification_view_flipper_pausing"
+ namespace: "systemui"
+ description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
+ bug: "309146176"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notification_async_group_header_inflation"
namespace: "systemui"
description: "Inflates the notification group summary header views from the background thread."
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 8dc7495..b124025 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,6 +27,7 @@
import android.text.Layout
import android.util.LruCache
import kotlin.math.roundToInt
+import android.util.Log
private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -140,7 +141,6 @@
}
sealed class PositionedGlyph {
-
/** Mutable X coordinate of the glyph position relative from drawing offset. */
var x: Float = 0f
@@ -269,41 +269,53 @@
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
delay: Long = 0,
- onAnimationEnd: Runnable? = null
+ onAnimationEnd: Runnable? = null,
+ ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true)
+
+ private fun setTextStyleInternal(
+ fvar: String?,
+ textSize: Float,
+ color: Int?,
+ strokeWidth: Float,
+ animate: Boolean,
+ duration: Long,
+ interpolator: TimeInterpolator?,
+ delay: Long,
+ onAnimationEnd: Runnable?,
+ updateLayoutOnFailure: Boolean,
) {
- if (animate) {
- animator.cancel()
- textInterpolator.rebase()
- }
+ try {
+ if (animate) {
+ animator.cancel()
+ textInterpolator.rebase()
+ }
- if (textSize >= 0) {
- textInterpolator.targetPaint.textSize = textSize
- }
+ if (textSize >= 0) {
+ textInterpolator.targetPaint.textSize = textSize
+ }
+ if (!fvar.isNullOrBlank()) {
+ textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+ }
+ if (color != null) {
+ textInterpolator.targetPaint.color = color
+ }
+ if (strokeWidth >= 0F) {
+ textInterpolator.targetPaint.strokeWidth = strokeWidth
+ }
+ textInterpolator.onTargetPaintModified()
- if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
- }
-
- if (color != null) {
- textInterpolator.targetPaint.color = color
- }
- if (strokeWidth >= 0F) {
- textInterpolator.targetPaint.strokeWidth = strokeWidth
- }
- textInterpolator.onTargetPaintModified()
-
- if (animate) {
- animator.startDelay = delay
- animator.duration =
- if (duration == -1L) {
- DEFAULT_ANIMATION_DURATION
- } else {
- duration
- }
- interpolator?.let { animator.interpolator = it }
- if (onAnimationEnd != null) {
- val listener =
- object : AnimatorListenerAdapter() {
+ if (animate) {
+ animator.startDelay = delay
+ animator.duration =
+ if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
+ interpolator?.let { animator.interpolator = it }
+ if (onAnimationEnd != null) {
+ val listener = object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onAnimationEnd.run()
animator.removeListener(this)
@@ -312,14 +324,25 @@
animator.removeListener(this)
}
}
- animator.addListener(listener)
+ animator.addListener(listener)
+ }
+ animator.start()
+ } else {
+ // No animation is requested, thus set base and target state to the same state.
+ textInterpolator.progress = 1f
+ textInterpolator.rebase()
+ invalidateCallback()
}
- animator.start()
- } else {
- // No animation is requested, thus set base and target state to the same state.
- textInterpolator.progress = 1f
- textInterpolator.rebase()
- invalidateCallback()
+ } catch (ex: IllegalArgumentException) {
+ if (updateLayoutOnFailure) {
+ Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" +
+ " due to the layout having changed unexpectedly without being notified.", ex)
+ updateLayout(textInterpolator.layout)
+ setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false)
+ } else {
+ throw ex
+ }
}
}
@@ -355,15 +378,13 @@
interpolator: TimeInterpolator? = null,
delay: Long = 0,
onAnimationEnd: Runnable? = null
- ) {
- val fvar = fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- )
- setTextStyle(
- fvar = fvar,
+ ) = setTextStyleInternal(
+ fvar = fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ ),
textSize = textSize,
color = color,
strokeWidth = strokeWidth,
@@ -372,6 +393,10 @@
interpolator = interpolator,
delay = delay,
onAnimationEnd = onAnimationEnd,
+ updateLayoutOnFailure = true,
)
+
+ companion object {
+ private val TAG = TextAnimator::class.simpleName!!
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 515c816..078da1c86 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -31,8 +31,8 @@
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -93,7 +93,6 @@
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.dimensionResource
-import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -119,7 +118,6 @@
import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
-import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.res.R
import kotlinx.coroutines.launch
@@ -199,37 +197,26 @@
}
},
) {
- Column(Modifier.align(Alignment.TopStart)) {
- CommunalHubLazyGrid(
- communalContent = communalContent,
- viewModel = viewModel,
- contentPadding = contentPadding,
- contentOffset = contentOffset,
- setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = { offset ->
- isDraggingToRemove =
- isPointerWithinCoordinates(
- offset = gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
- )
- isDraggingToRemove
- },
- onOpenWidgetPicker = onOpenWidgetPicker,
- gridState = gridState,
- contentListState = contentListState,
- selectedKey = selectedKey,
- widgetConfigurator = widgetConfigurator,
- )
- // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
- if (viewModel is CommunalViewModel) {
- val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
- Spacer(Modifier.height(24.dp))
- LockStateIcon(
- isUnlocked = isUnlocked,
- modifier = Modifier.align(Alignment.CenterHorizontally),
- )
- }
- }
+ CommunalHubLazyGrid(
+ communalContent = communalContent,
+ viewModel = viewModel,
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
+ setGridCoordinates = { gridCoordinates = it },
+ updateDragPositionForRemove = { offset ->
+ isDraggingToRemove =
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
+ isDraggingToRemove
+ },
+ onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedKey = selectedKey,
+ widgetConfigurator = widgetConfigurator,
+ )
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
Toolbar(
@@ -281,7 +268,7 @@
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun ColumnScope.CommunalHubLazyGrid(
+private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
@@ -295,7 +282,7 @@
widgetConfigurator: WidgetConfigurator?,
) {
var gridModifier =
- Modifier.align(Alignment.Start).onGloballyPositioned { setGridCoordinates(it) }
+ Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -377,26 +364,6 @@
}
}
-@Composable
-private fun LockStateIcon(
- isUnlocked: Boolean,
- modifier: Modifier = Modifier,
-) {
- val colors = LocalAndroidColorScheme.current
- val resource =
- if (isUnlocked) {
- R.drawable.ic_unlocked
- } else {
- R.drawable.ic_lock
- }
- Icon(
- painter = painterResource(id = resource),
- contentDescription = null,
- tint = colors.onPrimaryContainer,
- modifier = modifier.size(52.dp)
- )
-}
-
/**
* Toolbar that contains action buttons to
* 1) open the widget picker
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8f802b8..563aad1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,7 +39,6 @@
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -114,7 +113,6 @@
kosmos.communalInteractor,
kosmos.communalTutorialInteractor,
kosmos.shadeInteractor,
- kosmos.deviceEntryInteractor,
mediaHost,
logcatLogBuffer("CommunalViewModelTest"),
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..c80eb13
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val batteryController = FakeBatteryController(LeakCheck())
+ private val testUser = UserHandle.of(1)
+ private val underTest =
+ BatterySaverTileDataInteractor(testScope.testScheduler, batteryController)
+
+ @Test
+ fun availability_isTrue() =
+ testScope.runTest {
+ val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+ Truth.assertThat(availability).hasSize(1)
+ Truth.assertThat(availability.last()).isTrue()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerPowerSaving() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+
+ batteryController.setPowerSaveMode(true)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isTrue()
+
+ batteryController.setPowerSaveMode(false)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerIsPluggedIn() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+
+ batteryController.isPluggedIn = true
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isTrue()
+
+ batteryController.isPluggedIn = false
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..62c51e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileUserActionInteractorTest : SysuiTestCase() {
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val controller = FakeBatteryController(LeakCheck())
+ private val underTest = BatterySaverTileUserActionInteractor(inputHandler, controller)
+
+ @Test
+ fun handleClickWhenNotPluggedIn_flipsPowerSaverMode() = runTest {
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(BatterySaverTileModel.Standard(false, originalPowerSaveMode))
+ )
+
+ Truth.assertThat(controller.isPowerSave).isNotEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleClickWhenPluggedIn_doesNotTurnOnPowerSaverMode() = runTest {
+ controller.setPowerSaveMode(false)
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(
+ BatterySaverTileModel.Standard(controller.isPluggedIn, originalPowerSaveMode)
+ )
+ )
+
+ Truth.assertThat(controller.isPowerSave).isEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleLongClick() = runTest {
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(BatterySaverTileModel.Standard(false, false))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
new file mode 100644
index 0000000..6e9db2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig
+ private lateinit var mapper: BatterySaverTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ BatterySaverTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_battery_saver_icon_off, TestStubDrawable())
+ addOverride(R.drawable.qs_battery_saver_icon_on, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ )
+ }
+
+ @Test
+ fun map_standard_notPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_notPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.standard_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.standard_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.extreme_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.extreme_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createBatterySaverTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ iconRes: Int,
+ stateDescription: CharSequence?,
+ ): QSTileState {
+ val label = context.getString(R.string.battery_detail_switch_title)
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+ setOf(QSTileState.UserAction.LONG_CLICK)
+ else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ stateDescription,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
new file mode 100644
index 0000000..39755bf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val internetTileConfig = kosmos.qsInternetTileConfig
+ private val mapper by lazy {
+ InternetTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable())
+ addOverride(wifiRes, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ context
+ )
+ }
+
+ @Test
+ fun withActiveModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Active(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+ iconId = wifiRes,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_internet_label),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_networks_available),
+ Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
+ context.getString(R.string.quick_settings_internet_label)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun withInactiveModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_networks_unavailable),
+ Icon.Loaded(
+ context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
+ contentDescription = null
+ ),
+ context.getString(R.string.quick_settings_networks_unavailable)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createInternetTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ icon: Icon,
+ contentDescription: String,
+ ): QSTileState {
+ val label = context.getString(R.string.quick_settings_internet_label)
+ return QSTileState(
+ { icon },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ contentDescription,
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+
+ private companion object {
+ val wifiRes = WIFI_FULL_ICONS[4]
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
new file mode 100644
index 0000000..37ef6ad
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@TestableLooper.RunWithLooper
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileDataInteractorTest : SysuiTestCase() {
+ private val testUser = UserHandle.of(1)
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: InternetTileDataInteractor
+ private lateinit var mobileIconsInteractor: MobileIconsInteractor
+
+ private val airplaneModeRepository = FakeAirplaneModeRepository()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val ethernetInteractor = EthernetInteractor(connectivityRepository)
+ private val wifiRepository = FakeWifiRepository()
+ private val userSetupRepo = FakeUserSetupRepository()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+
+ private val tableLogBuffer: TableLogBuffer = mock()
+ private val carrierConfigTracker: CarrierConfigTracker = mock()
+
+ private val mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+ private val mobileConnectionRepository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+
+ private val flags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ }
+
+ private val internet = context.getString(R.string.quick_settings_internet_label)
+
+ @Before
+ fun setUp() {
+ mobileConnectionRepository.apply {
+ setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+
+ mobileConnectionsRepository.apply {
+ activeMobileDataRepository.value = mobileConnectionRepository
+ activeMobileDataSubscriptionId.value = SUB_1_ID
+ setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository))
+ }
+
+ mobileIconsInteractor =
+ MobileIconsInteractorImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ userSetupRepo,
+ testScope.backgroundScope,
+ context,
+ flags,
+ )
+
+ context.orCreateTestableResources.apply {
+ addOverride(com.android.internal.R.drawable.ic_signal_cellular, TestStubDrawable())
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_0,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_1,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_2,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_3,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_4,
+ TestStubDrawable()
+ )
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_phone, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_laptop, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_tablet, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_watch, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_auto, TestStubDrawable())
+ }
+
+ underTest =
+ InternetTileDataInteractor(
+ context,
+ testScope.backgroundScope,
+ airplaneModeRepository,
+ connectivityRepository,
+ ethernetInteractor,
+ mobileIconsInteractor,
+ wifiInteractor,
+ )
+ }
+
+ @Test
+ fun noDefault_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+
+ assertThat(latest?.secondaryLabel)
+ .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+ }
+
+ @Test
+ fun noDefault_networksAvailable() =
+ testScope.runTest {
+ // TODO(b/328419203): support [WifiInteractor.areNetworksAvailable]
+ }
+
+ @Test
+ fun wifiDefaultAndActive() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ )
+ val wifiIcon =
+ WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true)
+ as WifiIcon.Visible
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
+ assertThat(latest?.secondaryLabel).isNull()
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+
+ val actualIcon = latest?.icon
+ assertThat(actualIcon).isEqualTo(expectedIcon)
+ assertThat(latest?.iconId).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo("$internet,test ssid")
+ val expectedSd = wifiIcon.contentDescription
+ assertThat(latest?.stateDescription).isEqualTo(expectedSd)
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotNone() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .doesNotContain(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotTablet() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotLaptop() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotWatch() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotAuto() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotPhone() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotUnknown() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotInvalid() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = emptyList()
+
+ assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_networksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1"))
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+ assertThat(latest?.stateDescription).isNull()
+ val expectedCd =
+ "$internet,${context.getString(R.string.quick_settings_networks_available)}"
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(expectedCd)
+ }
+
+ @Test
+ fun mobileDefault_usesNetworkNameAndIcon() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ assertThat(latest).isNotNull()
+ assertThat(latest?.secondaryTitle).isNotNull()
+ assertThat(latest?.secondaryTitle.toString()).contains("test network")
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.icon).isInstanceOf(Icon.Loaded::class.java)
+ assertThat(latest?.iconId).isNull()
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryTitle.toString())
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(internet)
+ }
+
+ @Test
+ fun ethernetDefault_validated_matchesInteractor() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.stateDescription).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
+ }
+
+ @Test
+ fun ethernetDefault_notValidated_matchesInteractor() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.stateDescription).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
+ }
+
+ private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = hotspot,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ }
+
+ private companion object {
+ const val SUB_1_ID = 1
+
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..e1f3d97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class InternetTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+ private lateinit var underTest: InternetTileUserActionInteractor
+
+ @Mock private lateinit var internetDialogManager: InternetDialogManager
+ @Mock private lateinit var controller: AccessPointController
+
+ @Before
+ fun setup() {
+ internetDialogManager = mock<InternetDialogManager>()
+ controller = mock<AccessPointController>()
+
+ underTest =
+ InternetTileUserActionInteractor(
+ kosmos.testScope.coroutineContext,
+ internetDialogManager,
+ controller,
+ inputHandler,
+ )
+ }
+
+ @Test
+ fun handleClickWhenActive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Active()
+
+ underTest.handleInput(QSTileInputTestKtx.click(input))
+
+ verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+ }
+
+ @Test
+ fun handleClickWhenInactive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Inactive()
+
+ underTest.handleInput(QSTileInputTestKtx.click(input))
+
+ verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+ }
+
+ @Test
+ fun handleLongClickWhenActive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Active()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenInactive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Inactive()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d505b27..7fabe33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -25,6 +25,7 @@
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.UserHandle.USER_ALL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
@@ -115,7 +116,8 @@
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
FLAG_ALLOW_PRIVATE_PROFILE,
- FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
+ FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
}
public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
@@ -872,7 +874,7 @@
}
@Test
- @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testProfileAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -883,7 +885,7 @@
}
@Test
- @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testProfileUnAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -894,7 +896,7 @@
}
@Test
- @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testManagedProfileAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
mLockscreenUserManager.mCurrentManagedProfiles.clear();
@@ -908,7 +910,7 @@
}
@Test
- @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testManagedProfileUnAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
mLockscreenUserManager.mCurrentManagedProfiles.clear();
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 61f69c0..f6042e4 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -128,7 +128,8 @@
android:layout_marginStart="49dp"
android:layout_marginEnd="49dp"
android:overScrollMode="never"
- android:layout_marginBottom="16dp">
+ android:layout_marginBottom="16dp"
+ android:scrollbars="none">
<LinearLayout
android:id="@+id/keyboard_shortcuts_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e181d07..35f6a08 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -345,9 +345,6 @@
the notification is not swiped enough to dismiss it. -->
<bool name="config_showNotificationGear">true</bool>
- <!-- Whether or not a background should be drawn behind a notification. -->
- <bool name="config_drawNotificationBackground">false</bool>
-
<!-- Whether or the notifications can be shown and dismissed with a drag. -->
<bool name="config_enableNotificationShadeDrag">true</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b713417..5dbdd18 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -75,6 +75,11 @@
<!-- Battery saver notification dismiss action: Do not turn on battery saver. [CHAR LIMIT=NONE]-->
<string name="battery_saver_dismiss_action">No thanks</string>
+ <!-- Secondary label for Battery Saver tile when Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="standard_battery_saver_text">Standard</string>
+ <!-- Secondary label for Battery Saver tile when Extreme Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="extreme_battery_saver_text">Extreme</string>
+
<!-- Name of the button that links to the Settings app. [CHAR LIMIT=NONE] -->
<!-- Name of the button that links to the Wifi settings screen. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index f28d405..8a2245d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -404,7 +404,9 @@
if (nextAlarmMillis > 0) nextAlarmMillis else null,
SysuiR.string::status_bar_alarm.name
)
- .also { data -> clock?.run { events.onAlarmDataChanged(data) } }
+ .also { data ->
+ mainExecutor.execute { clock?.run { events.onAlarmDataChanged(data) } }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 31698a3..01c2cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -345,11 +345,25 @@
}
}
- private TextView loadPercentView() {
+ private TextView inflatePercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
}
+ private void addPercentView(TextView inflatedPercentView) {
+ mBatteryPercentView = inflatedPercentView;
+
+ if (mPercentageStyleId != 0) { // Only set if specified as attribute
+ mBatteryPercentView.setTextAppearance(mPercentageStyleId);
+ }
+ float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
+ mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
+ if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+ addView(mBatteryPercentView, new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ (int) Math.ceil(fontHeight)));
+ }
+
/**
* Updates percent view by removing old one and reinflating if necessary
*/
@@ -388,7 +402,9 @@
mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
(String estimate) -> {
if (mBatteryPercentView == null) {
- mBatteryPercentView = loadPercentView();
+ // Similar to the legacy behavior, inflate and add the view. We will
+ // only use it for the estimate text
+ addPercentView(inflatePercentView());
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
mEstimateText = estimate;
@@ -401,6 +417,10 @@
}
});
} else {
+ if (mBatteryPercentView != null) {
+ mEstimateText = null;
+ mBatteryPercentView.setText(null);
+ }
updateContentDescription();
}
}
@@ -485,22 +505,19 @@
return;
}
- if (mUnifiedBattery == null) {
- return;
+ if (!mShowPercentAvailable || mUnifiedBattery == null) return;
+
+ boolean shouldShow = mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE;
+ if (!mBatteryStateUnknown && !shouldShow && (mShowPercentMode != MODE_OFF)) {
+ // Slow case: fall back to the system setting
+ // TODO(b/140051051)
+ shouldShow = 0 != whitelistIpcs(() -> Settings.System
+ .getIntForUser(getContext().getContentResolver(),
+ SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
+ ? 1 : 0, UserHandle.USER_CURRENT));
}
- // TODO(b/140051051)
- final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
- .getIntForUser(getContext().getContentResolver(),
- SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
- ? 1 : 0, UserHandle.USER_CURRENT));
-
- boolean shouldShow =
- (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
- || mShowPercentMode == MODE_ON;
- shouldShow = shouldShow && !mBatteryStateUnknown;
-
setBatteryDrawableState(
new BatteryDrawableState(
mUnifiedBatteryState.getLevel(),
@@ -534,17 +551,8 @@
if (shouldShow) {
if (!showing) {
- mBatteryPercentView = loadPercentView();
- if (mPercentageStyleId != 0) { // Only set if specified as attribute
- mBatteryPercentView.setTextAppearance(mPercentageStyleId);
- }
- float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
- mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
- if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+ addPercentView(inflatePercentView());
updatePercentText();
- addView(mBatteryPercentView, new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- (int) Math.ceil(fontHeight)));
}
} else {
if (showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
index 8c9ae62..10a0d95 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -1,9 +1,21 @@
package com.android.systemui.battery
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.ui.BatterySaverTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -15,4 +27,39 @@
@IntoMap
@StringKey(BatterySaverTile.TILE_SPEC)
fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>
+
+ companion object {
+ private const val BATTERY_SAVER_TILE_SPEC = "battery"
+
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_battery_saver_icon_off,
+ labelRes = R.string.battery_detail_switch_title,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject BatterySaverTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileViewModel(
+ factory: QSTileViewModelFactory.Static<BatterySaverTileModel>,
+ mapper: BatterySaverTileMapper,
+ stateInteractor: BatterySaverTileDataInteractor,
+ userActionInteractor: BatterySaverTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
index 1b8495a..f3652b8 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable
import android.graphics.drawable.DrawableWrapper
import android.view.Gravity
+import kotlin.math.ceil
import kotlin.math.min
import kotlin.math.roundToInt
@@ -36,7 +37,7 @@
*/
@Suppress("RtlHardcoded")
class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
- /** One of [CENTER, LEFT]. Note that RTL is handled in the parent */
+ /** One of [CENTER, LEFT]. Note that number text does not RTL. */
var gravity = Gravity.CENTER
set(value) {
field = value
@@ -67,8 +68,8 @@
dr.setBounds(
bounds.left,
bounds.top,
- (bounds.left + dw).roundToInt(),
- (bounds.top + dh).roundToInt()
+ ceil(bounds.left + dw).toInt(),
+ ceil(bounds.top + dh).toInt()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
index 6d32067..5e34d29 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
@@ -26,6 +26,7 @@
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
import kotlin.math.floor
import kotlin.math.roundToInt
@@ -103,6 +104,11 @@
// saveLayer is needed here so we don't clip the other layers of our drawable
canvas.saveLayer(null, null)
+ // Fill from the opposite direction in rtl mode
+ if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ canvas.scale(-1f, 1f, bounds.width() / 2f, bounds.height() / 2f)
+ }
+
// We need to use 3 draw commands:
// 1. Clip to the current level
// 2. Clip anything outside of the path
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
index 199dd1f..706b9ec 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
@@ -26,7 +26,10 @@
import android.graphics.drawable.LayerDrawable
import android.util.PathParser
import android.view.Gravity
+import android.view.View
import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlin.math.floor
import kotlin.math.roundToInt
/**
@@ -69,8 +72,11 @@
) : LayerDrawable(arrayOf(frameBg, frame, fill, textOnly, spaceSharingText, attribution)) {
private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
- private val scaledAttrFullCanvas = RectF(Metrics.AttrFullCanvas)
- private val scaledAttrRightCanvas = RectF(Metrics.AttrRightCanvas)
+
+ private val attrFullCanvas = RectF()
+ private val attrRightCanvas = RectF()
+ private val scaledAttrFullCanvas = RectF()
+ private val scaledAttrRightCanvas = RectF()
var batteryState = batteryState
set(value) {
@@ -88,6 +94,12 @@
updateColors(batteryState.showErrorState, value)
}
+ init {
+ isAutoMirrored = true
+ // Initialize the canvas rects since they are not static
+ setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+ }
+
private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) {
if (new.showErrorState != old.showErrorState) {
updateColors(new.showErrorState, colors)
@@ -144,9 +156,42 @@
bounds.height() / Metrics.ViewportHeight
)
- // Scale the attribution bounds
- scaleMatrix.mapRect(scaledAttrFullCanvas, Metrics.AttrFullCanvas)
- scaleMatrix.mapRect(scaledAttrRightCanvas, Metrics.AttrRightCanvas)
+ scaleAttributionBounds()
+ }
+
+ override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+ setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+ scaleAttributionBounds()
+
+ return super.onLayoutDirectionChanged(layoutDirection)
+ }
+
+ private fun setAttrRects(rtl: Boolean) {
+ // Local refs make the math easier to parse
+ val full = Metrics.AttrFullCanvasInsets
+ val side = Metrics.AttrRightCanvasInsets
+ val sideRtl = Metrics.AttrRightCanvasInsetsRtl
+ val vh = Metrics.ViewportHeight
+ val vw = Metrics.ViewportWidth
+
+ attrFullCanvas.set(
+ if (rtl) full.right else full.left,
+ full.top,
+ vw - if (rtl) full.left else full.right,
+ vh - full.bottom,
+ )
+ attrRightCanvas.set(
+ if (rtl) sideRtl.left else side.left,
+ side.top,
+ vw - (if (rtl) sideRtl.right else side.right),
+ vh - side.bottom,
+ )
+ }
+
+ /** If bounds (i.e., scale), or RTL properties change, we have to recalculate the attr bounds */
+ private fun scaleAttributionBounds() {
+ scaleMatrix.mapRect(scaledAttrFullCanvas, attrFullCanvas)
+ scaleMatrix.mapRect(scaledAttrRightCanvas, attrRightCanvas)
}
override fun draw(canvas: Canvas) {
@@ -163,13 +208,14 @@
if (batteryState.showPercent && batteryState.attribution != null) {
// 4a. percent & attribution. Implies space-sharing
- // Configure the attribute to draw in a smaller bounding box and align left
+ // Configure the attribute to draw in a smaller bounding box and align left and use
+ // floor/ceil math to make sure we get every available pixel
attribution.gravity = Gravity.LEFT
attribution.setBounds(
- scaledAttrRightCanvas.left.roundToInt(),
- scaledAttrRightCanvas.top.roundToInt(),
- scaledAttrRightCanvas.right.roundToInt(),
- scaledAttrRightCanvas.bottom.roundToInt(),
+ floor(scaledAttrRightCanvas.left).toInt(),
+ floor(scaledAttrRightCanvas.top).toInt(),
+ ceil(scaledAttrRightCanvas.right).toInt(),
+ ceil(scaledAttrRightCanvas.bottom).toInt(),
)
attribution.draw(canvas)
@@ -196,16 +242,44 @@
*/
override fun setAlpha(alpha: Int) {}
+ /**
+ * Interface that describes relevant top-level metrics for the proper rendering of this icon.
+ * The overall canvas is defined as ViewportWidth x ViewportHeight, which is hard coded to 24x14
+ * points.
+ *
+ * The attr canvas insets are rect inset definitions. That is, they are defined as l,t,r,b
+ * points from the nearest edge. Note that for RTL, we don't actually flip the text since
+ * numbers do not reverse for RTL locales.
+ */
interface M {
val ViewportWidth: Float
val ViewportHeight: Float
- // Bounds, oriented in the above viewport, where we will fit-center and center-align
- // an attribution that is the sole foreground element
- val AttrFullCanvas: RectF
- // Bounds, oriented in the above viewport, where we will fit-center and left-align
- // an attribution that is sharing space with the percent text of the drawable
- val AttrRightCanvas: RectF
+ /**
+ * Insets, oriented in the above viewport in LTR, that define the full canvas for a single
+ * foreground element. The element will be fit-center and center-aligned on this canvas
+ *
+ * 18x8 point size
+ */
+ val AttrFullCanvasInsets: RectF
+
+ /**
+ * Insets, oriented in the above viewport in LTR, that define the partial canvas for a
+ * foreground element that shares space with the percent text. The element will be
+ * fit-center and left-aligned on this canvas.
+ *
+ * 6x6 point size
+ */
+ val AttrRightCanvasInsets: RectF
+
+ /**
+ * Insets, oriented in the above viewport in RTL, that define the partial canvas for a
+ * foreground element that shares space with the percent text. The element will be
+ * fit-center and left-aligned on this canvas.
+ *
+ * 6x6 point size
+ */
+ val AttrRightCanvasInsetsRtl: RectF
}
companion object {
@@ -220,20 +294,9 @@
override val ViewportWidth: Float = 24f
override val ViewportHeight: Float = 14f
- /**
- * Bounds, oriented in the above viewport, where we will fit-center and center-align
- * an attribution that is the sole foreground element
- *
- * 18x8 point size
- */
- override val AttrFullCanvas: RectF = RectF(4f, 3f, 22f, 11f)
- /**
- * Bounds, oriented in the above viewport, where we will fit-center and left-align
- * an attribution that is sharing space with the percent text of the drawable
- *
- * 6x6 point size
- */
- override val AttrRightCanvas: RectF = RectF(16f, 4f, 22f, 10f)
+ override val AttrFullCanvasInsets = RectF(4f, 3f, 2f, 3f)
+ override val AttrRightCanvasInsets = RectF(16f, 4f, 2f, 4f)
+ override val AttrRightCanvasInsetsRtl = RectF(14f, 4f, 4f, 4f)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
index 123d6ba..aa0e373 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
@@ -23,6 +23,7 @@
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
/**
@@ -71,6 +72,7 @@
}
override fun draw(canvas: Canvas) {
+ val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
val totalAvailableHeight = CanvasHeight * vScale
// Distribute the vertical whitespace around the text. This is a simplified version of
@@ -81,11 +83,12 @@
val totalAvailableWidth = CanvasWidth * hScale
val textWidth = textPaint.measureText(percentText)
val offsetX = (totalAvailableWidth - textWidth) / 2
+ val startOffset = if (rtl) ViewportInsetRight else ViewportInsetLeft
// Draw the text centered in the available area
canvas.drawText(
percentText,
- (ViewportInsetLeft * hScale) + offsetX,
+ (startOffset * hScale) + offsetX,
(ViewportInsetTop * vScale) + offsetY,
textPaint
)
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
index 0c418b9..3b4c779 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
@@ -23,6 +23,7 @@
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
/**
@@ -94,6 +95,7 @@
}
override fun draw(canvas: Canvas) {
+ val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
val totalAvailableHeight = CanvasHeight * vScale
// Distribute the vertical whitespace around the text. This is a simplified version of
@@ -107,7 +109,7 @@
canvas.drawText(
percentText,
- (ViewportInsetLeft * hScale) + offsetX,
+ ((if (rtl) ViewportInsetLeftRtl else ViewportInsetLeft) * hScale) + offsetX,
(ViewportInsetTop * vScale) + offsetY,
textPaint
)
@@ -128,6 +130,7 @@
companion object {
private const val ViewportInsetLeft = 4f
+ private const val ViewportInsetLeftRtl = 2f
private const val ViewportInsetTop = 2f
private const val CanvasWidth = 12f
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 6af0fa0..66aeda6 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -42,7 +42,7 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -69,7 +69,7 @@
private final Context mContext;
private final UiEventLogger mUiEventLogger;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final LocalBluetoothManager mLocalBluetoothManager;
private final BroadcastSender mBroadcastSender;
private final SystemUIDialog.Factory mSystemUIDialogFactory;
@@ -157,7 +157,7 @@
@AssistedInject
BroadcastDialogDelegate(
Context context,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
@Nullable LocalBluetoothManager localBluetoothManager,
UiEventLogger uiEventLogger,
@Background Executor bgExecutor,
@@ -166,7 +166,7 @@
@Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp,
@Assisted(OUTPUT_PKG_NAME) String outputPkgName) {
mContext = context;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mLocalBluetoothManager = localBluetoothManager;
mSystemUIDialogFactory = systemUIDialogFactory;
mCurrentBroadcastApp = currentBroadcastApp;
@@ -218,7 +218,7 @@
R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
changeOutput.setOnClickListener((view) -> {
- mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
+ mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
dialog.dismiss();
});
cancelBtn.setOnClickListener((view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 35b27aa..fc9a7df 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,7 +21,6 @@
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -47,7 +46,6 @@
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalViewModel
@Inject
@@ -56,7 +54,6 @@
private val communalInteractor: CommunalInteractor,
tutorialInteractor: CommunalTutorialInteractor,
shadeInteractor: ShadeInteractor,
- deviceEntryInteractor: DeviceEntryInteractor,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
@CommunalLog logBuffer: LogBuffer,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -90,8 +87,6 @@
/** Whether touches should be disabled in communal */
val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
- val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
-
init {
// Initialize our media host for the UMO. This only needs to happen once and must be done
// before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index c0a873a..989b0de 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Bundle
import android.view.View
+import android.view.WindowInsets
import android.widget.TextView
import androidx.core.view.updatePadding
import com.android.systemui.res.R
@@ -44,7 +45,10 @@
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
private lateinit var dualDisplayWarning: TextView
+ private lateinit var bottomSheet: View
private var enabledPressed = false
+ private val defaultDialogBottomInset =
+ context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -63,6 +67,8 @@
visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
}
+ bottomSheet = requireViewById(R.id.cd_bottom_sheet)
+
setOnDismissListener {
if (!enabledPressed) {
onCancelMirroring.onClick(null)
@@ -71,15 +77,17 @@
setupInsets()
}
- private fun setupInsets() {
+ private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
// This avoids overlap between dialog content and navigation bars.
- requireViewById<View>(R.id.cd_bottom_sheet).apply {
- val navbarInsets = navbarBottomInsetsProvider()
- val defaultDialogBottomInset =
- context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
- // we only care about the bottom inset as in all other configuration where navigations
- // are in other display sides there is no overlap with the dialog.
- updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ // we only care about the bottom inset as in all other configuration where navigations
+ // are in other display sides there is no overlap with the dialog.
+ bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ }
+
+ override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+ val navbarType = WindowInsets.Type.navigationBars()
+ if (changedTypes and navbarType != 0) {
+ setupInsets(insets.getInsets(navbarType).bottom)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 190062c..fbf0538 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -32,9 +32,12 @@
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@
private var dialog: Dialog? = null
/** Starts listening for pending displays. */
+ @OptIn(FlowPreview::class)
override fun start() {
val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
val concurrentDisplaysInProgessFlow =
@@ -66,6 +70,13 @@
flow { emit(false) }
}
pendingDisplayFlow
+ // Let's debounce for 2 reasons:
+ // - prevent fast dialog flashes in case pending displays are available for just a few
+ // millis
+ // - Prevent jumps related to inset changes: when in 3 buttons navigation, device
+ // unlock triggers a change in insets that might result in a jump of the dialog (if a
+ // display was connected while on the lockscreen).
+ .debounce(200.milliseconds)
.combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
->
if (pendingDisplay == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 931a869..ed82278 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -163,6 +163,6 @@
}
companion object {
- const val KEY_UP_TIMEOUT = 100L
+ const val KEY_UP_TIMEOUT = 60L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 840b309..26c63f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -109,7 +109,7 @@
import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
import com.android.systemui.media.controls.util.SmallHash;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
@@ -223,7 +223,7 @@
protected int mUid = Process.INVALID_UID;
private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final FalsingManager mFalsingManager;
private MetadataAnimationHandler mMetadataAnimationHandler;
private ColorSchemeTransition mColorSchemeTransition;
@@ -304,7 +304,7 @@
MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel,
Lazy<MediaDataManager> lazyMediaDataManager,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
MediaCarouselController mediaCarouselController,
FalsingManager falsingManager,
SystemClock systemClock,
@@ -324,7 +324,7 @@
mSeekBarViewModel = seekBarViewModel;
mMediaViewController = mediaViewController;
mMediaDataManagerLazy = lazyMediaDataManager;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
mSystemClock = systemClock;
@@ -737,7 +737,7 @@
mPackageName, mMediaViewHolder.getSeamlessButton());
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
- mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaOutputDialogManager.createAndShow(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
} else {
@@ -761,7 +761,7 @@
}
}
} else {
- mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaOutputDialogManager.createAndShow(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 5d113a9..452cb7e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,7 +31,8 @@
) {
/** Creates a [LocalMediaManager] for the given package. */
fun create(packageName: String?): LocalMediaManager {
- return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
- .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
+ return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
+ LocalMediaManager(context, localBluetoothManager, this, packageName)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
deleted file mode 100644
index b6e3937..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.media.dialog
-
-import android.app.KeyguardManager
-import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
-import android.view.View
-import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.systemui.animation.DialogTransitionAnimator
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import javax.inject.Inject
-
-/**
- * Factory to create [MediaOutputBroadcastDialog] objects.
- */
-class MediaOutputBroadcastDialogFactory @Inject constructor(
- private val context: Context,
- private val mediaSessionManager: MediaSessionManager,
- private val lbm: LocalBluetoothManager?,
- private val starter: ActivityStarter,
- private val broadcastSender: BroadcastSender,
- private val notifCollection: CommonNotifCollection,
- private val uiEventLogger: UiEventLogger,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
- private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags,
- private val userTracker: UserTracker
-) {
- var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
-
- /** Creates a [MediaOutputBroadcastDialog] for the given package. */
- fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
- // Dismiss the previous dialog, if any.
- mediaOutputBroadcastDialog?.dismiss()
-
- val controller = MediaOutputController(context, packageName,
- mediaSessionManager, lbm, starter, notifCollection,
- dialogTransitionAnimator, nearbyMediaDevicesManager, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags, userTracker)
- val dialog =
- MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
- mediaOutputBroadcastDialog = dialog
-
- // Show the dialog.
- if (view != null) {
- dialogTransitionAnimator.showFromView(dialog, view)
- } else {
- dialog.show()
- }
- }
-
- /** dismiss [MediaOutputBroadcastDialog] if exist. */
- fun dismiss() {
- mediaOutputBroadcastDialog?.dismiss()
- mediaOutputBroadcastDialog = null
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
new file mode 100644
index 0000000..54d175c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.media.dialog
+
+import android.content.Context
+import android.view.View
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import javax.inject.Inject
+
+/** Manager to create and show a [MediaOutputBroadcastDialog]. */
+class MediaOutputBroadcastDialogManager
+@Inject
+constructor(
+ private val context: Context,
+ private val broadcastSender: BroadcastSender,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val mediaOutputControllerFactory: MediaOutputController.Factory
+) {
+ var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
+
+ /** Creates a [MediaOutputBroadcastDialog] for the given package. */
+ fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ // Dismiss the previous dialog, if any.
+ mediaOutputBroadcastDialog?.dismiss()
+
+ val controller = mediaOutputControllerFactory.create(packageName)
+ val dialog =
+ MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
+ mediaOutputBroadcastDialog = dialog
+
+ // Show the dialog.
+ if (view != null) {
+ dialogTransitionAnimator.showFromView(dialog, view)
+ } else {
+ dialog.show()
+ }
+ }
+
+ /** dismiss [MediaOutputBroadcastDialog] if exist. */
+ fun dismiss() {
+ mediaOutputBroadcastDialog?.dismiss()
+ mediaOutputBroadcastDialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b3b7bce..adee7f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -64,6 +64,7 @@
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
@@ -91,6 +92,10 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
@@ -105,8 +110,6 @@
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
-import javax.inject.Inject;
-
/**
* Controller for media output dialog
*/
@@ -170,10 +173,13 @@
ACTION_BROADCAST_INFO_ICON
}
- @Inject
- public MediaOutputController(@NonNull Context context, String packageName,
- MediaSessionManager mediaSessionManager, LocalBluetoothManager
- lbm, ActivityStarter starter,
+ @AssistedInject
+ public MediaOutputController(
+ Context context,
+ @Assisted String packageName,
+ MediaSessionManager mediaSessionManager,
+ @Nullable LocalBluetoothManager lbm,
+ ActivityStarter starter,
CommonNotifCollection notifCollection,
DialogTransitionAnimator dialogTransitionAnimator,
NearbyMediaDevicesManager nearbyMediaDevicesManager,
@@ -193,7 +199,7 @@
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
- InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
+ InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -222,6 +228,12 @@
R.dimen.media_output_dialog_selectable_margin_end);
}
+ @AssistedFactory
+ public interface Factory {
+ /** Construct a MediaOutputController */
+ MediaOutputController create(String packageName);
+ }
+
protected void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
rename to packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 02be0c1..e7816a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -16,43 +16,24 @@
package com.android.systemui.media.dialog
-import android.app.KeyguardManager
import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import javax.inject.Inject
-/** Factory to create [MediaOutputDialog] objects. */
-open class MediaOutputDialogFactory
+/** Manager to create and show a [MediaOutputDialog]. */
+open class MediaOutputDialogManager
@Inject
constructor(
private val context: Context,
- private val mediaSessionManager: MediaSessionManager,
- private val lbm: LocalBluetoothManager?,
- private val starter: ActivityStarter,
private val broadcastSender: BroadcastSender,
- private val notifCollection: CommonNotifCollection,
private val uiEventLogger: UiEventLogger,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
- private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags,
- private val userTracker: UserTracker
+ private val mediaOutputControllerFactory: MediaOutputController.Factory,
) {
companion object {
const val INTERACTION_JANK_TAG = "media_output"
@@ -60,8 +41,8 @@
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
- createWithController(
+ open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ createAndShowWithController(
packageName,
aboveStatusBar,
controller =
@@ -78,12 +59,12 @@
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun createWithController(
+ open fun createAndShowWithController(
packageName: String,
aboveStatusBar: Boolean,
controller: DialogTransitionAnimator.Controller?,
) {
- create(
+ createAndShow(
packageName,
aboveStatusBar,
dialogTransitionAnimatorController = controller,
@@ -91,8 +72,10 @@
)
}
- open fun createDialogForSystemRouting(controller: DialogTransitionAnimator.Controller? = null) {
- create(
+ open fun createAndShowForSystemRouting(
+ controller: DialogTransitionAnimator.Controller? = null
+ ) {
+ createAndShow(
packageName = null,
aboveStatusBar = false,
dialogTransitionAnimatorController = null,
@@ -100,7 +83,7 @@
)
}
- private fun create(
+ private fun createAndShow(
packageName: String?,
aboveStatusBar: Boolean,
dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
@@ -109,23 +92,9 @@
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller =
- MediaOutputController(
- context,
- packageName,
- mediaSessionManager,
- lbm,
- starter,
- notifCollection,
- dialogTransitionAnimator,
- nearbyMediaDevicesManager,
- audioManager,
- powerExemptionManager,
- keyGuardManager,
- featureFlags,
- userTracker
- )
- val dialog =
+ val controller = mediaOutputControllerFactory.create(packageName)
+
+ val mediaOutputDialog =
MediaOutputDialog(
context,
aboveStatusBar,
@@ -135,16 +104,15 @@
uiEventLogger,
includePlaybackAndAppMetadata
)
- mediaOutputDialog = dialog
// Show the dialog.
if (dialogTransitionAnimatorController != null) {
dialogTransitionAnimator.show(
- dialog,
+ mediaOutputDialog,
dialogTransitionAnimatorController,
)
} else {
- dialog.show()
+ mediaOutputDialog.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 38d31ed..774792f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -31,8 +31,8 @@
* BroadcastReceiver for handling media output intent
*/
class MediaOutputDialogReceiver @Inject constructor(
- private val mediaOutputDialogFactory: MediaOutputDialogFactory,
- private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
+ private val mediaOutputDialogManager: MediaOutputDialogManager,
+ private val mediaOutputBroadcastDialogManager: MediaOutputBroadcastDialogManager
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
@@ -42,7 +42,7 @@
launchMediaOutputDialogIfPossible(packageName)
}
MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG -> {
- mediaOutputDialogFactory.createDialogForSystemRouting()
+ mediaOutputDialogManager.createAndShowForSystemRouting()
}
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> {
if (!legacyLeAudioSharing()) return
@@ -55,7 +55,7 @@
private fun launchMediaOutputDialogIfPossible(packageName: String?) {
if (!packageName.isNullOrEmpty()) {
- mediaOutputDialogFactory.create(packageName, false)
+ mediaOutputDialogManager.createAndShow(packageName, false)
} else if (DEBUG) {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
}
@@ -63,7 +63,7 @@
private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
if (!packageName.isNullOrEmpty()) {
- mediaOutputBroadcastDialogFactory.create(
+ mediaOutputBroadcastDialogManager.createAndShow(
packageName, aboveStatusBar = true, view = null)
} else if (DEBUG) {
Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index b5b1f0f..6e7e0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -34,15 +34,15 @@
private static final String TAG = "MediaOutputSwitcherDialogUI";
private final CommandQueue mCommandQueue;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
@Inject
public MediaOutputSwitcherDialogUI(
Context context,
CommandQueue commandQueue,
- MediaOutputDialogFactory mediaOutputDialogFactory) {
+ MediaOutputDialogManager mediaOutputDialogManager) {
mCommandQueue = commandQueue;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
}
@Override
@@ -54,7 +54,7 @@
@MainThread
public void showMediaOutputSwitcher(String packageName) {
if (!TextUtils.isEmpty(packageName)) {
- mMediaOutputDialogFactory.create(packageName, false, null);
+ mMediaOutputDialogManager.createAndShow(packageName, false, null);
} else {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index c0e688f..c7aae3c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -23,6 +23,7 @@
import android.app.role.RoleManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
@@ -37,20 +38,21 @@
interface NoteTaskModule {
@[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
- fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
+ fun bindNoteTaskControllerUpdateService(service: NoteTaskControllerUpdateService): Service
- @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
- fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+ @[Binds IntoMap ClassKey(NoteTaskBubblesService::class)]
+ fun bindNoteTaskBubblesService(service: NoteTaskBubblesService): Service
@[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
- fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
+ fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity
@[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
- fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
- Activity
+ fun bindLaunchNotesRoleSettingsTrampolineActivity(
+ activity: LaunchNotesRoleSettingsTrampolineActivity
+ ): Activity
@[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
- fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
+ fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
index 2d63dbc..7d3f2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -25,5 +25,7 @@
interface NoteTaskQuickAffordanceModule {
@[Binds IntoSet]
- fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
+ fun bindNoteTaskQuickAffordance(
+ impl: NoteTaskQuickAffordanceConfig
+ ): KeyguardQuickAffordanceConfig
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
new file mode 100644
index 0000000..22bbbbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.getBatteryLevel
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+
+/** Observes BatterySaver mode state changes providing the [BatterySaverTileModel.Standard]. */
+open class BatterySaverTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val batteryController: BatteryController,
+) : QSTileDataInteractor<BatterySaverTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<BatterySaverTileModel> =
+ combine(
+ batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(bgCoroutineContext),
+ batteryController
+ .isBatteryPowerSaveEnabled()
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext),
+ batteryController.getBatteryLevel().distinctUntilChanged().flowOn(bgCoroutineContext),
+ ) {
+ isPluggedIn: Boolean,
+ isPowerSaverEnabled: Boolean,
+ _, // we are only interested in battery level change, not the actual level
+ ->
+ BatterySaverTileModel.Standard(isPluggedIn, isPowerSaverEnabled)
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..1e4eb38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class BatterySaverTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val batteryController: BatteryController
+) : QSTileUserActionInteractor<BatterySaverTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<BatterySaverTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ if (!data.isPluggedIn) {
+ batteryController.setPowerSaveMode(!data.isPowerSaving, action.view)
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
new file mode 100644
index 0000000..dbec50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.model
+
+/** BatterySaver mode tile model. */
+sealed interface BatterySaverTileModel {
+
+ val isPluggedIn: Boolean
+ val isPowerSaving: Boolean
+
+ /** For when the device does not support extreme battery saver mode. */
+ data class Standard(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ ) : BatterySaverTileModel
+
+ /**
+ * For when device supports extreme battery saver mode. Whether or not that mode is enabled is
+ * determined through [isExtremeSaving].
+ */
+ data class Extreme(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ val isExtremeSaving: Boolean,
+ ) : BatterySaverTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
new file mode 100644
index 0000000..0c08fba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [BatterySaverTileModel] to [QSTileState]. */
+open class BatterySaverTileMapper
+@Inject
+constructor(
+ @Main protected val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
+
+ override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.battery_detail_switch_title)
+ contentDescription = label
+
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
+ else R.drawable.qs_battery_saver_icon_off,
+ theme
+ ),
+ null
+ )
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isPluggedIn) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ } else if (data.isPowerSaving) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+
+ if (data is BatterySaverTileModel.Extreme) {
+ secondaryLabel =
+ resources.getString(
+ if (data.isExtremeSaving) R.string.extreme_battery_saver_text
+ else R.string.standard_battery_saver_text
+ )
+ stateDescription = secondaryLabel
+ } else {
+ secondaryLabel = ""
+ }
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
new file mode 100644
index 0000000..caae4d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain
+
+import android.content.Context
+import android.content.res.Resources
+import android.widget.Switch
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [InternetTileModel] to [QSTileState]. */
+class InternetTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ private val context: Context,
+) : QSTileDataToStateMapper<InternetTileModel> {
+
+ override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.quick_settings_internet_label)
+ expandedAccessibilityClass = Switch::class
+
+ if (data.secondaryLabel != null) {
+ secondaryLabel = data.secondaryLabel.loadText(context)
+ } else {
+ secondaryLabel = data.secondaryTitle
+ }
+
+ stateDescription = data.stateDescription.loadContentDescription(context)
+ contentDescription = data.contentDescription.loadContentDescription(context)
+
+ if (data.icon != null) {
+ this.icon = { data.icon }
+ } else if (data.iconId != null) {
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(data.iconId!!, theme),
+ contentDescription = null
+ )
+ this.icon = { loadedIcon }
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+
+ activationState =
+ if (data is InternetTileModel.Active) QSTileState.ActivationState.ACTIVE
+ else QSTileState.ActivationState.INACTIVE
+
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
new file mode 100644
index 0000000..fdc596b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.annotation.StringRes
+import android.content.Context
+import android.os.UserHandle
+import android.text.Html
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+/** Observes internet state changes providing the [InternetTileModel]. */
+class InternetTileDataInteractor
+@Inject
+constructor(
+ private val context: Context,
+ @Application private val scope: CoroutineScope,
+ airplaneModeRepository: AirplaneModeRepository,
+ private val connectivityRepository: ConnectivityRepository,
+ ethernetInteractor: EthernetInteractor,
+ mobileIconsInteractor: MobileIconsInteractor,
+ wifiInteractor: WifiInteractor,
+) : QSTileDataInteractor<InternetTileModel> {
+ private val internetLabel: String = context.getString(R.string.quick_settings_internet_label)
+
+ // Three symmetrical Flows that can be switched upon based on the value of
+ // [DefaultConnectionModel]
+ private val wifiIconFlow: Flow<InternetTileModel> =
+ wifiInteractor.wifiNetwork.flatMapLatest {
+ val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
+ if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
+ val secondary = removeDoubleQuotes(it.ssid)
+ flowOf(
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
+ stateDescription = wifiIcon.contentDescription,
+ contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
+ )
+ )
+ } else {
+ notConnectedFlow
+ }
+ }
+
+ private val mobileDataContentName: Flow<CharSequence?> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ flowOf(null)
+ } else {
+ combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup ->
+ val cd = loadString(networkTypeIconGroup.contentDescription)
+ if (isRoaming) {
+ val roaming = context.getString(R.string.data_connection_roaming)
+ if (cd != null) {
+ context.getString(R.string.mobile_data_text_format, roaming, cd)
+ } else {
+ roaming
+ }
+ } else {
+ cd
+ }
+ }
+ }
+ }
+
+ private val mobileIconFlow: Flow<InternetTileModel> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ combine(
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
+
+ val stateLevel = signalIcon.level
+ val drawable = SignalDrawable(context)
+ drawable.setLevel(stateLevel)
+ val loadedIcon = Icon.Loaded(drawable, null)
+
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = loadedIcon,
+ stateDescription = ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(context)
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun mobileDataContentConcat(
+ networkName: String?,
+ dataContentDescription: CharSequence?
+ ): CharSequence {
+ if (dataContentDescription == null) {
+ return networkName ?: ""
+ }
+ if (networkName == null) {
+ return Html.fromHtml(dataContentDescription.toString(), 0)
+ }
+
+ return Html.fromHtml(
+ context.getString(
+ R.string.mobile_carrier_text_format,
+ networkName,
+ dataContentDescription
+ ),
+ 0
+ )
+ }
+
+ private fun loadString(@StringRes resId: Int): CharSequence? =
+ if (resId != 0) {
+ context.getString(resId)
+ } else {
+ null
+ }
+
+ private val ethernetIconFlow: Flow<InternetTileModel> =
+ ethernetInteractor.icon.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ val secondary = it.contentDescription
+ flowOf(
+ InternetTileModel.Active(
+ secondaryLabel = secondary?.toText(),
+ iconId = it.res,
+ stateDescription = null,
+ contentDescription = secondary,
+ )
+ )
+ }
+ }
+
+ private val notConnectedFlow: StateFlow<InternetTileModel> =
+ combine(
+ wifiInteractor.areNetworksAvailable,
+ airplaneModeRepository.isAirplaneMode,
+ ) { networksAvailable, isAirplaneMode ->
+ when {
+ isAirplaneMode -> {
+ val secondary = context.getString(R.string.status_bar_airplane)
+ InternetTileModel.Inactive(
+ secondaryTitle = secondary,
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription = ContentDescription.Loaded(secondary),
+ )
+ }
+ networksAvailable -> {
+ val secondary =
+ context.getString(R.string.quick_settings_networks_available)
+ InternetTileModel.Inactive(
+ secondaryTitle = secondary,
+ iconId = R.drawable.ic_qs_no_internet_available,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Loaded("$internetLabel,$secondary")
+ )
+ }
+ else -> {
+ NOT_CONNECTED_NETWORKS_UNAVAILABLE
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+
+ /**
+ * Consumable flow describing the correct state for the InternetTile.
+ *
+ * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of
+ * the interim providers (wifi, mobile, ethernet, or not-connected).
+ */
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<InternetTileModel> =
+ connectivityRepository.defaultConnections.flatMapLatest {
+ when {
+ it.ethernet.isDefault -> ethernetIconFlow
+ it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow
+ it.wifi.isDefault -> wifiIconFlow
+ else -> notConnectedFlow
+ }
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+ private companion object {
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+
+ fun removeDoubleQuotes(string: String?): String? {
+ if (string == null) return null
+ return if (string.firstOrNull() == '"' && string.lastOrNull() == '"') {
+ string.substring(1, string.length - 1)
+ } else string
+ }
+
+ fun ContentDescription.toText(): Text =
+ when (this) {
+ is ContentDescription.Loaded -> Text.Loaded(this.description)
+ is ContentDescription.Resource -> Text.Resource(this.res)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
new file mode 100644
index 0000000..2620cd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles internet tile clicks. */
+class InternetTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val mainContext: CoroutineContext,
+ private val internetDialogManager: InternetDialogManager,
+ private val accessPointController: AccessPointController,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<InternetTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ withContext(mainContext) {
+ internetDialogManager.create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ action.view,
+ )
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_WIFI_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
new file mode 100644
index 0000000..ece90461
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.model
+
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+
+/** Model describing the state that the QS Internet tile should be in. */
+sealed interface InternetTileModel {
+ val secondaryTitle: CharSequence?
+ val secondaryLabel: Text?
+ val iconId: Int?
+ val icon: Icon?
+ val stateDescription: ContentDescription?
+ val contentDescription: ContentDescription?
+
+ data class Active(
+ override val secondaryTitle: CharSequence? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: Icon? = null,
+ override val stateDescription: ContentDescription? = null,
+ override val contentDescription: ContentDescription? = null,
+ ) : InternetTileModel
+
+ data class Inactive(
+ override val secondaryTitle: CharSequence? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: Icon? = null,
+ override val stateDescription: ContentDescription? = null,
+ override val contentDescription: ContentDescription? = null,
+ ) : InternetTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index d0585d3..20bd7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -64,8 +64,6 @@
private String mScrimName;
private int mTintColor;
private boolean mBlendWithMainColor = true;
- private Runnable mChangeRunnable;
- private Executor mChangeRunnableExecutor;
private Executor mExecutor;
private Looper mExecutorLooper;
@Nullable
@@ -270,9 +268,6 @@
mDrawable.invalidateSelf();
}
- if (mChangeRunnable != null) {
- mChangeRunnableExecutor.execute(mChangeRunnable);
- }
}
public int getTint() {
@@ -300,9 +295,6 @@
mViewAlpha = alpha;
mDrawable.setAlpha((int) (255 * alpha));
- if (mChangeRunnable != null) {
- mChangeRunnableExecutor.execute(mChangeRunnable);
- }
}
});
}
@@ -311,14 +303,6 @@
return mViewAlpha;
}
- /**
- * Sets a callback that is invoked whenever the alpha, color, or tint change.
- */
- public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) {
- mChangeRunnable = changeRunnable;
- mChangeRunnableExecutor = changeRunnableExecutor;
- }
-
@Override
protected boolean canReceivePointerEvents() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4ee8349..81f644f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -352,10 +352,6 @@
/** Called by the touch helper when the drag down was aborted and should be reset. */
internal fun onDragDownReset() {
logger.logDragDownAborted()
- nsslController.setDimmed(
- /* dimmed= */ true,
- /* animate= */ true,
- )
nsslController.resetScrollPosition()
nsslController.resetCheckSnoozeLeavebehind()
setDragDownAmountAnimated(0f)
@@ -366,12 +362,7 @@
*
* @param above whether they dragged above it
*/
- internal fun onCrossedThreshold(above: Boolean) {
- nsslController.setDimmed(
- /* dimmed= */ !above,
- /* animate= */ true,
- )
- }
+ internal fun onCrossedThreshold(above: Boolean) {}
/** Called by the touch helper when the drag down was started */
internal fun onDragDownStarted(startingChild: ExpandableView?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 1a06eec..0091bc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -420,7 +420,7 @@
filter.addAction(Intent.ACTION_USER_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- if (allowPrivateProfile()){
+ if (privateSpaceFlagsEnabled()) {
filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
}
@@ -813,13 +813,17 @@
}
private boolean profileAvailabilityActions(String action){
- return allowPrivateProfile()?
+ return privateSpaceFlagsEnabled()?
Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)||
Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE):
Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)||
Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
}
+ private static boolean privateSpaceFlagsEnabled() {
+ return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 642eacc..c4d9cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.internet.domain.InternetTileMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
@@ -90,6 +94,7 @@
const val AIRPLANE_MODE_TILE_SPEC = "airplane"
const val DATA_SAVER_TILE_SPEC = "saver"
+ const val INTERNET_TILE_SPEC = "internet"
/** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
@Provides
@@ -168,5 +173,36 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(INTERNET_TILE_SPEC)
+ fun provideInternetTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(INTERNET_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_no_internet_available,
+ labelRes = R.string.quick_settings_internet_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject InternetTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(INTERNET_TILE_SPEC)
+ fun provideInternetTileViewModel(
+ factory: QSTileViewModelFactory.Static<InternetTileModel>,
+ mapper: InternetTileMapper,
+ stateInteractor: InternetTileDataInteractor,
+ userActionInteractor: InternetTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(INTERNET_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index ea9df9a..05e8717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -275,15 +275,6 @@
return getHeight();
}
- /**
- * Sets the notification as dimmed. The default implementation does nothing.
- *
- * @param dimmed Whether the notification should be dimmed.
- * @param fade Whether an animation should be played to change the state.
- */
- public void setDimmed(boolean dimmed, boolean fade) {
- }
-
public boolean isRemoved() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index dab89c5..b90aa10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -19,7 +19,9 @@
import android.widget.flags.Flags.notifLinearlayoutOptimized
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import javax.inject.Inject
+import javax.inject.Provider
interface NotifRemoteViewsFactoryContainer {
val factories: Set<NotifRemoteViewsFactory>
@@ -31,7 +33,8 @@
featureFlags: FeatureFlags,
precomputedTextViewFactory: PrecomputedTextViewFactory,
bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
- optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
+ optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
+ notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
) : NotifRemoteViewsFactoryContainer {
override val factories: Set<NotifRemoteViewsFactory> = buildSet {
add(precomputedTextViewFactory)
@@ -41,5 +44,8 @@
if (notifLinearlayoutOptimized()) {
add(optimizedLinearLayoutFactory)
}
+ if (NotificationViewFlipperPausing.isEnabled) {
+ add(notificationViewFlipperFactory.get())
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index c17ee39..f835cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -42,6 +42,7 @@
import android.view.ViewGroup;
import android.widget.RemoteViews;
+import com.android.app.tracing.TraceUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
@@ -369,49 +370,55 @@
ExpandableNotificationRow row,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
NotificationContentInflaterLogger logger) {
- InflationProgress result = new InflationProgress();
- final NotificationEntry entryForLogging = row.getEntry();
+ return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
+ InflationProgress result = new InflationProgress();
+ final NotificationEntry entryForLogging = row.getEntry();
- if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
- result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
- result.newExpandedView = createExpandedView(builder, isLowPriority);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
- result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
- result.newPublicView = builder.makePublicContentView(isLowPriority);
- }
-
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
- if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
- logger.logAsyncTaskProgress(entryForLogging,
- "creating group summary remote view");
- result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
+ result.newContentView = createContentView(builder, isLowPriority,
+ usesIncreasedHeight);
}
- if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
- logger.logAsyncTaskProgress(entryForLogging,
- "creating low-priority group summary remote view");
- result.mNewLowPriorityGroupHeaderView =
- builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
+ result.newExpandedView = createExpandedView(builder, isLowPriority);
}
- }
- setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
- result.packageContext = packageContext;
- result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
- result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
- true /* showingPublic */);
- return result;
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
+ result.newHeadsUpView = builder.createHeadsUpContentView(
+ usesIncreasedHeadsUpHeight);
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
+ result.newPublicView = builder.makePublicContentView(isLowPriority);
+ }
+
+ if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging,
+ "creating group summary remote view");
+ result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+ }
+
+ if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging,
+ "creating low-priority group summary remote view");
+ result.mNewLowPriorityGroupHeaderView =
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ }
+ }
+ setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
+ result.packageContext = packageContext;
+ result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
+ false /* showingPublic */);
+ result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
+ true /* showingPublic */);
+
+ return result;
+ });
}
private static void setNotifsViewsInflaterFactory(InflationProgress result,
@@ -445,6 +452,8 @@
RemoteViews.InteractionHandler remoteViewClickHandler,
@Nullable InflationCallback callback,
NotificationContentInflaterLogger logger) {
+ Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
+
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
@@ -621,6 +630,7 @@
cancellationSignal.setOnCancelListener(
() -> {
logger.logAsyncTaskProgress(entry, "apply cancelled");
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
runningInflations.values().forEach(CancellationSignal::cancel);
});
@@ -769,17 +779,17 @@
if (!requiresHeightCheck(entry)) {
return true;
}
- Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement");
- int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
- int referenceWidth = resources.getDimensionPixelSize(
- R.dimen.notification_validation_reference_width);
- int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY);
- view.measure(widthSpec, heightSpec);
- int minHeight = resources.getDimensionPixelSize(
- R.dimen.notification_validation_minimum_allowed_height);
- boolean result = view.getMeasuredHeight() >= minHeight;
- Trace.endSection();
- return result;
+ return TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement", () -> {
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ int referenceWidth = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_reference_width);
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth,
+ View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ int minHeight = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_minimum_allowed_height);
+ return view.getMeasuredHeight() >= minHeight;
+ });
}
/**
@@ -966,6 +976,7 @@
entry.headsUpStatusBarText = result.headsUpStatusBarText;
entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
if (endListener != null) {
endListener.onAsyncInflationFinished(entry);
}
@@ -1102,83 +1113,97 @@
}
@Override
+ protected void onPreExecute() {
+ Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+ }
+
+ @Override
protected InflationProgress doInBackground(Void... params) {
- try {
- final StatusBarNotification sbn = mEntry.getSbn();
- // Ensure the ApplicationInfo is updated before a builder is recovered.
- updateApplicationInfo(sbn);
- final Notification.Builder recoveredBuilder
- = Notification.Builder.recoverBuilder(mContext,
- sbn.getNotification());
+ return TraceUtils.trace("NotificationContentInflater.AsyncInflationTask#doInBackground",
+ () -> {
+ try {
+ return doInBackgroundInternal();
+ } catch (Exception e) {
+ mError = e;
+ mLogger.logAsyncTaskException(mEntry, "inflating", e);
+ return null;
+ }
+ });
+ }
- Context packageContext = sbn.getPackageContext(mContext);
- if (recoveredBuilder.usesTemplate()) {
- // For all of our templates, we want it to be RTL
- packageContext = new RtlEnabledContext(packageContext);
- }
- boolean isConversation = mEntry.getRanking().isConversation();
- Notification.MessagingStyle messagingStyle = null;
- if (isConversation) {
- messagingStyle = mConversationProcessor.processNotification(
- mEntry, recoveredBuilder, mLogger);
- }
- InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
- recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
- mUsesIncreasedHeadsUpHeight, packageContext, mRow,
- mNotifLayoutInflaterFactoryProvider, mLogger);
+ private InflationProgress doInBackgroundInternal() {
+ final StatusBarNotification sbn = mEntry.getSbn();
+ // Ensure the ApplicationInfo is updated before a builder is recovered.
+ updateApplicationInfo(sbn);
+ final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
+ mContext, sbn.getNotification());
- mLogger.logAsyncTaskProgress(mEntry,
- "getting existing smart reply state (on wrong thread!)");
- InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
- mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
- InflationProgress result = inflateSmartReplyViews(
- /* result = */ inflationProgress,
- mReInflateFlags,
- mEntry,
- mContext,
- packageContext,
- previousSmartReplyState,
- mSmartRepliesInflater,
- mLogger);
-
- if (AsyncHybridViewInflation.isEnabled()) {
- // Inflate the single-line content view's ViewModel and ViewHolder from the
- // background thread, the ViewHolder needs to be bind with ViewModel later from
- // the main thread.
- result.mInflatedSingleLineViewModel = SingleLineViewInflater
- .inflateSingleLineViewModel(
- mEntry.getSbn().getNotification(),
- messagingStyle,
- recoveredBuilder,
- mContext
- );
- result.mInflatedSingleLineViewHolder =
- SingleLineViewInflater.inflateSingleLineViewHolder(
- isConversation,
- mReInflateFlags,
- mEntry,
- mContext,
- mLogger
- );
- }
-
- mLogger.logAsyncTaskProgress(mEntry,
- "getting row image resolver (on wrong thread!)");
- final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
- // wait for image resolver to finish preloading
- mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
- imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
-
- return result;
- } catch (Exception e) {
- mError = e;
- mLogger.logAsyncTaskException(mEntry, "inflating", e);
- return null;
+ Context packageContext = sbn.getPackageContext(mContext);
+ if (recoveredBuilder.usesTemplate()) {
+ // For all of our templates, we want it to be RTL
+ packageContext = new RtlEnabledContext(packageContext);
}
+ boolean isConversation = mEntry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation) {
+ messagingStyle = mConversationProcessor.processNotification(
+ mEntry, recoveredBuilder, mLogger);
+ }
+ InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
+ recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
+ mUsesIncreasedHeadsUpHeight, packageContext, mRow,
+ mNotifLayoutInflaterFactoryProvider, mLogger);
+
+ mLogger.logAsyncTaskProgress(mEntry,
+ "getting existing smart reply state (on wrong thread!)");
+ InflatedSmartReplyState previousSmartReplyState =
+ mRow.getExistingSmartReplyState();
+ mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
+ InflationProgress result = inflateSmartReplyViews(
+ /* result = */ inflationProgress,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ packageContext,
+ previousSmartReplyState,
+ mSmartRepliesInflater,
+ mLogger);
+
+ if (AsyncHybridViewInflation.isEnabled()) {
+ // Inflate the single-line content view's ViewModel and ViewHolder from the
+ // background thread, the ViewHolder needs to be bind with ViewModel later from
+ // the main thread.
+ result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ .inflateSingleLineViewModel(
+ mEntry.getSbn().getNotification(),
+ messagingStyle,
+ recoveredBuilder,
+ mContext
+ );
+ result.mInflatedSingleLineViewHolder =
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ isConversation,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ mLogger
+ );
+ }
+
+ mLogger.logAsyncTaskProgress(mEntry,
+ "getting row image resolver (on wrong thread!)");
+ final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
+ // wait for image resolver to finish preloading
+ mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
+ imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+
+ return result;
}
@Override
protected void onPostExecute(InflationProgress result) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+
if (mError == null) {
// Logged in detail in apply.
mCancellationSignal = apply(
@@ -1197,6 +1222,11 @@
}
}
+ @Override
+ protected void onCancelled(InflationProgress result) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+ }
+
private void handleError(Exception e) {
mEntry.onInflationTaskFinished();
StatusBarNotification sbn = mEntry.getSbn();
@@ -1294,4 +1324,8 @@
public abstract void setResultView(View v);
public abstract RemoteViews getRemoteView();
}
+
+ private static final String ASYNC_TASK_TRACE_METHOD =
+ "NotificationContentInflater.AsyncInflationTask";
+ private static final String APPLY_TRACE_METHOD = "NotificationContentInflater#apply";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
new file mode 100644
index 0000000..0594c12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ViewFlipper
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import javax.inject.Inject
+
+/**
+ * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it
+ * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing.
+ */
+class NotificationViewFlipperFactory
+@Inject
+constructor(
+ private val viewModel: NotificationViewFlipperViewModel,
+) : NotifRemoteViewsFactory {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ override fun instantiate(
+ row: ExpandableNotificationRow,
+ @InflationFlag layoutType: Int,
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ ViewFlipper::class.java.name,
+ ViewFlipper::class.java.simpleName ->
+ ViewFlipper(context, attrs).also { viewFlipper ->
+ NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel)
+ }
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
new file mode 100644
index 0000000..133d3e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import android.widget.ViewFlipper
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */
+object NotificationViewFlipperBinder {
+ fun bindWhileAttached(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ): DisposableHandle {
+ if (viewFlipper.isAutoStart) {
+ // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless
+ return DisposableHandle {}
+ }
+ return viewFlipper.repeatWhenAttached {
+ lifecycleScope.launch { bind(viewFlipper, viewModel) }
+ }
+ }
+
+ suspend fun bind(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } }
+
+ private fun ViewFlipper.setPaused(paused: Boolean) {
+ if (paused) {
+ stopFlipping()
+ } else if (isAutoStart) {
+ startFlipping()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
new file mode 100644
index 0000000..7694e58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+
+/** A model which represents whether ViewFlippers inside notifications should be paused. */
+@SysUISingleton
+class NotificationViewFlipperViewModel
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ stackInteractor: NotificationStackInteractor,
+) : FlowDumperImpl(dumpManager) {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
new file mode 100644
index 0000000..cea6a2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification view flipper pausing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationViewFlipperPausing {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationViewFlipperPausing()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index c90acee..ab2f664 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -61,7 +61,6 @@
*/
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private int mScrollY;
- private boolean mDimmed;
private float mOverScrollTopAmount;
private float mOverScrollBottomAmount;
private boolean mDozing;
@@ -344,14 +343,6 @@
this.mScrollY = Math.max(scrollY, 0);
}
- /**
- * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
- * translucent and everything is scaled back a bit.
- */
- public void setDimmed(boolean dimmed) {
- mDimmed = dimmed;
- }
-
/** While dozing, we draw as little as possible, assuming a black background */
public void setDozing(boolean dozing) {
mDozing = dozing;
@@ -375,12 +366,6 @@
mHideSensitive = hideSensitive;
}
- public boolean isDimmed() {
- // While we are expanding from pulse, we want the notifications not to be dimmed, otherwise
- // you'd see the difference to the pulsing notification
- return mDimmed && !(isPulseExpanding() && mDozeAmount == 1.0f);
- }
-
public boolean isDozing() {
return mDozing;
}
@@ -768,7 +753,6 @@
pw.println("mHideSensitive=" + mHideSensitive);
pw.println("mShadeExpanded=" + mShadeExpanded);
pw.println("mClearAllInProgress=" + mClearAllInProgress);
- pw.println("mDimmed=" + mDimmed);
pw.println("mStatusBarState=" + mStatusBarState);
pw.println("mExpansionChanging=" + mExpansionChanging);
pw.println("mPanelFullWidth=" + mIsSmallScreen);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 5343cbf..03a1082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -35,7 +35,6 @@
boolean animateZ;
boolean animateHeight;
boolean animateTopInset;
- boolean animateDimmed;
boolean animateHideSensitive;
boolean hasDelays;
boolean hasGoToFullShadeEvent;
@@ -83,11 +82,6 @@
return this;
}
- public AnimationFilter animateDimmed() {
- animateDimmed = true;
- return this;
- }
-
public AnimationFilter animateHideSensitive() {
animateHideSensitive = true;
return this;
@@ -128,7 +122,6 @@
animateZ |= filter.animateZ;
animateHeight |= filter.animateHeight;
animateTopInset |= filter.animateTopInset;
- animateDimmed |= filter.animateDimmed;
animateHideSensitive |= filter.animateHideSensitive;
hasDelays |= filter.hasDelays;
mAnimatedProperties.addAll(filter.mAnimatedProperties);
@@ -142,7 +135,6 @@
animateZ = false;
animateHeight = false;
animateTopInset = false;
- animateDimmed = false;
animateHideSensitive = false;
hasDelays = false;
hasGoToFullShadeEvent = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index d0c5c82..d1e5ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -88,7 +88,6 @@
| ExpandableViewState.LOCATION_MAIN_AREA;
public int height;
- public boolean dimmed;
public boolean hideSensitive;
public boolean belowSpeedBump;
public boolean inShelf;
@@ -128,7 +127,6 @@
if (viewState instanceof ExpandableViewState) {
ExpandableViewState svs = (ExpandableViewState) viewState;
height = svs.height;
- dimmed = svs.dimmed;
hideSensitive = svs.hideSensitive;
belowSpeedBump = svs.belowSpeedBump;
clipTopAmount = svs.clipTopAmount;
@@ -155,9 +153,6 @@
expandableView.setActualHeight(newHeight, false /* notifyListeners */);
}
- // apply dimming
- expandableView.setDimmed(this.dimmed, false /* animate */);
-
// apply hiding sensitive
expandableView.setHideSensitive(
this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
@@ -216,9 +211,6 @@
abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
}
- // start dimmed animation
- expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
-
// apply below the speed bump
if (!NotificationIconContainerRefactor.isEnabled()) {
expandableView.setBelowSpeedBump(this.belowSpeedBump);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index fa97300..28f874d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -795,7 +795,6 @@
} else {
childState.setZTranslation(0);
}
- childState.dimmed = parentState.dimmed;
childState.hideSensitive = parentState.hideSensitive;
childState.belowSpeedBump = parentState.belowSpeedBump;
childState.clipTopAmount = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 27db84f..b47b18d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.stack;
import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_UP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
@@ -29,10 +31,6 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeAnimator;
-import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
@@ -79,7 +77,6 @@
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -163,18 +160,11 @@
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
- /**
- * The distance in pixels between sections when the sections are directly adjacent (no visible
- * gap is drawn between them). In this case we don't want to round their corners.
- */
- private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
private boolean mKeyguardBypassEnabled;
private final ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
private int mCurrentStackHeight = Integer.MAX_VALUE;
- private final Paint mBackgroundPaint = new Paint();
- private final boolean mShouldDrawNotificationBackground;
private boolean mHighPriorityBeforeSpeedBump;
private float mExpandedHeight;
@@ -263,7 +253,6 @@
private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
- private boolean mDimmedNeedsAnimation;
private boolean mHideSensitiveNeedsAnimation;
private boolean mActivateNeedsAnimation;
private boolean mGoToFullShadeNeedsAnimation;
@@ -350,40 +339,15 @@
}
};
private final NotificationSection[] mSections;
- private boolean mAnimateNextBackgroundTop;
- private boolean mAnimateNextBackgroundBottom;
- private boolean mAnimateNextSectionBoundsChange;
- private @ColorInt int mBgColor;
- private float mDimAmount;
- private ValueAnimator mDimAnimator;
private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
- private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mDimAnimator = null;
- }
- };
- private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setDimAmount((Float) animation.getAnimatedValue());
- }
- };
protected ViewGroup mQsHeader;
// Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
private final Rect mQsHeaderBound = new Rect();
private boolean mContinuousShadowUpdate;
- private boolean mContinuousBackgroundUpdate;
private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
updateViewShadows();
return true;
};
- private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
- updateBackground();
- return true;
- };
private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
float endY = view.getTranslationY() + view.getActualHeight();
float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -481,7 +445,6 @@
private boolean mHeadsUpAnimatingAway;
private int mStatusBarState;
private int mUpcomingStatusBarState;
- private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private final Runnable mReflingAndAnimateScroll = this::animateScroll;
private int mCornerRadius;
@@ -581,7 +544,6 @@
*/
private boolean mDismissUsingRowTranslationX = true;
private ExpandableNotificationRow mTopHeadsUpRow;
- private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
@@ -595,7 +557,7 @@
mSplitShadeStateController = splitShadeStateController;
updateSplitNotificationShade();
}
- private FeatureFlags mFeatureFlags;
+ private final FeatureFlags mFeatureFlags;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
new ExpandableView.OnHeightChangedListener() {
@@ -667,8 +629,6 @@
mSections = mSectionsManager.createSectionsForBuckets();
mAmbientState = Dependency.get(AmbientState.class);
- mBgColor = Utils.getColorAttr(mContext,
- com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
mSplitShadeMinContentHeight = res.getDimensionPixelSize(
@@ -680,16 +640,12 @@
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
mStateAnimator = new StackStateAnimator(context, this);
- mShouldDrawNotificationBackground =
- res.getBoolean(R.bool.config_drawNotificationBackground);
setOutlineProvider(mOutlineProvider);
// We could set this whenever we 'requestChildUpdate' much like the viewTreeObserver, but
// that adds a bunch of complexity, and drawing nothing isn't *that* expensive.
- boolean willDraw = SceneContainerFlag.isEnabled()
- || mShouldDrawNotificationBackground || mDebugLines;
+ boolean willDraw = SceneContainerFlag.isEnabled() || mDebugLines;
setWillNotDraw(!willDraw);
- mBackgroundPaint.setAntiAlias(true);
if (mDebugLines) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0xffff0000);
@@ -812,9 +768,6 @@
}
void updateBgColor() {
- mBgColor = Utils.getColorAttr(mContext,
- com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
- updateBackgroundDimming();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof ActivatableNotificationView activatableView) {
@@ -835,14 +788,6 @@
protected void onDraw(Canvas canvas) {
onJustBeforeDraw();
- if (mShouldDrawNotificationBackground
- && (mSections[0].getCurrentBounds().top
- < mSections[mSections.length - 1].getCurrentBounds().bottom
- || mAmbientState.isDozing())) {
- drawBackground(canvas);
- } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
- drawHeadsUpBackground(canvas);
- }
if (mDebugLines) {
onDrawDebug(canvas);
@@ -930,150 +875,6 @@
return textY;
}
- private void drawBackground(Canvas canvas) {
- int lockScreenLeft = mSidePaddings;
- int lockScreenRight = getWidth() - mSidePaddings;
- int lockScreenTop = mSections[0].getCurrentBounds().top;
- int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
- int hiddenLeft = getWidth() / 2;
- int hiddenTop = mTopPadding;
-
- float yProgress = 1 - mInterpolatedHideAmount;
- float xProgress = mHideXInterpolator.getInterpolation(
- (1 - mLinearHideAmount) * mBackgroundXFactor);
-
- int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
- int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
- int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
- int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
- mBackgroundAnimationRect.set(
- left,
- top,
- right,
- bottom);
-
- int backgroundTopAnimationOffset = top - lockScreenTop;
- // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
- boolean anySectionHasVisibleChild = false;
- for (NotificationSection section : mSections) {
- if (section.needsBackground()) {
- anySectionHasVisibleChild = true;
- break;
- }
- }
- boolean shouldDrawBackground;
- if (mKeyguardBypassEnabled && onKeyguard()) {
- shouldDrawBackground = isPulseExpanding();
- } else {
- shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
- }
- if (shouldDrawBackground) {
- drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
- }
-
- updateClipping();
- }
-
- /**
- * Draws round rects for each background section.
- * <p>
- * We want to draw a round rect for each background section as defined by {@link #mSections}.
- * However, if two sections are directly adjacent with no gap between them (e.g. on the
- * lockscreen where the shelf can appear directly below the high priority section, or while
- * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
- * section), we don't want to round the adjacent corners.
- * <p>
- * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
- * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
- * This method tracks the top of each rect we need to draw, then iterates through the visible
- * sections. If a section is not adjacent to the previous section, we draw the previous rect
- * behind the sections we've accumulated up to that point, then start a new rect at the top of
- * the current section. When we're done iterating we will always have one rect left to draw.
- */
- private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
- int animationYOffset) {
- int backgroundRectTop = top;
- int lastSectionBottom =
- mSections[0].getCurrentBounds().bottom + animationYOffset;
- int currentLeft = left;
- int currentRight = right;
- boolean first = true;
- for (NotificationSection section : mSections) {
- if (!section.needsBackground()) {
- continue;
- }
- int sectionTop = section.getCurrentBounds().top + animationYOffset;
- int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
- int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
- // If sections are directly adjacent to each other, we don't want to draw them
- // as separate roundrects, as the rounded corners right next to each other look
- // bad.
- if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
- || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
- canvas.drawRoundRect(currentLeft,
- backgroundRectTop,
- currentRight,
- lastSectionBottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- backgroundRectTop = sectionTop;
- }
- currentLeft = ownLeft;
- currentRight = ownRight;
- lastSectionBottom =
- section.getCurrentBounds().bottom + animationYOffset;
- first = false;
- }
- canvas.drawRoundRect(currentLeft,
- backgroundRectTop,
- currentRight,
- lastSectionBottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- }
-
- private void drawHeadsUpBackground(Canvas canvas) {
- int left = mSidePaddings;
- int right = getWidth() - mSidePaddings;
-
- float top = getHeight();
- float bottom = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE
- && child instanceof ExpandableNotificationRow row) {
- if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
- && row.getProvider().shouldShowGutsOnSnapOpen()) {
- top = Math.min(top, row.getTranslationY());
- bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
- }
- }
- }
-
- if (top < bottom) {
- canvas.drawRoundRect(
- left, top, right, bottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- }
- }
-
- void updateBackgroundDimming() {
- // No need to update the background color if it's not being drawn.
- if (!mShouldDrawNotificationBackground) {
- return;
- }
- // Interpolate between semi-transparent notification panel background color
- // and white AOD separator.
- float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
- mLinearHideAmount);
- int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
-
- if (mCachedBackgroundColor != color) {
- mCachedBackgroundColor = color;
- mBackgroundPaint.setColor(color);
- invalidate();
- }
- }
-
private void reinitView() {
initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
}
@@ -1359,9 +1160,6 @@
private void onPreDrawDuringAnimation() {
mShelf.updateAppearance();
- if (!mNeedsAnimation && !mChildrenUpdateRequested) {
- updateBackground();
- }
}
private void updateScrollStateForAddedChildren() {
@@ -2565,125 +2363,6 @@
}
}
- private void updateBackground() {
- // No need to update the background color if it's not being drawn.
- if (!mShouldDrawNotificationBackground) {
- return;
- }
-
- updateBackgroundBounds();
- if (didSectionBoundsChange()) {
- boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
- || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
- if (!isExpanded()) {
- abortBackgroundAnimators();
- animate = false;
- }
- if (animate) {
- startBackgroundAnimation();
- } else {
- for (NotificationSection section : mSections) {
- section.resetCurrentBounds();
- }
- invalidate();
- }
- } else {
- abortBackgroundAnimators();
- }
- mAnimateNextBackgroundTop = false;
- mAnimateNextBackgroundBottom = false;
- mAnimateNextSectionBoundsChange = false;
- }
-
- private void abortBackgroundAnimators() {
- for (NotificationSection section : mSections) {
- section.cancelAnimators();
- }
- }
-
- private boolean didSectionBoundsChange() {
- for (NotificationSection section : mSections) {
- if (section.didBoundsChange()) {
- return true;
- }
- }
- return false;
- }
-
- private boolean areSectionBoundsAnimating() {
- for (NotificationSection section : mSections) {
- if (section.areBoundsAnimating()) {
- return true;
- }
- }
- return false;
- }
-
- private void startBackgroundAnimation() {
- // TODO(kprevas): do we still need separate fields for top/bottom?
- // or can each section manage its own animation state?
- NotificationSection firstVisibleSection = getFirstVisibleSection();
- NotificationSection lastVisibleSection = getLastVisibleSection();
- for (NotificationSection section : mSections) {
- section.startBackgroundAnimation(
- section == firstVisibleSection
- ? mAnimateNextBackgroundTop
- : mAnimateNextSectionBoundsChange,
- section == lastVisibleSection
- ? mAnimateNextBackgroundBottom
- : mAnimateNextSectionBoundsChange);
- }
- }
-
- /**
- * Update the background bounds to the new desired bounds
- */
- private void updateBackgroundBounds() {
- int left = mSidePaddings;
- int right = getWidth() - mSidePaddings;
- for (NotificationSection section : mSections) {
- section.getBounds().left = left;
- section.getBounds().right = right;
- }
-
- if (!mIsExpanded) {
- for (NotificationSection section : mSections) {
- section.getBounds().top = 0;
- section.getBounds().bottom = 0;
- }
- return;
- }
- int minTopPosition;
- NotificationSection lastSection = getLastVisibleSection();
- boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
- if (!onKeyguard) {
- minTopPosition = (int) (mTopPadding + mStackTranslation);
- } else if (lastSection == null) {
- minTopPosition = mTopPadding;
- } else {
- // The first sections could be empty while there could still be elements in later
- // sections. The position of these first few sections is determined by the position of
- // the first visible section.
- NotificationSection firstVisibleSection = getFirstVisibleSection();
- firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
- false /* shiftPulsingWithFirst */);
- minTopPosition = firstVisibleSection.getBounds().top;
- }
- boolean shiftPulsingWithFirst = mNumHeadsUp <= 1
- && (mAmbientState.isDozing() || (mKeyguardBypassEnabled && onKeyguard));
- for (NotificationSection section : mSections) {
- int minBottomPosition = minTopPosition;
- if (section == lastSection) {
- // We need to make sure the section goes all the way to the shelf
- minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
- + mShelf.getIntrinsicHeight());
- }
- minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
- shiftPulsingWithFirst);
- shiftPulsingWithFirst = false;
- }
- }
-
private NotificationSection getFirstVisibleSection() {
for (NotificationSection section : mSections) {
if (section.getFirstVisibleChild() != null) {
@@ -3184,13 +2863,7 @@
mSections, getChildrenWithBackground());
if (mAnimationsEnabled && mIsExpanded) {
- mAnimateNextBackgroundTop = firstChild != previousFirstChild;
- mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
- mAnimateNextSectionBoundsChange = sectionViewsChanged;
} else {
- mAnimateNextBackgroundTop = false;
- mAnimateNextBackgroundBottom = false;
- mAnimateNextSectionBoundsChange = false;
}
mAmbientState.setLastVisibleBackgroundChild(lastChild);
mAnimateBottomOnLayout = false;
@@ -3344,7 +3017,6 @@
setAnimationRunning(true);
mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
mAnimationEvents.clear();
- updateBackground();
updateViewShadows();
} else {
applyCurrentState();
@@ -3359,7 +3031,6 @@
generatePositionChangeEvents();
generateTopPaddingEvent();
generateActivateEvent();
- generateDimmedEvent();
generateHideSensitiveEvent();
generateGoToFullShadeEvent();
generateViewResizeEvent();
@@ -3577,14 +3248,6 @@
mEverythingNeedsAnimation = false;
}
- private void generateDimmedEvent() {
- if (mDimmedNeedsAnimation) {
- mAnimationEvents.add(
- new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
- }
- mDimmedNeedsAnimation = false;
- }
-
private void generateHideSensitiveEvent() {
if (mHideSensitiveNeedsAnimation) {
mAnimationEvents.add(
@@ -3645,7 +3308,11 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
- if (!mSendingTouchesToSceneFramework) {
+ int action = ev.getActionMasked();
+ boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
+ if (mSendingTouchesToSceneFramework) {
+ mController.sendTouchToSceneFramework(ev);
+ } else if (!isUpOrCancel) {
// if this is the first touch being sent to the scene framework,
// convert it into a synthetic DOWN event.
mSendingTouchesToSceneFramework = true;
@@ -3653,14 +3320,9 @@
downEvent.setAction(MotionEvent.ACTION_DOWN);
mController.sendTouchToSceneFramework(downEvent);
downEvent.recycle();
- } else {
- mController.sendTouchToSceneFramework(ev);
}
- if (
- ev.getActionMasked() == MotionEvent.ACTION_UP
- || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
- ) {
+ if (isUpOrCancel) {
setIsBeingDragged(false);
}
return false;
@@ -3817,7 +3479,7 @@
}
}
break;
- case MotionEvent.ACTION_UP:
+ case ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
@@ -3854,7 +3516,7 @@
}
break;
- case MotionEvent.ACTION_CANCEL:
+ case ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
getScrollRange())) {
@@ -3963,7 +3625,7 @@
mTouchIsClick = false;
}
break;
- case MotionEvent.ACTION_UP:
+ case ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
debugShadeLog("handleEmptySpaceClick: touch event propagated further");
@@ -4104,8 +3766,8 @@
break;
}
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
+ case ACTION_CANCEL:
+ case ACTION_UP:
/* Release the drag */
setIsBeingDragged(false);
mActivePointerId = INVALID_POINTER;
@@ -4486,48 +4148,6 @@
mAnimationFinishedRunnables.clear();
}
- /**
- * See {@link AmbientState#setDimmed}.
- */
- void setDimmed(boolean dimmed, boolean animate) {
- dimmed &= onKeyguard();
- mAmbientState.setDimmed(dimmed);
- if (animate && mAnimationsEnabled) {
- mDimmedNeedsAnimation = true;
- mNeedsAnimation = true;
- animateDimmed(dimmed);
- } else {
- setDimAmount(dimmed ? 1.0f : 0.0f);
- }
- requestChildrenUpdate();
- }
-
- @VisibleForTesting
- boolean isDimmed() {
- return mAmbientState.isDimmed();
- }
-
- private void setDimAmount(float dimAmount) {
- mDimAmount = dimAmount;
- updateBackgroundDimming();
- }
-
- private void animateDimmed(boolean dimmed) {
- if (mDimAnimator != null) {
- mDimAnimator.cancel();
- }
- float target = dimmed ? 1.0f : 0.0f;
- if (target == mDimAmount) {
- return;
- }
- mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
- mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
- mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mDimAnimator.addListener(mDimEndListener);
- mDimAnimator.addUpdateListener(mDimUpdateListener);
- mDimAnimator.start();
- }
-
void updateSensitiveness(boolean animate, boolean hideSensitive) {
if (hideSensitive != mAmbientState.isHideSensitive()) {
int childCount = getChildCount();
@@ -4564,7 +4184,6 @@
runAnimationFinishedRunnables();
setAnimationRunning(false);
- updateBackground();
updateViewShadows();
}
@@ -4714,7 +4333,6 @@
invalidateOutline();
}
updateAlgorithmHeightAndPadding();
- updateBackgroundDimming();
requestChildrenUpdate();
updateOwnTranslationZ();
}
@@ -4747,21 +4365,6 @@
}
}
- private int getNotGoneIndex(View child) {
- int count = getChildCount();
- int notGoneIndex = 0;
- for (int i = 0; i < count; i++) {
- View v = getChildAt(i);
- if (child == v) {
- return notGoneIndex;
- }
- if (v.getVisibility() != View.GONE) {
- notGoneIndex++;
- }
- }
- return -1;
- }
-
/**
* Returns whether or not a History button is shown in the footer. If there is no footer, then
* this will return false.
@@ -5266,13 +4869,10 @@
void onStatePostChange(boolean fromShadeLocked) {
boolean onKeyguard = onKeyguard();
- mAmbientState.setDimmed(onKeyguard);
-
if (mHeadsUpAppearanceController != null) {
mHeadsUpAppearanceController.onStateChanged();
}
- setDimmed(onKeyguard, fromShadeLocked);
setExpandingEnabled(!onKeyguard);
if (!FooterViewRefactor.isEnabled()) {
updateFooter();
@@ -5676,7 +5276,6 @@
*/
public void setDozeAmount(float dozeAmount) {
mAmbientState.setDozeAmount(dozeAmount);
- updateContinuousBackgroundDrawing();
updateStackPosition();
requestChildrenUpdate();
}
@@ -5711,7 +5310,6 @@
view.setTranslationY(wakeUplocation);
}
}
- mDimmedNeedsAnimation = true;
}
void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
@@ -5763,7 +5361,6 @@
updateFirstAndLastBackgroundViews();
requestDisallowInterceptTouchEvent(true);
updateContinuousShadowDrawing();
- updateContinuousBackgroundDrawing();
requestChildrenUpdate();
}
@@ -5786,7 +5383,6 @@
* @param numHeadsUp the number of active alerting notifications.
*/
public void setNumHeadsUp(long numHeadsUp) {
- mNumHeadsUp = numHeadsUp;
mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0);
}
@@ -6160,19 +5756,6 @@
mSpeedBumpIndexDirty = true;
}
- void updateContinuousBackgroundDrawing() {
- boolean continuousBackground = !mAmbientState.isFullyAwake()
- && mSwipeHelper.isSwiping();
- if (continuousBackground != mContinuousBackgroundUpdate) {
- mContinuousBackgroundUpdate = continuousBackground;
- if (continuousBackground) {
- getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
- } else {
- getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
- }
- }
- }
-
private void resetAllSwipeState() {
Trace.beginSection("NSSL.resetAllSwipeState()");
mSwipeHelper.resetTouchState();
@@ -6259,7 +5842,6 @@
.animateHeight()
.animateTopInset()
.animateY()
- .animateDimmed()
.animateZ(),
// ANIMATION_TYPE_ACTIVATED_CHILD
@@ -6267,8 +5849,7 @@
.animateZ(),
// ANIMATION_TYPE_DIMMED
- new AnimationFilter()
- .animateDimmed(),
+ new AnimationFilter(),
// ANIMATION_TYPE_CHANGE_POSITION
new AnimationFilter()
@@ -6283,7 +5864,6 @@
.animateHeight()
.animateTopInset()
.animateY()
- .animateDimmed()
.animateZ()
.hasDelays(),
@@ -6339,7 +5919,6 @@
// ANIMATION_TYPE_EVERYTHING
new AnimationFilter()
.animateAlpha()
- .animateDimmed()
.animateHideSensitive()
.animateHeight()
.animateTopInset()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7c13877..3bdd0e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -875,8 +875,6 @@
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
- mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
-
mLockscreenShadeTransitionController.setStackScroller(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
@@ -1743,13 +1741,6 @@
}
/**
- * Set the dimmed state for all of the notification views.
- */
- public void setDimmed(boolean dimmed, boolean animate) {
- mView.setDimmed(dimmed, animate);
- }
-
- /**
* @return the inset during the full shade transition, that needs to be added to the position
* of the quick settings edge. This is relevant for media, that is transitioning
* from the keyguard host to the quick settings one.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 1ef9a8f..9b1952b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -369,13 +369,11 @@
/** Updates the dimmed and hiding sensitive states of the children. */
private void updateDimmedAndHideSensitive(AmbientState ambientState,
StackScrollAlgorithmState algorithmState) {
- boolean dimmed = ambientState.isDimmed();
boolean hideSensitive = ambientState.isHideSensitive();
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
- childViewState.dimmed = dimmed;
childViewState.hideSensitive = hideSensitive;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d2e36b8..088f894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -207,8 +207,6 @@
private ScrimView mNotificationsScrim;
private ScrimView mScrimBehind;
- private Runnable mScrimBehindChangeRunnable;
-
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
@@ -415,11 +413,6 @@
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
mNotificationsScrim.enableRoundedCorners(true);
- if (mScrimBehindChangeRunnable != null) {
- mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
- mScrimBehindChangeRunnable = null;
- }
-
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
@@ -1542,16 +1535,6 @@
mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
}
- public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
- // TODO: remove this. This is necessary because of an order-of-operations limitation.
- // The fix is to move more of these class into @SysUISingleton.
- if (mScrimBehind == null) {
- mScrimBehindChangeRunnable = changeRunnable;
- } else {
- mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
- }
- }
-
private void updateThemeColors() {
if (mScrimBehind == null) return;
int background = Utils.getColorAttr(mScrimBehind.getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 71e25e9..541ac48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -23,6 +23,9 @@
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.InsetsType
+import android.view.WindowInsetsAnimation
import android.view.WindowManager.LayoutParams.MATCH_PARENT
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
@@ -70,16 +73,43 @@
override fun onStart() {
super.onStart()
configurationController?.addCallback(onConfigChanged)
+ window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
}
override fun onStop() {
super.onStop()
configurationController?.removeCallback(onConfigChanged)
+ window?.decorView?.setWindowInsetsAnimationCallback(null)
}
+ /** Called after any insets change. */
+ open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+
/** Can be overridden by subclasses to receive config changed events. */
open fun onConfigurationChanged() {}
+ private val insetsAnimationCallback =
+ object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+ private var lastInsets: WindowInsets? = null
+
+ override fun onEnd(animation: WindowInsetsAnimation) {
+ lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+ }
+
+ override fun onProgress(
+ insets: WindowInsets,
+ animations: MutableList<WindowInsetsAnimation>,
+ ): WindowInsets {
+ lastInsets = insets
+ onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+ return insets
+ }
+
+ private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+ animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+ }
+
private val onConfigChanged =
object : ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 44c684c..b5efc44 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -379,7 +379,9 @@
+ " was received. Deferring... Managed profile? " + isManagedProfile);
return;
}
- if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && isPrivateProfile(newUserHandle)) {
mDeferredThemeEvaluation = true;
Log.i(TAG, "Deferring theme for private profile till user setup is complete");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
index 0128eb7..80ccd64 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -22,6 +22,24 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onStart
+fun BatteryController.isDevicePluggedIn(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(pluggedIn)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isPluggedIn) }
+}
+
fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
return conflatedCallbackFlow {
val batteryCallback =
@@ -35,3 +53,35 @@
}
.onStart { emit(isPowerSave) }
}
+
+fun BatteryController.getBatteryLevel(): Flow<Int> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(level)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(0) }
+}
+
+fun BatteryController.isExtremePowerSaverEnabled(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onExtremeBatterySaverChanged(isExtreme: Boolean) {
+ trySend(isExtreme)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isExtremeSaverOn) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4045630..deec215 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,7 @@
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
@@ -265,7 +265,7 @@
private final Object mSafetyWarningLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
private final ConfigurationController mConfigurationController;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
private final VolumeNavigator mVolumeNavigator;
@@ -316,7 +316,7 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
InteractionJankMonitor interactionJankMonitor,
VolumePanelNavigationInteractor volumePanelNavigationInteractor,
VolumeNavigator volumeNavigator,
@@ -340,7 +340,7 @@
mAccessibilityMgr = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
mConfigurationController = configurationController;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mCsdWarningDialogFactory = csdWarningDialogFactory;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
@@ -1199,7 +1199,7 @@
mSettingsIcon.setOnClickListener(v -> {
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
- mMediaOutputDialogFactory.dismiss();
+ mMediaOutputDialogManager.dismiss();
mVolumeNavigator.openVolumePanel(
mVolumePanelNavigationInteractor.getVolumePanelRoute());
});
@@ -2082,6 +2082,9 @@
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
+ // The animator can't keep up with the volume changes so haptics need to be
+ // triggered here. This happens when the volume keys are continuously pressed.
+ row.deliverOnProgressChangedHaptics(false, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2486,10 +2489,10 @@
if (getActiveRow().equals(mRow)
&& mRow.slider.getVisibility() == VISIBLE
&& mRow.mHapticPlugin != null) {
- mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
- if (!fromUser) {
- // Consider a change from program as the volume key being continuously pressed
- mRow.mHapticPlugin.onKeyDown();
+ if (fromUser || mRow.animTargetProgress == progress) {
+ // Deliver user-generated slider changes immediately, or when the animation
+ // completes
+ mRow.deliverOnProgressChangedHaptics(fromUser, progress);
}
}
if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2571,11 +2574,11 @@
/* progressInterpolatorFactor= */ 1f,
/* progressBasedDragMinScale= */ 0f,
/* progressBasedDragMaxScale= */ 0.2f,
- /* additionalVelocityMaxBump= */ 0.15f,
+ /* additionalVelocityMaxBump= */ 0.25f,
/* deltaMillisForDragInterval= */ 0f,
- /* deltaProgressForDragThreshold= */ 0.015f,
- /* numberOfLowTicks= */ 5,
- /* maxVelocityToScale= */ 300f,
+ /* deltaProgressForDragThreshold= */ 0.05f,
+ /* numberOfLowTicks= */ 4,
+ /* maxVelocityToScale= */ 200,
/* velocityAxis= */ MotionEvent.AXIS_Y,
/* upperBookendScale= */ 1f,
/* lowerBookendScale= */ 0.05f,
@@ -2642,6 +2645,14 @@
void removeHaptics() {
slider.setOnTouchListener(null);
}
+
+ void deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+ mHapticPlugin.onProgressChanged(slider, progress, fromUser);
+ if (!fromUser) {
+ // Consider a change from program as the volume key being continuously pressed
+ mHapticPlugin.onKeyDown();
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 64a5644..1f87ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -24,7 +24,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -100,7 +100,7 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
InteractionJankMonitor interactionJankMonitor,
VolumePanelNavigationInteractor volumePanelNavigationInteractor,
VolumeNavigator volumeNavigator,
@@ -116,7 +116,7 @@
accessibilityManagerWrapper,
deviceProvisionedController,
configurationController,
- mediaOutputDialogFactory,
+ mediaOutputDialogManager,
interactionJankMonitor,
volumePanelNavigationInteractor,
volumeNavigator,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 2ab5998..cb16abe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -20,7 +20,7 @@
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -30,20 +30,22 @@
class MediaOutputActionsInteractor
@Inject
constructor(
- private val mediaOutputDialogFactory: MediaOutputDialogFactory,
+ private val mediaOutputDialogManager: MediaOutputDialogManager,
) {
fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
when (session) {
is MediaDeviceSession.Active -> {
- mediaOutputDialogFactory.createWithController(
+ mediaOutputDialogManager.createAndShowWithController(
session.packageName,
false,
expandable.dialogController()
)
}
is MediaDeviceSession.Inactive -> {
- mediaOutputDialogFactory.createDialogForSystemRouting(expandable.dialogController())
+ mediaOutputDialogManager.createAndShowForSystemRouting(
+ expandable.dialogController()
+ )
}
else -> {
/* do nothing */
@@ -56,7 +58,7 @@
cuj =
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- MediaOutputDialogFactory.INTERACTION_JANK_TAG
+ MediaOutputDialogManager.INTERACTION_JANK_TAG
)
)
}
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 f07932c..e3be3822 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(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 043dcaa..3c073d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -193,6 +193,34 @@
}
@Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun modeEstimate_batteryPercentView_isNotNull_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ // New battery icon only uses the percent view for the estimate text
+ assertThat(mBatteryMeterView.batteryPercentView).isNotNull()
+ // Make sure that it was added to the view hierarchy
+ assertThat(mBatteryMeterView.batteryPercentView.parent).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun modePercent_batteryPercentView_isNull_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ // New battery icon only uses the percent view for the estimate text
+ assertThat(mBatteryMeterView.batteryPercentView).isNull()
+ }
+
+ @Test
fun contentDescription_manyUpdates_alwaysUpdated() {
// BatteryDefender
mBatteryMeterView.onBatteryLevelChanged(90, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 7a18477..a569cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -42,7 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -77,7 +77,8 @@
@Mock SysUiState mSysUiState;
@Mock
DialogTransitionAnimator mDialogTransitionAnimator;
- @Mock MediaOutputDialogFactory mMediaOutputDialogFactory;
+ @Mock
+ MediaOutputDialogManager mMediaOutputDialogManager;
private SystemUIDialog mDialog;
private TextView mTitle;
private TextView mSubTitle;
@@ -96,7 +97,7 @@
mBroadcastDialogDelegate = new BroadcastDialogDelegate(
mContext,
- mMediaOutputDialogFactory,
+ mMediaOutputDialogManager,
mLocalBluetoothManager,
new UiEventLoggerFake(),
mFakeExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index b25fb6e..30519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -16,14 +16,17 @@
package com.android.systemui.display.ui.view
+import android.graphics.Insets
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import android.view.WindowInsets
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -41,6 +44,7 @@
private val onStartMirroringCallback = mock<View.OnClickListener>()
private val onCancelCallback = mock<View.OnClickListener>()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -96,10 +100,40 @@
verify(onStartMirroringCallback).onClick(any())
}
+ @Test
+ fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+ dialog.show()
+
+ val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+ dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ @Test
+ fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+ dialog.show()
+
+ val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+ dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isNotEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+ return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+ }
+
@After
fun teardown() {
if (::dialog.isInitialized) {
dialog.dismiss()
}
}
+
+ private companion object {
+ const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 2f92afa..83e4d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -84,7 +84,7 @@
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.ActivityStarter
@@ -160,7 +160,7 @@
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var expandedSet: ConstraintSet
@Mock private lateinit var collapsedSet: ConstraintSet
- @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
+ @Mock private lateinit var mediaOutputDialogManager: MediaOutputDialogManager
@Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var transitionParent: ViewGroup
@@ -266,7 +266,7 @@
mediaViewController,
seekBarViewModel,
Lazy { mediaDataManager },
- mediaOutputDialogFactory,
+ mediaOutputDialogManager,
mediaCarouselController,
falsingManager,
clock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 0879884..83def8e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -42,16 +42,16 @@
private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
- private final MediaOutputDialogFactory mMockMediaOutputDialogFactory =
- mock(MediaOutputDialogFactory.class);
+ private final MediaOutputDialogManager mMockMediaOutputDialogManager =
+ mock(MediaOutputDialogManager.class);
- private final MediaOutputBroadcastDialogFactory mMockMediaOutputBroadcastDialogFactory =
- mock(MediaOutputBroadcastDialogFactory.class);
+ private final MediaOutputBroadcastDialogManager mMockMediaOutputBroadcastDialogManager =
+ mock(MediaOutputBroadcastDialogManager.class);
@Before
public void setup() {
- mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogFactory,
- mMockMediaOutputBroadcastDialogFactory);
+ mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogManager,
+ mMockMediaOutputBroadcastDialogManager);
}
@Test
@@ -60,9 +60,10 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, times(1))
- .create(getContext().getPackageName(), false, null);
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, times(1))
+ .createAndShow(getContext().getPackageName(), false, null);
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -71,8 +72,9 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -80,8 +82,9 @@
Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -92,8 +95,9 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -104,9 +108,9 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, times(1))
- .create(getContext().getPackageName(), true, null);
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, times(1))
+ .createAndShow(getContext().getPackageName(), true, null);
}
@Test
@@ -117,8 +121,9 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -128,8 +133,9 @@
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -139,8 +145,9 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -148,7 +155,8 @@
Intent intent = new Intent("UnKnown Action");
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
index dbfab64..bda0e1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -21,32 +21,25 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
- private val fakeActivityTaskManager = FakeActivityTaskManager()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val repo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
+ private val kosmos = taskSwitcherKosmos()
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val testScope = kosmos.testScope
+ private val repo = kosmos.activityTaskManagerTasksRepository
@Test
fun launchRecentTask_taskIsMovedToForeground() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index fdd434a..6043ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,50 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
import android.os.Binder
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
- private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val repo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper
- )
+ private val repo = kosmos.mediaProjectionManagerRepository
@Test
fun switchProjectedTask_stateIsUpdatedWithNewTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index dfb688b..33e65f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,55 +17,33 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherInteractor
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitchInteractorTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val interactor = kosmos.taskSwitcherInteractor
@Test
fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index c4e9393..9382c58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,26 +18,22 @@
import android.app.Notification
import android.app.NotificationManager
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
-import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -46,39 +42,16 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
private val notificationManager = mock<NotificationManager>()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
private lateinit var coordinator: TaskSwitcherNotificationCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 687f970..a468953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,64 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestCoroutineScheduler
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
- private val scheduler = TestCoroutineScheduler()
- private val dispatcher = UnconfinedTestDispatcher(scheduler)
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
@Test
fun uiState_notProjecting_emitsNotShowing() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index a59ba07..eb692eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -32,9 +32,9 @@
import android.view.LayoutInflater;
import android.view.View;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +51,7 @@
import java.util.List;
import java.util.Map;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -65,7 +66,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
+ mDependency.injectMockDependency(MediaOutputDialogManager.class);
allowTestableLooperAsMainThread();
when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
mDynamicChildBindController =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index fb49499f..718f998 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -58,7 +58,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -159,7 +159,7 @@
dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
- dependency.injectMockDependency(MediaOutputDialogFactory.class);
+ dependency.injectMockDependency(MediaOutputDialogManager.class);
mMockLogger = mock(ExpandableNotificationRowLogger.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mKeyguardBypassController = mock(KeyguardBypassController.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
new file mode 100644
index 0000000..f88bd7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationViewFlipperPausing.FLAG_NAME)
+class NotificationViewFlipperViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ val underTest
+ get() = kosmos.notificationViewFlipperViewModel
+
+ @Test
+ fun testIsPaused_falseWhenViewingShade() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN view flippers should NOT be paused
+ assertThat(isPaused).isFalse()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenViewingKeyguard() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN on keyguard
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenStartingToSleep() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ // AND device is starting to go to sleep
+ kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 3d75288..4715b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -57,39 +57,6 @@
)
}
- // region isDimmed
- @Test
- fun isDimmed_whenTrue_shouldReturnTrue() {
- sut.arrangeDimmed(true)
-
- assertThat(sut.isDimmed).isTrue()
- }
-
- @Test
- fun isDimmed_whenFalse_shouldReturnFalse() {
- sut.arrangeDimmed(false)
-
- assertThat(sut.isDimmed).isFalse()
- }
-
- @Test
- fun isDimmed_whenDozeAmountIsEmpty_shouldReturnTrue() {
- sut.arrangeDimmed(true)
- sut.dozeAmount = 0f
-
- assertThat(sut.isDimmed).isTrue()
- }
-
- @Test
- fun isDimmed_whenPulseExpandingIsFalse_shouldReturnTrue() {
- sut.arrangeDimmed(true)
- sut.arrangePulseExpanding(false)
- sut.dozeAmount = 1f // arrangePulseExpanding changes dozeAmount
-
- assertThat(sut.isDimmed).isTrue()
- }
- // endregion
-
// region pulseHeight
@Test
fun pulseHeight_whenValueChanged_shouldCallListener() {
@@ -383,12 +350,6 @@
}
// region Arrange helper methods.
-private fun AmbientState.arrangeDimmed(value: Boolean) {
- isDimmed = value
- dozeAmount = if (value) 0f else 1f
- arrangePulseExpanding(!value)
-}
-
private fun AmbientState.arrangePulseExpanding(value: Boolean) {
if (value) {
dozeAmount = 1f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index f326cea..220305c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -38,7 +38,6 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -73,11 +72,11 @@
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -97,13 +96,11 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import org.junit.Assert;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -232,6 +229,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testUpdateStackHeight_qsExpansionZero() {
final float expansionFraction = 0.2f;
final float overExpansion = 50f;
@@ -307,14 +305,6 @@
}
@Test
- public void testNotDimmedOnKeyguard() {
- when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
- mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
- mStackScroller.setDimmed(true /* dimmed */, true /* animate */);
- assertFalse(mStackScroller.isDimmed());
- }
-
- @Test
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
@@ -738,6 +728,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_noOffset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -754,6 +745,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_Offset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -773,12 +765,14 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void setFractionToShade_recomputesStackHeight() {
mStackScroller.setFractionToShade(1f);
verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
// Given: shade is not closing, scrollY is 0
mAmbientState.setScrollY(0);
@@ -877,6 +871,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSplitShade_hasTopOverscroll() {
mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -949,6 +944,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSetMaxDisplayedNotifications_notifiesListeners() {
ExpandableView.OnHeightChangedListener listener =
mock(ExpandableView.OnHeightChangedListener.class);
@@ -963,9 +959,8 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
public void testDispatchTouchEvent_sceneContainerDisabled() {
- Assume.assumeFalse(SceneContainerFlag.isEnabled());
-
MotionEvent event = MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
@@ -981,34 +976,60 @@
}
@Test
+ @EnableSceneContainer
public void testDispatchTouchEvent_sceneContainerEnabled() {
- Assume.assumeTrue(SceneContainerFlag.isEnabled());
mStackScroller.setIsBeingDragged(true);
- MotionEvent moveEvent = MotionEvent.obtain(
- SystemClock.uptimeMillis(),
- SystemClock.uptimeMillis(),
+ long downTime = SystemClock.uptimeMillis() - 100;
+ MotionEvent moveEvent1 = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE,
- 0,
- 0,
+ 101,
+ 201,
0
);
- MotionEvent syntheticDownEvent = moveEvent.copy();
+ MotionEvent syntheticDownEvent = moveEvent1.copy();
syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
- mStackScroller.dispatchTouchEvent(moveEvent);
+ mStackScroller.dispatchTouchEvent(moveEvent1);
- verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(syntheticDownEvent)));
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(syntheticDownEvent);
+ assertTrue(mStackScroller.getIsBeingDragged());
+ clearInvocations(mStackScrollLayoutController);
- mStackScroller.dispatchTouchEvent(moveEvent);
+ MotionEvent moveEvent2 = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 102,
+ 202,
+ 0
+ );
- verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+ mStackScroller.dispatchTouchEvent(moveEvent2);
+
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(moveEvent2);
+ assertTrue(mStackScroller.getIsBeingDragged());
+ clearInvocations(mStackScrollLayoutController);
+
+ MotionEvent upEvent = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 103,
+ 203,
+ 0
+ );
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(upEvent);
+ assertFalse(mStackScroller.getIsBeingDragged());
}
@Test
- @EnableFlags(FLAG_SCENE_CONTAINER)
- public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
- Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ @EnableSceneContainer
+ public void testDispatchTouchEvent_sceneContainerEnabled_ignoresInitialActionUp() {
mStackScroller.setIsBeingDragged(true);
MotionEvent upEvent = MotionEvent.obtain(
@@ -1019,21 +1040,18 @@
0,
0
);
- MotionEvent syntheticDownEvent = upEvent.copy();
- syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
mStackScroller.dispatchTouchEvent(upEvent);
-
- verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(syntheticDownEvent)));
-
- mStackScroller.dispatchTouchEvent(upEvent);
-
- verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(upEvent)));
+ verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
assertFalse(mStackScroller.getIsBeingDragged());
}
+ private MotionEvent captureTouchSentToSceneFramework() {
+ ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
+ return captor.getValue();
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -1081,20 +1099,23 @@
);
}
- private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
- private final MotionEvent mLeftEvent;
+ private MotionEventSubject assertThatMotionEvent(MotionEvent actual) {
+ return new MotionEventSubject(actual);
+ }
- MotionEventMatcher(MotionEvent leftEvent) {
- mLeftEvent = leftEvent;
+ private static class MotionEventSubject {
+ private final MotionEvent mActual;
+
+ MotionEventSubject(MotionEvent actual) {
+ mActual = actual;
}
- @Override
- public boolean matches(MotionEvent right) {
- return mLeftEvent.getActionMasked() == right.getActionMasked()
- && mLeftEvent.getDownTime() == right.getDownTime()
- && mLeftEvent.getEventTime() == right.getEventTime()
- && mLeftEvent.getX() == right.getX()
- && mLeftEvent.getY() == right.getY();
+ public void matches(MotionEvent expected) {
+ assertThat(mActual.getActionMasked()).isEqualTo(expected.getActionMasked());
+ assertThat(mActual.getDownTime()).isEqualTo(expected.getDownTime());
+ assertThat(mActual.getEventTime()).isEqualTo(expected.getEventTime());
+ assertThat(mActual.getX()).isEqualTo(expected.getX());
+ assertThat(mActual.getY()).isEqualTo(expected.getY());
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index c02583a..ab28a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -718,7 +718,8 @@
@Test
public void onPrivateProfileAdded_ignoresUntilStartComplete() {
- mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
reset(mDeviceProvisionedController);
when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
mBroadcastReceiver.getValue().onReceive(null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 3a6324d..d0261ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -69,7 +69,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.res.R;
@@ -130,7 +130,7 @@
@Mock
DeviceProvisionedController mDeviceProvisionedController;
@Mock
- MediaOutputDialogFactory mMediaOutputDialogFactory;
+ MediaOutputDialogManager mMediaOutputDialogManager;
@Mock
InteractionJankMonitor mInteractionJankMonitor;
@Mock
@@ -196,7 +196,7 @@
mAccessibilityMgr,
mDeviceProvisionedController,
mConfigurationController,
- mMediaOutputDialogFactory,
+ mMediaOutputDialogManager,
mInteractionJankMonitor,
mVolumePanelNavigationInteractor,
mVolumeNavigator,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
index e1b1966..e788669 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.media
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.util.mockito.mock
-var Kosmos.mediaOutputDialogFactory: MediaOutputDialogFactory by Kosmos.Fixture { mock {} }
+var Kosmos.mediaOutputDialogManager: MediaOutputDialogManager by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
index 920e5ee..41d2d60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.app.ActivityManager.RunningTaskInfo
import android.app.IActivityTaskManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 28393e8..2b6032c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
new file mode 100644
index 0000000..d344b75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import android.os.Handler
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+
+val Kosmos.fakeActivityTaskManager by Kosmos.Fixture { FakeActivityTaskManager() }
+
+val Kosmos.fakeMediaProjectionManager by Kosmos.Fixture { FakeMediaProjectionManager() }
+
+val Kosmos.activityTaskManagerTasksRepository by
+ Kosmos.Fixture {
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = applicationCoroutineScope,
+ backgroundDispatcher = testDispatcher
+ )
+ }
+
+val Kosmos.mediaProjectionManagerRepository by
+ Kosmos.Fixture {
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = applicationCoroutineScope,
+ tasksRepository = activityTaskManagerTasksRepository,
+ backgroundDispatcher = testDispatcher,
+ mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ )
+ }
+
+val Kosmos.taskSwitcherInteractor by
+ Kosmos.Fixture {
+ TaskSwitchInteractor(mediaProjectionManagerRepository, activityTaskManagerTasksRepository)
+ }
+
+val Kosmos.taskSwitcherViewModel by
+ Kosmos.Fixture { TaskSwitcherNotificationViewModel(taskSwitcherInteractor, testDispatcher) }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun taskSwitcherKosmos() = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
new file mode 100644
index 0000000..a2d1d93
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery
+
+import com.android.systemui.battery.BatterySaverModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsBatterySaverTileConfig by
+ Kosmos.Fixture { BatterySaverModule.provideBatterySaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
new file mode 100644
index 0000000..6772ba3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
+
+val Kosmos.qsInternetTileConfig by
+ Kosmos.Fixture { ConnectivityModule.provideInternetTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
new file mode 100644
index 0000000..7ffa262
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
+
+val Kosmos.notificationViewFlipperViewModel by Fixture {
+ NotificationViewFlipperViewModel(
+ dumpManager = dumpManager,
+ stackInteractor = notificationStackInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 5ae033c..d798b3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -30,6 +30,8 @@
private boolean mIsAodPowerSave = false;
private boolean mWirelessCharging;
private boolean mPowerSaveMode = false;
+ private boolean mIsPluggedIn = false;
+ private boolean mIsExtremePowerSave = false;
private final List<BatteryStateChangeCallback> mCallbacks = new ArrayList<>();
@@ -64,8 +66,35 @@
}
@Override
+ public boolean isExtremeSaverOn() {
+ return mIsExtremePowerSave;
+ }
+
+ /**
+ * Note: this does not affect the regular power saver. Triggers all callbacks, only on change.
+ */
+ public void setExtremeSaverOn(Boolean extremePowerSave) {
+ if (extremePowerSave == mIsExtremePowerSave) return;
+
+ mIsExtremePowerSave = extremePowerSave;
+ for (BatteryStateChangeCallback callback: mCallbacks) {
+ callback.onExtremeBatterySaverChanged(extremePowerSave);
+ }
+ }
+
+ @Override
public boolean isPluggedIn() {
- return false;
+ return mIsPluggedIn;
+ }
+
+ /**
+ * Notifies all registered callbacks
+ */
+ public void setPluggedIn(boolean pluggedIn) {
+ mIsPluggedIn = pluggedIn;
+ for (BatteryStateChangeCallback cb : mCallbacks) {
+ cb.onBatteryLevelChanged(0, pluggedIn, false);
+ }
}
@Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index a3b1a0e..3938f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -24,7 +24,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.mediaOutputDialogFactory
+import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -42,7 +42,7 @@
Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory) }
+ Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogManager) }
val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
val Kosmos.mediaOutputInteractor by
Kosmos.Fixture {
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index f31eb44..23e269a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -39,7 +39,9 @@
import android.content.SharedPreferences;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -49,16 +51,22 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import android.view.Display;
+import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -102,6 +110,9 @@
@VisibleForTesting
static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
+ @VisibleForTesting
+ static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
+
static final String EMPTY_SENTINEL = "empty";
static final String QUOTA_SENTINEL = "quota";
@@ -110,6 +121,11 @@
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
+ /**
+ * An approximate area threshold to compare device dimension similarity
+ */
+ static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+
// If this file exists, it means we exceeded our quota last time
private File mQuotaFile;
private boolean mQuotaExceeded;
@@ -121,6 +137,8 @@
private boolean mSystemHasLiveComponent;
private boolean mLockHasLiveComponent;
+ private DisplayManager mDisplayManager;
+
@Override
public void onCreate() {
if (DEBUG) {
@@ -137,6 +155,8 @@
mBackupManager = new BackupManager(getBaseContext());
mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
+
+ mDisplayManager = getSystemService(DisplayManager.class);
}
@Override
@@ -175,9 +195,11 @@
mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
+ // performing backup of each file based on order of importance
backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
+ backupDeviceInfoFile(data);
} catch (Exception e) {
Slog.e(TAG, "Unable to back up wallpaper", e);
mEventLogger.onBackupException(e);
@@ -191,6 +213,54 @@
}
}
+ /**
+ * This method backs up the device dimension information. The device data will always get
+ * overwritten when triggering a backup
+ */
+ private void backupDeviceInfoFile(FullBackupDataOutput data)
+ throws IOException {
+ final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
+
+ // save the dimensions of the device with xml formatting
+ Point dimensions = getScreenDimensions();
+ Display smallerDisplay = getSmallerDisplayIfExists();
+ Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+ new Point(0, 0);
+
+ deviceInfoStage.createNewFile();
+ FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+ out.startTag(null, "dimensions");
+
+ out.startTag(null, "width");
+ out.text(String.valueOf(dimensions.x));
+ out.endTag(null, "width");
+
+ out.startTag(null, "height");
+ out.text(String.valueOf(dimensions.y));
+ out.endTag(null, "height");
+
+ if (smallerDisplay != null) {
+ out.startTag(null, "secondarywidth");
+ out.text(String.valueOf(secondaryDimensions.x));
+ out.endTag(null, "secondarywidth");
+
+ out.startTag(null, "secondaryheight");
+ out.text(String.valueOf(secondaryDimensions.y));
+ out.endTag(null, "secondaryheight");
+ }
+
+ out.endTag(null, "dimensions");
+ out.endDocument();
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+
+ if (DEBUG) Slog.v(TAG, "Storing device dimension data");
+ backupFile(deviceInfoStage, data);
+ }
+
private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
throws IOException {
final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
@@ -364,9 +434,22 @@
final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
+ final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
boolean lockImageStageExists = lockImageStage.exists();
try {
+ // Parse the device dimensions of the source device and compare with target to
+ // to identify whether we need to skip the remainder of the restore process
+ Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
+ deviceDimensionsStage);
+
+ Point targetDeviceDimensions = getScreenDimensions();
+ if (sourceDeviceDimensions != null && targetDeviceDimensions != null
+ && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
+ targetDeviceDimensions)) {
+ Slog.d(TAG, "The source device is significantly smaller than target");
+ }
+
// First parse the live component name so that we know for logging if we care about
// logging errors with the image restore.
ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -400,6 +483,7 @@
infoStage.delete();
imageStage.delete();
lockImageStage.delete();
+ deviceDimensionsStage.delete();
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit()
@@ -409,6 +493,66 @@
}
}
+ /**
+ * This method parses the given file for the backed up device dimensions
+ *
+ * @param deviceDimensions the file which holds the device dimensions
+ * @return the backed up device dimensions
+ */
+ private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) {
+ int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0;
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(
+ new FileInputStream(deviceDimensions));
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ switch (name) {
+ case "width":
+ String widthText = readText(parser);
+ width = Integer.valueOf(widthText);
+ break;
+
+ case "height":
+ String textHeight = readText(parser);
+ height = Integer.valueOf(textHeight);
+ break;
+
+ case "secondarywidth":
+ String secondaryWidthText = readText(parser);
+ secondaryWidth = Integer.valueOf(secondaryWidthText);
+ break;
+
+ case "secondaryheight":
+ String secondaryHeightText = readText(parser);
+ secondaryHeight = Integer.valueOf(secondaryHeightText);
+ break;
+ default:
+ break;
+ }
+ }
+ return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight));
+
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static String readText(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String result = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ return result;
+ }
+
@VisibleForTesting
void updateWallpaperComponent(ComponentName wpService, int which)
throws IOException {
@@ -691,6 +835,94 @@
};
}
+ /**
+ * This method retrieves the dimensions of the largest display of the device
+ *
+ * @return a @{Point} object that contains the dimensions of the largest display on the device
+ */
+ private Point getScreenDimensions() {
+ Point largetDimensions = null;
+ int maxArea = 0;
+
+ for (Display display : getInternalDisplays()) {
+ Point displaySize = getRealSize(display);
+
+ int width = displaySize.x;
+ int height = displaySize.y;
+ int area = width * height;
+
+ if (area > maxArea) {
+ maxArea = area;
+ largetDimensions = displaySize;
+ }
+ }
+
+ return largetDimensions;
+ }
+
+ private Point getRealSize(Display display) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ display.getDisplayInfo(displayInfo);
+ return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
+ }
+
+ /**
+ * This method returns the smaller display on a multi-display device
+ *
+ * @return Display that corresponds to the smaller display on a device or null if ther is only
+ * one Display on a device
+ */
+ private Display getSmallerDisplayIfExists() {
+ List<Display> internalDisplays = getInternalDisplays();
+ Point largestDisplaySize = getScreenDimensions();
+
+ // Find the first non-matching internal display
+ for (Display display : internalDisplays) {
+ Point displaySize = getRealSize(display);
+ if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) {
+ return display;
+ }
+ }
+
+ // If no smaller display found, return null, as there is only a single display
+ return null;
+ }
+
+ /**
+ * This method retrieves the collection of Display objects available in the device.
+ * i.e. non-external displays are ignored
+ *
+ * @return list of displays corresponding to each display in the device
+ */
+ private List<Display> getInternalDisplays() {
+ Display[] allDisplays = mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+
+ List<Display> internalDisplays = new ArrayList<>();
+ for (Display display : allDisplays) {
+ if (display.getType() == Display.TYPE_INTERNAL) {
+ internalDisplays.add(display);
+ }
+ }
+ return internalDisplays;
+ }
+
+ /**
+ * This method compares the source and target dimensions, and returns true if there is a
+ * significant difference in area between them and the source dimensions are smaller than the
+ * target dimensions.
+ *
+ * @param sourceDimensions is the dimensions of the source device
+ * @param targetDimensions is the dimensions of the target device
+ */
+ @VisibleForTesting
+ boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
+ Point targetDimensions) {
+ int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
+ - (sourceDimensions.x * sourceDimensions.y);
+ return rawAreaDelta > AREA_THRESHOLD;
+ }
+
@VisibleForTesting
boolean isDeviceInRestore() {
try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f..ec9223c 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,6 +59,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
@@ -840,6 +841,26 @@
testParseCropHints(testMap);
}
+ @Test
+ public void test_sourceDimensionsAreLargerThanTarget() {
+ // source device is larger than target, expecting to get false
+ Point sourceDimensions = new Point(2208, 1840);
+ Point targetDimensions = new Point(1080, 2092);
+ boolean isSourceSmaller = mWallpaperBackupAgent
+ .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+ assertThat(isSourceSmaller).isEqualTo(false);
+ }
+
+ @Test
+ public void test_sourceDimensionsMuchSmallerThanTarget() {
+ // source device is smaller than target, expecting to get true
+ Point sourceDimensions = new Point(1080, 2092);
+ Point targetDimensions = new Point(2208, 1840);
+ boolean isSourceSmaller = mWallpaperBackupAgent
+ .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+ assertThat(isSourceSmaller).isEqualTo(true);
+ }
+
private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
assumeTrue(multiCrop());
mockRestoredStaticWallpaperFile(testMap);
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 29b9d44..1749ee3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -84,6 +84,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -98,6 +99,7 @@
import android.service.appwidget.AppWidgetServiceDumpProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AttributeSet;
@@ -148,6 +150,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -159,6 +162,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.LongSupplier;
class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
OnCrossProfileWidgetProvidersChangeListener {
@@ -187,6 +191,13 @@
// used to verify which request has successfully been received by the host.
private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
+ // Default reset interval for generated preview API rate limiting.
+ private static final long DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS =
+ Duration.ofHours(1).toMillis();
+ // Default max API calls per reset interval for generated preview API rate limiting.
+ private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
+
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -266,6 +277,8 @@
// Mark widget lifecycle broadcasts as 'interactive'
private Bundle mInteractiveBroadcast;
+ private ApiCounter mGeneratedPreviewsApiCounter;
+
AppWidgetServiceImpl(Context context) {
mContext = context;
}
@@ -294,6 +307,17 @@
mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
+ final long generatedPreviewResetInterval = DeviceConfig.getLong(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS);
+ final int generatedPreviewMaxCallsPerInterval = DeviceConfig.getInt(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL);
+ mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
+ generatedPreviewMaxCallsPerInterval);
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
+ new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
+
BroadcastOptions opts = BroadcastOptions.makeBasic();
opts.setBackgroundActivityStartsAllowed(false);
opts.setInteractive(true);
@@ -2426,7 +2450,8 @@
AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing);
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) {
+ && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
// Do not add widget providers for profiles with items restricted on home screen.
if (info != null && mUserManager
.getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) {
@@ -2480,6 +2505,7 @@
private void deleteProviderLocked(Provider provider) {
deleteWidgetsLocked(provider, UserHandle.USER_ALL);
mProviders.remove(provider);
+ mGeneratedPreviewsApiCounter.remove(provider.id);
// no need to send the DISABLE broadcast, since the receiver is gone anyway
cancelBroadcastsLocked(provider);
@@ -4004,7 +4030,7 @@
}
@Override
- public void setWidgetPreview(@NonNull ComponentName providerComponent,
+ public boolean setWidgetPreview(@NonNull ComponentName providerComponent,
@AppWidgetProviderInfo.CategoryFlags int widgetCategories,
@NonNull RemoteViews preview) {
final int userId = UserHandle.getCallingUserId();
@@ -4026,8 +4052,12 @@
throw new IllegalArgumentException(
providerComponent + " is not a valid AppWidget provider");
}
- provider.setGeneratedPreviewLocked(widgetCategories, preview);
- scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ return true;
+ }
+ return false;
}
}
@@ -4068,6 +4098,26 @@
}
}
+ private void handleSystemUiDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changed = properties.getKeyset();
+ synchronized (mLock) {
+ if (changed.contains(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS)) {
+ long resetIntervalMs = properties.getLong(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ /* defaultValue= */ mGeneratedPreviewsApiCounter.getResetIntervalMs());
+ mGeneratedPreviewsApiCounter.setResetIntervalMs(resetIntervalMs);
+ }
+ if (changed.contains(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL)) {
+ int maxCallsPerInterval = properties.getInt(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL,
+ /* defaultValue= */ mGeneratedPreviewsApiCounter.getMaxCallsPerInterval());
+ mGeneratedPreviewsApiCounter.setMaxCallsPerInterval(maxCallsPerInterval);
+ }
+ }
+ }
+
private final class CallbackHandler extends Handler {
public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4541,11 +4591,11 @@
}
}
- private static final class ProviderId {
+ static final class ProviderId {
final int uid;
final ComponentName componentName;
- private ProviderId(int uid, ComponentName componentName) {
+ ProviderId(int uid, ComponentName componentName) {
this.uid = uid;
this.componentName = componentName;
}
@@ -4788,6 +4838,96 @@
}
}
+ /**
+ * This class keeps track of API calls and implements rate limiting. One instance of this class
+ * tracks calls from all providers for one API, or a group of APIs that should share the same
+ * rate limit.
+ */
+ static final class ApiCounter {
+
+ private static final class ApiCallRecord {
+ // Number of times the API has been called for this provider.
+ public int apiCallCount = 0;
+ // The last time (from SystemClock.elapsedRealtime) the api call count was reset.
+ public long lastResetTimeMs = 0;
+
+ void reset(long nowMs) {
+ apiCallCount = 0;
+ lastResetTimeMs = nowMs;
+ }
+ }
+
+ private final Map<ProviderId, ApiCallRecord> mCallCount = new ArrayMap<>();
+ // The interval at which the call count is reset.
+ private long mResetIntervalMs;
+ // The max number of API calls per interval.
+ private int mMaxCallsPerInterval;
+ // Returns the current time (monotonic). By default this is SystemClock.elapsedRealtime.
+ private LongSupplier mMonotonicClock;
+
+ ApiCounter(long resetIntervalMs, int maxCallsPerInterval) {
+ this(resetIntervalMs, maxCallsPerInterval, SystemClock::elapsedRealtime);
+ }
+
+ ApiCounter(long resetIntervalMs, int maxCallsPerInterval,
+ LongSupplier monotonicClock) {
+ mResetIntervalMs = resetIntervalMs;
+ mMaxCallsPerInterval = maxCallsPerInterval;
+ mMonotonicClock = monotonicClock;
+ }
+
+ public void setResetIntervalMs(long resetIntervalMs) {
+ mResetIntervalMs = resetIntervalMs;
+ }
+
+ public long getResetIntervalMs() {
+ return mResetIntervalMs;
+ }
+
+ public void setMaxCallsPerInterval(int maxCallsPerInterval) {
+ mMaxCallsPerInterval = maxCallsPerInterval;
+ }
+
+ public int getMaxCallsPerInterval() {
+ return mMaxCallsPerInterval;
+ }
+
+ /**
+ * Returns true if the API call for the provider should be allowed, false if it should be
+ * rate-limited.
+ */
+ public boolean tryApiCall(@NonNull ProviderId provider) {
+ final ApiCallRecord record = getOrCreateRecord(provider);
+ final long now = mMonotonicClock.getAsLong();
+ final long timeSinceLastResetMs = now - record.lastResetTimeMs;
+ // If the last reset was beyond the reset interval, reset now.
+ if (timeSinceLastResetMs > mResetIntervalMs) {
+ record.reset(now);
+ }
+ if (record.apiCallCount < mMaxCallsPerInterval) {
+ record.apiCallCount++;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove the provider's call record from this counter, when the provider is no longer
+ * tracked.
+ */
+ public void remove(@NonNull ProviderId id) {
+ mCallCount.remove(id);
+ }
+
+ @NonNull
+ private ApiCallRecord getOrCreateRecord(@NonNull ProviderId provider) {
+ if (!mCallCount.containsKey(provider)) {
+ mCallCount.put(provider, new ApiCallRecord());
+ }
+ return mCallCount.get(provider);
+ }
+ }
+
private class LoadedWidgetState {
final Widget widget;
final int hostTag;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 551297b..7afb780 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3935,6 +3935,24 @@
*/
@GuardedBy("mLock")
@Nullable
+ private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) {
+ final int numContexts = mContexts.size();
+ for (int i = numContexts - 1; i >= 0; i--) {
+ final FillContext context = mContexts.get(i);
+ final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
+ autofillId);
+ if (node != null) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the latest non-empty value for the given id in the autofill contexts.
+ */
+ @GuardedBy("mLock")
+ @Nullable
private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) {
final int numContexts = mContexts.size();
for (int i = numContexts - 1; i >= 0; i--) {
@@ -6417,7 +6435,21 @@
mClient.onGetCredentialException(id, viewId, exception.getType(),
exception.getMessage());
} else if (response != null) {
- mClient.onGetCredentialResponse(id, viewId, response);
+ if (viewId.isVirtualInt()) {
+ ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
+ if (viewNode != null && viewNode.getCredentialManagerCallback() != null) {
+ Bundle resultData = new Bundle();
+ resultData.putParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ response);
+ viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR,
+ resultData);
+ } else {
+ Slog.w(TAG, "View node not found after GetCredentialResponse");
+ }
+ } else {
+ mClient.onGetCredentialResponse(id, viewId, response);
+ }
} else {
Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response"
+ "and exception");
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6d731b2..b1672ed 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -210,7 +210,7 @@
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -220,7 +220,7 @@
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -229,7 +229,7 @@
try {
mActivityListener.onDisplayEmpty(displayId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
};
@@ -1213,7 +1213,7 @@
mContext.startActivityAsUser(
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
- mContext.getUser());
+ UserHandle.SYSTEM);
}
private void onSecureWindowShown(int displayId, int uid) {
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f82a6aa..748253f 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -106,7 +106,7 @@
private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
private static final int DEFAULT_MAX_FILES = 1000;
private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
- private static final int DEFAULT_QUOTA_KB = 10 * 1024;
+ private static final int DEFAULT_QUOTA_KB = Build.IS_USERDEBUG ? 20 * 1024 : 10 * 1024;
private static final int DEFAULT_QUOTA_PERCENT = 10;
private static final int DEFAULT_RESERVE_PERCENT = 0;
private static final int QUOTA_RESCAN_MILLIS = 5000;
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 8dc15ad..25337a4 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -100,7 +100,7 @@
"file_patterns": ["VcnManagementService\\.java"]
},
{
- "name": "FrameworksNetTests",
+ "name": "FrameworksVpnTests",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
@@ -163,9 +163,6 @@
}
],
"file_patterns": ["PinnerService\\.java"]
- },
- {
- "name": "FrameworksVpnTests"
}
]
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cfe1e18..5e36709 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -141,7 +141,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -726,29 +725,19 @@
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
- // Use an offload queue for long broadcasts, e.g. BOOT_COMPLETED.
- // For simplicity, since we statically declare the size of the array of BroadcastQueues,
- // we still create this new offload queue, but never ever put anything on it.
- final boolean mEnableOffloadQueue;
-
/**
* Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
* of the default {@link BroadcastQueueImpl}.
*/
final boolean mEnableModernQueue;
- static final int BROADCAST_QUEUE_FG = 0;
- static final int BROADCAST_QUEUE_BG = 1;
- static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
- static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
-
@GuardedBy("this")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
// Convenient for easy iteration over the queues. Foreground is first
// so that dispatch of foreground broadcasts gets precedence.
- final BroadcastQueue[] mBroadcastQueues;
+ private BroadcastQueue mBroadcastQueue;
@GuardedBy("this")
BroadcastStats mLastBroadcastStats;
@@ -758,43 +747,6 @@
TraceErrorLogger mTraceErrorLogger;
- BroadcastQueue broadcastQueueForIntent(Intent intent) {
- return broadcastQueueForFlags(intent.getFlags(), intent);
- }
-
- BroadcastQueue broadcastQueueForFlags(int flags) {
- return broadcastQueueForFlags(flags, null);
- }
-
- BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) {
- if (mEnableModernQueue) {
- return mBroadcastQueues[0];
- }
-
- if (isOnFgOffloadQueue(flags)) {
- if (DEBUG_BROADCAST_BACKGROUND) {
- Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on foreground offload queue");
- }
- return mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD];
- }
-
- if (isOnBgOffloadQueue(flags)) {
- if (DEBUG_BROADCAST_BACKGROUND) {
- Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on background offload queue");
- }
- return mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD];
- }
-
- final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
- if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on "
- + (isFg ? "foreground" : "background") + " queue");
- return (isFg) ? mBroadcastQueues[BROADCAST_QUEUE_FG]
- : mBroadcastQueues[BROADCAST_QUEUE_BG];
- }
-
private volatile int mDeviceOwnerUid = INVALID_UID;
/**
@@ -2556,9 +2508,8 @@
mInternal = new LocalService();
mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
- mEnableOffloadQueue = false;
mEnableModernQueue = false;
- mBroadcastQueues = injector.getBroadcastQueues(this);
+ mBroadcastQueue = injector.getBroadcastQueue(this);
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -2599,12 +2550,10 @@
? new OomAdjusterModernImpl(this, mProcessList, activeUids)
: new OomAdjuster(this, mProcessList, activeUids);
- mEnableOffloadQueue = SystemProperties.getBoolean(
- "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
mEnableModernQueue = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS).MODERN_QUEUE_ENABLED;
- mBroadcastQueues = mInjector.getBroadcastQueues(this);
+ mBroadcastQueue = mInjector.getBroadcastQueue(this);
mServices = new ActiveServices(this);
mCpHelper = new ContentProviderHelper(this, true);
@@ -2671,6 +2620,14 @@
mComponentAliasResolver = new ComponentAliasResolver(this);
}
+ void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+ mBroadcastQueue = broadcastQueue;
+ }
+
+ BroadcastQueue getBroadcastQueue() {
+ return mBroadcastQueue;
+ }
+
public void setSystemServiceManager(SystemServiceManager mgr) {
mSystemServiceManager = mgr;
}
@@ -4280,19 +4237,14 @@
}
// Clean-up disabled broadcast receivers.
- for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
- mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- packageName, disabledClasses, userId);
- }
+ mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+ packageName, disabledClasses, userId);
}
final boolean clearBroadcastQueueForUserLocked(int userId) {
- boolean didSomething = false;
- for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
- didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- null, null, userId);
- }
+ boolean didSomething = mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+ null, null, userId);
return didSomething;
}
@@ -4445,10 +4397,8 @@
mUgmInternal.removeUriPermissionsForPackage(packageName, userId, false, false);
if (doit) {
- for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
- didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
+ didSomething |= mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
packageName, null, userId);
- }
}
if (packageName == null || uninstalling || packageStateStopped) {
@@ -4515,9 +4465,7 @@
// Take care of any services that are waiting for the process.
mServices.processStartTimedOutLocked(app);
// Take care of any broadcasts waiting for the process.
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.onApplicationTimeoutLocked(app);
- }
+ mBroadcastQueue.onApplicationTimeoutLocked(app);
if (!isKillTimeout) {
mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
app.killLocked("start timeout",
@@ -4959,9 +4907,7 @@
// Check if a next-broadcast receiver is in this process...
if (!badApp) {
try {
- for (BroadcastQueue queue : mBroadcastQueues) {
- didSomething |= queue.onApplicationAttachedLocked(app);
- }
+ didSomething |= mBroadcastQueue.onApplicationAttachedLocked(app);
checkTime(startTime, "finishAttachApplicationInner: "
+ "after dispatching broadcasts");
} catch (BroadcastDeliveryFailedException e) {
@@ -9101,9 +9047,7 @@
}
private void startBroadcastObservers() {
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.start(mContext.getContentResolver());
- }
+ mBroadcastQueue.start(mContext.getContentResolver());
}
private void updateForceBackgroundCheck(boolean enabled) {
@@ -11509,9 +11453,7 @@
}
}
mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
- for (BroadcastQueue q : mBroadcastQueues) {
- q.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
- }
+ mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
synchronized (mStickyBroadcasts) {
for (int user = 0; user < mStickyBroadcasts.size(); user++) {
long token = proto.start(
@@ -11661,11 +11603,9 @@
}
if (!onlyReceivers) {
- for (BroadcastQueue q : mBroadcastQueues) {
- needSep = q.dumpLocked(fd, pw, args, opti,
- dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
- printedAnything |= needSep;
- }
+ needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+ dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+ printedAnything |= needSep;
}
needSep = true;
@@ -11721,9 +11661,8 @@
if (!onlyHistory && !onlyReceivers && dumpAll) {
pw.println();
- for (BroadcastQueue queue : mBroadcastQueues) {
- pw.println(" Queue " + queue.toString() + ": " + queue.describeStateLocked());
- }
+ pw.println(" Queue " + mBroadcastQueue.toString() + ": "
+ + mBroadcastQueue.describeStateLocked());
pw.println(" mHandler:");
mHandler.dump(new PrintWriterPrinter(pw), " ");
needSep = true;
@@ -13571,9 +13510,7 @@
mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
}
mAppProfiler.onCleanupApplicationRecordLocked(app);
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.onApplicationCleanupLocked(app);
- }
+ mBroadcastQueue.onApplicationCleanupLocked(app);
clearProcessForegroundLocked(app);
mServices.killServicesLocked(app, allowRestart);
mPhantomProcessList.onAppDied(pid);
@@ -14687,7 +14624,7 @@
originalStickyCallingUid))) {
sticky = broadcast.intent;
}
- BroadcastQueue queue = broadcastQueueForIntent(broadcast.intent);
+ BroadcastQueue queue = mBroadcastQueue;
BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE,
BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
@@ -15686,7 +15623,7 @@
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, registeredReceivers);
}
- final BroadcastQueue queue = broadcastQueueForIntent(intent);
+ final BroadcastQueue queue = mBroadcastQueue;
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
@@ -15779,7 +15716,7 @@
if ((receivers != null && receivers.size() > 0)
|| resultTo != null) {
- BroadcastQueue queue = broadcastQueueForIntent(intent);
+ BroadcastQueue queue = mBroadcastQueue;
filterNonExportedComponents(intent, callingUid, callingPid, receivers,
mPlatformCompat, callerPackage, resolvedType);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
@@ -16067,9 +16004,7 @@
}
void backgroundServicesFinishedLocked(int userId) {
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.backgroundServicesFinishedLocked(userId);
- }
+ mBroadcastQueue.backgroundServicesFinishedLocked(userId);
}
public void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -16090,8 +16025,7 @@
return;
}
- final BroadcastQueue queue = broadcastQueueForFlags(flags);
- queue.finishReceiverLocked(callerApp, resultCode,
+ mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
resultData, resultExtras, resultAbort, true);
// updateOomAdjLocked() will be done here
trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -16682,10 +16616,7 @@
// =========================================================
boolean isReceivingBroadcastLocked(ProcessRecord app, int[] outSchedGroup) {
- int res = ProcessList.SCHED_GROUP_UNDEFINED;
- for (BroadcastQueue queue : mBroadcastQueues) {
- res = Math.max(res, queue.getPreferredSchedulingGroupLocked(app));
- }
+ final int res = mBroadcastQueue.getPreferredSchedulingGroupLocked(app);
outSchedGroup[0] = res;
return res != ProcessList.SCHED_GROUP_UNDEFINED;
}
@@ -16809,10 +16740,8 @@
*/
@GuardedBy("this")
final boolean canGcNowLocked() {
- for (BroadcastQueue q : mBroadcastQueues) {
- if (!q.isIdleLocked()) {
- return false;
- }
+ if (!mBroadcastQueue.isIdleLocked()) {
+ return false;
}
return mAtmInternal.canGcNow();
}
@@ -18022,9 +17951,7 @@
}
void onProcessFreezableChangedLocked(ProcessRecord app) {
- if (mEnableModernQueue) {
- mBroadcastQueues[0].onProcessFreezableChangedLocked(app);
- }
+ mBroadcastQueue.onProcessFreezableChangedLocked(app);
}
@VisibleForTesting
@@ -19715,9 +19642,7 @@
if (flushBroadcastLoopers) {
BroadcastLoopers.waitForIdle(pw);
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForIdle(pw);
- }
+ mBroadcastQueue.waitForIdle(pw);
pw.println("All broadcast queues are idle!");
pw.flush();
}
@@ -19733,9 +19658,7 @@
if (flushBroadcastLoopers) {
BroadcastLoopers.waitForBarrier(pw);
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForBarrier(pw);
- }
+ mBroadcastQueue.waitForBarrier(pw);
if (flushApplicationThreads) {
waitForApplicationBarrier(pw);
}
@@ -19811,9 +19734,7 @@
void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch");
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForDispatched(intent, pw);
- }
+ mBroadcastQueue.waitForDispatched(intent, pw);
}
void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
@@ -19858,9 +19779,7 @@
return;
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
- }
+ mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
}
@Override
@@ -20414,7 +20333,7 @@
return mNmi != null;
}
- public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
+ public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
// Broadcast policy parameters
final BroadcastConstants foreConstants = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS);
@@ -20430,26 +20349,8 @@
// by default, no "slow" policy in this queue
offloadConstants.SLOW_TIME = Integer.MAX_VALUE;
- final BroadcastQueue[] broadcastQueues;
- final Handler handler = service.mHandler;
- if (service.mEnableModernQueue) {
- broadcastQueues = new BroadcastQueue[1];
- broadcastQueues[0] = new BroadcastQueueModernImpl(service, handler,
+ return new BroadcastQueueModernImpl(service, service.mHandler,
foreConstants, backConstants);
- } else {
- broadcastQueues = new BroadcastQueue[4];
- broadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(service, handler,
- "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
- broadcastQueues[BROADCAST_QUEUE_BG] = new BroadcastQueueImpl(service, handler,
- "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
- broadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD] = new BroadcastQueueImpl(service,
- handler, "offload_bg", offloadConstants, true,
- ProcessList.SCHED_GROUP_BACKGROUND);
- broadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD] = new BroadcastQueueImpl(service,
- handler, "offload_fg", foreConstants, true,
- ProcessList.SCHED_GROUP_BACKGROUND);
- }
- return broadcastQueues;
}
/** @see Binder#getCallingUid */
@@ -20771,14 +20672,6 @@
}
}
- private boolean isOnFgOffloadQueue(int flags) {
- return ((flags & Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND) != 0);
- }
-
- private boolean isOnBgOffloadQueue(int flags) {
- return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0));
- }
-
@Override
public ParcelFileDescriptor getLifeMonitor() {
if (!isCallerShell()) {
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
deleted file mode 100644
index 8aa3921..0000000
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ /dev/null
@@ -1,1266 +0,0 @@
-/*
- * Copyright (C) 2018 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.am;
-
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UptimeMillisLong;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.AlarmManagerInternal;
-import com.android.server.LocalServices;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-
-/**
- * Manages ordered broadcast delivery, applying policy to mitigate the effects of
- * slow receivers.
- */
-public class BroadcastDispatcher {
- private static final String TAG = "BroadcastDispatcher";
-
- // Deferred broadcasts to one app; times are all uptime time base like
- // other broadcast-related timekeeping
- static class Deferrals {
- final int uid;
- long deferredAt; // when we started deferring
- long deferredBy; // how long did we defer by last time?
- long deferUntil; // when does the next element become deliverable?
- int alarmCount;
-
- final ArrayList<BroadcastRecord> broadcasts;
-
- Deferrals(int uid, long now, long backoff, int count) {
- this.uid = uid;
- this.deferredAt = now;
- this.deferredBy = backoff;
- this.deferUntil = now + backoff;
- this.alarmCount = count;
- broadcasts = new ArrayList<>();
- }
-
- void add(BroadcastRecord br) {
- broadcasts.add(br);
- }
-
- int size() {
- return broadcasts.size();
- }
-
- boolean isEmpty() {
- return broadcasts.isEmpty();
- }
-
- @NeverCompile
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- for (BroadcastRecord br : broadcasts) {
- br.dumpDebug(proto, fieldId);
- }
- }
-
- @NeverCompile
- void dumpLocked(Dumper d) {
- for (BroadcastRecord br : broadcasts) {
- d.dump(br);
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Deferrals{uid=");
- sb.append(uid);
- sb.append(", deferUntil=");
- sb.append(deferUntil);
- sb.append(", #broadcasts=");
- sb.append(broadcasts.size());
- sb.append("}");
- return sb.toString();
- }
- }
-
- // Carrying dump formatting state across multiple concatenated datasets
- class Dumper {
- final PrintWriter mPw;
- final String mQueueName;
- final String mDumpPackage;
- final SimpleDateFormat mSdf;
- boolean mPrinted;
- boolean mNeedSep;
- String mHeading;
- String mLabel;
- int mOrdinal;
-
- Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) {
- mPw = pw;
- mQueueName = queueName;
- mDumpPackage = dumpPackage;
- mSdf = sdf;
-
- mPrinted = false;
- mNeedSep = true;
- }
-
- void setHeading(String heading) {
- mHeading = heading;
- mPrinted = false;
- }
-
- void setLabel(String label) {
- //" Active Ordered Broadcast " + mQueueName + " #" + i + ":"
- mLabel = " " + label + " " + mQueueName + " #";
- mOrdinal = 0;
- }
-
- boolean didPrint() {
- return mPrinted;
- }
-
- @NeverCompile
- void dump(BroadcastRecord br) {
- if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
- if (!mPrinted) {
- if (mNeedSep) {
- mPw.println();
- }
- mPrinted = true;
- mNeedSep = true;
- mPw.println(" " + mHeading + " [" + mQueueName + "]:");
- }
- mPw.println(mLabel + mOrdinal + ":");
- mOrdinal++;
-
- br.dump(mPw, " ", mSdf);
- }
- }
- }
-
- private final Object mLock;
- private final BroadcastQueueImpl mQueue;
- private final BroadcastConstants mConstants;
- private final Handler mHandler;
- private AlarmManagerInternal mAlarm;
-
- // Current alarm targets; mapping uid -> in-flight alarm count
- final SparseIntArray mAlarmUids = new SparseIntArray();
- final AlarmManagerInternal.InFlightListener mAlarmListener =
- new AlarmManagerInternal.InFlightListener() {
- @Override
- public void broadcastAlarmPending(final int recipientUid) {
- synchronized (mLock) {
- final int newCount = mAlarmUids.get(recipientUid, 0) + 1;
- mAlarmUids.put(recipientUid, newCount);
- // any deferred broadcasts to this app now get fast-tracked
- final int numEntries = mDeferredBroadcasts.size();
- for (int i = 0; i < numEntries; i++) {
- if (recipientUid == mDeferredBroadcasts.get(i).uid) {
- Deferrals d = mDeferredBroadcasts.remove(i);
- mAlarmDeferrals.add(d);
- break;
- }
- }
- }
- }
-
- @Override
- public void broadcastAlarmComplete(final int recipientUid) {
- synchronized (mLock) {
- final int newCount = mAlarmUids.get(recipientUid, 0) - 1;
- if (newCount >= 0) {
- mAlarmUids.put(recipientUid, newCount);
- } else {
- Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid);
- mAlarmUids.put(recipientUid, 0);
- }
-
- // No longer an alarm target, so resume ordinary deferral policy
- if (newCount <= 0) {
- final int numEntries = mAlarmDeferrals.size();
- for (int i = 0; i < numEntries; i++) {
- if (recipientUid == mAlarmDeferrals.get(i).uid) {
- Deferrals d = mAlarmDeferrals.remove(i);
- insertLocked(mDeferredBroadcasts, d);
- break;
- }
- }
- }
- }
- }
- };
-
- // Queue recheck operation used to tickle broadcast delivery when appropriate
- final Runnable mScheduleRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.v(TAG, "Deferral recheck of pending broadcasts");
- }
- mQueue.scheduleBroadcastsLocked();
- mRecheckScheduled = false;
- }
- }
- };
- private boolean mRecheckScheduled = false;
-
- // Usual issuance-order outbound queue
- private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
- // General deferrals not holding up alarms
- private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>();
- // Deferrals that *are* holding up alarms; ordered by alarm dispatch time
- private final ArrayList<Deferrals> mAlarmDeferrals = new ArrayList<>();
- // Under the "deliver alarm broadcasts immediately" policy, the queue of
- // upcoming alarm broadcasts. These are always delivered first - if the
- // policy is changed on the fly from immediate-alarm-delivery to the previous
- // in-order-queueing behavior, pending immediate alarm deliveries will drain
- // and then the behavior settle into the pre-U semantics.
- private final ArrayList<BroadcastRecord> mAlarmQueue = new ArrayList<>();
-
- // Next outbound broadcast, established by getNextBroadcastLocked()
- private BroadcastRecord mCurrentBroadcast;
-
- // Map userId to its deferred boot completed broadcasts.
- private SparseArray<DeferredBootCompletedBroadcastPerUser> mUser2Deferred = new SparseArray<>();
-
- /**
- * Deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts that is sent to a user.
- */
- static class DeferredBootCompletedBroadcastPerUser {
- private int mUserId;
- // UID that has process started at least once, ready to execute LOCKED_BOOT_COMPLETED
- // receivers.
- @VisibleForTesting
- SparseBooleanArray mUidReadyForLockedBootCompletedBroadcast = new SparseBooleanArray();
- // UID that has process started at least once, ready to execute BOOT_COMPLETED receivers.
- @VisibleForTesting
- SparseBooleanArray mUidReadyForBootCompletedBroadcast = new SparseBooleanArray();
- // Map UID to deferred LOCKED_BOOT_COMPLETED broadcasts.
- // LOCKED_BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has
- // any process started.
- @VisibleForTesting
- SparseArray<BroadcastRecord> mDeferredLockedBootCompletedBroadcasts = new SparseArray<>();
- // is the LOCKED_BOOT_COMPLETED broadcast received by the user.
- @VisibleForTesting
- boolean mLockedBootCompletedBroadcastReceived;
- // Map UID to deferred BOOT_COMPLETED broadcasts.
- // BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has any
- // process started.
- @VisibleForTesting
- SparseArray<BroadcastRecord> mDeferredBootCompletedBroadcasts = new SparseArray<>();
- // is the BOOT_COMPLETED broadcast received by the user.
- @VisibleForTesting
- boolean mBootCompletedBroadcastReceived;
-
- DeferredBootCompletedBroadcastPerUser(int userId) {
- this.mUserId = userId;
- }
-
- public void updateUidReady(int uid) {
- if (!mLockedBootCompletedBroadcastReceived
- || mDeferredLockedBootCompletedBroadcasts.size() != 0) {
- mUidReadyForLockedBootCompletedBroadcast.put(uid, true);
- }
- if (!mBootCompletedBroadcastReceived
- || mDeferredBootCompletedBroadcasts.size() != 0) {
- mUidReadyForBootCompletedBroadcast.put(uid, true);
- }
- }
-
- public void enqueueBootCompletedBroadcasts(String action,
- SparseArray<BroadcastRecord> deferred) {
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
- enqueueBootCompletedBroadcasts(deferred, mDeferredLockedBootCompletedBroadcasts,
- mUidReadyForLockedBootCompletedBroadcast);
- mLockedBootCompletedBroadcastReceived = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- dumpBootCompletedBroadcastRecord(mDeferredLockedBootCompletedBroadcasts);
- }
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- enqueueBootCompletedBroadcasts(deferred, mDeferredBootCompletedBroadcasts,
- mUidReadyForBootCompletedBroadcast);
- mBootCompletedBroadcastReceived = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- dumpBootCompletedBroadcastRecord(mDeferredBootCompletedBroadcasts);
- }
- }
- }
-
- /**
- * Merge UID to BroadcastRecord map into {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- * @param from the UID to BroadcastRecord map.
- * @param into The UID to list of BroadcastRecord map.
- */
- private void enqueueBootCompletedBroadcasts(SparseArray<BroadcastRecord> from,
- SparseArray<BroadcastRecord> into, SparseBooleanArray uidReadyForReceiver) {
- // remove unwanted uids from uidReadyForReceiver.
- for (int i = uidReadyForReceiver.size() - 1; i >= 0; i--) {
- if (from.indexOfKey(uidReadyForReceiver.keyAt(i)) < 0) {
- uidReadyForReceiver.removeAt(i);
- }
- }
- for (int i = 0, size = from.size(); i < size; i++) {
- final int uid = from.keyAt(i);
- into.put(uid, from.valueAt(i));
- if (uidReadyForReceiver.indexOfKey(uid) < 0) {
- // uid is wanted but not ready.
- uidReadyForReceiver.put(uid, false);
- }
- }
- }
-
- public @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
- boolean isAllUidReady) {
- BroadcastRecord next = dequeueDeferredBootCompletedBroadcast(
- mDeferredLockedBootCompletedBroadcasts,
- mUidReadyForLockedBootCompletedBroadcast, isAllUidReady);
- if (next == null) {
- next = dequeueDeferredBootCompletedBroadcast(mDeferredBootCompletedBroadcasts,
- mUidReadyForBootCompletedBroadcast, isAllUidReady);
- }
- return next;
- }
-
- private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
- SparseArray<BroadcastRecord> uid2br, SparseBooleanArray uidReadyForReceiver,
- boolean isAllUidReady) {
- for (int i = 0, size = uid2br.size(); i < size; i++) {
- final int uid = uid2br.keyAt(i);
- if (isAllUidReady || uidReadyForReceiver.get(uid)) {
- final BroadcastRecord br = uid2br.valueAt(i);
- if (DEBUG_BROADCAST_DEFERRAL) {
- final Object receiver = br.receivers.get(0);
- if (receiver instanceof BroadcastFilter) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
- + " BroadcastFilter:" + (BroadcastFilter) receiver
- + " broadcast:" + br.intent.getAction());
- }
- } else /* if (receiver instanceof ResolveInfo) */ {
- ResolveInfo info = (ResolveInfo) receiver;
- String packageName = info.activityInfo.applicationInfo.packageName;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
- + " packageName:" + packageName
- + " broadcast:" + br.intent.getAction());
- }
- }
- }
- // remove the BroadcastRecord.
- uid2br.removeAt(i);
- if (uid2br.size() == 0) {
- // All deferred receivers are executed, do not need uidReadyForReceiver
- // any more.
- uidReadyForReceiver.clear();
- }
- return br;
- }
- }
- return null;
- }
-
- private @Nullable SparseArray<BroadcastRecord> getDeferredList(String action) {
- SparseArray<BroadcastRecord> brs = null;
- if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
- brs = mDeferredLockedBootCompletedBroadcasts;
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- brs = mDeferredBootCompletedBroadcasts;
- }
- return brs;
- }
-
- /**
- * Return the total number of UIDs in all BroadcastRecord in
- * {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- */
- private int getBootCompletedBroadcastsUidsSize(String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- return brs != null ? brs.size() : 0;
- }
-
- /**
- * Return the total number of receivers in all BroadcastRecord in
- * {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- */
- private int getBootCompletedBroadcastsReceiversSize(String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- if (brs == null) {
- return 0;
- }
- int size = 0;
- for (int i = 0, s = brs.size(); i < s; i++) {
- size += brs.valueAt(i).receivers.size();
- }
- return size;
- }
-
- @NeverCompile
- public void dump(Dumper dumper, String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- if (brs == null) {
- return;
- }
- for (int i = 0, size = brs.size(); i < size; i++) {
- dumper.dump(brs.valueAt(i));
- }
- }
-
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) {
- mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
- }
- for (int i = 0, size = mDeferredBootCompletedBroadcasts.size(); i < size; i++) {
- mDeferredBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
- }
- }
-
- @NeverCompile
- private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) {
- for (int i = 0, size = brs.size(); i < size; i++) {
- final Object receiver = brs.valueAt(i).receivers.get(0);
- String packageName = null;
- if (receiver instanceof BroadcastFilter) {
- BroadcastFilter recv = (BroadcastFilter) receiver;
- packageName = recv.receiverList.app.processName;
- } else /* if (receiver instanceof ResolveInfo) */ {
- ResolveInfo info = (ResolveInfo) receiver;
- packageName = info.activityInfo.applicationInfo.packageName;
- }
- Slog.i(TAG, "uid:" + brs.keyAt(i)
- + " packageName:" + packageName
- + " receivers:" + brs.valueAt(i).receivers.size());
- }
- }
- }
-
- private DeferredBootCompletedBroadcastPerUser getDeferredPerUser(int userId) {
- if (mUser2Deferred.contains(userId)) {
- return mUser2Deferred.get(userId);
- } else {
- final DeferredBootCompletedBroadcastPerUser temp =
- new DeferredBootCompletedBroadcastPerUser(userId);
- mUser2Deferred.put(userId, temp);
- return temp;
- }
- }
-
- /**
- * ActivityManagerService.attachApplication() call this method to notify that the UID is ready
- * to accept deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts.
- * @param uid
- */
- public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
- getDeferredPerUser(UserHandle.getUserId(uid)).updateUidReady(uid);
- }
-
- private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast() {
- final boolean isAllUidReady = (mQueue.mService.mConstants.mDeferBootCompletedBroadcast
- == DEFER_BOOT_COMPLETED_BROADCAST_NONE);
- BroadcastRecord next = null;
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- next = mUser2Deferred.valueAt(i).dequeueDeferredBootCompletedBroadcast(isAllUidReady);
- if (next != null) {
- break;
- }
- }
- return next;
- }
-
- /**
- * Constructed & sharing a lock with its associated BroadcastQueue instance
- */
- public BroadcastDispatcher(BroadcastQueueImpl queue, BroadcastConstants constants,
- Handler handler, Object lock) {
- mQueue = queue;
- mConstants = constants;
- mHandler = handler;
- mLock = lock;
- }
-
- /**
- * Spin up the integration with the alarm manager service; done lazily to manage
- * service availability ordering during boot.
- */
- public void start() {
- // Set up broadcast alarm tracking
- mAlarm = LocalServices.getService(AlarmManagerInternal.class);
- mAlarm.registerInFlightListener(mAlarmListener);
- }
-
- /**
- * Standard contents-are-empty check
- */
- public boolean isEmpty() {
- synchronized (mLock) {
- return isIdle()
- && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0
- && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0;
- }
- }
-
- /**
- * Have less check than {@link #isEmpty()}.
- * The dispatcher is considered as idle even with deferred LOCKED_BOOT_COMPLETED/BOOT_COMPLETED
- * broadcasts because those can be deferred until the first time the uid's process is started.
- * @return
- */
- public boolean isIdle() {
- synchronized (mLock) {
- return mCurrentBroadcast == null
- && mOrderedBroadcasts.isEmpty()
- && mAlarmQueue.isEmpty()
- && isDeferralsListEmpty(mDeferredBroadcasts)
- && isDeferralsListEmpty(mAlarmDeferrals);
- }
- }
-
- private static boolean isDeferralsBeyondBarrier(@NonNull ArrayList<Deferrals> list,
- @UptimeMillisLong long barrierTime) {
- for (int i = 0; i < list.size(); i++) {
- if (!isBeyondBarrier(list.get(i).broadcasts, barrierTime)) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isBeyondBarrier(@NonNull ArrayList<BroadcastRecord> list,
- @UptimeMillisLong long barrierTime) {
- for (int i = 0; i < list.size(); i++) {
- if (list.get(i).enqueueTime <= barrierTime) {
- return false;
- }
- }
- return true;
- }
-
- public boolean isBeyondBarrier(@UptimeMillisLong long barrierTime) {
- synchronized (mLock) {
- if ((mCurrentBroadcast != null) && mCurrentBroadcast.enqueueTime <= barrierTime) {
- return false;
- }
- return isBeyondBarrier(mOrderedBroadcasts, barrierTime)
- && isBeyondBarrier(mAlarmQueue, barrierTime)
- && isDeferralsBeyondBarrier(mDeferredBroadcasts, barrierTime)
- && isDeferralsBeyondBarrier(mAlarmDeferrals, barrierTime);
- }
- }
-
- private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list,
- @NonNull Intent intent) {
- for (int i = 0; i < list.size(); i++) {
- if (!isDispatched(list.get(i).broadcasts, intent)) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list,
- @NonNull Intent intent) {
- for (int i = 0; i < list.size(); i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- return false;
- }
- }
- return true;
- }
-
- public boolean isDispatched(@NonNull Intent intent) {
- synchronized (mLock) {
- if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) {
- return false;
- }
- return isDispatched(mOrderedBroadcasts, intent)
- && isDispatched(mAlarmQueue, intent)
- && isDispatchedInDeferrals(mDeferredBroadcasts, intent)
- && isDispatchedInDeferrals(mAlarmDeferrals, intent);
- }
- }
-
- private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
- int pending = 0;
- final int numEntries = list.size();
- for (int i = 0; i < numEntries; i++) {
- pending += list.get(i).size();
- }
- return pending;
- }
-
- private static boolean isDeferralsListEmpty(ArrayList<Deferrals> list) {
- return pendingInDeferralsList(list) == 0;
- }
-
- /**
- * Strictly for logging, describe the currently pending contents in a human-
- * readable way
- */
- public String describeStateLocked() {
- final StringBuilder sb = new StringBuilder(128);
- if (mCurrentBroadcast != null) {
- sb.append("1 in flight, ");
- }
- sb.append(mOrderedBroadcasts.size());
- sb.append(" ordered");
- int n = mAlarmQueue.size();
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" alarms");
- }
- n = pendingInDeferralsList(mAlarmDeferrals);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferrals in alarm recipients");
- }
- n = pendingInDeferralsList(mDeferredBroadcasts);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred");
- }
- n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred LOCKED_BOOT_COMPLETED/");
- sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_LOCKED_BOOT_COMPLETED));
- sb.append(" receivers");
- }
-
- n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred BOOT_COMPLETED/");
- sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_BOOT_COMPLETED));
- sb.append(" receivers");
- }
- return sb.toString();
- }
-
- // ----------------------------------
- // BroadcastQueue operation support
- void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
- final ArrayList<BroadcastRecord> queue =
- (r.alarm && mQueue.mService.mConstants.mPrioritizeAlarmBroadcasts)
- ? mAlarmQueue
- : mOrderedBroadcasts;
-
- if (r.receivers == null || r.receivers.isEmpty()) {
- // Fast no-op path for broadcasts that won't actually be dispatched to
- // receivers - we still need to handle completion callbacks and historical
- // records, but we don't need to consider the fancy cases.
- queue.add(r);
- return;
- }
-
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(r.intent.getAction())) {
- // Create one BroadcastRecord for each UID that can be deferred.
- final SparseArray<BroadcastRecord> deferred =
- r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
- mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
- getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
- Intent.ACTION_LOCKED_BOOT_COMPLETED, deferred);
- if (!r.receivers.isEmpty()) {
- // The non-deferred receivers.
- mOrderedBroadcasts.add(r);
- return;
- }
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(r.intent.getAction())) {
- // Create one BroadcastRecord for each UID that can be deferred.
- final SparseArray<BroadcastRecord> deferred =
- r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
- mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
- getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
- Intent.ACTION_BOOT_COMPLETED, deferred);
- if (!r.receivers.isEmpty()) {
- // The non-deferred receivers.
- mOrderedBroadcasts.add(r);
- return;
- }
- } else {
- // Ordinary broadcast, so put it on the appropriate queue and carry on
- queue.add(r);
- }
- }
-
- /**
- * Return the total number of UIDs in all deferred boot completed BroadcastRecord.
- */
- private int getBootCompletedBroadcastsUidsSize(String action) {
- int size = 0;
- for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
- size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsUidsSize(action);
- }
- return size;
- }
-
- /**
- * Return the total number of receivers in all deferred boot completed BroadcastRecord.
- */
- private int getBootCompletedBroadcastsReceiversSize(String action) {
- int size = 0;
- for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
- size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsReceiversSize(action);
- }
- return size;
- }
-
- // Returns the now-replaced broadcast record, or null if none
- BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) {
- // Simple case, in the ordinary queue.
- BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging);
- // ... or possibly in the simple alarm queue
- if (old == null) {
- old = replaceBroadcastLocked(mAlarmQueue, r, typeForLogging);
- }
- // If we didn't find it, less-simple: in a deferral queue?
- if (old == null) {
- old = replaceDeferredBroadcastLocked(mAlarmDeferrals, r, typeForLogging);
- }
- if (old == null) {
- old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging);
- }
- return old;
- }
-
- private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list,
- BroadcastRecord r, String typeForLogging) {
- BroadcastRecord old;
- final int numEntries = list.size();
- for (int i = 0; i < numEntries; i++) {
- final Deferrals d = list.get(i);
- old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging);
- if (old != null) {
- return old;
- }
- }
- return null;
- }
-
- private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list,
- BroadcastRecord r, String typeForLogging) {
- BroadcastRecord old;
- final Intent intent = r.intent;
- // Any in-flight broadcast has already been popped, and cannot be replaced.
- // (This preserves existing behavior of the replacement API)
- for (int i = list.size() - 1; i >= 0; i--) {
- old = list.get(i);
- if (old.userId == r.userId && intent.filterEquals(old.intent)) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "***** Replacing " + typeForLogging
- + " [" + mQueue.mQueueName + "]: " + intent);
- }
- // Clone deferral state too if any
- r.deferred = old.deferred;
- list.set(i, r);
- return old;
- }
- }
- return null;
- }
-
- boolean cleanupDisabledPackageReceiversLocked(final String packageName,
- Set<String> filterByClasses, final int userId, final boolean doit) {
- // Note: fast short circuits when 'doit' is false, as soon as we hit any
- // "yes we would do something" circumstance
- boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts,
- packageName, filterByClasses, userId, doit);
- if (doit || !didSomething) {
- didSomething = cleanupBroadcastListDisabledReceiversLocked(mAlarmQueue,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- ArrayList<BroadcastRecord> lockedBootCompletedBroadcasts = new ArrayList<>();
- for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
- SparseArray<BroadcastRecord> brs =
- mUser2Deferred.valueAt(u).mDeferredLockedBootCompletedBroadcasts;
- for (int i = 0, size = brs.size(); i < size; i++) {
- lockedBootCompletedBroadcasts.add(brs.valueAt(i));
- }
- }
- didSomething = cleanupBroadcastListDisabledReceiversLocked(
- lockedBootCompletedBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- ArrayList<BroadcastRecord> bootCompletedBroadcasts = new ArrayList<>();
- for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
- SparseArray<BroadcastRecord> brs =
- mUser2Deferred.valueAt(u).mDeferredBootCompletedBroadcasts;
- for (int i = 0, size = brs.size(); i < size; i++) {
- bootCompletedBroadcasts.add(brs.valueAt(i));
- }
- }
- didSomething = cleanupBroadcastListDisabledReceiversLocked(bootCompletedBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmDeferrals,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if ((doit || !didSomething) && mCurrentBroadcast != null) {
- didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked(
- packageName, filterByClasses, userId, doit);
- }
-
- return didSomething;
- }
-
- private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list,
- final String packageName, Set<String> filterByClasses, final int userId,
- final boolean doit) {
- boolean didSomething = false;
- for (Deferrals d : list) {
- didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts,
- packageName, filterByClasses, userId, doit);
- if (!doit && didSomething) {
- return true;
- }
- }
- return didSomething;
- }
-
- private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list,
- final String packageName, Set<String> filterByClasses, final int userId,
- final boolean doit) {
- boolean didSomething = false;
- for (BroadcastRecord br : list) {
- didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName,
- filterByClasses, userId, doit);
- if (!doit && didSomething) {
- return true;
- }
- }
- return didSomething;
- }
-
- /**
- * Standard proto dump entry point
- */
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- if (mCurrentBroadcast != null) {
- mCurrentBroadcast.dumpDebug(proto, fieldId);
- }
- for (Deferrals d : mAlarmDeferrals) {
- d.dumpDebug(proto, fieldId);
- }
- for (BroadcastRecord br : mOrderedBroadcasts) {
- br.dumpDebug(proto, fieldId);
- }
- for (BroadcastRecord br : mAlarmQueue) {
- br.dumpDebug(proto, fieldId);
- }
- for (Deferrals d : mDeferredBroadcasts) {
- d.dumpDebug(proto, fieldId);
- }
-
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dumpDebug(proto, fieldId);
- }
- }
-
- // ----------------------------------
- // Dispatch & deferral management
-
- public BroadcastRecord getActiveBroadcastLocked() {
- return mCurrentBroadcast;
- }
-
- /**
- * If there is a deferred broadcast that is being sent to an alarm target, return
- * that one. If there's no deferred alarm target broadcast but there is one
- * that has reached the end of its deferral, return that.
- *
- * This stages the broadcast internally until it is retired, and returns that
- * staged record if this is called repeatedly, until retireBroadcast(r) is called.
- */
- public BroadcastRecord getNextBroadcastLocked(final long now) {
- if (mCurrentBroadcast != null) {
- return mCurrentBroadcast;
- }
-
- BroadcastRecord next = null;
-
- // Alarms in flight take precedence over everything else. This queue
- // will be non-empty only when the relevant policy is in force, but if
- // policy has changed on the fly we still need to drain this before we
- // settle into the legacy behavior.
- if (!mAlarmQueue.isEmpty()) {
- next = mAlarmQueue.remove(0);
- }
-
- // Next in precedence are deferred BOOT_COMPLETED broadcasts
- if (next == null) {
- next = dequeueDeferredBootCompletedBroadcast();
- }
-
- // Alarm-related deferrals are next in precedence...
- if (next == null && !mAlarmDeferrals.isEmpty()) {
- next = popLocked(mAlarmDeferrals);
- if (DEBUG_BROADCAST_DEFERRAL && next != null) {
- Slog.i(TAG, "Next broadcast from alarm targets: " + next);
- }
- }
-
- final boolean someQueued = !mOrderedBroadcasts.isEmpty();
-
- if (next == null && !mDeferredBroadcasts.isEmpty()) {
- // A this point we're going to deliver either:
- // 1. the next "overdue" deferral; or
- // 2. the next ordinary ordered broadcast; *or*
- // 3. the next not-yet-overdue deferral.
-
- for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
- Deferrals d = mDeferredBroadcasts.get(i);
- if (now < d.deferUntil && someQueued) {
- // stop looking when we haven't hit the next time-out boundary
- // but only if we have un-deferred broadcasts waiting,
- // otherwise we can deliver whatever deferred broadcast
- // is next available.
- break;
- }
-
- if (d.broadcasts.size() > 0) {
- next = d.broadcasts.remove(0);
- // apply deferral-interval decay policy and move this uid's
- // deferred broadcasts down in the delivery queue accordingly
- mDeferredBroadcasts.remove(i); // already 'd'
- d.deferredBy = calculateDeferral(d.deferredBy);
- d.deferUntil += d.deferredBy;
- insertLocked(mDeferredBroadcasts, d);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Next broadcast from deferrals " + next
- + ", deferUntil now " + d.deferUntil);
- }
- break;
- }
- }
- }
-
- if (next == null && someQueued) {
- next = mOrderedBroadcasts.remove(0);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Next broadcast from main queue: " + next);
- }
- }
-
- mCurrentBroadcast = next;
- return next;
- }
-
- /**
- * Called after the broadcast queue finishes processing the currently
- * active broadcast (obtained by calling getNextBroadcastLocked()).
- */
- public void retireBroadcastLocked(final BroadcastRecord r) {
- // ERROR if 'r' is not the active broadcast
- if (r != mCurrentBroadcast) {
- Slog.wtf(TAG, "Retiring broadcast " + r
- + " doesn't match current outgoing " + mCurrentBroadcast);
- }
- mCurrentBroadcast = null;
- }
-
- /**
- * Called prior to broadcast dispatch to check whether the intended
- * recipient is currently subject to deferral policy.
- */
- public boolean isDeferringLocked(final int uid) {
- Deferrals d = findUidLocked(uid);
- if (d != null && d.broadcasts.isEmpty()) {
- // once we've caught up with deferred broadcasts to this uid
- // and time has advanced sufficiently that we wouldn't be
- // deferring newly-enqueued ones, we're back to normal policy.
- if (SystemClock.uptimeMillis() >= d.deferUntil) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid);
- }
- removeDeferral(d);
- return false;
- }
- }
- return (d != null);
- }
-
- /**
- * Defer broadcasts for the given app. If 'br' is non-null, this also makes
- * sure that broadcast record is enqueued as the next upcoming broadcast for
- * the app.
- */
- public void startDeferring(final int uid) {
- synchronized (mLock) {
- Deferrals d = findUidLocked(uid);
-
- // If we're not yet tracking this app, set up that bookkeeping
- if (d == null) {
- // Start a new deferral
- final long now = SystemClock.uptimeMillis();
- d = new Deferrals(uid,
- now,
- mConstants.DEFERRAL,
- mAlarmUids.get(uid, 0));
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Now deferring broadcasts to " + uid
- + " until " + d.deferUntil);
- }
- // where it goes depends on whether it is coming into an alarm-related situation
- if (d.alarmCount == 0) {
- // common case, put it in the ordinary priority queue
- insertLocked(mDeferredBroadcasts, d);
- scheduleDeferralCheckLocked(true);
- } else {
- // alarm-related: strict order-encountered
- mAlarmDeferrals.add(d);
- }
- } else {
- // We're already deferring, but something was slow again. Reset the
- // deferral decay progression.
- d.deferredBy = mConstants.DEFERRAL;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to "
- + d.deferredBy);
- }
- }
- }
- }
-
- /**
- * Key entry point when a broadcast about to be delivered is instead
- * set aside for deferred delivery
- */
- public void addDeferredBroadcast(final int uid, BroadcastRecord br) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Enqueuing deferred broadcast " + br);
- }
- synchronized (mLock) {
- Deferrals d = findUidLocked(uid);
- if (d == null) {
- Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid);
- } else {
- if (br == null) {
- Slog.wtf(TAG, "Deferring null broadcast to " + uid);
- } else {
- br.deferred = true;
- d.add(br);
- }
- }
- }
- }
-
- /**
- * When there are deferred broadcasts, we need to make sure to recheck the
- * dispatch queue when they come due. Alarm-sensitive deferrals get dispatched
- * aggressively, so we only need to use the ordinary deferrals timing to figure
- * out when to recheck.
- */
- public void scheduleDeferralCheckLocked(boolean force) {
- if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) {
- final Deferrals d = mDeferredBroadcasts.get(0);
- if (!d.broadcasts.isEmpty()) {
- mHandler.removeCallbacks(mScheduleRunnable);
- mHandler.postAtTime(mScheduleRunnable, d.deferUntil);
- mRecheckScheduled = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil);
- }
- }
- }
- }
-
- /**
- * Cancel all current deferrals; that is, make all currently-deferred broadcasts
- * immediately deliverable. Used by the wait-for-broadcast-idle mechanism.
- */
- public void cancelDeferralsLocked() {
- zeroDeferralTimes(mAlarmDeferrals);
- zeroDeferralTimes(mDeferredBroadcasts);
- }
-
- private static void zeroDeferralTimes(ArrayList<Deferrals> list) {
- final int num = list.size();
- for (int i = 0; i < num; i++) {
- Deferrals d = list.get(i);
- // Safe to do this in-place because it won't break ordering
- d.deferUntil = d.deferredBy = 0;
- }
- }
-
- // ----------------------------------
-
- /**
- * If broadcasts to this uid are being deferred, find the deferrals record about it.
- * @return null if this uid's broadcasts are not being deferred
- */
- private Deferrals findUidLocked(final int uid) {
- // The common case is that they it isn't also an alarm target...
- Deferrals d = findUidLocked(uid, mDeferredBroadcasts);
- // ...but if not there, also check alarm-prioritized deferrals
- if (d == null) {
- d = findUidLocked(uid, mAlarmDeferrals);
- }
- return d;
- }
-
- /**
- * Remove the given deferral record from whichever queue it might be in at present
- * @return true if the deferral was in fact found, false if this made no changes
- */
- private boolean removeDeferral(Deferrals d) {
- boolean didRemove = mDeferredBroadcasts.remove(d);
- if (!didRemove) {
- didRemove = mAlarmDeferrals.remove(d);
- }
- return didRemove;
- }
-
- /**
- * Find the deferrals record for the given uid in the given list
- */
- private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) {
- final int numElements = list.size();
- for (int i = 0; i < numElements; i++) {
- Deferrals d = list.get(i);
- if (uid == d.uid) {
- return d;
- }
- }
- return null;
- }
-
- /**
- * Pop the next broadcast record from the head of the given deferrals list,
- * if one exists.
- */
- private static BroadcastRecord popLocked(ArrayList<Deferrals> list) {
- final Deferrals d = list.get(0);
- return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0);
- }
-
- /**
- * Insert the given Deferrals into the priority queue, sorted by defer-until milestone
- */
- private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) {
- // Simple linear search is appropriate here because we expect to
- // have very few entries in the deferral lists (i.e. very few badly-
- // behaving apps currently facing deferral)
- int i;
- final int numElements = list.size();
- for (i = 0; i < numElements; i++) {
- if (d.deferUntil < list.get(i).deferUntil) {
- break;
- }
- }
- list.add(i, d);
- }
-
- /**
- * Calculate a new deferral time based on the previous time. This should decay
- * toward zero, though a small nonzero floor is an option.
- */
- private long calculateDeferral(long previous) {
- return Math.max(mConstants.DEFERRAL_FLOOR,
- (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR));
- }
-
- // ----------------------------------
-
- @NeverCompile
- boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
- SimpleDateFormat sdf) {
- final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
- boolean printed = false;
-
- dumper.setHeading("Currently in flight");
- dumper.setLabel("In-Flight Ordered Broadcast");
- if (mCurrentBroadcast != null) {
- dumper.dump(mCurrentBroadcast);
- } else {
- pw.println(" (null)");
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Active alarm broadcasts");
- dumper.setLabel("Active Alarm Broadcast");
- for (BroadcastRecord br : mAlarmQueue) {
- dumper.dump(br);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Active ordered broadcasts");
- dumper.setLabel("Active Ordered Broadcast");
- for (Deferrals d : mAlarmDeferrals) {
- d.dumpLocked(dumper);
- }
- for (BroadcastRecord br : mOrderedBroadcasts) {
- dumper.dump(br);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred ordered broadcasts");
- dumper.setLabel("Deferred Ordered Broadcast");
- for (Deferrals d : mDeferredBroadcasts) {
- d.dumpLocked(dumper);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred LOCKED_BOOT_COMPLETED broadcasts");
- dumper.setLabel("Deferred LOCKED_BOOT_COMPLETED Broadcast");
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_LOCKED_BOOT_COMPLETED);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred BOOT_COMPLETED broadcasts");
- dumper.setLabel("Deferred BOOT_COMPLETED Broadcast");
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_BOOT_COMPLETED);
- }
- printed |= dumper.didPrint();
-
- return printed;
- }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
deleted file mode 100644
index 3c56752..0000000
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ /dev/null
@@ -1,1983 +0,0 @@
-/*
- * Copyright (C) 2012 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.am;
-
-import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
-import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
-import static android.text.TextUtils.formatSimple;
-
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ApplicationExitInfo;
-import android.app.BroadcastOptions;
-import android.app.IApplicationThread;
-import android.app.usage.UsageEvents.Event;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.IIntentReceiver;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerExemptionManager.ReasonCode;
-import android.os.PowerExemptionManager.TempAllowListType;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.os.TimeoutRecord;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserJourneyLogger;
-import com.android.server.pm.UserManagerInternal;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-
-/**
- * BROADCASTS
- *
- * We keep three broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts, and one to
- * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
- */
-public class BroadcastQueueImpl extends BroadcastQueue {
- private static final String TAG_MU = TAG + POSTFIX_MU;
- private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
-
- final BroadcastConstants mConstants;
-
- /**
- * If true, we can delay broadcasts while waiting services to finish in the previous
- * receiver's process.
- */
- final boolean mDelayBehindServices;
-
- final int mSchedGroup;
-
- /**
- * Lists of all active broadcasts that are to be executed immediately
- * (without waiting for another broadcast to finish). Currently this only
- * contains broadcasts to registered receivers, to avoid spinning up
- * a bunch of processes to execute IntentReceiver components. Background-
- * and foreground-priority broadcasts are queued separately.
- */
- final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
-
- /**
- * Tracking of the ordered broadcast queue, including deferral policy and alarm
- * prioritization.
- */
- final BroadcastDispatcher mDispatcher;
-
- /**
- * Refcounting for completion callbacks of split/deferred broadcasts. The key
- * is an opaque integer token assigned lazily when a broadcast is first split
- * into multiple BroadcastRecord objects.
- */
- final SparseIntArray mSplitRefcounts = new SparseIntArray();
- private int mNextToken = 0;
-
- /**
- * Set when we current have a BROADCAST_INTENT_MSG in flight.
- */
- boolean mBroadcastsScheduled = false;
-
- /**
- * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
- */
- boolean mPendingBroadcastTimeoutMessage;
-
- /**
- * Intent broadcasts that we have tried to start, but are
- * waiting for the application's process to be created. We only
- * need one per scheduling class (instead of a list) because we always
- * process broadcasts one at a time, so no others can be started while
- * waiting for this one.
- */
- BroadcastRecord mPendingBroadcast = null;
-
- /**
- * The receiver index that is pending, to restart the broadcast if needed.
- */
- int mPendingBroadcastRecvIndex;
-
- static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
- static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
-
- // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
- boolean mLogLatencyMetrics = true;
-
- final BroadcastHandler mHandler;
-
- private final class BroadcastHandler extends Handler {
- public BroadcastHandler(Looper looper) {
- super(looper, null);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case BROADCAST_INTENT_MSG: {
- if (DEBUG_BROADCAST) Slog.v(
- TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
- + mQueueName + "]");
- processNextBroadcast(true);
- } break;
- case BROADCAST_TIMEOUT_MSG: {
- synchronized (mService) {
- broadcastTimeoutLocked(true);
- }
- } break;
- }
- }
- }
-
- BroadcastQueueImpl(ActivityManagerService service, Handler handler,
- String name, BroadcastConstants constants, boolean allowDelayBehindServices,
- int schedGroup) {
- this(service, handler, name, constants, new BroadcastSkipPolicy(service),
- new BroadcastHistory(constants), allowDelayBehindServices, schedGroup);
- }
-
- BroadcastQueueImpl(ActivityManagerService service, Handler handler,
- String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
- BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) {
- super(service, handler, name, skipPolicy, history);
- mHandler = new BroadcastHandler(handler.getLooper());
- mConstants = constants;
- mDelayBehindServices = allowDelayBehindServices;
- mSchedGroup = schedGroup;
- mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
- }
-
- public void start(ContentResolver resolver) {
- mDispatcher.start();
- mConstants.startObserving(mHandler, resolver);
- }
-
- public boolean isDelayBehindServices() {
- return mDelayBehindServices;
- }
-
- public BroadcastRecord getPendingBroadcastLocked() {
- return mPendingBroadcast;
- }
-
- public BroadcastRecord getActiveBroadcastLocked() {
- return mDispatcher.getActiveBroadcastLocked();
- }
-
- public int getPreferredSchedulingGroupLocked(ProcessRecord app) {
- final BroadcastRecord active = getActiveBroadcastLocked();
- if (active != null && active.curApp == app) {
- return mSchedGroup;
- }
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if (pending != null && pending.curApp == app) {
- return mSchedGroup;
- }
- return ProcessList.SCHED_GROUP_UNDEFINED;
- }
-
- public void enqueueBroadcastLocked(BroadcastRecord r) {
- r.applySingletonPolicy(mService);
-
- final boolean replacePending = (r.intent.getFlags()
- & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
- // Ordered broadcasts obviously need to be dispatched in serial order,
- // but this implementation expects all manifest receivers to also be
- // dispatched in a serial fashion
- boolean serialDispatch = r.ordered;
- if (!serialDispatch) {
- final int N = (r.receivers != null) ? r.receivers.size() : 0;
- for (int i = 0; i < N; i++) {
- if (r.receivers.get(i) instanceof ResolveInfo) {
- serialDispatch = true;
- break;
- }
- }
- }
-
- if (serialDispatch) {
- final BroadcastRecord oldRecord =
- replacePending ? replaceOrderedBroadcastLocked(r) : null;
- if (oldRecord != null) {
- // Replaced, fire the result-to receiver.
- if (oldRecord.resultTo != null) {
- try {
- oldRecord.mIsReceiverAppRunning = true;
- performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
- oldRecord.intent,
- Activity.RESULT_CANCELED, null, null,
- false, false, oldRecord.shareIdentity, oldRecord.userId,
- oldRecord.callingUid, r.callingUid, r.callerPackage,
- SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0,
- oldRecord.resultToApp != null
- ? oldRecord.resultToApp.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure ["
- + mQueueName + "] sending broadcast result of "
- + oldRecord.intent, e);
-
- }
- }
- } else {
- enqueueOrderedBroadcastLocked(r);
- scheduleBroadcastsLocked();
- }
- } else {
- final boolean replaced = replacePending
- && (replaceParallelBroadcastLocked(r) != null);
- // Note: We assume resultTo is null for non-ordered broadcasts.
- if (!replaced) {
- enqueueParallelBroadcastLocked(r);
- scheduleBroadcastsLocked();
- }
- }
- }
-
- public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
- r.enqueueClockTime = System.currentTimeMillis();
- r.enqueueTime = SystemClock.uptimeMillis();
- r.enqueueRealTime = SystemClock.elapsedRealtime();
- mParallelBroadcasts.add(r);
- enqueueBroadcastHelper(r);
- }
-
- public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
- r.enqueueClockTime = System.currentTimeMillis();
- r.enqueueTime = SystemClock.uptimeMillis();
- r.enqueueRealTime = SystemClock.elapsedRealtime();
- mDispatcher.enqueueOrderedBroadcastLocked(r);
- enqueueBroadcastHelper(r);
- }
-
- /**
- * Don't call this method directly; call enqueueParallelBroadcastLocked or
- * enqueueOrderedBroadcastLocked.
- */
- private void enqueueBroadcastHelper(BroadcastRecord r) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- }
- }
-
- /**
- * Find the same intent from queued parallel broadcast, replace with a new one and return
- * the old one.
- */
- public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
- return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
- }
-
- /**
- * Find the same intent from queued ordered broadcast, replace with a new one and return
- * the old one.
- */
- public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
- return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
- }
-
- private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
- BroadcastRecord r, String typeForLogging) {
- final Intent intent = r.intent;
- for (int i = queue.size() - 1; i >= 0; i--) {
- final BroadcastRecord old = queue.get(i);
- if (old.userId == r.userId && intent.filterEquals(old.intent)) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG_BROADCAST, "***** DROPPING "
- + typeForLogging + " [" + mQueueName + "]: " + intent);
- }
- queue.set(i, r);
- return old;
- }
- }
- return null;
- }
-
- private final void processCurBroadcastLocked(BroadcastRecord r,
- ProcessRecord app) throws RemoteException {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + " for app " + app);
- final IApplicationThread thread = app.getThread();
- if (thread == null) {
- throw new RemoteException();
- }
- if (app.isInFullBackup()) {
- skipReceiverLocked(r);
- return;
- }
-
- r.curApp = app;
- r.curAppLastProcessState = app.mState.getCurProcState();
- final ProcessReceiverRecord prr = app.mReceivers;
- prr.addCurReceiver(r);
- app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
- // Don't bump its LRU position if it's in the background restricted.
- if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
- < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
- mService.updateLruProcessLocked(app, false, null);
- }
- // Make sure the oom adj score is updated before delivering the broadcast.
- // Force an update, even if there are other pending requests, overall it still saves time,
- // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
- mService.enqueueOomAdjTargetLocked(app);
- mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
-
- // Tell the application to launch this receiver.
- maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
- r.intent.setComponent(r.curComponent);
-
- // See if we need to delay the freezer based on BroadcastOptions
- if (r.options != null
- && r.options.getTemporaryAppAllowlistDuration() > 0
- && r.options.getTemporaryAppAllowlistType()
- == TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
- CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER,
- r.options.getTemporaryAppAllowlistDuration());
- }
-
- boolean started = false;
- try {
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "Delivering to component " + r.curComponent
- + ": " + r);
- mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
- PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- final boolean assumeDelivered = false;
- thread.scheduleReceiver(
- prepareReceiverIntent(r.intent, r.curFilteredExtras),
- r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
- r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
- r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
- app.mState.getReportedProcState(),
- r.shareIdentity ? r.callerPackage : null);
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + " DELIVERED for app " + app);
- started = true;
- } finally {
- if (!started) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + ": NOT STARTED!");
- r.curApp = null;
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
- prr.removeCurReceiver(r);
- }
- }
-
- // if something bad happens here, launch the app and try again
- if (app.isKilled()) {
- throw new RemoteException("app gets killed during broadcasting");
- }
- }
-
- /**
- * Called by ActivityManagerService to notify that the uid has process started, if there is any
- * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
- * @param uid
- */
- public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
- mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
- scheduleBroadcastsLocked();
- }
-
- public boolean onApplicationAttachedLocked(ProcessRecord app)
- throws BroadcastDeliveryFailedException {
- updateUidReadyForBootCompletedBroadcastLocked(app.uid);
-
- if (mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
- return sendPendingBroadcastsLocked(app);
- } else {
- return false;
- }
- }
-
- public void onApplicationTimeoutLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onApplicationProblemLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onApplicationCleanupLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onProcessFreezableChangedLocked(ProcessRecord app) {
- // Not supported; ignore
- }
-
- public boolean sendPendingBroadcastsLocked(ProcessRecord app)
- throws BroadcastDeliveryFailedException {
- boolean didSomething = false;
- final BroadcastRecord br = mPendingBroadcast;
- if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
- if (br.curApp != app) {
- Slog.e(TAG, "App mismatch when sending pending broadcast to "
- + app.processName + ", intended target is " + br.curApp.processName);
- return false;
- }
- try {
- mPendingBroadcast = null;
- br.mIsReceiverAppRunning = false;
- processCurBroadcastLocked(br, app);
- didSomething = true;
- } catch (Exception e) {
- Slog.w(TAG, "Exception in new application when starting receiver "
- + br.curComponent.flattenToShortString(), e);
- logBroadcastReceiverDiscardLocked(br);
- finishReceiverLocked(br, br.resultCode, br.resultData,
- br.resultExtras, br.resultAbort, false);
- scheduleBroadcastsLocked();
- // We need to reset the state if we failed to start the receiver.
- br.state = BroadcastRecord.IDLE;
- throw new BroadcastDeliveryFailedException(e);
- }
- }
- return didSomething;
- }
-
- // Skip the current receiver, if any, that is in flight to the given process
- public boolean skipCurrentOrPendingReceiverLocked(ProcessRecord app) {
- BroadcastRecord r = null;
- final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
- if (curActive != null && curActive.curApp == app) {
- // confirmed: the current active broadcast is to the given app
- r = curActive;
- }
-
- // If the current active broadcast isn't this BUT we're waiting for
- // mPendingBroadcast to spin up the target app, that's what we use.
- if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "[" + mQueueName + "] skip & discard pending app " + r);
- r = mPendingBroadcast;
- }
-
- if (r != null) {
- skipReceiverLocked(r);
- return true;
- } else {
- return false;
- }
- }
-
- private void skipReceiverLocked(BroadcastRecord r) {
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- }
-
- public void scheduleBroadcastsLocked() {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
- + mQueueName + "]: current="
- + mBroadcastsScheduled);
-
- if (mBroadcastsScheduled) {
- return;
- }
- mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
- mBroadcastsScheduled = true;
- }
-
- public BroadcastRecord getMatchingOrderedReceiver(ProcessRecord app) {
- BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
- if (br == null) {
- Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
- + "] no active broadcast");
- return null;
- }
- if (br.curApp != app) {
- Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
- + "] active broadcast " + br.curApp + " doesn't match " + app);
- return null;
- }
- return br;
- }
-
- // > 0 only, no worry about "eventual" recycling
- private int nextSplitTokenLocked() {
- int next = mNextToken + 1;
- if (next <= 0) {
- next = 1;
- }
- mNextToken = next;
- return next;
- }
-
- private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
- // the receiver had run for less than allowed bg activity start timeout,
- // so allow the process to still start activities from bg for some more time
- String msgToken = (app.toShortString() + r.toString()).intern();
- // first, if there exists a past scheduled request to remove this token, drop
- // that request - we don't want the token to be swept from under our feet...
- mHandler.removeCallbacksAndMessages(msgToken);
- // ...then schedule the removal of the token after the extended timeout
- mHandler.postAtTime(() -> {
- synchronized (mService) {
- app.removeBackgroundStartPrivileges(r);
- }
- }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
- }
-
- public boolean finishReceiverLocked(ProcessRecord app, int resultCode,
- String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
- final BroadcastRecord r = getMatchingOrderedReceiver(app);
- if (r != null) {
- return finishReceiverLocked(r, resultCode,
- resultData, resultExtras, resultAbort, waitForServices);
- } else {
- return false;
- }
- }
-
- public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
- String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
- final int state = r.state;
- final ActivityInfo receiver = r.curReceiver;
- final long finishTime = SystemClock.uptimeMillis();
- final long elapsed = finishTime - r.receiverTime;
- r.state = BroadcastRecord.IDLE;
- final int curIndex = r.nextReceiver - 1;
-
- final int packageState = r.mWasReceiverAppStopped
- ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
- : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-
- if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
- final Object curReceiver = r.receivers.get(curIndex);
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
- r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
- r.intent.getAction(),
- curReceiver instanceof BroadcastFilter
- ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
- : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
- r.mIsReceiverAppRunning
- ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
- : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
- r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime,
- finishTime - r.receiverTime,
- packageState,
- r.curApp.info.packageName,
- r.callerPackage,
- r.calculateTypeForLogging(),
- r.getDeliveryGroupPolicy(),
- r.intent.getFlags(),
- BroadcastRecord.getReceiverPriority(curReceiver),
- r.callerProcState,
- r.curAppLastProcessState);
- }
- if (state == BroadcastRecord.IDLE) {
- Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
- }
- if (r.mBackgroundStartPrivileges.allowsAny() && r.curApp != null) {
- if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
- // if the receiver has run for more than allowed bg activity start timeout,
- // just remove the token for this process now and we're done
- r.curApp.removeBackgroundStartPrivileges(r);
- } else {
- // It gets more time; post the removal to happen at the appropriate moment
- postActivityStartTokenRemoval(r.curApp, r);
- }
- }
- // If we're abandoning this broadcast before any receivers were actually spun up,
- // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
- if (r.nextReceiver > 0) {
- r.terminalTime[r.nextReceiver - 1] = finishTime;
- }
-
- // if this receiver was slow, impose deferral policy on the app. This will kick in
- // when processNextBroadcastLocked() next finds this uid as a receiver identity.
- if (!r.timeoutExempt) {
- // r.curApp can be null if finish has raced with process death - benign
- // edge case, and we just ignore it because we're already cleaning up
- // as expected.
- if (r.curApp != null
- && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
- // Core system packages are exempt from deferral policy
- if (!UserHandle.isCore(r.curApp.uid)) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
- + " was slow: " + receiver + " br=" + r);
- }
- mDispatcher.startDeferring(r.curApp.uid);
- } else {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
- + " receiver was slow but not deferring: "
- + receiver + " br=" + r);
- }
- }
- }
- } else {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
- + " is exempt from deferral policy");
- }
- }
-
- r.intent.setComponent(null);
- if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
- r.curApp.mReceivers.removeCurReceiver(r);
- mService.enqueueOomAdjTargetLocked(r.curApp);
- }
- if (r.curFilter != null) {
- r.curFilter.receiverList.curBroadcast = null;
- }
- r.curFilter = null;
- r.curReceiver = null;
- r.curApp = null;
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
- r.curFilteredExtras = null;
- r.mWasReceiverAppStopped = false;
- mPendingBroadcast = null;
-
- r.resultCode = resultCode;
- r.resultData = resultData;
- r.resultExtras = resultExtras;
- if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
- r.resultAbort = resultAbort;
- } else {
- r.resultAbort = false;
- }
-
- // If we want to wait behind services *AND* we're finishing the head/
- // active broadcast on its queue
- if (waitForServices && r.curComponent != null && r.queue.isDelayBehindServices()
- && ((BroadcastQueueImpl) r.queue).getActiveBroadcastLocked() == r) {
- ActivityInfo nextReceiver;
- if (r.nextReceiver < r.receivers.size()) {
- Object obj = r.receivers.get(r.nextReceiver);
- nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
- } else {
- nextReceiver = null;
- }
- // Don't do this if the next receive is in the same process as the current one.
- if (receiver == null || nextReceiver == null
- || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
- || !receiver.processName.equals(nextReceiver.processName)) {
- // In this case, we are ready to process the next receiver for the current broadcast,
- // but are on a queue that would like to wait for services to finish before moving
- // on. If there are background services currently starting, then we will go into a
- // special state where we hold off on continuing this broadcast until they are done.
- if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
- Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
- r.state = BroadcastRecord.WAITING_SERVICES;
- return false;
- }
- }
- }
-
- r.curComponent = null;
-
- // We will process the next receiver right now if this is finishing
- // an app receiver (which is always asynchronous) or after we have
- // come back from calling a receiver.
- final boolean doNext = (state == BroadcastRecord.APP_RECEIVE)
- || (state == BroadcastRecord.CALL_DONE_RECEIVE);
- if (doNext) {
- processNextBroadcastLocked(/* fromMsg= */ false, /* skipOomAdj= */ true);
- }
- return doNext;
- }
-
- public void backgroundServicesFinishedLocked(int userId) {
- BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
- if (br != null) {
- if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
- Slog.i(TAG, "Resuming delayed broadcast");
- br.curComponent = null;
- br.state = BroadcastRecord.IDLE;
- processNextBroadcastLocked(false, false);
- }
- }
- }
-
- public void performReceiveLocked(BroadcastRecord r, ProcessRecord app, IIntentReceiver receiver,
- Intent intent, int resultCode, String data, Bundle extras,
- boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
- int receiverUid, int callingUid, String callingPackage,
- long dispatchDelay, long receiveDelay, int priority,
- int receiverProcessState) throws RemoteException {
- // If the broadcaster opted-in to sharing their identity, then expose package visibility for
- // the receiver.
- if (shareIdentity) {
- mService.mPackageManagerInt.grantImplicitAccess(sendingUser, intent,
- UserHandle.getAppId(receiverUid), callingUid, true);
- }
- // Send the intent to the receiver asynchronously using one-way binder calls.
- if (app != null) {
- final IApplicationThread thread = app.getThread();
- if (thread != null) {
- // If we have an app thread, do the call through that so it is
- // correctly ordered with other one-way calls.
- try {
- final boolean assumeDelivered = !ordered;
- thread.scheduleRegisteredReceiver(
- receiver, intent, resultCode,
- data, extras, ordered, sticky, assumeDelivered, sendingUser,
- app.mState.getReportedProcState(),
- shareIdentity ? callingUid : Process.INVALID_UID,
- shareIdentity ? callingPackage : null);
- } catch (RemoteException ex) {
- // Failed to call into the process. It's either dying or wedged. Kill it gently.
- synchronized (mService) {
- final String msg = "Failed to schedule " + intent + " to " + receiver
- + " via " + app + ": " + ex;
- Slog.w(TAG, msg);
- app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- }
- throw ex;
- }
- } else {
- // Application has died. Receiver doesn't exist.
- throw new RemoteException("app.thread must not be null");
- }
- } else {
- receiver.performReceive(intent, resultCode, data, extras, ordered,
- sticky, sendingUser);
- }
- if (!ordered) {
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
- receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
- callingUid == -1 ? Process.SYSTEM_UID : callingUid,
- intent.getAction(),
- BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
- BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
- dispatchDelay, receiveDelay, 0 /* finish_delay */,
- SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
- app != null ? app.info.packageName : null, callingPackage,
- r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
- priority, r.callerProcState, receiverProcessState);
- }
- }
-
- private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
- BroadcastFilter filter, boolean ordered, int index) {
- boolean skip = mSkipPolicy.shouldSkip(r, filter);
-
- // Filter packages in the intent extras, skipping delivery if none of the packages is
- // visible to the receiver.
- Bundle filteredExtras = null;
- if (!skip && r.filterExtrasForReceiver != null) {
- final Bundle extras = r.intent.getExtras();
- if (extras != null) {
- filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
- if (filteredExtras == null) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Skipping delivery to "
- + filter.receiverList.app
- + " : receiver is filtered by the package visibility");
- }
- skip = true;
- }
- }
- }
-
- if (skip) {
- r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
- return;
- }
-
- r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
-
- // If this is not being sent as an ordered broadcast, then we
- // don't want to touch the fields that keep track of the current
- // state of ordered broadcasts.
- if (ordered) {
- r.curFilter = filter;
- filter.receiverList.curBroadcast = r;
- r.state = BroadcastRecord.CALL_IN_RECEIVE;
- if (filter.receiverList.app != null) {
- // Bump hosting application to no longer be in background
- // scheduling class. Note that we can't do that if there
- // isn't an app... but we can only be in that case for
- // things that directly call the IActivityManager API, which
- // are already core system stuff so don't matter for this.
- r.curApp = filter.receiverList.app;
- r.curAppLastProcessState = r.curApp.mState.getCurProcState();
- filter.receiverList.app.mReceivers.addCurReceiver(r);
- mService.enqueueOomAdjTargetLocked(r.curApp);
- mService.updateOomAdjPendingTargetsLocked(
- OOM_ADJ_REASON_START_RECEIVER);
- }
- } else if (filter.receiverList.app != null) {
- mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
- CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
- }
-
- try {
- if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
- "Delivering to " + filter + " : " + r);
- final boolean isInFullBackup = (filter.receiverList.app != null)
- && filter.receiverList.app.isInFullBackup();
- final boolean isKilled = (filter.receiverList.app != null)
- && filter.receiverList.app.isKilled();
- if (isInFullBackup || isKilled) {
- // Skip delivery if full backup in progress
- // If it's an ordered broadcast, we need to continue to the next receiver.
- if (ordered) {
- skipReceiverLocked(r);
- }
- } else {
- r.receiverTime = SystemClock.uptimeMillis();
- r.scheduledTime[index] = r.receiverTime;
- maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
- maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
- maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
- performReceiveLocked(r, filter.receiverList.app, filter.receiverList.receiver,
- prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
- r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
- filter.receiverList.uid, r.callingUid, r.callerPackage,
- r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime, filter.getPriority(),
- filter.receiverList.app != null
- ? filter.receiverList.app.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- // parallel broadcasts are fire-and-forget, not bookended by a call to
- // finishReceiverLocked(), so we manage their activity-start token here
- if (filter.receiverList.app != null
- && r.mBackgroundStartPrivileges.allowsAny()
- && !r.ordered) {
- postActivityStartTokenRemoval(filter.receiverList.app, r);
- }
- }
- if (ordered) {
- r.state = BroadcastRecord.CALL_DONE_RECEIVE;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
- // Clean up ProcessRecord state related to this broadcast attempt
- if (filter.receiverList.app != null) {
- filter.receiverList.app.removeBackgroundStartPrivileges(r);
- if (ordered) {
- filter.receiverList.app.mReceivers.removeCurReceiver(r);
- // Something wrong, its oom adj could be downgraded, but not in a hurry.
- mService.enqueueOomAdjTargetLocked(r.curApp);
- }
- }
- // And BroadcastRecord state related to ordered delivery, if appropriate
- if (ordered) {
- r.curFilter = null;
- filter.receiverList.curBroadcast = null;
- }
- }
- }
-
- void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
- @Nullable BroadcastOptions brOptions) {
- if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
- return;
- }
- long duration = brOptions.getTemporaryAppAllowlistDuration();
- final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
- final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
- final String reason = brOptions.getTemporaryAppAllowlistReason();
-
- if (duration > Integer.MAX_VALUE) {
- duration = Integer.MAX_VALUE;
- }
- // XXX ideally we should pause the broadcast until everything behind this is done,
- // or else we will likely start dispatching the broadcast before we have opened
- // access to the app (there is a lot of asynchronicity behind this). It is probably
- // not that big a deal, however, because the main purpose here is to allow apps
- // to hold wake locks, and they will be able to acquire their wake lock immediately
- // it just won't be enabled until we get through this work.
- StringBuilder b = new StringBuilder();
- b.append("broadcast:");
- UserHandle.formatUid(b, r.callingUid);
- b.append(":");
- if (r.intent.getAction() != null) {
- b.append(r.intent.getAction());
- } else if (r.intent.getComponent() != null) {
- r.intent.getComponent().appendShortString(b);
- } else if (r.intent.getData() != null) {
- b.append(r.intent.getData());
- }
- b.append(",reason:");
- b.append(reason);
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
- + " type=" + type + " : " + b.toString());
- }
-
- // Only add to temp allowlist if it's not the APP_FREEZING_DELAYED type. That will be
- // handled when the broadcast is actually being scheduled on the app thread.
- if (type != TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
- mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
- r.callingUid);
- }
- }
-
- private void processNextBroadcast(boolean fromMsg) {
- synchronized (mService) {
- processNextBroadcastLocked(fromMsg, false);
- }
- }
-
- private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
- @Nullable Bundle filteredExtras) {
- final Intent intent = new Intent(originalIntent);
- if (filteredExtras != null) {
- intent.replaceExtras(filteredExtras);
- }
- return intent;
- }
-
- public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
- BroadcastRecord r;
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
- + mQueueName + "]: "
- + mParallelBroadcasts.size() + " parallel broadcasts; "
- + mDispatcher.describeStateLocked());
-
- mService.updateCpuStats();
-
- if (fromMsg) {
- mBroadcastsScheduled = false;
- }
-
- // First, deliver any non-serialized broadcasts right away.
- while (mParallelBroadcasts.size() > 0) {
- r = mParallelBroadcasts.remove(0);
- r.dispatchTime = SystemClock.uptimeMillis();
- r.dispatchRealTime = SystemClock.elapsedRealtime();
- r.dispatchClockTime = System.currentTimeMillis();
- r.mIsReceiverAppRunning = true;
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(r));
- }
-
- final int N = r.receivers.size();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
- + mQueueName + "] " + r);
- for (int i=0; i<N; i++) {
- Object target = r.receivers.get(i);
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Delivering non-ordered on [" + mQueueName + "] to registered "
- + target + ": " + r);
- deliverToRegisteredReceiverLocked(r,
- (BroadcastFilter) target, false, i);
- }
- addBroadcastToHistoryLocked(r);
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
- + mQueueName + "] " + r);
- }
-
- // Now take care of the next serialized one...
-
- // If we are waiting for a process to come up to handle the next
- // broadcast, then do nothing at this point. Just in case, we
- // check that the process we're waiting for still exists.
- if (mPendingBroadcast != null) {
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "processNextBroadcast [" + mQueueName + "]: waiting for "
- + mPendingBroadcast.curApp);
-
- boolean isDead;
- if (mPendingBroadcast.curApp.getPid() > 0) {
- synchronized (mService.mPidsSelfLocked) {
- ProcessRecord proc = mService.mPidsSelfLocked.get(
- mPendingBroadcast.curApp.getPid());
- isDead = proc == null || proc.mErrorState.isCrashing();
- }
- } else {
- final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
- mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
- isDead = proc == null || !proc.isPendingStart();
- }
- if (!isDead) {
- // It's still alive, so keep waiting
- return;
- } else {
- Slog.w(TAG, "pending app ["
- + mQueueName + "]" + mPendingBroadcast.curApp
- + " died before responding to broadcast");
- mPendingBroadcast.state = BroadcastRecord.IDLE;
- mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
- mPendingBroadcast = null;
- }
- }
-
- boolean looped = false;
-
- do {
- final long now = SystemClock.uptimeMillis();
- r = mDispatcher.getNextBroadcastLocked(now);
-
- if (r == null) {
- // No more broadcasts are deliverable right now, so all done!
- mDispatcher.scheduleDeferralCheckLocked(false);
- synchronized (mService.mAppProfiler.mProfilerLock) {
- mService.mAppProfiler.scheduleAppGcsLPf();
- }
- if (looped && !skipOomAdj) {
- // If we had finished the last ordered broadcast, then
- // make sure all processes have correct oom and sched
- // adjustments.
- mService.updateOomAdjPendingTargetsLocked(
- OOM_ADJ_REASON_START_RECEIVER);
- }
-
- // when we have no more ordered broadcast on this queue, stop logging
- if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
- mLogLatencyMetrics = false;
- }
-
- return;
- }
-
- boolean forceReceive = false;
-
- // Ensure that even if something goes awry with the timeout
- // detection, we catch "hung" broadcasts here, discard them,
- // and continue to make progress.
- //
- // This is only done if the system is ready so that early-stage receivers
- // don't get executed with timeouts; and of course other timeout-
- // exempt broadcasts are ignored.
- int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
- if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
- if ((numReceivers > 0) &&
- (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
- Slog.w(TAG, "Hung broadcast ["
- + mQueueName + "] discarded after timeout failure:"
- + " now=" + now
- + " dispatchTime=" + r.dispatchTime
- + " startTime=" + r.receiverTime
- + " intent=" + r.intent
- + " numReceivers=" + numReceivers
- + " nextReceiver=" + r.nextReceiver
- + " state=" + r.state);
- broadcastTimeoutLocked(false); // forcibly finish this broadcast
- forceReceive = true;
- r.state = BroadcastRecord.IDLE;
- }
- }
-
- if (r.state != BroadcastRecord.IDLE) {
- if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
- "processNextBroadcast("
- + mQueueName + ") called when not idle (state="
- + r.state + ")");
- return;
- }
-
- // Is the current broadcast is done for any reason?
- if (r.receivers == null || r.nextReceiver >= numReceivers
- || r.resultAbort || forceReceive) {
- // Send the final result if requested
- if (r.resultTo != null) {
- boolean sendResult = true;
-
- // if this was part of a split/deferral complex, update the refcount and only
- // send the completion when we clear all of them
- if (r.splitToken != 0) {
- int newCount = mSplitRefcounts.get(r.splitToken) - 1;
- if (newCount == 0) {
- // done! clear out this record's bookkeeping and deliver
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Sending broadcast completion for split token "
- + r.splitToken + " : " + r.intent.getAction());
- }
- mSplitRefcounts.delete(r.splitToken);
- } else {
- // still have some split broadcast records in flight; update refcount
- // and hold off on the callback
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Result refcount now " + newCount + " for split token "
- + r.splitToken + " : " + r.intent.getAction()
- + " - not sending completion yet");
- }
- sendResult = false;
- mSplitRefcounts.put(r.splitToken, newCount);
- }
- }
- if (sendResult) {
- if (r.callerApp != null) {
- mService.mOomAdjuster.unfreezeTemporarily(
- r.callerApp,
- CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
- }
- try {
- if (DEBUG_BROADCAST) {
- Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
- + r.intent.getAction() + " app=" + r.callerApp);
- }
- if (r.dispatchTime == 0) {
- // The dispatch time here could be 0, in case it's a parallel
- // broadcast but it has a result receiver. Set it to now.
- r.dispatchTime = now;
- }
- r.mIsReceiverAppRunning = true;
- performReceiveLocked(r, r.resultToApp, r.resultTo,
- new Intent(r.intent), r.resultCode,
- r.resultData, r.resultExtras, false, false, r.shareIdentity,
- r.userId, r.callingUid, r.callingUid, r.callerPackage,
- r.dispatchTime - r.enqueueTime,
- now - r.dispatchTime, 0,
- r.resultToApp != null
- ? r.resultToApp.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- logBootCompletedBroadcastCompletionLatencyIfPossible(r);
- // Set this to null so that the reference
- // (local and remote) isn't kept in the mBroadcastHistory.
- r.resultTo = null;
- } catch (RemoteException e) {
- r.resultTo = null;
- Slog.w(TAG, "Failure ["
- + mQueueName + "] sending broadcast result of "
- + r.intent, e);
- }
- }
- }
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
- cancelBroadcastTimeoutLocked();
-
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "Finished with ordered broadcast " + r);
-
- // ... and on to the next...
- addBroadcastToHistoryLocked(r);
- if (r.intent.getComponent() == null && r.intent.getPackage() == null
- && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- // This was an implicit broadcast... let's record it for posterity.
- mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
- r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
- }
- mDispatcher.retireBroadcastLocked(r);
- r = null;
- looped = true;
- continue;
- }
-
- // Check whether the next receiver is under deferral policy, and handle that
- // accordingly. If the current broadcast was already part of deferred-delivery
- // tracking, we know that it must now be deliverable as-is without re-deferral.
- if (!r.deferred) {
- final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
- if (mDispatcher.isDeferringLocked(receiverUid)) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
- + " at " + r.nextReceiver + " is under deferral");
- }
- // If this is the only (remaining) receiver in the broadcast, "splitting"
- // doesn't make sense -- just defer it as-is and retire it as the
- // currently active outgoing broadcast.
- BroadcastRecord defer;
- if (r.nextReceiver + 1 == numReceivers) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Sole receiver of " + r
- + " is under deferral; setting aside and proceeding");
- }
- defer = r;
- mDispatcher.retireBroadcastLocked(r);
- } else {
- // Nontrivial case; split out 'uid's receivers to a new broadcast record
- // and defer that, then loop and pick up continuing delivery of the current
- // record (now absent those receivers).
-
- // The split operation is guaranteed to match at least at 'nextReceiver'
- defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Post split:");
- Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
- for (int i = 0; i < r.receivers.size(); i++) {
- Slog.i(TAG_BROADCAST, " " + r.receivers.get(i));
- }
- Slog.i(TAG_BROADCAST, "Split receivers:");
- for (int i = 0; i < defer.receivers.size(); i++) {
- Slog.i(TAG_BROADCAST, " " + defer.receivers.get(i));
- }
- }
- // Track completion refcount as well if relevant
- if (r.resultTo != null) {
- int token = r.splitToken;
- if (token == 0) {
- // first split of this record; refcount for 'r' and 'deferred'
- r.splitToken = defer.splitToken = nextSplitTokenLocked();
- mSplitRefcounts.put(r.splitToken, 2);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Broadcast needs split refcount; using new token "
- + r.splitToken);
- }
- } else {
- // new split from an already-refcounted situation; increment count
- final int curCount = mSplitRefcounts.get(token);
- if (DEBUG_BROADCAST_DEFERRAL) {
- if (curCount == 0) {
- Slog.wtf(TAG_BROADCAST,
- "Split refcount is zero with token for " + r);
- }
- }
- mSplitRefcounts.put(token, curCount + 1);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "New split count for token " + token
- + " is " + (curCount + 1));
- }
- }
- }
- }
- mDispatcher.addDeferredBroadcast(receiverUid, defer);
- r = null;
- looped = true;
- continue;
- }
- }
- } while (r == null);
-
- // Get the next receiver...
- int recIdx = r.nextReceiver++;
-
- // Keep track of when this receiver started, and make sure there
- // is a timeout message pending to kill it if need be.
- r.receiverTime = SystemClock.uptimeMillis();
- r.scheduledTime[recIdx] = r.receiverTime;
- if (recIdx == 0) {
- r.dispatchTime = r.receiverTime;
- r.dispatchRealTime = SystemClock.elapsedRealtime();
- r.dispatchClockTime = System.currentTimeMillis();
-
- if (mLogLatencyMetrics) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
- r.dispatchClockTime - r.enqueueClockTime);
- }
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(r));
- }
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
- + mQueueName + "] " + r);
- }
- if (! mPendingBroadcastTimeoutMessage) {
- long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Submitting BROADCAST_TIMEOUT_MSG ["
- + mQueueName + "] for " + r + " at " + timeoutTime);
- setBroadcastTimeoutLocked(timeoutTime);
- }
-
- final BroadcastOptions brOptions = r.options;
- final Object nextReceiver = r.receivers.get(recIdx);
-
- if (nextReceiver instanceof BroadcastFilter) {
- // Simple case: this is a registered receiver who gets
- // a direct call.
- BroadcastFilter filter = (BroadcastFilter)nextReceiver;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Delivering ordered ["
- + mQueueName + "] to registered "
- + filter + ": " + r);
- r.mIsReceiverAppRunning = true;
- deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
- if ((r.curReceiver == null && r.curFilter == null) || !r.ordered) {
- // The receiver has already finished, so schedule to
- // process the next one.
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
- + mQueueName + "]: ordered=" + r.ordered
- + " curFilter=" + r.curFilter
- + " curReceiver=" + r.curReceiver);
- r.state = BroadcastRecord.IDLE;
- scheduleBroadcastsLocked();
- } else {
- if (filter.receiverList != null) {
- maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
- // r is guaranteed ordered at this point, so we know finishReceiverLocked()
- // will get a callback and handle the activity start token lifecycle.
- }
- }
- return;
- }
-
- // Hard case: need to instantiate the receiver, possibly
- // starting its application process to host it.
-
- final ResolveInfo info =
- (ResolveInfo)nextReceiver;
- final ComponentName component = new ComponentName(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- final int receiverUid = info.activityInfo.applicationInfo.uid;
-
- final String targetProcess = info.activityInfo.processName;
- final ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
- info.activityInfo.applicationInfo.uid);
-
- boolean skip = mSkipPolicy.shouldSkip(r, info);
-
- // Filter packages in the intent extras, skipping delivery if none of the packages is
- // visible to the receiver.
- Bundle filteredExtras = null;
- if (!skip && r.filterExtrasForReceiver != null) {
- final Bundle extras = r.intent.getExtras();
- if (extras != null) {
- filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
- if (filteredExtras == null) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Skipping delivery to "
- + info.activityInfo.packageName + " / " + receiverUid
- + " : receiver is filtered by the package visibility");
- }
- skip = true;
- }
- }
- }
-
- if (skip) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Skipping delivery of ordered [" + mQueueName + "] "
- + r + " for reason described above");
- r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
- r.curFilter = null;
- r.state = BroadcastRecord.IDLE;
- r.manifestSkipCount++;
- scheduleBroadcastsLocked();
- return;
- }
- r.manifestCount++;
-
- r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
- r.state = BroadcastRecord.APP_RECEIVE;
- r.curComponent = component;
- r.curReceiver = info.activityInfo;
- r.curFilteredExtras = filteredExtras;
- if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
- Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
- + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
- + receiverUid);
- }
- final boolean isActivityCapable =
- (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
- maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
-
- // Report that a component is used for explicit broadcasts.
- if (r.intent.getComponent() != null && r.curComponent != null
- && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
- mService.mUsageStatsService.reportEvent(
- r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
- }
-
- try {
- mService.mPackageManagerInt.notifyComponentUsed(
- r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "Failed trying to unstop package "
- + r.curComponent.getPackageName() + ": " + e);
- }
-
- // Is this receiver's application already running?
- if (app != null && app.getThread() != null && !app.isKilled()) {
- try {
- app.addPackage(info.activityInfo.packageName,
- info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
- maybeAddBackgroundStartPrivileges(app, r);
- r.mIsReceiverAppRunning = true;
- processCurBroadcastLocked(r, app);
- return;
- } catch (RemoteException e) {
- final String msg = "Failed to schedule " + r.intent + " to " + info
- + " via " + app + ": " + e;
- Slog.w(TAG, msg);
- app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- } catch (RuntimeException e) {
- Slog.wtf(TAG, "Failed sending broadcast to "
- + r.curComponent + " with " + r.intent, e);
- // If some unexpected exception happened, just skip
- // this broadcast. At this point we are not in the call
- // from a client, so throwing an exception out from here
- // will crash the entire system instead of just whoever
- // sent the broadcast.
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- // We need to reset the state if we failed to start the receiver.
- r.state = BroadcastRecord.IDLE;
- return;
- }
-
- // If a dead object exception was thrown -- fall through to
- // restart the application.
- }
-
- // Registered whether we're bringing this package out of a stopped state
- r.mWasReceiverAppStopped =
- (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
- // Not running -- get it started, to be executed when the app comes up.
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Need to start app ["
- + mQueueName + "] " + targetProcess + " for broadcast " + r);
- r.curApp = mService.startProcessLocked(targetProcess,
- info.activityInfo.applicationInfo, true,
- r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
- new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction(), r.getHostingRecordTriggerType()),
- isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
- (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- if (r.curApp == null) {
- // Ah, this recipient is unavailable. Finish it if necessary,
- // and mark the broadcast record as ready for the next.
- Slog.w(TAG, "Unable to launch app "
- + info.activityInfo.applicationInfo.packageName + "/"
- + receiverUid + " for broadcast "
- + r.intent + ": process is bad");
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- r.state = BroadcastRecord.IDLE;
- return;
- }
-
- maybeAddBackgroundStartPrivileges(r.curApp, r);
- mPendingBroadcast = r;
- mPendingBroadcastRecvIndex = recIdx;
- }
-
- @Nullable
- private String getTargetPackage(BroadcastRecord r) {
- if (r.intent == null) {
- return null;
- }
- if (r.intent.getPackage() != null) {
- return r.intent.getPackage();
- } else if (r.intent.getComponent() != null) {
- return r.intent.getComponent().getPackageName();
- }
- return null;
- }
-
- static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
- // Only log after last receiver.
- // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
- // last BroadcastRecord of the split broadcast which has non-null resultTo.
- final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
- if (r.nextReceiver < numReceivers) {
- return;
- }
- final String action = r.intent.getAction();
- int event = 0;
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
- event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
- }
- if (event != 0) {
- final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
- final int completeLatency = (int)
- (SystemClock.uptimeMillis() - r.enqueueTime);
- final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
- final int completeRealLatency = (int)
- (SystemClock.elapsedRealtime() - r.enqueueRealTime);
- int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
- // This method is called very infrequently, no performance issue we call
- // LocalServices.getService() here.
- final UserManagerInternal umInternal = LocalServices.getService(
- UserManagerInternal.class);
- final UserInfo userInfo =
- (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
- if (userInfo != null) {
- userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
- }
- Slog.i(TAG_BROADCAST,
- "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
- + action
- + " dispatchLatency:" + dispatchLatency
- + " completeLatency:" + completeLatency
- + " dispatchRealLatency:" + dispatchRealLatency
- + " completeRealLatency:" + completeRealLatency
- + " receiversSize:" + numReceivers
- + " userId:" + r.userId
- + " userType:" + (userInfo != null? userInfo.userType : null));
- FrameworkStatsLog.write(
- BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
- event,
- dispatchLatency,
- completeLatency,
- dispatchRealLatency,
- completeRealLatency,
- r.userId,
- userType);
- }
- }
-
- private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
- if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
- return;
- }
- final String targetPackage = getTargetPackage(r);
- // Ignore non-explicit broadcasts
- if (targetPackage == null) {
- return;
- }
- mService.mUsageStatsService.reportBroadcastDispatched(
- r.callingUid, targetPackage, UserHandle.of(r.userId),
- r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
- mService.getUidStateLocked(targetUid));
- }
-
- private void maybeAddBackgroundStartPrivileges(ProcessRecord proc, BroadcastRecord r) {
- if (r == null || proc == null || !r.mBackgroundStartPrivileges.allowsAny()) {
- return;
- }
- String msgToken = (proc.toShortString() + r.toString()).intern();
- // first, if there exists a past scheduled request to remove this token, drop
- // that request - we don't want the token to be swept from under our feet...
- mHandler.removeCallbacksAndMessages(msgToken);
- // ...then add the token
- proc.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
- }
-
- final void setBroadcastTimeoutLocked(long timeoutTime) {
- if (! mPendingBroadcastTimeoutMessage) {
- Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
- mHandler.sendMessageAtTime(msg, timeoutTime);
- mPendingBroadcastTimeoutMessage = true;
- }
- }
-
- final void cancelBroadcastTimeoutLocked() {
- if (mPendingBroadcastTimeoutMessage) {
- mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
- mPendingBroadcastTimeoutMessage = false;
- }
- }
-
- final void broadcastTimeoutLocked(boolean fromMsg) {
- if (fromMsg) {
- mPendingBroadcastTimeoutMessage = false;
- }
-
- if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
- return;
- }
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastTimeoutLocked()");
- try {
- long now = SystemClock.uptimeMillis();
- BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
- if (fromMsg) {
- if (!mService.mProcessesReady) {
- // Only process broadcast timeouts if the system is ready; some early
- // broadcasts do heavy work setting up system facilities
- return;
- }
-
- // If the broadcast is generally exempt from timeout tracking, we're done
- if (r.timeoutExempt) {
- if (DEBUG_BROADCAST) {
- Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
- + r.intent.getAction());
- }
- return;
- }
- long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
- if (timeoutTime > now) {
- // We can observe premature timeouts because we do not cancel and reset the
- // broadcast timeout message after each receiver finishes. Instead, we set up
- // an initial timeout then kick it down the road a little further as needed
- // when it expires.
- if (DEBUG_BROADCAST) {
- Slog.v(TAG_BROADCAST,
- "Premature timeout ["
- + mQueueName + "] @ " + now
- + ": resetting BROADCAST_TIMEOUT_MSG for "
- + timeoutTime);
- }
- setBroadcastTimeoutLocked(timeoutTime);
- return;
- }
- }
-
- if (r.state == BroadcastRecord.WAITING_SERVICES) {
- // In this case the broadcast had already finished, but we had decided to wait
- // for started services to finish as well before going on. So if we have actually
- // waited long enough time timeout the broadcast, let's give up on the whole thing
- // and just move on to the next.
- Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
- ? r.curComponent.flattenToShortString() : "(null)"));
- r.curComponent = null;
- r.state = BroadcastRecord.IDLE;
- processNextBroadcastLocked(false, false);
- return;
- }
-
- // If the receiver app is being debugged we quietly ignore unresponsiveness, just
- // tidying up and moving on to the next broadcast without crashing or ANRing this
- // app just because it's stopped at a breakpoint.
- final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
-
- long timeoutDurationMs = now - r.receiverTime;
- Slog.w(TAG, "Timeout of broadcast " + r + " - curFilter=" + r.curFilter
- + " curReceiver=" + r.curReceiver + ", started " + timeoutDurationMs
- + "ms ago");
- r.receiverTime = now;
- if (!debugging) {
- r.anrCount++;
- }
-
- ProcessRecord app = null;
- Object curReceiver;
- if (r.nextReceiver > 0) {
- curReceiver = r.receivers.get(r.nextReceiver - 1);
- r.delivery[r.nextReceiver - 1] = BroadcastRecord.DELIVERY_TIMEOUT;
- } else {
- curReceiver = r.curReceiver;
- }
- Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
- logBroadcastReceiverDiscardLocked(r);
- TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
- timeoutDurationMs);
- if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter) curReceiver;
- if (bf.receiverList.pid != 0
- && bf.receiverList.pid != ActivityManagerService.MY_PID) {
- timeoutRecord.mLatencyTracker.waitingOnPidLockStarted();
- synchronized (mService.mPidsSelfLocked) {
- timeoutRecord.mLatencyTracker.waitingOnPidLockEnded();
- app = mService.mPidsSelfLocked.get(
- bf.receiverList.pid);
- }
- }
- } else {
- app = r.curApp;
- }
-
- if (mPendingBroadcast == r) {
- mPendingBroadcast = null;
- }
-
- // Move on to the next receiver.
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
-
- // The ANR should only be triggered if we have a process record (app is non-null)
- if (!debugging && app != null) {
- mService.appNotResponding(app, timeoutRecord);
- }
-
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
-
- }
-
- private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
- if (original.callingUid < 0) {
- // This was from a registerReceiver() call; ignore it.
- return;
- }
- original.finishTime = SystemClock.uptimeMillis();
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(original));
- }
-
- mService.notifyBroadcastFinishedLocked(original);
- mHistory.addBroadcastToHistoryLocked(original);
- }
-
- public boolean cleanupDisabledPackageReceiversLocked(
- String packageName, Set<String> filterByClasses, int userId) {
- boolean didSomething = false;
- for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
- packageName, filterByClasses, userId, true);
- }
-
- didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
- filterByClasses, userId, true);
-
- return didSomething;
- }
-
- final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
- final int logIndex = r.nextReceiver - 1;
- if (logIndex >= 0 && logIndex < r.receivers.size()) {
- Object curReceiver = r.receivers.get(logIndex);
- if (curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter) curReceiver;
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
- bf.owningUserId, System.identityHashCode(r),
- r.intent.getAction(), logIndex, System.identityHashCode(bf));
- } else {
- ResolveInfo ri = (ResolveInfo) curReceiver;
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
- System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
- }
- } else {
- if (logIndex < 0) Slog.w(TAG,
- "Discarding broadcast before first receiver is invoked: " + r);
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- -1, System.identityHashCode(r),
- r.intent.getAction(),
- r.nextReceiver,
- "NONE");
- }
- }
-
- private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
- return formatSimple("Broadcast %s from %s (%s) %s",
- state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
- record.callerPackage == null ? "" : record.callerPackage,
- record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
- record.intent == null ? "" : record.intent.getAction());
- }
-
- public boolean isIdleLocked() {
- return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
- && (mPendingBroadcast == null);
- }
-
- public boolean isBeyondBarrierLocked(long barrierTime) {
- // If nothing active, we're beyond barrier
- if (isIdleLocked()) return true;
-
- // Check if parallel broadcasts are beyond barrier
- for (int i = 0; i < mParallelBroadcasts.size(); i++) {
- if (mParallelBroadcasts.get(i).enqueueTime <= barrierTime) {
- return false;
- }
- }
-
- // Check if pending broadcast is beyond barrier
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if ((pending != null) && pending.enqueueTime <= barrierTime) {
- return false;
- }
-
- return mDispatcher.isBeyondBarrier(barrierTime);
- }
-
- public boolean isDispatchedLocked(Intent intent) {
- if (isIdleLocked()) return true;
-
- for (int i = 0; i < mParallelBroadcasts.size(); i++) {
- if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
- return false;
- }
- }
-
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if ((pending != null) && intent.filterEquals(pending.intent)) {
- return false;
- }
-
- return mDispatcher.isDispatched(intent);
- }
-
- public void waitForIdle(PrintWriter pw) {
- waitFor(() -> isIdleLocked(), pw, "idle");
- }
-
- public void waitForBarrier(PrintWriter pw) {
- final long barrierTime = SystemClock.uptimeMillis();
- waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier");
- }
-
- public void waitForDispatched(Intent intent, PrintWriter pw) {
- waitFor(() -> isDispatchedLocked(intent), pw, "dispatch");
- }
-
- private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) {
- long lastPrint = 0;
- while (true) {
- synchronized (mService) {
- if (condition.getAsBoolean()) {
- final String msg = "Queue [" + mQueueName + "] reached " + conditionName
- + " condition";
- Slog.v(TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- return;
- }
- }
-
- // Print at most every second
- final long now = SystemClock.uptimeMillis();
- if (now >= lastPrint + 1000) {
- lastPrint = now;
- final String msg = "Queue [" + mQueueName + "] waiting for " + conditionName
- + " condition; state is " + describeStateLocked();
- Slog.v(TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- }
-
- // Push through any deferrals to try meeting our condition
- cancelDeferrals();
- SystemClock.sleep(100);
- }
- }
-
- // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
- // be immediately deliverable.
- public void cancelDeferrals() {
- synchronized (mService) {
- mDispatcher.cancelDeferralsLocked();
- scheduleBroadcastsLocked();
- }
- }
-
- public String describeStateLocked() {
- return mParallelBroadcasts.size() + " parallel; "
- + mDispatcher.describeStateLocked();
- }
-
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- long token = proto.start(fieldId);
- proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
- int N;
- N = mParallelBroadcasts.size();
- for (int i = N - 1; i >= 0; i--) {
- mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
- }
- mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
- if (mPendingBroadcast != null) {
- mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
- }
- mHistory.dumpDebug(proto);
- proto.end(token);
- }
-
- @NeverCompile
- public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpConstants, boolean dumpHistory, boolean dumpAll,
- String dumpPackage, boolean needSep) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
- || mPendingBroadcast != null) {
- boolean printed = false;
- for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- BroadcastRecord br = mParallelBroadcasts.get(i);
- if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
- continue;
- }
- if (!printed) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- printed = true;
- pw.println(" Active broadcasts [" + mQueueName + "]:");
- }
- pw.println(" Active Broadcast " + mQueueName + " #" + i + ":");
- br.dump(pw, " ", sdf);
- }
-
- mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
-
- if (dumpPackage == null || (mPendingBroadcast != null
- && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
- pw.println();
- pw.println(" Pending broadcast [" + mQueueName + "]:");
- if (mPendingBroadcast != null) {
- mPendingBroadcast.dump(pw, " ", sdf);
- } else {
- pw.println(" (null)");
- }
- needSep = true;
- }
- }
- if (dumpConstants) {
- mConstants.dump(new IndentingPrintWriter(pw));
- }
- if (dumpHistory) {
- needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
- }
- return needSep;
- }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 98263df..4422608 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -20,6 +20,9 @@
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
@@ -59,6 +62,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.BundleMerger;
import android.os.Handler;
@@ -85,9 +89,12 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.AnrTimer;
import dalvik.annotation.optimization.NeverCompile;
@@ -2120,7 +2127,7 @@
r.nextReceiver = r.receivers.size();
mHistory.onBroadcastFinishedLocked(r);
- BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ logBootCompletedBroadcastCompletionLatencyIfPossible(r);
if (r.intent.getComponent() == null && r.intent.getPackage() == null
&& (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
@@ -2216,6 +2223,59 @@
return null;
}
+ private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+ // Only log after last receiver.
+ // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+ // last BroadcastRecord of the split broadcast which has non-null resultTo.
+ final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+ if (r.nextReceiver < numReceivers) {
+ return;
+ }
+ final String action = r.intent.getAction();
+ int event = 0;
+ if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+ }
+ if (event != 0) {
+ final int dispatchLatency = (int) (r.dispatchTime - r.enqueueTime);
+ final int completeLatency = (int) (SystemClock.uptimeMillis() - r.enqueueTime);
+ final int dispatchRealLatency = (int) (r.dispatchRealTime - r.enqueueRealTime);
+ final int completeRealLatency = (int)
+ (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+ int userType =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+ // This method is called very infrequently, no performance issue we call
+ // LocalServices.getService() here.
+ final UserManagerInternal umInternal = LocalServices.getService(
+ UserManagerInternal.class);
+ final UserInfo userInfo =
+ (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
+ if (userInfo != null) {
+ userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
+ }
+ Slog.i(TAG, "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+ + action
+ + " dispatchLatency:" + dispatchLatency
+ + " completeLatency:" + completeLatency
+ + " dispatchRealLatency:" + dispatchRealLatency
+ + " completeRealLatency:" + completeRealLatency
+ + " receiversSize:" + numReceivers
+ + " userId:" + r.userId
+ + " userType:" + (userInfo != null ? userInfo.userType : null));
+ FrameworkStatsLog.write(
+ BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+ event,
+ dispatchLatency,
+ completeLatency,
+ dispatchRealLatency,
+ completeRealLatency,
+ r.userId,
+ userType);
+ }
+ }
+
@Override
@NeverCompile
public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 4ef31bf..f2b9b25 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import android.annotation.Nullable;
import android.app.IServiceConnection;
@@ -37,7 +38,7 @@
/**
* Description of a single binding to a service.
*/
-final class ConnectionRecord {
+final class ConnectionRecord implements OomAdjusterModernImpl.Connection{
final AppBindRecord binding; // The application/service binding.
final ActivityServiceConnectionsHolder<ConnectionRecord> activity; // If non-null, the owning activity.
final IServiceConnection conn; // The client connection.
@@ -127,6 +128,14 @@
aliasComponent = _aliasComponent;
}
+ @Override
+ public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ int oomAdjReason, int cachedAdj) {
+ oomAdjuster.computeServiceHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+ false, oomAdjReason, UNKNOWN_ADJ, false, false);
+ }
+
public long getFlags() {
return flags;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 825b938..3988277 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -18,6 +18,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import android.annotation.UserIdInt;
import android.os.Binder;
@@ -32,7 +33,8 @@
/**
* Represents a link between a content provider and client.
*/
-public final class ContentProviderConnection extends Binder {
+public final class ContentProviderConnection extends Binder implements
+ OomAdjusterModernImpl.Connection {
public final ContentProviderRecord provider;
public final ProcessRecord client;
public final String clientPackage;
@@ -72,6 +74,14 @@
createTime = SystemClock.elapsedRealtime();
}
+ @Override
+ public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ int oomAdjReason, int cachedAdj) {
+ oomAdjuster.computeProviderHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+ false, oomAdjReason, UNKNOWN_ADJ, false, false);
+ }
+
public void startAssociationIfNeeded() {
// If we don't already have an active association, create one... but only if this
// is an association between two different processes.
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 31328ae..cd6964e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -587,7 +587,7 @@
}
@GuardedBy({"mService", "mProcLock"})
- private void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
// Clear any pending ones because we are doing a full update now.
mPendingProcessSet.clear();
@@ -913,7 +913,7 @@
}
@GuardedBy("mService")
- private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -941,7 +941,7 @@
* must have called {@link collectReachableProcessesLocked} on it.
*/
@GuardedBy({"mService", "mProcLock"})
- protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+ private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
boolean startProfiling) {
final boolean fullUpdate = processes == null;
@@ -1775,12 +1775,11 @@
state.setAdjSeq(mAdjSeq);
state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
+ state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
state.setCurAdj(CACHED_APP_MAX_ADJ);
state.setCurRawAdj(CACHED_APP_MAX_ADJ);
state.setCompletedAdjSeq(state.getAdjSeq());
state.setCurCapability(PROCESS_CAPABILITY_NONE);
- onProcessStateChanged(app, prevProcState);
- onProcessOomAdjChanged(app, prevAppAdj);
return false;
}
@@ -1843,8 +1842,6 @@
state.setCurRawProcState(state.getCurProcState());
state.setCurAdj(state.getMaxAdj());
state.setCompletedAdjSeq(state.getAdjSeq());
- onProcessStateChanged(app, prevProcState);
- onProcessOomAdjChanged(app, prevAppAdj);
// if curAdj is less than prevAppAdj, then this process was promoted
return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
}
@@ -2561,7 +2558,7 @@
}
@GuardedBy({"mService", "mProcLock"})
- protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+ public boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
boolean couldRecurse, boolean dryRun) {
@@ -2991,7 +2988,11 @@
return updated;
}
- protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
+ /**
+ * Computes the impact on {@code app} the provider connections from {@code client} has.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ public boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp,
boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason,
int cachedAdj, boolean couldRecurse, boolean dryRun) {
@@ -3572,7 +3573,7 @@
int initialCapability = PROCESS_CAPABILITY_NONE;
boolean initialCached = true;
final ProcessStateRecord state = app.mState;
- final int prevProcState = state.getCurRawProcState();
+ final int prevProcState = state.getCurProcState();
final int prevAdj = state.getCurRawAdj();
// If the process has been marked as foreground, it is starting as the top app (with
// Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 1bf779a..dd75bc0 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -38,7 +38,6 @@
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
@@ -67,8 +66,10 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.content.pm.ServiceInfo;
+import android.os.IBinder;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -80,6 +81,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -275,6 +277,7 @@
mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
}
mLastNode = new ProcessRecordNode[size];
+ resetLastNodes();
}
int size() {
@@ -285,7 +288,7 @@
void reset() {
for (int i = 0; i < mProcessRecordNodes.length; i++) {
mProcessRecordNodes[i].reset();
- mLastNode[i] = null;
+ setLastNodeToHead(i);
}
}
@@ -509,26 +512,118 @@
}
/**
- * A helper consumer for collecting processes that have not been reached yet. To avoid object
- * allocations every OomAdjuster update, the results will be stored in
- * {@link UnreachedProcessCollector#processList}. The process list reader is responsible
- * for setting it before usage, as well as, clearing the reachable state of each process in the
- * list.
+ * A {@link Connection} represents any connection between two processes that can cause a
+ * change in importance in the host process based on the client process and connection state.
*/
- private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
- public ArrayList<ProcessRecord> processList = null;
+ public interface Connection {
+ /**
+ * Compute the impact this connection has on the host's importance values.
+ */
+ void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client,
+ long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj);
+ }
+
+ /**
+ * A helper consumer for marking and collecting reachable processes.
+ */
+ private static class ReachableCollectingConsumer implements
+ BiConsumer<Connection, ProcessRecord> {
+ ArrayList<ProcessRecord> mReachables = null;
+
+ public void init(ArrayList<ProcessRecord> reachables) {
+ mReachables = reachables;
+ }
+
@Override
- public void accept(ProcessRecord process) {
- if (process.mState.isReachable()) {
+ public void accept(Connection unused, ProcessRecord host) {
+ if (host.mState.isReachable()) {
return;
}
- process.mState.setReachable(true);
- processList.add(process);
+ host.mState.setReachable(true);
+ mReachables.add(host);
}
}
- private final UnreachedProcessCollector mUnreachedProcessCollector =
- new UnreachedProcessCollector();
+ private final ReachableCollectingConsumer mReachableCollectingConsumer =
+ new ReachableCollectingConsumer();
+
+ /**
+ * A helper consumer for computing the importance of a connection from a client.
+ * Connections for clients marked reachable will be ignored.
+ */
+ private class ComputeConnectionIgnoringReachableClientsConsumer implements
+ BiConsumer<Connection, ProcessRecord> {
+ public OomAdjusterArgs args = null;
+
+ @Override
+ public void accept(Connection conn, ProcessRecord client) {
+ final ProcessRecord host = args.mApp;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+
+ if (client.mState.isReachable()) return;
+
+ conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false,
+ oomAdjReason, UNKNOWN_ADJ);
+ }
+ }
+
+ private final ComputeConnectionIgnoringReachableClientsConsumer
+ mComputeConnectionIgnoringReachableClientsConsumer =
+ new ComputeConnectionIgnoringReachableClientsConsumer();
+
+ /**
+ * A helper consumer for computing host process importance from a connection from a client app.
+ */
+ private class ComputeHostConsumer implements BiConsumer<Connection, ProcessRecord> {
+ public OomAdjusterArgs args = null;
+
+ @Override
+ public void accept(Connection conn, ProcessRecord host) {
+ final ProcessRecord client = args.mApp;
+ final int cachedAdj = args.mCachedAdj;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+ final boolean fullUpdate = args.mFullUpdate;
+
+ final int prevProcState = host.mState.getCurProcState();
+ final int prevAdj = host.mState.getCurRawAdj();
+
+ conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp,
+ fullUpdate, oomAdjReason, cachedAdj);
+
+ updateProcStateSlotIfNecessary(host, prevProcState);
+ updateAdjSlotIfNecessary(host, prevAdj);
+ }
+ }
+ private final ComputeHostConsumer mComputeHostConsumer = new ComputeHostConsumer();
+
+ /**
+ * A helper consumer for computing all connections from an app.
+ */
+ private class ComputeConnectionsConsumer implements Consumer<OomAdjusterArgs> {
+ @Override
+ public void accept(OomAdjusterArgs args) {
+ final ProcessRecord app = args.mApp;
+ final ActiveUids uids = args.mUids;
+
+ // This process was updated in some way, mark that it was last calculated this sequence.
+ app.mState.setCompletedAdjSeq(mAdjSeq);
+ if (uids != null) {
+ final UidRecord uidRec = app.getUidRecord();
+
+ if (uidRec != null) {
+ uids.put(uidRec.getUid(), uidRec);
+ }
+ }
+ mComputeHostConsumer.args = args;
+ forEachConnectionLSP(app, mComputeHostConsumer);
+ }
+ }
+ private final ComputeConnectionsConsumer mComputeConnectionsConsumer =
+ new ComputeConnectionsConsumer();
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
ActiveUids activeUids) {
@@ -617,6 +712,12 @@
}
}
+ private void updateAdjSlot(ProcessRecord app, int prevRawAdj) {
+ final int slot = adjToSlot(app.mState.getCurRawAdj());
+ final int prevSlot = adjToSlot(prevRawAdj);
+ mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ }
+
private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
if (app.mState.getCurProcState() != prevProcState) {
final int slot = processStateToSlot(app.mState.getCurProcState());
@@ -627,359 +728,247 @@
}
}
+ private void updateProcStateSlot(ProcessRecord app, int prevProcState) {
+ final int slot = processStateToSlot(app.mState.getCurProcState());
+ final int prevSlot = processStateToSlot(prevProcState);
+ mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ }
+
@Override
- protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
+ // Clear any pending ones because we are doing a full update now.
+ mPendingProcessSet.clear();
+ mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
- mAdjSeq++;
- final ProcessStateRecord state = app.mState;
- final int oldAdj = state.getCurRawAdj();
- final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
- ? oldAdj : UNKNOWN_ADJ;
-
- final ActiveUids uids = mTmpUidRecords;
- final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
- final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
- final long now = SystemClock.uptimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
-
- uids.clear();
- targetProcesses.clear();
- targetProcesses.add(app);
- reachableProcesses.clear();
-
- // Find out all reachable processes from this app.
- collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
-
- // Copy all of the reachable processes into the target process set.
- targetProcesses.addAll(reachableProcesses);
- reachableProcesses.clear();
-
- final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
- topApp, targetProcesses, uids, false, now, cachedAdj);
-
- reachableProcesses.addAll(targetProcesses);
- assignCachedAdjIfNecessary(reachableProcesses);
- for (int i = uids.size() - 1; i >= 0; i--) {
- final UidRecord uidRec = uids.valueAt(i);
- uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
- }
- updateUidsLSP(uids, nowElapsed);
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
- }
- targetProcesses.clear();
- reachableProcesses.clear();
+ fullUpdateLSP(oomAdjReason);
mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- return result;
}
@GuardedBy({"mService", "mProcLock"})
@Override
- protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
- ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
- boolean startProfiling) {
- final boolean fullUpdate = processes == null;
- final ArrayList<ProcessRecord> activeProcesses = fullUpdate
- ? mProcessList.getLruProcessesLOSP() : processes;
- ActiveUids activeUids = uids;
- if (activeUids == null) {
- final int numUids = mActiveUids.size();
- activeUids = mTmpUidRecords;
- activeUids.clear();
- for (int i = 0; i < numUids; i++) {
- UidRecord uidRec = mActiveUids.valueAt(i);
- activeUids.put(uidRec.getUid(), uidRec);
- }
- }
-
- if (startProfiling) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
- }
- final long now = SystemClock.uptimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
- final int numProc = activeProcesses.size();
-
- mAdjSeq++;
- if (fullUpdate) {
- mNewNumServiceProcs = 0;
- mNewNumAServiceProcs = 0;
- }
-
- final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
- targetProcesses.clear();
- if (!fullUpdate) {
- targetProcesses.addAll(activeProcesses);
- }
-
- performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
- fullUpdate, now, UNKNOWN_ADJ);
-
- // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
- postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
- targetProcesses.clear();
-
- if (startProfiling) {
- mService.mOomAdjProfiler.oomAdjEnded();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- return;
- }
-
- /**
- * Perform the oom adj update on the given {@code targetProcesses}.
- *
- * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
- * must have called {@link collectReachableProcessesLocked} on it.
- */
- private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
- ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
- boolean fullUpdate, long now, int cachedAdj) {
-
- final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
- clientProcesses.clear();
-
- // We'll need to collect the upstream processes of the target apps here, because those
- // processes would potentially impact the procstate/adj via bindings.
- if (!fullUpdate) {
- collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
-
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- final ProcessRecord app = targetProcesses.valueAt(i);
- app.mState.resetCachedInfo();
- final UidRecord uidRec = app.getUidRecord();
- if (uidRec != null) {
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
- }
- }
- } else {
- final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
- for (int i = 0, size = lru.size(); i < size; i++) {
- final ProcessRecord app = lru.get(i);
- app.mState.resetCachedInfo();
- final UidRecord uidRec = app.getUidRecord();
- if (uidRec != null) {
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
- }
- }
- }
-
- updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
- cachedAdj, now, fullUpdate);
-
- clientProcesses.clear();
-
+ protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ mPendingProcessSet.add(app);
+ performUpdateOomAdjPendingTargetsLocked(oomAdjReason);
return true;
}
- /**
- * Collect the client processes from the given {@code apps}, the result will be returned in the
- * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
- * {@code apps}.
- */
@GuardedBy("mService")
- private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
- ArrayList<ProcessRecord> clientProcesses) {
- // Mark all of the provided apps as reachable to avoid including them in the client list.
- final int appsSize = apps.size();
- for (int i = 0; i < appsSize; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.mState.setReachable(true);
- }
+ @Override
+ protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+ mService.mOomAdjProfiler.oomAdjStarted();
- clientProcesses.clear();
- mUnreachedProcessCollector.processList = clientProcesses;
- for (int i = 0; i < appsSize; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.forEachClient(mUnreachedProcessCollector);
+ synchronized (mProcLock) {
+ partialUpdateLSP(oomAdjReason, mPendingProcessSet);
}
- mUnreachedProcessCollector.processList = null;
+ mPendingProcessSet.clear();
- // Reset the temporary bits.
- for (int i = clientProcesses.size() - 1; i >= 0; i--) {
- clientProcesses.get(i).mState.setReachable(false);
- }
- for (int i = 0, size = apps.size(); i < size; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.mState.setReachable(false);
- }
+ mService.mOomAdjProfiler.oomAdjEnded();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
+ /**
+ * Perform a full update on the entire process list.
+ */
@GuardedBy({"mService", "mProcLock"})
- private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
- ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
- ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
- mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
-
- mProcessRecordProcStateNodes.resetLastNodes();
- mProcessRecordAdjNodes.resetLastNodes();
-
- final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
- final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+ private void fullUpdateLSP(@OomAdjReason int oomAdjReason) {
+ final ProcessRecord topApp = mService.getTopApp();
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
mAdjSeq++;
- // All apps to be updated will be moved to the lowest slot.
- if (fullUpdate) {
- // Move all the process record node to the lowest slot, we'll do recomputation on all of
- // them. Use the processes from the lru list, because the scanning order matters here.
- final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
- for (int i = procStateTarget; i >= 0; i--) {
- mProcessRecordProcStateNodes.reset(i);
- // Force the last node to the head since we'll recompute all of them.
- mProcessRecordProcStateNodes.setLastNodeToHead(i);
+
+ mNewNumServiceProcs = 0;
+ mNewNumAServiceProcs = 0;
+
+ // Clear the priority queues.
+ mProcessRecordProcStateNodes.reset();
+ mProcessRecordAdjNodes.reset();
+
+ final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+ for (int i = lru.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = lru.get(i);
+ final int prevProcState = app.mState.getCurProcState();
+ final int prevAdj = app.mState.getCurRawAdj();
+ app.mState.resetCachedInfo();
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null) {
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+ }
+ uidRec.reset();
}
- // enqueue the targets in the reverse order of the lru list.
- for (int i = lruList.size() - 1; i >= 0; i--) {
- mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
- }
- // Do the same to the adj nodes.
- for (int i = adjTarget; i >= 0; i--) {
- mProcessRecordAdjNodes.reset(i);
- // Force the last node to the head since we'll recompute all of them.
- mProcessRecordAdjNodes.setLastNodeToHead(i);
- }
- for (int i = lruList.size() - 1; i >= 0; i--) {
- mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
- }
- } else {
- // Move the target processes to the lowest slot.
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- final ProcessRecord app = targetProcesses.valueAt(i);
- final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
- final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
- mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
- mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
- }
- // Move the "lastNode" to head to make sure we scan all nodes in this slot.
- mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
- mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
+
+ // Compute initial values, the procState and adj priority queues will be populated here.
+ computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason,
+ false);
+ updateProcStateSlot(app, prevProcState);
+ updateAdjSlot(app, prevAdj);
}
- // All apps to be updated have been moved to the lowest slot.
- // Do an initial pass of the computation.
- mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
- this::computeInitialOomAdjLSP);
-
- if (!fullUpdate) {
- // We didn't update the client processes with the computeInitialOomAdjLSP
- // because they don't need to do so. But they'll be playing vital roles in
- // computing the bindings. So include them into the scan list below.
- for (int i = 0, size = clientProcesses.size(); i < size; i++) {
- mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
- }
- // We don't update the adj list since we're resetting it below.
- }
-
- // Now nodes are set into their slots, without factoring in the bindings.
- // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
- //
- // The whole rationale here is that, the bindings from client to host app, won't elevate
- // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
- // is a special case here, but client app's raw adj is still no less than the host app's).
- // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
- // check its bindings, elevate its host app's slot if necessary.
- //
- // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
- // Because the procstate and adj are not always in sync - there are cases where
- // the processes with lower proc state could be getting a higher oom adj score.
- // And because of this, the procstate and adj node lists are basically two priority heaps.
- //
- // As the 2nd pass with the adj node lists potentially includes a significant amount of
- // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
- // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
- // gets bumped, we'll only scan those in 2nd pass.
-
+ // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+ // iteration if they adj score changed during the procState node iteration.
mProcessRecordAdjNodes.resetLastNodes();
+ mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
+ computeConnectionsLSP();
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime);
+ }
+
+ /**
+ * Traverse the process graph and update processes based on changes in connection importances.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private void computeConnectionsLSP() {
// 1st pass, scan each slot in the procstate node list.
for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
- mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+ mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
}
// 2nd pass, scan each slot in the adj node list.
for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
- mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+ mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
}
}
- @GuardedBy({"mService", "mProcLock"})
- private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
- final ProcessRecord app = args.mApp;
- final int cachedAdj = args.mCachedAdj;
- final ProcessRecord topApp = args.mTopApp;
- final long now = args.mNow;
- final int oomAdjReason = args.mOomAdjReason;
- final ActiveUids uids = args.mUids;
- final boolean fullUpdate = args.mFullUpdate;
-
- if (DEBUG_OOM_ADJ) {
- Slog.i(TAG, "OOM ADJ initial args app=" + app
- + " cachedAdj=" + cachedAdj
- + " topApp=" + topApp
- + " now=" + now
- + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
- + " fullUpdate=" + fullUpdate);
- }
-
- if (uids != null) {
- final UidRecord uidRec = app.getUidRecord();
-
- if (uidRec != null) {
- uids.put(uidRec.getUid(), uidRec);
- }
- }
-
- computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
- false);
- }
-
/**
- * @return The proposed change to the schedGroup.
+ * Perform a partial update on the target processes and their reachable processes.
*/
@GuardedBy({"mService", "mProcLock"})
- @Override
- protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
- int schedGroup) {
- schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
+ private void partialUpdateLSP(@OomAdjReason int oomAdjReason, ArraySet<ProcessRecord> targets) {
+ final ProcessRecord topApp = mService.getTopApp();
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
- updateAdjSlotIfNecessary(app, prevRawAppAdj);
+ ActiveUids activeUids = mTmpUidRecords;
+ activeUids.clear();
+ mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, activeUids, false);
- return schedGroup;
+ mAdjSeq++;
+
+ final ArrayList<ProcessRecord> reachables = mTmpProcessList;
+ reachables.clear();
+
+ for (int i = 0, size = targets.size(); i < size; i++) {
+ final ProcessRecord target = targets.valueAtUnchecked(i);
+ target.mState.resetCachedInfo();
+ target.mState.setReachable(true);
+ reachables.add(target);
+ }
+
+ // Collect all processes that are reachable.
+ // Any process not found in this step will not change in importance during this update.
+ collectAndMarkReachableProcessesLSP(reachables);
+
+ // Initialize the reachable processes based on their own values plus any
+ // connections from processes not found in the previous step. Since those non-reachable
+ // processes cannot change as a part of this update, their current values can be used
+ // right now.
+ mProcessRecordProcStateNodes.resetLastNodes();
+ initReachableStatesLSP(reachables, mTmpOomAdjusterArgs);
+
+ // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+ // iteration if they adj score changed during the procState node iteration.
+ mProcessRecordAdjNodes.resetLastNodes();
+ // Now traverse and compute the connections of processes with changed importance.
+ computeConnectionsLSP();
+
+ boolean unassignedAdj = false;
+ for (int i = 0, size = reachables.size(); i < size; i++) {
+ final ProcessStateRecord state = reachables.get(i).mState;
+ state.setReachable(false);
+ state.setCompletedAdjSeq(mAdjSeq);
+ if (state.getCurAdj() >= UNKNOWN_ADJ) {
+ unassignedAdj = true;
+ }
+ }
+
+ // If all processes have an assigned adj, no need to calculate and assign cached adjs.
+ if (unassignedAdj) {
+ // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ }
+
+ // Repopulate any uid record that may have changed.
+ for (int i = 0, size = activeUids.size(); i < size; i++) {
+ final UidRecord ur = activeUids.valueAt(i);
+ ur.reset();
+ for (int j = ur.getNumOfProcs() - 1; j >= 0; j--) {
+ final ProcessRecord proc = ur.getProcessRecordByIndex(j);
+ updateAppUidRecIfNecessaryLSP(proc);
+ }
+ }
+
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
}
+ /**
+ * Mark all processes reachable from the {@code reachables} processes and add them to the
+ * provided {@code reachables} list (targets excluded).
+ *
+ * Returns true if a cycle exists within the reachable process graph.
+ */
@GuardedBy({"mService", "mProcLock"})
- @Override
- protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
- int prevProcState) {
- super.setIntermediateProcStateLSP(app, procState, prevProcState);
-
- updateProcStateSlotIfNecessary(app, prevProcState);
+ private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) {
+ mReachableCollectingConsumer.init(reachables);
+ for (int i = 0; i < reachables.size(); i++) {
+ ProcessRecord pr = reachables.get(i);
+ forEachConnectionLSP(pr, mReachableCollectingConsumer);
+ }
}
+ /**
+ * Calculate initial importance states for {@code reachables} and update their slot position
+ * if necessary.
+ */
+ private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) {
+ for (int i = 0, size = reachables.size(); i < size; i++) {
+ final ProcessRecord reachable = reachables.get(i);
+ final int prevProcState = reachable.mState.getCurProcState();
+ final int prevAdj = reachable.mState.getCurRawAdj();
+
+ args.mApp = reachable;
+ computeOomAdjIgnoringReachablesLSP(args);
+
+ updateProcStateSlot(reachable, prevProcState);
+ updateAdjSlot(reachable, prevAdj);
+ }
+ }
+
+ /**
+ * Calculate initial importance states for {@code app}.
+ * Processes not marked reachable cannot change as a part of this update, so connections from
+ * those process can be calculated now.
+ */
@GuardedBy({"mService", "mProcLock"})
- private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+ private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) {
final ProcessRecord app = args.mApp;
- final int cachedAdj = args.mCachedAdj;
final ProcessRecord topApp = args.mTopApp;
final long now = args.mNow;
final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
- final boolean fullUpdate = args.mFullUpdate;
- final ActiveUids uids = args.mUids;
+ computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false);
+
+ mComputeConnectionIgnoringReachableClientsConsumer.args = args;
+ forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer);
+ }
+
+ /**
+ * Stream the connections with {@code app} as a client to
+ * {@code connectionConsumer}.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private static void forEachConnectionLSP(ProcessRecord app,
+ BiConsumer<Connection, ProcessRecord> connectionConsumer) {
final ProcessServiceRecord psr = app.mServices;
for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
ConnectionRecord cr = psr.getConnectionAt(i);
@@ -987,16 +976,14 @@
? cr.binding.service.isolationHostProc : cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
- && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
|| (service.isSdkSandbox && cr.binding.attributedClient != null)) {
continue;
}
-
- computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ connectionConsumer.accept(cr, service);
}
for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
@@ -1004,15 +991,13 @@
final ProcessRecord service = cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
- && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
-
- computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ connectionConsumer.accept(cr, service);
}
final ProcessProviderRecord ppr = app.mProviders;
@@ -1021,15 +1006,51 @@
ProcessRecord provider = cpc.provider.proc;
if (provider == null || provider == app
|| (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
- && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
+ connectionConsumer.accept(cpc, provider);
+ }
+ }
- computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ /**
+ * Stream the connections from clients with {@code app} as the host to {@code
+ * connectionConsumer}.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private static void forEachClientConnectionLSP(ProcessRecord app,
+ BiConsumer<Connection, ProcessRecord> connectionConsumer) {
+ final ProcessServiceRecord psr = app.mServices;
+
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ final ServiceRecord s = psr.getRunningServiceAt(i);
+ final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+ final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+ for (int k = clist.size() - 1; k >= 0; k--) {
+ final ConnectionRecord cr = clist.get(k);
+ final ProcessRecord client;
+ if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+ client = cr.binding.attributedClient;
+ } else {
+ client = cr.binding.client;
+ }
+ connectionConsumer.accept(cr, client);
+ }
+ }
+ }
+
+ final ProcessProviderRecord ppr = app.mProviders;
+ for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+ final ContentProviderRecord cpr = ppr.getProviderAt(i);
+ for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+ final ContentProviderConnection conn = cpr.connections.get(j);
+ connectionConsumer.accept(conn, conn.client);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 2ef433c..0aa1a69 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -718,9 +718,7 @@
mService.mContext, mApp.info.packageName, mApp.info.flags);
}
}
- for (BroadcastQueue queue : mService.mBroadcastQueues) {
- queue.onApplicationProblemLocked(mApp);
- }
+ mService.getBroadcastQueue().onApplicationProblemLocked(mApp);
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 89c8994..a1fdd50 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -208,7 +208,7 @@
// Number of levels we have available for different service connection group importance
// levels.
- static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
+ public static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7009bd0..7356588 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -69,10 +69,8 @@
import com.android.server.wm.WindowProcessListener;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.function.Consumer;
/**
* Full information about a particular process that
@@ -1659,34 +1657,4 @@
&& !mOptRecord.shouldNotFreeze()
&& mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
}
-
- /**
- * Traverses all client processes and feed them to consumer.
- */
- @GuardedBy("mProcLock")
- void forEachClient(@NonNull Consumer<ProcessRecord> consumer) {
- for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) {
- final ServiceRecord s = mServices.getRunningServiceAt(i);
- final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
- s.getConnections();
- for (int j = serviceConnections.size() - 1; j >= 0; j--) {
- final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
- for (int k = clist.size() - 1; k >= 0; k--) {
- final ConnectionRecord cr = clist.get(k);
- if (isSdkSandbox && cr.binding.attributedClient != null) {
- consumer.accept(cr.binding.attributedClient);
- } else {
- consumer.accept(cr.binding.client);
- }
- }
- }
- }
- for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) {
- final ContentProviderRecord cpr = mProviders.getProviderAt(i);
- for (int j = cpr.connections.size() - 1; j >= 0; j--) {
- final ContentProviderConnection conn = cpr.connections.get(j);
- consumer.accept(conn.client);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 562beaf..3d695bc 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -541,7 +541,7 @@
private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
final ProcessRecord attributedClient = connection.binding.attributedClient;
if (attributedClient != null && connection.binding.service.isSdkSandbox) {
- if (attributedClient.mServices.mSdkSandboxConnections == null) {
+ if (attributedClient.mServices.mSdkSandboxConnections != null) {
attributedClient.mServices.mSdkSandboxConnections.remove(connection);
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3abfe082..13a1807 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1419,7 +1419,8 @@
private boolean allowBiometricUnlockForPrivateProfile() {
return android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 68b4e3f..7ee2a7a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -149,11 +149,7 @@
return proto.getBytes();
}
- @android.annotation.EnforcePermission(
- anyOf = {
- android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
- })
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
@@ -297,29 +293,6 @@
restricted, statsClient, isKeyguard);
}
- @android.annotation.EnforcePermission(
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION)
- @Override // Binder call
- public long authenticateInBackground(final IBinder token, final long operationId,
- final IFaceServiceReceiver receiver, final FaceAuthenticateOptions options) {
- // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
- // lockdown, something wrong happened. See similar path in FingerprintService.
-
- super.authenticateInBackground_enforcePermission();
-
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
- if (provider == null) {
- Slog.w(TAG, "Null provider for authenticate");
- return -1;
- }
- options.setSensorId(provider.first);
-
- return provider.second.scheduleAuthenticate(token, operationId,
- 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
- false /* restricted */, BiometricsProtoEnums.CLIENT_UNKNOWN /* statsClient */,
- true /* allowBackgroundAuthentication */);
- }
-
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public long detectFace(final IBinder token,
@@ -583,11 +556,7 @@
return provider.getEnrolledFaces(sensorId, userId);
}
- @android.annotation.EnforcePermission(
- anyOf = {
- android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
- })
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
super.hasEnrolledFaces_enforcePermission();
diff --git a/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
new file mode 100644
index 0000000..413020e
--- /dev/null
+++ b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.clipboard;
+
+import android.annotation.Nullable;
+import android.content.ClipData;
+
+import com.android.server.LocalServices;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class ArcClipboardMonitor implements Consumer<ClipData> {
+ private static final String TAG = "ArcClipboardMonitor";
+
+ public interface ArcClipboardBridge {
+ /**
+ * Called when a clipboard content is updated.
+ */
+ void onPrimaryClipChanged(ClipData data);
+
+ /**
+ * Passes the callback to set a new clipboard content with a uid.
+ */
+ void setHandler(BiConsumer<ClipData, Integer> setAndroidClipboard);
+ }
+
+ private ArcClipboardBridge mBridge;
+ private BiConsumer<ClipData, Integer> mAndroidClipboardSetter;
+
+ ArcClipboardMonitor(final BiConsumer<ClipData, Integer> setAndroidClipboard) {
+ mAndroidClipboardSetter = setAndroidClipboard;
+ LocalServices.addService(ArcClipboardMonitor.class, this);
+ }
+
+ @Override
+ public void accept(final @Nullable ClipData clip) {
+ if (mBridge != null) {
+ mBridge.onPrimaryClipChanged(clip);
+ }
+ }
+
+ /**
+ * Sets the other end of the clipboard bridge.
+ */
+ public void setClipboardBridge(ArcClipboardBridge bridge) {
+ mBridge = bridge;
+ mBridge.setHandler(mAndroidClipboardSetter);
+ }
+}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 49f6070..4c3020f 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -151,7 +151,7 @@
private final ContentCaptureManagerInternal mContentCaptureInternal;
private final AutofillManagerInternal mAutofillInternal;
private final IBinder mPermissionOwner;
- private final Consumer<ClipData> mEmulatorClipboardMonitor;
+ private final Consumer<ClipData> mClipboardMonitor;
private final Handler mWorkerHandler;
@GuardedBy("mLock")
@@ -192,7 +192,7 @@
final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
mPermissionOwner = permOwner;
if (Build.IS_EMULATOR) {
- mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
+ mClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
if (clipboard != null) {
@@ -201,8 +201,12 @@
}
}
});
+ } else if (Build.IS_ARC) {
+ mClipboardMonitor = new ArcClipboardMonitor((clip, uid) -> {
+ setPrimaryClipInternal(clip, uid);
+ });
} else {
- mEmulatorClipboardMonitor = (clip) -> {};
+ mClipboardMonitor = (clip) -> {};
}
updateConfig();
@@ -937,7 +941,7 @@
private void setPrimaryClipInternalLocked(
@Nullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage) {
if (deviceId == DEVICE_ID_DEFAULT) {
- mEmulatorClipboardMonitor.accept(clip);
+ mClipboardMonitor.accept(clip);
}
final int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index f508319..55601bc 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -8,6 +8,19 @@
}
],
"file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ },
+ {
+ "name":"FrameworksVpnTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns":[
+ "Vpn\\.java",
+ "VpnIkeV2Utils\\.java",
+ "VpnProfileStore\\.java"
+ ]
}
],
"presubmit-large": [
@@ -26,10 +39,5 @@
],
"file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
}
- ],
- "postsubmit":[
- {
- "name":"FrameworksVpnTests"
- }
]
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a43f93a..91e560e 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import android.os.Trace;
/**
@@ -110,7 +109,6 @@
try {
mDisplayOffloader.stopOffload();
mIsActive = false;
- mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2010aca..7f014f6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -375,7 +375,8 @@
// information.
// At the time of this writing, this value is changed within updatePowerState() only, which is
// limited to the thread used by DisplayControllerHandler.
- private final BrightnessReason mBrightnessReason = new BrightnessReason();
+ @VisibleForTesting
+ final BrightnessReason mBrightnessReason = new BrightnessReason();
private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
// Brightness animation ramp rates in brightness units per second
@@ -1379,7 +1380,7 @@
// Switch to doze auto-brightness mode if needed
if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
&& !mAutomaticBrightnessController.isInIdleMode()) {
- mAutomaticBrightnessController.switchMode(mPowerRequest.policy == POLICY_DOZE
+ mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
}
@@ -1434,7 +1435,9 @@
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
int brightnessAdjustmentFlags = 0;
- if (Float.isNaN(brightnessState)) {
+ // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+ if (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
mTempBrightnessEvent);
@@ -1455,8 +1458,11 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
}
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} else {
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+ // Restore the lower-priority brightness strategy
+ brightnessState = displayBrightnessState.getBrightness();
}
}
} else {
@@ -3020,9 +3026,10 @@
setDwbcLoggingEnabled(msg.arg1);
break;
case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
- mDisplayBrightnessController.setBrightnessFromOffload(
- Float.intBitsToFloat(msg.arg1));
- updatePowerState();
+ if (mDisplayBrightnessController.setBrightnessFromOffload(
+ Float.intBitsToFloat(msg.arg1))) {
+ updatePowerState();
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index f6d02db..34d53be 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -26,6 +26,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessMappingStrategy;
import com.android.server.display.BrightnessSetting;
@@ -175,14 +176,19 @@
/**
* Sets the brightness from the offload session.
+ * @return Whether the offload brightness has changed
*/
- public void setBrightnessFromOffload(float brightness) {
+ public boolean setBrightnessFromOffload(float brightness) {
synchronized (mLock) {
- if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+ if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null
+ && !BrightnessSynchronizer.floatEquals(mDisplayBrightnessStrategySelector
+ .getOffloadBrightnessStrategy().getOffloadScreenBrightness(), brightness)) {
mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
.setOffloadScreenBrightness(brightness);
+ return true;
}
}
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 8e84450..71cc872 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -133,7 +133,8 @@
} else if (BrightnessUtils.isValidBrightnessValue(
mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
displayBrightnessStrategy = mTemporaryBrightnessStrategy;
- } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+ } else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
displayBrightnessStrategy = mOffloadBrightnessStrategy;
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index d1ca49b..8b54b22 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -108,7 +108,6 @@
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
&& (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
- && brightnessReason != BrightnessReason.REASON_OFFLOAD
&& mAutomaticBrightnessController != null;
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
&& !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a23c08a..d876a38 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -198,7 +198,7 @@
addValidationInfo(Constants.MESSAGE_CEC_VERSION,
oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT);
addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
- new AsciiValidator(3), ADDR_NOT_UNREGISTERED, ADDR_BROADCAST);
+ new AsciiValidator(3), ADDR_TV, ADDR_BROADCAST);
ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03);
addValidationInfo(Constants.MESSAGE_DECK_CONTROL,
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c8c66238..d0532b99 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4665,15 +4665,27 @@
public void onAudioDeviceVolumeChanged(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo) {
+ int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
- // Do nothing if the System Audio device does not support <Set Audio Volume Level>
+ // We can't send <Set Audio Volume Level> if the System Audio device doesn't support it.
+ // But AudioService has already updated its volume and expects us to set it.
+ // So the best we can do is to send <Give Audio Status>, which triggers
+ // <Report Audio Status>, which should update AudioService with its correct volume.
if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
!= DeviceFeatures.FEATURE_SUPPORTED) {
+ // Update the volume tracked in AbsoluteVolumeAudioStatusAction
+ // so it correctly processes the next <Report Audio Status>
+ HdmiCecLocalDevice avbDevice = isTvDevice() ? tv() : playback();
+ avbDevice.updateAvbVolume(volumeInfo.getVolumeIndex());
+ // Send <Give Audio Status>
+ sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+ localDeviceAddress,
+ mSystemAudioDevice.getLogicalAddress()
+ ));
return;
}
// Send <Set Audio Volume Level> to notify the System Audio device of the volume change
- int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
sendCecCommand(SetAudioVolumeLevelMessage.build(
localDeviceAddress,
mSystemAudioDevice.getLogicalAddress(),
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fc99471..0ca4808 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -449,7 +449,7 @@
}
/**
- * Sends a reliable message rom this client to a nanoapp.
+ * Sends a reliable message from this client to a nanoapp.
*
* @param message the message to send
* @param transactionCallback The callback to use to confirm the delivery of the message for
@@ -473,6 +473,12 @@
@Nullable IContextHubTransactionCallback transactionCallback) {
ContextHubServiceUtil.checkPermissions(mContext);
+ // Clear the isReliable and messageSequenceNumber fields.
+ // These will be set to true and a real value if the message
+ // is reliable.
+ message.setIsReliable(false);
+ message.setMessageSequenceNumber(0);
+
@ContextHubTransaction.Result int result;
if (isRegistered()) {
int authState = mMessageChannelNanoappIdMap.getOrDefault(
@@ -485,7 +491,9 @@
// Return a bland error code for apps targeting old SDKs since they wouldn't be able
// to use an error code added in S.
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
- } else if (authState == AUTHORIZATION_UNKNOWN) {
+ }
+
+ if (authState == AUTHORIZATION_UNKNOWN) {
// Only check permissions the first time a nanoapp is queried since nanoapp
// permissions don't currently change at runtime. If the host permission changes
// later, that'll be checked by onOpChanged.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 29ea071..c80f988 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -814,7 +814,8 @@
// storage is locked, instead of when the user is stopped. This would ensure the flags get
// reset if CE storage is locked later for a user that allows delayed locking.
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
return;
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index 8db5905..4fc1a17 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -27,11 +27,15 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
- }
- ],
- "postsubmit":[
+ },
{
- "name":"FrameworksVpnTests"
+ "name": "FrameworksVpnTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["VpnManagerService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f645eaa..3ecc58e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -942,6 +942,23 @@
return false;
}
+ protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component,
+ int userId) {
+ if (!(isPackageOrComponentAllowed(component.flattenToString(), userId)
+ || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
+ return false;
+ }
+ return componentHasBindPermission(component, userId);
+ }
+
+ private boolean componentHasBindPermission(ComponentName component, int userId) {
+ ServiceInfo info = getServiceInfo(component, userId);
+ if (info == null) {
+ return false;
+ }
+ return mConfig.bindPermission.equals(info.permission);
+ }
+
boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) {
synchronized (mApproved) {
ArraySet<String> services = mUserSetServices.get(userId);
@@ -1003,6 +1020,7 @@
for (int uid : uidList) {
if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
anyServicesInvolved = true;
+ trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
}
}
}
@@ -1135,8 +1153,7 @@
synchronized (mMutex) {
if (enabled) {
- if (isPackageOrComponentAllowed(component.flattenToString(), userId)
- || isPackageOrComponentAllowed(component.getPackageName(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(component, userId)) {
registerServiceLocked(component, userId);
} else {
Slog.d(TAG, component + " no longer has permission to be bound");
@@ -1270,6 +1287,33 @@
return removed;
}
+ private void trimApprovedListsForInvalidServices(String packageName, int userId) {
+ synchronized (mApproved) {
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+ if (approvedByType == null) {
+ return;
+ }
+ for (int i = 0; i < approvedByType.size(); i++) {
+ final ArraySet<String> approved = approvedByType.valueAt(i);
+ for (int j = approved.size() - 1; j >= 0; j--) {
+ final String approvedPackageOrComponent = approved.valueAt(j);
+ if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
+ final ComponentName component = ComponentName.unflattenFromString(
+ approvedPackageOrComponent);
+ if (component != null && !componentHasBindPermission(component, userId)) {
+ approved.removeAt(j);
+ if (DEBUG) {
+ Slog.v(TAG, "Removing " + approvedPackageOrComponent
+ + " from approved list; no bind permission found "
+ + mConfig.bindPermission);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
protected String getPackageName(String packageOrComponent) {
final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
if (component != null) {
@@ -1519,8 +1563,7 @@
void reregisterService(final ComponentName cn, final int userId) {
// If rebinding a package that died, ensure it still has permission
// after the rebind delay
- if (isPackageOrComponentAllowed(cn.getPackageName(), userId)
- || isPackageOrComponentAllowed(cn.flattenToString(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(cn, userId)) {
registerService(cn, userId);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ba5882c..b98424c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1204,6 +1204,10 @@
}
}
+ private static boolean privateSpaceFlagsEnabled() {
+ return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+ }
+
private final class SavePolicyFileRunnable implements Runnable {
@Override
public void run() {
@@ -2142,7 +2146,7 @@
}
private boolean isProfileUnavailable(String action) {
- return allowPrivateProfile() ?
+ return privateSpaceFlagsEnabled() ?
action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
}
@@ -2744,7 +2748,7 @@
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- if (allowPrivateProfile()){
+ if (privateSpaceFlagsEnabled()){
filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
}
getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index bc86c82..289faf4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -35,6 +35,8 @@
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -536,36 +538,40 @@
public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
requirePublicOrigin("updateAutomaticZenRule", origin);
- ZenModeConfig newConfig;
+ if (ruleId == null) {
+ throw new IllegalArgumentException("ruleId cannot be null");
+ }
synchronized (mConfigLock) {
if (mConfig == null) return false;
if (DEBUG) {
Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+ " reason=" + reason);
}
- newConfig = mConfig.copy();
- ZenModeConfig.ZenRule rule;
- if (ruleId == null) {
- throw new IllegalArgumentException("Rule doesn't exist");
- } else {
- rule = newConfig.automaticRules.get(ruleId);
- if (rule == null || !canManageAutomaticZenRule(rule)) {
- throw new SecurityException(
- "Cannot update rules not owned by your condition provider");
- }
+ ZenModeConfig.ZenRule oldRule = mConfig.automaticRules.get(ruleId);
+ if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
+ throw new SecurityException(
+ "Cannot update rules not owned by your condition provider");
}
+ ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
if (!Flags.modesApi()) {
- if (rule.enabled != automaticZenRule.isEnabled()) {
- dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
+ if (newRule.enabled != automaticZenRule.isEnabled()) {
+ dispatchOnAutomaticRuleStatusChanged(mConfig.user, newRule.getPkg(), ruleId,
automaticZenRule.isEnabled()
? AUTOMATIC_RULE_STATUS_ENABLED
: AUTOMATIC_RULE_STATUS_DISABLED);
}
}
- populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+ boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newRule,
+ origin, /* isNew= */ false);
+ if (Flags.modesApi() && !updated) {
+ // Bail out so we don't have the side effects of updating a rule (i.e. dropping
+ // condition) when no changes happen.
+ return true;
+ }
return setConfigLocked(newConfig, origin, reason,
- rule.component, true, callingUid);
+ newRule.component, true, callingUid);
}
}
@@ -1073,31 +1079,67 @@
return null;
}
- private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+ /**
+ * Populates a {@code ZenRule} with the content of the {@link AutomaticZenRule}. Can be used for
+ * both rule creation or update (distinguished by the {@code isNew} parameter. The change is
+ * applied differently depending on the origin; for example app-provided changes might be
+ * ignored (if the rule was previously customized by the user), while user-provided changes
+ * update the user-modified bitmasks for any modifications.
+ *
+ * <p>Returns {@code true} if the rule was modified. Note that this is not equivalent to
+ * {@link ZenRule#equals} or {@link AutomaticZenRule#equals}, for various reasons:
+ * <ul>
+ * <li>some metadata-related fields are not considered
+ * <li>some fields (like {@code condition} are always reset, and ignored for this result
+ * <li>an app may provide changes that are not actually applied, as described above
+ * </ul>
+ */
+ private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
@ConfigChangeOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
+ boolean modified = false;
// These values can always be edited by the app, so we apply changes immediately.
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
rule.creationTime = mClock.millis();
- rule.component = automaticZenRule.getOwner();
+ rule.component = azr.getOwner();
rule.pkg = pkg;
+ modified = true;
}
rule.condition = null;
- rule.conditionId = automaticZenRule.getConditionId();
- if (rule.enabled != automaticZenRule.isEnabled()) {
- rule.snoozing = false;
+ if (!Objects.equals(rule.conditionId, azr.getConditionId())) {
+ rule.conditionId = azr.getConditionId();
+ modified = true;
}
- rule.enabled = automaticZenRule.isEnabled();
- rule.configurationActivity = automaticZenRule.getConfigurationActivity();
- rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
- rule.iconResName =
- drawableResIdToResName(rule.pkg, automaticZenRule.getIconResId());
- rule.triggerDescription = automaticZenRule.getTriggerDescription();
- rule.type = automaticZenRule.getType();
+ if (rule.enabled != azr.isEnabled()) {
+ rule.enabled = azr.isEnabled();
+ rule.snoozing = false;
+ modified = true;
+ }
+ if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
+ rule.configurationActivity = azr.getConfigurationActivity();
+ modified = true;
+ }
+ if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) {
+ rule.allowManualInvocation = azr.isManualInvocationAllowed();
+ modified = true;
+ }
+ String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
+ if (!Objects.equals(rule.iconResName, iconResName)) {
+ rule.iconResName = iconResName;
+ modified = true;
+ }
+ if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) {
+ rule.triggerDescription = azr.getTriggerDescription();
+ modified = true;
+ }
+ if (rule.type != azr.getType()) {
+ rule.type = azr.getType();
+ modified = true;
+ }
// TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
- rule.modified = automaticZenRule.isModified();
+ rule.modified = azr.isModified();
// Name is treated differently than other values:
// App is allowed to update name if the name was not modified by the user (even if
@@ -1107,7 +1149,8 @@
String previousName = rule.name;
if (isNew || doesOriginAlwaysUpdateValues(origin)
|| (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
- rule.name = automaticZenRule.getName();
+ rule.name = azr.getName();
+ modified |= !Objects.equals(rule.name, previousName);
}
// For the remaining values, rules can always have all values updated if:
@@ -1119,50 +1162,56 @@
// For all other values, if updates are not allowed, we discard the update.
if (!updateValues) {
- return;
+ return modified;
}
// Updates the bitmasks if the origin of the change is the user.
boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
- if (updateBitmask && !TextUtils.equals(previousName, automaticZenRule.getName())) {
+ if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
}
int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
- automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- if (updateBitmask && rule.zenMode != newZenMode) {
- rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ if (rule.zenMode != newZenMode) {
+ rule.zenMode = newZenMode;
+ if (updateBitmask) {
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ }
+ modified = true;
}
- // Updates the values in the ZenRule itself.
- rule.zenMode = newZenMode;
-
// Updates the bitmask and values for all policy fields, based on the origin.
- updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew);
+ modified |= updatePolicy(rule, azr.getZenPolicy(), updateBitmask, isNew);
// Updates the bitmask and values for all device effect fields, based on the origin.
- updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
+ modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
origin == UPDATE_ORIGIN_APP, updateBitmask);
+
+ return modified;
} else {
- if (rule.enabled != automaticZenRule.isEnabled()) {
+ if (rule.enabled != azr.isEnabled()) {
rule.snoozing = false;
}
- rule.name = automaticZenRule.getName();
+ rule.name = azr.getName();
rule.condition = null;
- rule.conditionId = automaticZenRule.getConditionId();
- rule.enabled = automaticZenRule.isEnabled();
- rule.modified = automaticZenRule.isModified();
- rule.zenPolicy = automaticZenRule.getZenPolicy();
+ rule.conditionId = azr.getConditionId();
+ rule.enabled = azr.isEnabled();
+ rule.modified = azr.isModified();
+ rule.zenPolicy = azr.getZenPolicy();
rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
- automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+ azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ rule.configurationActivity = azr.getConfigurationActivity();
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
rule.creationTime = System.currentTimeMillis();
- rule.component = automaticZenRule.getOwner();
+ rule.component = azr.getOwner();
rule.pkg = pkg;
}
+
+ // Only the MODES_API path cares about the result, so just return whatever here.
+ return true;
}
}
@@ -1182,16 +1231,19 @@
* provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy}
* for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to
* reflect the changes being applied (if applicable, i.e. if the update is from the user).
+ *
+ * <p>Returns {@code true} if the policy of the rule was modified.
*/
- private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+ private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
boolean updateBitmask, boolean isNew) {
if (newPolicy == null) {
if (isNew) {
// Newly created rule with no provided policy; fill in with the default.
zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+ return true;
}
// Otherwise, a null policy means no policy changes, so we can stop here.
- return;
+ return false;
}
// If oldPolicy is null, we compare against the default policy when determining which
@@ -1272,6 +1324,8 @@
}
zenRule.zenPolicyUserModifiedFields = userModifiedFields;
}
+
+ return !newPolicy.equals(oldPolicy);
}
/**
@@ -1283,12 +1337,14 @@
* <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
* treated especially: for a new rule, they are blanked out; for an updated rule, previous
* values are preserved.
+ *
+ * <p>Returns {@code true} if the device effects of the rule were modified.
*/
- private static void updateZenDeviceEffects(ZenRule zenRule,
+ private static boolean updateZenDeviceEffects(ZenRule zenRule,
@Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
// Same as with ZenPolicy, supplying null effects means keeping the previous ones.
if (newEffects == null) {
- return;
+ return false;
}
ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
@@ -1349,6 +1405,8 @@
}
zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
}
+
+ return !newEffects.equals(oldEffects);
}
private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -2505,7 +2563,7 @@
if (resId == 0) {
return null;
}
- Objects.requireNonNull(packageName);
+ requireNonNull(packageName);
try {
final Resources res = mPm.getResourcesForApplication(packageName);
return res.getResourceName(resId);
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 0abe50f..71800ef 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -32,12 +32,13 @@
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -47,11 +48,12 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
import android.service.ondeviceintelligence.IRemoteProcessingService;
import android.service.ondeviceintelligence.IRemoteStorageService;
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.text.TextUtils;
import android.util.Slog;
@@ -69,7 +71,7 @@
* This is the system service for handling calls on the
* {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This
* service holds connection references to the underlying remote services i.e. the isolated service
- * {@link OnDeviceTrustedInferenceService} and a regular
+ * {@link OnDeviceSandboxedInferenceService} and a regular
* service counter part {@link OnDeviceIntelligenceService}.
*
* Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
@@ -90,7 +92,7 @@
protected final Object mLock = new Object();
- private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService;
+ private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
@@ -165,7 +167,7 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeature(id, featureCallback));
+ service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
}
@Override
@@ -185,7 +187,7 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.listFeatures(listFeaturesCallback));
+ service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
}
@Override
@@ -207,7 +209,8 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeatureDetails(feature, featureDetailsCallback));
+ service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+ featureDetailsCallback));
}
@Override
@@ -227,33 +230,35 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.requestFeatureDownload(feature, cancellationSignal,
+ service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+ cancellationSignal,
downloadCallback));
}
@Override
- public void requestTokenCount(Feature feature,
+ public void requestTokenInfo(Feature feature,
Content request, ICancellationSignal cancellationSignal,
- ITokenCountCallback tokenCountcallback) throws RemoteException {
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
Objects.requireNonNull(feature);
Objects.requireNonNull(request);
- Objects.requireNonNull(tokenCountcallback);
+ Objects.requireNonNull(tokenInfoCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
- tokenCountcallback.onFailure(
+ tokenInfoCallback.onFailure(
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.requestTokenCount(feature, request, cancellationSignal,
- tokenCountcallback));
+ service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
+ cancellationSignal,
+ tokenInfoCallback));
}
@Override
@@ -267,7 +272,6 @@
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
Objects.requireNonNull(feature);
Objects.requireNonNull(responseCallback);
- Objects.requireNonNull(request);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
@@ -277,9 +281,10 @@
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.processRequest(feature, request, requestType,
+ service -> service.processRequest(Binder.getCallingUid(), feature, request,
+ requestType,
cancellationSignal, processingSignal,
responseCallback));
}
@@ -293,7 +298,6 @@
IStreamingResponseCallback streamingCallback) throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(streamingCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
@@ -304,9 +308,10 @@
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.processRequestStreaming(feature, request, requestType,
+ service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+ request, requestType,
cancellationSignal, processingSignal,
streamingCallback));
}
@@ -346,7 +351,7 @@
Bundle processingState,
IProcessingUpdateStatusCallback callback) {
try {
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
service -> service.updateProcessingState(
processingState, callback));
@@ -363,22 +368,24 @@
};
}
- private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException {
+ private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceTrustedInferenceService);
+ R.string.config_defaultOnDeviceSandboxedInferenceService);
validateService(serviceName, true);
- mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext,
+ mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
UserHandle.SYSTEM.getIdentifier());
mRemoteInferenceService.setServiceLifecycleCallbacks(
new ServiceConnector.ServiceLifecycleCallbacks<>() {
@Override
public void onConnected(
- @NonNull IOnDeviceTrustedInferenceService service) {
+ @NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
} catch (RemoteException ex) {
@@ -433,7 +440,7 @@
}
checkServiceRequiresPermission(serviceInfo,
- Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE);
+ Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
if (!isIsolatedService(serviceInfo)) {
throw new SecurityException(
"Call required an isolated service, but the configured service: "
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
similarity index 73%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
rename to services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index cc8e788..69ba1d2 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -22,18 +22,18 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
-import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import com.android.internal.infra.ServiceConnector;
/**
- * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding
* logic set by the service implementation via a SecureSettings flag.
*/
-public class RemoteOnDeviceTrustedInferenceService extends
- ServiceConnector.Impl<IOnDeviceTrustedInferenceService> {
+public class RemoteOnDeviceSandboxedInferenceService extends
+ ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
/**
* Creates an instance of {@link ServiceConnector}
*
@@ -43,12 +43,12 @@
* {@link Context#unbindService unbinding}
* @param userId to be used for {@link Context#bindServiceAsUser binding}
*/
- RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName,
+ RemoteOnDeviceSandboxedInferenceService(Context context, ComponentName serviceName,
int userId) {
super(context, new Intent(
- OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ OnDeviceSandboxedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
- IOnDeviceTrustedInferenceService.Stub::asInterface);
+ IOnDeviceSandboxedInferenceService.Stub::asInterface);
// Bind right away
connect();
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 23d48e8..9af2b3f 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -389,7 +389,8 @@
*/
boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) {
if (android.os.Flags.allowPrivateProfile()
- && Flags.enablePermissionToAccessHiddenProfiles()) {
+ && Flags.enablePermissionToAccessHiddenProfiles()
+ && Flags.enablePrivateSpaceFeatures()) {
if (mUmInternal.getUserProperties(userId).getProfileApiVisibility()
!= UserProperties.PROFILE_API_VISIBILITY_HIDDEN) {
return true;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 6b56b85..c7ebb3c 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -584,7 +584,8 @@
return android.os.Flags.allowPrivateProfile()
&& Flags.enableHidingProfiles()
&& Flags.enableLauncherAppsHiddenProfileChecks()
- && Flags.enablePermissionToAccessHiddenProfiles();
+ && Flags.enablePermissionToAccessHiddenProfiles()
+ && Flags.enablePrivateSpaceFeatures();
}
@VisibleForTesting // We override it in unit tests
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index ef8453d..29de26e 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,6 +31,7 @@
import static android.content.pm.PackageManager.DELETE_ALL_USERS;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
@@ -754,8 +755,9 @@
int draftSessionId;
try {
- draftSessionId = Binder.withCleanCallingIdentity(() ->
- createDraftSession(packageName, installerPackage, statusReceiver, userId));
+ draftSessionId = Binder.withCleanCallingIdentity(
+ () -> createDraftSession(packageName, installerPackage, callerPackageName,
+ statusReceiver, userId));
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -795,11 +797,18 @@
}
private int createDraftSession(String packageName, String installerPackage,
+ String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
- sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+ sessionParams.setAppLabel(
+ mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
+ sessionParams.setAppIcon(
+ getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // To make sure SessionInfo::isUnarchival returns true for draft sessions,
+ // INSTALL_UNARCHIVE is also set.
+ sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 211b754..4c653f6 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2833,7 +2833,8 @@
@VisibleForTesting
boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) {
- if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) {
+ if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()
+ || !android.multiuser.Flags.enablePrivateSpaceFeatures()) {
return true;
}
final long start = getStatStartTime();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7349755..88e7596 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,10 +21,15 @@
import static android.content.Intent.EXTRA_USER_ID;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
@@ -1006,9 +1011,17 @@
emulateSystemUserModeIfNeeded();
}
+ private boolean doesDeviceHardwareSupportPrivateSpace() {
+ return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0)
+ && !mPm.hasSystemFeature(FEATURE_WATCH, 0)
+ && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0)
+ && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0);
+ }
+
private static boolean isAutoLockForPrivateSpaceEnabled() {
return android.os.Flags.allowPrivateProfile()
- && Flags.supportAutolockForPrivateSpace();
+ && Flags.supportAutolockForPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
void systemReady() {
@@ -1052,7 +1065,8 @@
private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
return android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts();
+ && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
/**
@@ -1493,7 +1507,8 @@
private boolean isProfileHidden(int userId) {
UserProperties userProperties = getUserPropertiesCopy(userId);
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableHidingProfiles()) {
+ && android.multiuser.Flags.enableHidingProfiles()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
return userProperties.getProfileApiVisibility()
== UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
}
@@ -1693,7 +1708,8 @@
setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
return true;
}
- if (android.os.Flags.allowPrivateProfile()) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
final UserProperties userProperties = getUserPropertiesInternal(userId);
if (userProperties != null
&& userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
@@ -1839,7 +1855,8 @@
logQuietModeEnabled(userId, enableQuietMode, callingPackage);
// Broadcast generic intents for all profiles
- if (android.os.Flags.allowPrivateProfile()) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(),
enableQuietMode, false);
}
@@ -1852,7 +1869,8 @@
private void stopUserForQuietMode(int userId) throws RemoteException {
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
// Allow delayed locking since some profile types want to be able to unlock again via
// biometrics.
ActivityManager.getService()
@@ -2751,6 +2769,18 @@
}
@Override
+ public boolean canAddPrivateProfile(@UserIdInt int userId) {
+ checkCreateUsersPermission("canHaveRestrictedProfile");
+ UserInfo parentUserInfo = getUserInfo(userId);
+ return isUserTypeEnabled(USER_TYPE_PROFILE_PRIVATE)
+ && canAddMoreProfilesToUser(USER_TYPE_PROFILE_PRIVATE,
+ userId, /* allowedToRemoveOne */ false)
+ && (parentUserInfo != null && parentUserInfo.isMain())
+ && doesDeviceHardwareSupportPrivateSpace()
+ && !hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, userId);
+ }
+
+ @Override
public boolean hasRestrictedProfiles(@UserIdInt int userId) {
checkManageUsersPermission("hasRestrictedProfiles");
synchronized (mUsersLock) {
@@ -5308,7 +5338,7 @@
if (!isUserTypeEnabled(userTypeDetails)) {
throwCheckedUserOperationException(
"Cannot add a user of disabled type " + userType + ".",
- UserManager.USER_OPERATION_ERROR_MAX_USERS);
+ UserManager.USER_OPERATION_ERROR_DISABLED_USER);
}
synchronized (mUsersLock) {
@@ -5341,6 +5371,7 @@
final boolean isDemo = UserManager.isUserTypeDemo(userType);
final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
final boolean isCommunalProfile = UserManager.isUserTypeCommunalProfile(userType);
+ final boolean isPrivateProfile = UserManager.isUserTypePrivateProfile(userType);
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo;
@@ -5387,6 +5418,12 @@
+ " for user " + parentId,
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
+ if (android.multiuser.Flags.blockPrivateSpaceCreation()
+ && isPrivateProfile && !canAddPrivateProfile(parentId)) {
+ throwCheckedUserOperationException(
+ "Cannot add profile of type " + userType + " for user " + parentId,
+ UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE);
+ }
if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
&& !isCreationOverrideEnabled()) {
throwCheckedUserOperationException(
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 114daaa..7f9c1cf 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -292,6 +292,7 @@
.setName(USER_TYPE_PROFILE_PRIVATE)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
+ .setEnabled(UserManager.isPrivateProfileEnabled() ? 1 : 0)
.setLabels(R.string.profile_label_private)
.setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge)
.setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge)
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 305b087..5c8215e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,10 @@
package com.android.server.pm.verify.domain;
+import static android.content.IntentFilter.WILDCARD;
+
+import static com.android.server.pm.verify.domain.DomainVerificationUtils.isValidDomain;
+
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
@@ -253,9 +257,18 @@
Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap =
pkgState.getUriRelativeFilterGroupMap();
for (String domain : bundle.keySet()) {
+ if (!isValidDomain(domain)) {
+ continue;
+ }
ArrayList<UriRelativeFilterGroupParcel> parcels =
bundle.getParcelableArrayList(domain, UriRelativeFilterGroupParcel.class);
- domainToGroupsMap.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels));
+ List<UriRelativeFilterGroup> groups =
+ UriRelativeFilterGroup.parcelsToGroups(parcels);
+ if (groups == null || groups.isEmpty()) {
+ domainToGroupsMap.remove(domain);
+ } else {
+ domainToGroupsMap.put(domain, groups);
+ }
}
}
}
@@ -273,9 +286,11 @@
Map<String, List<UriRelativeFilterGroup>> map =
pkgState.getUriRelativeFilterGroupMap();
for (int i = 0; i < domains.size(); i++) {
- List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
- bundle.putParcelableList(domains.get(i),
- UriRelativeFilterGroup.groupsToParcels(groups));
+ if (map.containsKey(domains.get(i))) {
+ List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
+ bundle.putParcelableList(domains.get(i),
+ UriRelativeFilterGroup.groupsToParcels(groups));
+ }
}
}
}
@@ -285,15 +300,29 @@
@NonNull
private List<UriRelativeFilterGroup> getUriRelativeFilterGroups(@NonNull String packageName,
@NonNull String domain) {
- List<UriRelativeFilterGroup> groups = Collections.emptyList();
+ List<UriRelativeFilterGroup> groups;
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState != null) {
- groups = pkgState.getUriRelativeFilterGroupMap().getOrDefault(domain,
- Collections.emptyList());
+ Map<String, List<UriRelativeFilterGroup>> groupMap =
+ pkgState.getUriRelativeFilterGroupMap();
+ groups = groupMap.get(domain);
+ if (groups != null) {
+ return groups;
+ }
+ int first = domain.indexOf(".");
+ int second = domain.indexOf('.', first + 1);
+ while (first > 0 && second > 0) {
+ groups = groupMap.get(WILDCARD + domain.substring(first));
+ if (groups != null) {
+ return groups;
+ }
+ first = second;
+ second = domain.indexOf('.', second + 1);
+ }
}
}
- return groups;
+ return Collections.emptyList();
}
@NonNull
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 3fd00c6..b8c4d22 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -35,6 +35,9 @@
public final class DomainVerificationUtils {
+ public static final int MAX_DOMAIN_LENGTH = 254;
+ public static final int MAX_DOMAIN_LABEL_LENGTH = 63;
+
private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
() -> Patterns.DOMAIN_NAME.matcher(""));
@@ -108,4 +111,41 @@
appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
return appInfo;
}
+
+ static boolean isValidDomain(String domain) {
+ if (domain.length() > MAX_DOMAIN_LENGTH || domain.equals("*")) {
+ return false;
+ }
+ if (domain.charAt(0) == '*') {
+ if (domain.charAt(1) != '.') {
+ return false;
+ }
+ domain = domain.substring(2);
+ }
+ int labels = 1;
+ int labelStart = -1;
+ for (int i = 0; i < domain.length(); i++) {
+ char c = domain.charAt(i);
+ if (c == '.') {
+ int labelLength = i - labelStart - 1;
+ if (labelLength == 0 || labelLength > MAX_DOMAIN_LABEL_LENGTH) {
+ return false;
+ }
+ labelStart = i;
+ labels += 1;
+ } else if (!isValidDomainChar(c)) {
+ return false;
+ }
+ }
+ int lastLabelLength = domain.length() - labelStart - 1;
+ if (lastLabelLength == 0 || lastLabelLength > 63) {
+ return false;
+ }
+ return labels > 1;
+ }
+
+ private static boolean isValidDomainChar(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9') || c == '-';
+ }
}
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 24d7acd..5360788 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -700,6 +700,8 @@
return runOverrideStatus();
case "reset":
return runReset();
+ case "headroom":
+ return runHeadroom();
default:
return handleDefaultCommands(cmd);
}
@@ -862,6 +864,36 @@
}
}
+ private int runHeadroom() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final PrintWriter pw = getOutPrintWriter();
+ int forecastSecs;
+ try {
+ forecastSecs = Integer.parseInt(getNextArgRequired());
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex);
+ return -1;
+ }
+ if (!mHalReady.get()) {
+ pw.println("Error: thermal HAL is not ready");
+ return -1;
+ }
+
+ if (forecastSecs < MIN_FORECAST_SEC || forecastSecs > MAX_FORECAST_SEC) {
+ pw.println(
+ "Error: forecast second input should be in range [" + MIN_FORECAST_SEC
+ + "," + MAX_FORECAST_SEC + "]");
+ return -1;
+ }
+ float headroom = mTemperatureWatcher.getForecast(forecastSecs);
+ pw.println("Headroom in " + forecastSecs + " seconds: " + headroom);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -877,6 +909,9 @@
pw.println(" status code is defined in android.os.Temperature.");
pw.println(" reset");
pw.println(" unlocks the thermal status of the device.");
+ pw.println(" headroom FORECAST_SECONDS");
+ pw.println(" gets the thermal headroom forecast in specified seconds, from ["
+ + MIN_FORECAST_SEC + "," + MAX_FORECAST_SEC + "].");
pw.println();
}
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index ffce50e..79adcb4 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -349,7 +349,6 @@
}
}
- userState.mIAppMap.clear();
userState.mAdServiceMap = adServiceMap;
}
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 5b77433..2fc183d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -532,7 +532,7 @@
return false;
}
- if (Flags.keyboardCategoryEnabled()) {
+ if (Flags.keyboardCategoryEnabled() && mVibrationConfig.hasFixedKeyboardAmplitude()) {
int category = callerInfo.attrs.getCategory();
if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
// Keyboard touch has a different user setting.
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 9616c28..5175b74 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -131,19 +131,23 @@
(bitmapSize.y - crop.height()) / 2);
return crop;
}
+
+ // If any suggested crop is invalid, fallback to case 1
+ for (int i = 0; i < suggestedCrops.size(); i++) {
+ Rect testCrop = suggestedCrops.valueAt(i);
+ if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
+ || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
+ Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
+ return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ }
+ }
+
int orientation = getOrientation(displaySize);
// Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
- if (suggestedCrop.left < 0 || suggestedCrop.top < 0
- || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
- Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
- Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
- return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
- } else {
return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
- }
}
// Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -247,6 +251,7 @@
Rect adjustedCrop = new Rect(crop);
float cropRatio = ((float) crop.width()) / crop.height();
float screenRatio = ((float) screenSize.x) / screenSize.y;
+ if (cropRatio == screenRatio) return crop;
if (cropRatio > screenRatio) {
if (!parallax) {
// rotate everything 90 degrees clockwise, compute the result, and rotate back
@@ -276,6 +281,7 @@
}
}
} else {
+ // TODO (b/281648899) the third case is not always correct, fix that.
int widthToAdd = mode == REMOVE ? 0
: mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
: (int) (0.5 + crop.height() - crop.width());
@@ -646,6 +652,9 @@
if (!success) {
Slog.e(TAG, "Unable to apply new wallpaper");
wallpaper.getCropFile().delete();
+ wallpaper.mCropHints.clear();
+ wallpaper.cropHint.set(0, 0, 0, 0);
+ wallpaper.mSampleSize = 1f;
}
if (wallpaper.getCropFile().exists()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 88e9672..0165d65 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -341,6 +341,7 @@
} else {
wallpaper.cropHint.set(totalCropHint);
}
+ wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
wallpaper.cropHint.set(totalCropHint);
}
@@ -493,6 +494,7 @@
out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+ out.attributeFloat(null, "sampleSize", wallpaper.mSampleSize);
} else if (!multiCrop()) {
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d30a216..7ba953d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -658,7 +658,7 @@
boolean mVoiceInteraction;
- private int mPendingRelaunchCount;
+ int mPendingRelaunchCount;
long mRelaunchStartTime;
// True if we are current in the process of removing this app token from the display
@@ -3988,7 +3988,7 @@
// If the display does not have running activity, the configuration may need to be
// updated for restoring original orientation of the display.
if (next == null) {
- mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */, mDisplayContent,
true /* deferResume */);
}
if (activityRemoved) {
@@ -6463,12 +6463,6 @@
* state to match that fact.
*/
void completeResumeLocked() {
- final boolean wasVisible = mVisibleRequested;
- setVisibility(true);
- if (!wasVisible) {
- // Visibility has changed, so take a note of it so we call the TaskStackChangedListener
- mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
- }
idle = false;
results = null;
if (newIntents != null && newIntents.size() > 0) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6ad056f..2c39c58 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1625,7 +1625,7 @@
final ActivityRecord currentTop = startedActivityRootTask.topRunningActivity();
if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
mRootWindowContainer.ensureVisibilityAndConfig(
- currentTop, currentTop.getDisplayId(), false /* deferResume */);
+ currentTop, currentTop.mDisplayContent, false /* deferResume */);
}
if (!avoidMoveToFront() && mDoResume && mRootWindowContainer
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2cda1f5..826e332 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -842,7 +842,7 @@
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
// configurations and trying to resume top activity of focused root task.
- mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
+ mRootWindowContainer.ensureVisibilityAndConfig(r, r.mDisplayContent,
true /* deferResume */);
}
@@ -1011,7 +1011,8 @@
if (andResume && readyToResume()) {
// As part of the process of launching, ActivityThread also performs
// a resume.
- rootTask.minimalResumeActivityLocked(r);
+ r.setState(RESUMED, "realStartActivityLocked");
+ r.completeResumeLocked();
} else {
// This activity is not starting in the resumed state... which should look like we asked
// it to pause+stop (but remain visible), and it has done so and reported back the
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07a03eb..a75d3b6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1742,30 +1742,21 @@
*
* @param starting The currently starting activity or {@code null} if there is
* none.
- * @param displayId The id of the display where operation is executed.
+ * @param displayContent The display where the operation is executed.
* @param deferResume Whether to defer resume while updating config.
- * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
- * because of configuration update.
*/
- boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) {
+ void ensureVisibilityAndConfig(@Nullable ActivityRecord starting,
+ @NonNull DisplayContent displayContent, boolean deferResume) {
// First ensure visibility without updating the config just yet. We need this to know what
// activities are affecting configuration now.
// Passing null here for 'starting' param value, so that visibility of actual starting
// activity will be properly updated.
ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
- if (displayId == INVALID_DISPLAY) {
- // The caller didn't provide a valid display id, skip updating config.
- return true;
- }
-
// Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
- final DisplayContent displayContent = getDisplayContent(displayId);
- Configuration config = null;
- if (displayContent != null) {
- config = displayContent.updateOrientation(starting, true /* forceUpdate */);
- }
+ final Configuration config =
+ displayContent.updateOrientation(starting, true /* forceUpdate */);
// Visibilities may change so let the starting activity have a chance to report. Can't do it
// when visibility is changed in each AppWindowToken because it may trigger wrong
// configuration push because the visibility of some activities may not be updated yet.
@@ -1773,13 +1764,8 @@
starting.reportDescendantOrientationChangeIfNeeded();
}
- if (displayContent != null) {
- // Update the configuration of the activities on the display.
- return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
- deferResume);
- } else {
- return true;
- }
+ // Update the configuration of the activities on the display.
+ displayContent.updateDisplayOverrideConfigurationLocked(config, starting, deferResume);
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index e16d869..73aa307 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -978,7 +978,6 @@
}
effectiveUid = info.applicationInfo.uid;
mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
- stringName = null;
if (info.targetActivity == null) {
if (_intent != null) {
@@ -1045,6 +1044,7 @@
updateTaskDescription();
}
mSupportsPictureInPicture = info.supportsPictureInPicture();
+ stringName = null;
// Re-adding the task to Recents once updated
if (inRecents) {
@@ -4948,13 +4948,6 @@
}
}
- void minimalResumeActivityLocked(ActivityRecord r) {
- ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (starting new instance) "
- + "callers=%s", r, Debug.getCallers(5));
- r.setState(RESUMED, "minimalResumeActivityLocked");
- r.completeResumeLocked();
- }
-
void checkReadyForSleep() {
if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) {
mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
@@ -5863,7 +5856,7 @@
}
mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
- mDisplayContent.mDisplayId, false /* deferResume */);
+ mDisplayContent, false /* deferResume */);
} finally {
if (mTransitionController.isShellTransitionsEnabled()) {
mAtmService.continueWindowLayout();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a818a72..597e901 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1536,10 +1536,6 @@
next.setState(RESUMED, "resumeTopActivity");
- // Have the window manager re-evaluate the orientation of
- // the screen based on the new activity order.
- boolean notUpdated = true;
-
// Activity should also be visible if set mLaunchTaskBehind to true (see
// ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
if (shouldBeVisible(next)) {
@@ -1551,28 +1547,15 @@
// result of invisible window resize.
// TODO: Remove this once visibilities are set correctly immediately when
// starting an activity.
- notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ final int originalRelaunchingCount = next.mPendingRelaunchCount;
+ mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayContent,
false /* deferResume */);
- }
-
- if (notUpdated) {
- // The configuration update wasn't able to keep the existing
- // instance of the activity, and instead started a new one.
- // We should be all done, but let's just make sure our activity
- // is still at the top and schedule another run if something
- // weird happened.
- ActivityRecord nextNext = topRunningActivity();
- ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
- + "%s, new next: %s", next, nextNext);
- if (nextNext != next) {
- // Do over!
- mTaskSupervisor.scheduleResumeTopActivities();
+ if (next.mPendingRelaunchCount > originalRelaunchingCount) {
+ // The activity is scheduled to relaunch, then ResumeActivityItem will be also
+ // included (see ActivityRecord#relaunchActivityLocked) if it should resume.
+ next.completeResumeLocked();
+ return true;
}
- if (!next.isVisibleRequested() || next.mAppStopped) {
- next.setVisibility(true);
- }
- next.completeResumeLocked();
- return true;
}
try {
@@ -1655,17 +1638,7 @@
return true;
}
- // From this point on, if something goes wrong there is no way
- // to recover the activity.
- try {
- next.completeResumeLocked();
- } catch (Exception e) {
- // If any exception gets thrown, toss away this
- // activity and try the next one.
- Slog.w(TAG, "Exception thrown during resume of " + next, e);
- next.finishIfPossible("resume-exception", true /* oomAdj */);
- return true;
- }
+ next.completeResumeLocked();
} else {
// Whoops, need to restart this activity!
if (!next.hasBeenLaunched) {
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 7fc61e1..a84a99a 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -573,7 +573,7 @@
// Capture the animation surface control for activity's main window
static class StartingWindowAnimationAdaptor implements AnimationAdapter {
- SurfaceControl mAnimationLeash;
+
@Override
public boolean getShowWallpaper() {
return false;
@@ -582,14 +582,10 @@
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
- mAnimationLeash = animationLeash;
}
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
- if (mAnimationLeash == animationLeash) {
- mAnimationLeash = null;
- }
}
@Override
@@ -604,9 +600,6 @@
@Override
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash=");
- pw.print(mAnimationLeash);
- pw.println();
}
@Override
@@ -616,16 +609,16 @@
static SurfaceControl applyStartingWindowAnimation(WindowState window) {
final SurfaceControl.Transaction t = window.getPendingTransaction();
- final Rect mainFrame = window.getRelativeFrame();
final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
- if (adaptor.mAnimationLeash == null) {
+ final SurfaceControl leash = window.getAnimationLeash();
+ if (leash == null) {
Slog.e(TAG, "Cannot start starting window animation, the window " + window
+ " was removed");
return null;
}
- t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
- return adaptor.mAnimationLeash;
+ t.setPosition(leash, window.mSurfacePosition.x, window.mSurfacePosition.y);
+ return leash;
}
boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
@@ -696,7 +689,9 @@
removalInfo.roundedCornerRadius =
topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
- removalInfo.mainFrame = mainWindow.getRelativeFrame();
+ removalInfo.mainFrame = new Rect(mainWindow.getFrame());
+ removalInfo.mainFrame.offsetTo(mainWindow.mSurfacePosition.x,
+ mainWindow.mSurfacePosition.y);
}
}
try {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0b29f96..a6db310f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -49,6 +49,7 @@
import com.android.internal.os.ByteTransferPipe;
import com.android.internal.protolog.LegacyProtoLogImpl;
+import com.android.internal.protolog.PerfettoProtoLogImpl;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.IoThread;
@@ -111,8 +112,13 @@
case "logging":
IProtoLog instance = ProtoLog.getSingleInstance();
int result = 0;
- if (instance instanceof LegacyProtoLogImpl) {
- result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+ if (instance instanceof LegacyProtoLogImpl
+ || instance instanceof PerfettoProtoLogImpl) {
+ if (instance instanceof LegacyProtoLogImpl) {
+ result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+ } else {
+ result = ((PerfettoProtoLogImpl) instance).onShellCommand(this);
+ }
if (result != 0) {
pw.println("Not handled, please use "
+ "`adb shell dumpsys activity service SystemUIService "
@@ -120,8 +126,7 @@
}
} else {
result = -1;
- pw.println("Command not supported. "
- + "Only supported when using legacy ProtoLog.");
+ pw.println("ProtoLog impl doesn't support handling commands");
}
return result;
case "user-rotation":
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 1106a95..46bac16 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -695,7 +695,8 @@
*/
private boolean mDrawnStateEvaluated;
- private final Point mSurfacePosition = new Point();
+ /** The surface position relative to the parent container. */
+ final Point mSurfacePosition = new Point();
/**
* A region inside of this window to be excluded from touch.
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0b58543..b32c544 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1112,6 +1112,8 @@
)
enforceCallingOrSelfAnyPermission(
"getAllPermissionStates",
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
Manifest.permission.GET_RUNTIME_PERMISSIONS
)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 66e0717..c54a94e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -83,11 +83,7 @@
}
val bundle = service.getUriRelativeFilterGroups(PKG_ONE, listOf(DOMAIN_1, DOMAIN_2))
- assertThat(bundle.keySet()).containsExactlyElementsIn(listOf(DOMAIN_1, DOMAIN_2))
- assertThat(bundle.getParcelableArrayList(DOMAIN_1, UriRelativeFilterGroup::class.java))
- .isEmpty()
- assertThat(bundle.getParcelableArrayList(DOMAIN_2, UriRelativeFilterGroup::class.java))
- .isEmpty()
+ assertThat(bundle.keySet()).isEmpty()
val pathGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_ALLOW)
pathGroup.addUriRelativeFilter(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index fbb14c3..4409051 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -26,7 +26,6 @@
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import org.junit.Before;
import org.junit.Test;
@@ -66,8 +65,6 @@
mSession.stopOffload();
assertFalse(mSession.isActive());
- verify(mDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
// An inactive session shouldn't be stopped again
mSession.stopOffload();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index e9315c8..14d8a9c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -22,6 +22,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -77,6 +78,7 @@
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
import com.android.server.display.brightness.BrightnessEvent;
+import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
@@ -1559,6 +1561,43 @@
verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+ }
+
+ @Test
+ public void testBrightness_AutomaticHigherPriorityThanOffload() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ float brightness = 0.34f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ mHolder.dpc.setBrightnessFromOffload(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+
+ // Now automatic brightness becomes available
+ brightness = 0.22f;
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(brightness);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_AUTOMATIC, mHolder.dpc.mBrightnessReason.getReason());
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b99ecf3..14de527 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -46,7 +46,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.PowerManager;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -1229,8 +1228,6 @@
verify(mDisplayOffloader).stopOffload();
assertFalse(mDisplayOffloadSession.isActive());
- verify(mMockedDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
private void initDisplayOffloadSession() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 289d54b..9b6cc0a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -393,7 +393,31 @@
OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
offloadBrightnessStrategy);
- mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+ assertTrue(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_OffloadStrategyNull() {
+ float brightness = 0.4f;
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ assertFalse(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_BrightnessUnchanged() {
+ float brightness = 0.4f;
+ OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+ when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness);
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+ offloadBrightnessStrategy);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness);
+ assertFalse(brightnessUpdated);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 1c681ce..0e89d83 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -247,6 +247,7 @@
displayPowerRequest.screenBrightnessOverride = Float.NaN;
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
displayPowerRequest, Display.STATE_ON));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index ba462e3..a5dc668 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,29 +188,6 @@
}
@Test
- public void testAutoBrightnessState_BrightnessReasonIsOffload() {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
- int targetDisplayState = Display.STATE_ON;
- boolean allowAutoBrightnessWhileDozing = false;
- int brightnessReason = BrightnessReason.REASON_OFFLOAD;
- int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
- float lastUserSetBrightness = 0.2f;
- boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
- mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
- allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
- userSetBrightnessChanged);
- verify(mAutomaticBrightnessController)
- .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
- mBrightnessConfiguration,
- lastUserSetBrightness,
- userSetBrightnessChanged, 0.5f,
- false, policy, true);
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
- }
-
- @Test
public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_DOZE;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index fb47aa8..9d3caa5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -98,8 +98,6 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
- final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
-
@Mock
AppOpsService mAppOpsService;
@Mock
@@ -120,6 +118,7 @@
HandlerThread mHandlerThread;
TestLooperManager mLooper;
AtomicInteger mNextPid;
+ BroadcastHistory mEmptyHistory;
/**
* Map from PID to registered registered runtime receivers.
@@ -137,6 +136,13 @@
.acquireLooperManager(mHandlerThread.getLooper()));
mNextPid = new AtomicInteger(100);
+ mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+ mEmptyHistory = new BroadcastHistory(mConstants) {
+ public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+ // Ignored
+ }
+ };
+
LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -164,8 +170,6 @@
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
-
- mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
}
public void tearDown() throws Exception {
@@ -213,8 +217,8 @@
}
@Override
- public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
- return mBroadcastQueues;
+ public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
+ return null;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index cc6fc80..bcf297f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -118,15 +118,9 @@
mConstants.DELAY_NORMAL_MILLIS = 10_000;
mConstants.DELAY_CACHED_MILLIS = 120_000;
- final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
- public void addBroadcastToHistoryLocked(BroadcastRecord original) {
- // Ignored
- }
- };
-
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- mConstants, mConstants, mSkipPolicy, emptyHistory);
- mBroadcastQueues[0] = mImpl;
+ mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+ mAms.setBroadcastQueueForTest(mImpl);
doReturn(1L).when(mQueue1).getRunnableAt();
doReturn(2L).when(mQueue2).getRunnableAt();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e141faf..56e5bd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -85,12 +85,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.verification.VerificationMode;
@@ -100,7 +96,6 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -114,18 +109,10 @@
* Common tests for {@link BroadcastQueue} implementations.
*/
@MediumTest
-@RunWith(Parameterized.class)
@SuppressWarnings("GuardedBy")
public class BroadcastQueueTest extends BaseBroadcastQueueTest {
private static final String TAG = "BroadcastQueueTest";
- private final Impl mImpl;
-
- private enum Impl {
- DEFAULT,
- MODERN,
- }
-
private BroadcastQueue mQueue;
private UidObserver mUidObserver;
@@ -157,15 +144,6 @@
*/
private List<Pair<Integer, String>> mScheduledBroadcasts = new ArrayList<>();
- @Parameters(name = "impl={0}")
- public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
- }
-
- public BroadcastQueueTest(Impl impl) {
- mImpl = impl;
- }
-
@Before
public void setUp() throws Exception {
super.setUp();
@@ -251,24 +229,9 @@
}).when(mAms).registerUidObserver(any(), anyInt(),
eq(ActivityManager.PROCESS_STATE_TOP), any());
- final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
- public void addBroadcastToHistoryLocked(BroadcastRecord original) {
- // Ignored
- }
- };
-
- if (mImpl == Impl.DEFAULT) {
- mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
- mConstants, mSkipPolicy, emptyHistory, false,
- ProcessList.SCHED_GROUP_DEFAULT);
- } else if (mImpl == Impl.MODERN) {
- mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- mConstants, mConstants, mSkipPolicy, emptyHistory);
- } else {
- throw new UnsupportedOperationException();
- }
- mBroadcastQueues[0] = mQueue;
-
+ mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+ mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+ mAms.setBroadcastQueueForTest(mQueue);
mQueue.start(mContext.getContentResolver());
// Set the constants after invoking BroadcastQueue.start() to ensure they don't
@@ -489,10 +452,8 @@
}
private void assertHealth() {
- if (mImpl == Impl.MODERN) {
- // If this fails, it'll throw a clear reason message
- ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
- }
+ // If this fails, it'll throw a clear reason message
+ ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
}
private static Map<String, Object> asMap(Bundle bundle) {
@@ -814,18 +775,13 @@
.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
- if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
- // Nuance: the default implementation doesn't ask for manifest
- // cold-started apps to be thawed, but the modern stack does
- } else {
- // Confirm that app was thawed
- verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
- eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
+ // Confirm that app was thawed
+ verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
+ eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
- // Confirm that we added package to process
- verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
- anyLong(), any());
- }
+ // Confirm that we added package to process
+ verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+ anyLong(), any());
// Confirm that we've reported package as being used
verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
@@ -868,9 +824,6 @@
*/
@Test
public void testWedged_Registered_Ordered() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
ProcessBehavior.WEDGE);
@@ -891,9 +844,6 @@
*/
@Test
public void testWedged_Registered_ResultTo() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
ProcessBehavior.WEDGE);
@@ -994,9 +944,7 @@
getUidForPackage(PACKAGE_GREEN));
// Modern queue always kills the target process when broadcast delivery fails, where as
// the legacy queue leaves the process killing task to AMS
- if (mImpl == Impl.MODERN) {
- assertNull(receiverGreenApp);
- }
+ assertNull(receiverGreenApp);
final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
getUidForPackage(PACKAGE_BLUE));
verifyScheduleReceiver(receiverBlueApp, airplane);
@@ -1110,10 +1058,8 @@
waitForIdle();
// Legacy stack does not remove registered receivers as part of
// cleanUpDisabledPackageReceiversLocked() call, so verify this only on modern queue.
- if (mImpl == Impl.MODERN) {
- verifyScheduleReceiver(never(), callerApp, USER_GUEST);
- verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
- }
+ verifyScheduleReceiver(never(), callerApp, USER_GUEST);
+ verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
for (String pkg : new String[] {
PACKAGE_GREEN, PACKAGE_BLUE, PACKAGE_YELLOW
}) {
@@ -1199,9 +1145,6 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
public void testRepeatedKillWithoutNotify() throws Exception {
- // Legacy queue does not handle repeated kills that don't get notified.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1227,9 +1170,7 @@
// Modern queue always kills the target process when broadcast delivery fails, where as
// the legacy queue leaves the process killing task to AMS
- if (mImpl == Impl.MODERN) {
- assertNull(receiverGreenApp);
- }
+ assertNull(receiverGreenApp);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
@@ -1273,11 +1214,7 @@
assertNotEquals(receiverBlueApp, restartedReceiverBlueApp);
// Legacy queue will always try delivering the broadcast even if the process
// has been killed.
- if (mImpl == Impl.MODERN) {
- verifyScheduleReceiver(never(), receiverBlueApp, airplane);
- } else {
- verifyScheduleReceiver(times(1), receiverBlueApp, airplane);
- }
+ verifyScheduleReceiver(never(), receiverBlueApp, airplane);
// Verify that the new process receives the broadcast.
verifyScheduleReceiver(times(1), restartedReceiverBlueApp, airplane);
}
@@ -1671,9 +1608,6 @@
*/
@Test
public void testPrioritized_withDeferrableBroadcasts() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1834,10 +1768,6 @@
@Test
public void testReplacePending_withUrgentBroadcast() throws Exception {
- // The behavior is same with the legacy queue but AMS takes care of finding
- // the right queue and replacing the broadcast.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final Intent timeTickFirst = new Intent(Intent.ACTION_TIME_TICK);
@@ -1903,15 +1833,9 @@
waitForIdle();
- if (mImpl == Impl.MODERN) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
- verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
- } else {
- verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
- verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
- verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
- }
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
}
@Test
@@ -1931,14 +1855,10 @@
withPriority(receiverGreenA, 5))));
waitForIdle();
- if (mImpl == Impl.DEFAULT) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- } else {
- // In the modern queue, we don't end up replacing the old broadcast to
- // avoid creating priority inversion and so the process will receive
- // both the old and new broadcasts.
- verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
- }
+ // In the modern queue, we don't end up replacing the old broadcast to
+ // avoid creating priority inversion and so the process will receive
+ // both the old and new broadcasts.
+ verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
}
@Test
@@ -1966,11 +1886,7 @@
verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
- if (mImpl == Impl.MODERN) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- } else {
- verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
- }
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
}
@@ -2002,9 +1918,6 @@
@Test
public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2224,16 +2137,9 @@
}
waitForIdle();
- final int expectedTimes;
- switch (mImpl) {
- // Original stack requested for every single receiver; yikes
- case DEFAULT: expectedTimes = 64; break;
- // Modern stack requests once each time we promote a process to
- // running; we promote "green" twice, and "blue" and "yellow" once
- case MODERN: expectedTimes = 4; break;
- default: throw new UnsupportedOperationException();
- }
-
+ // Modern stack requests once each time we promote a process to
+ // running; we promote "green" twice, and "blue" and "yellow" once
+ final int expectedTimes = 4;
verify(mAms, times(expectedTimes))
.updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
}
@@ -2302,9 +2208,6 @@
*/
@Test
public void testDeferralPolicy_UntilActive() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2350,9 +2253,6 @@
*/
@Test
public void testDeferralPolicy_UntilActive_WithMultiProcessUid() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp1 = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverGreenApp2 = makeActiveProcessRecord(PACKAGE_GREEN,
@@ -2384,9 +2284,6 @@
@Test
public void testBroadcastDelivery_uidForeground() throws Exception {
- // Legacy stack doesn't support prioritization to foreground app.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2418,9 +2315,6 @@
@Test
public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
- // Legacy stack doesn't support prioritization to foreground app.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index f0efb79..878b945 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -61,8 +61,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -747,68 +745,6 @@
}
}
- /**
- * Test the class {@link BroadcastDispatcher#DeferredBootCompletedBroadcastPerUser}
- */
- @Test
- public void testDeferBootCompletedBroadcast_dispatcher() {
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, false);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, false);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, true);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, true);
- }
-
- private void testDeferBootCompletedBroadcast_dispatcher_internal(String action,
- boolean isAllUidReady) {
- final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, new int[] {USER0});
- final BroadcastRecord br = createBroadcastRecord(receivers, USER0, new Intent(action));
- assertEquals(PACKAGE_LIST.length, br.receivers.size());
-
- SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked(
- mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL);
- // original BroadcastRecord receivers list is empty now.
- assertTrue(br.receivers.isEmpty());
- assertEquals(PACKAGE_LIST.length, deferred.size());
-
- DeferredBootCompletedBroadcastPerUser deferredPerUser =
- new DeferredBootCompletedBroadcastPerUser(USER0);
- deferredPerUser.enqueueBootCompletedBroadcasts(action, deferred);
-
- if (action.equals(ACTION_LOCKED_BOOT_COMPLETED)) {
- assertEquals(PACKAGE_LIST.length,
- deferredPerUser.mDeferredLockedBootCompletedBroadcasts.size());
- assertTrue(deferredPerUser.mLockedBootCompletedBroadcastReceived);
- for (int i = 0; i < PACKAGE_LIST.length; i++) {
- final int uid = UserHandle.getUid(USER0, getAppId(i));
- if (!isAllUidReady) {
- deferredPerUser.updateUidReady(uid);
- }
- BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
- isAllUidReady);
- final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
- assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
- assertEquals(uid, info.activityInfo.applicationInfo.uid);
- }
- assertEquals(0, deferredPerUser.mUidReadyForLockedBootCompletedBroadcast.size());
- } else if (action.equals(ACTION_BOOT_COMPLETED)) {
- assertEquals(PACKAGE_LIST.length,
- deferredPerUser.mDeferredBootCompletedBroadcasts.size());
- assertTrue(deferredPerUser.mBootCompletedBroadcastReceived);
- for (int i = 0; i < PACKAGE_LIST.length; i++) {
- final int uid = UserHandle.getUid(USER0, getAppId(i));
- if (!isAllUidReady) {
- deferredPerUser.updateUidReady(uid);
- }
- BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
- isAllUidReady);
- final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
- assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
- assertEquals(uid, info.activityInfo.applicationInfo.uid);
- }
- assertEquals(0, deferredPerUser.mUidReadyForBootCompletedBroadcast.size());
- }
- }
-
private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
String packageName, int userId) {
record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1226f0c..872ac40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -297,6 +297,10 @@
} else {
updateProcessRecordNodes(Arrays.asList(apps));
if (apps.length == 1) {
+ final ProcessRecord app = apps[0];
+ if (!sService.mProcessList.getLruProcessesLOSP().contains(app)) {
+ sService.mProcessList.getLruProcessesLOSP().add(app);
+ }
sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
@@ -475,7 +479,16 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
- assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
+ final int expectedAdj;
+ if (sService.mConstants.ENABLE_NEW_OOMADJ) {
+ // A cached empty process can be at best a level higher than the min cached adj.
+ expectedAdj = sFirstCachedAdj;
+ } else {
+ // This is wrong but legacy behavior is going to be removed and not worth fixing.
+ expectedAdj = CACHED_APP_MIN_ADJ;
+ }
+
+ assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj,
SCHED_GROUP_BACKGROUND);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 7bbcd50..bc7c9a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,6 +15,10 @@
*/
package com.android.server.pm;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -41,6 +45,7 @@
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.KeyguardManager;
import android.content.Context;
@@ -48,6 +53,7 @@
import android.content.pm.UserInfo;
import android.multiuser.Flags;
import android.os.PowerManager;
+import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -76,6 +82,7 @@
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -120,11 +127,14 @@
private static final String TAG_RESTRICTIONS = "restrictions";
+ private static final String PRIVATE_PROFILE_NAME = "TestPrivateProfile";
+
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.spyStatic(UserManager.class)
.spyStatic(LocalServices.class)
.spyStatic(SystemProperties.class)
+ .spyStatic(ActivityManager.class)
.mockStatic(Settings.Global.class)
.mockStatic(Settings.Secure.class)
.build();
@@ -163,6 +173,7 @@
@Before
@UiThreadTest // Needed to initialize main handler
public void setFixtures() {
+ MockitoAnnotations.initMocks(this);
mSpiedContext = spy(mRealContext);
// Called when WatchedUserStates is constructed
@@ -172,11 +183,12 @@
when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
- when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+ doReturn(mKeyguardManager).when(mSpiedContext).getSystemService(KeyguardManager.class);
when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+ mockIsLowRamDevice(false);
// Must construct UserManagerService in the UiThread
mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -570,9 +582,10 @@
@Test
public void testAutoLockPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
@@ -587,10 +600,11 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
@@ -606,10 +620,11 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
@@ -623,10 +638,11 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
@@ -641,13 +657,14 @@
@Test
public void testAutoLockAfterInactityForPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
when(mPowerManager.isInteractive()).thenReturn(false);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
@@ -662,6 +679,7 @@
@Test
public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -675,8 +693,9 @@
@Test
public void testSetOrUpdateAutoLockPreference() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
- mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
// Set the preference to auto lock on device lock
@@ -733,6 +752,77 @@
}
}
+ @Test
+ public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ UserManagerService mSpiedUms = spy(mUms);
+ int mainUser = mSpiedUms.getMainUserId();
+ doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode();
+ assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue();
+ assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(
+ PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull();
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
+ assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, user.id, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
@@ -800,6 +890,10 @@
any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
}
+ private void mockIsLowRamDevice(boolean isLowRamDevice) {
+ doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
+ }
+
private void mockDeviceDemoMode(boolean enabled) {
doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt(
any(), eq(android.provider.Settings.Global.DEVICE_DEMO_MODE), anyInt()));
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5e5181b..0089d4c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -109,7 +109,6 @@
<uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
- <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index cea10ea..ea1a68a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -825,7 +825,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
verifyUserUnassignedFromDisplay(TEST_USER_ID1);
@@ -842,7 +843,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ false);
@@ -852,19 +854,28 @@
public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.disableFlags(
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
- mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+ mSetFlagsRule.disableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID3, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ true);
}
/** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
@@ -874,7 +885,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
@@ -890,7 +902,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
new file mode 100644
index 0000000..79766f8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.appwidget
+
+import android.content.ComponentName
+import com.android.server.appwidget.AppWidgetServiceImpl.ApiCounter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ApiCounterTest {
+ private companion object {
+ const val RESET_INTERVAL_MS = 10L
+ const val MAX_CALLS_PER_INTERVAL = 2
+ }
+
+ private var currentTime = 0L
+
+ private val id =
+ AppWidgetServiceImpl.ProviderId(
+ /* uid= */ 123,
+ ComponentName("com.android.server.appwidget", "FakeProviderClass")
+ )
+ private val counter = ApiCounter(RESET_INTERVAL_MS, MAX_CALLS_PER_INTERVAL) { currentTime }
+
+ @Test
+ fun tryApiCall() {
+ for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+ assertThat(counter.tryApiCall(id)).isFalse()
+ currentTime = 5L
+ assertThat(counter.tryApiCall(id)).isFalse()
+ currentTime = 11L
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+
+ @Test
+ fun remove() {
+ for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+ assertThat(counter.tryApiCall(id)).isFalse()
+ // remove should cause the call count to be 0 on the next tryApiCall
+ counter.remove(id)
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c8a5583d..3aaac2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,7 +16,6 @@
package com.android.server.biometrics.sensors.face;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
@@ -235,26 +234,6 @@
}
@Test
- public void testAuthenticateInBackground() throws Exception {
- FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
- .build();
- initService();
- mFaceService.mServiceWrapper.registerAuthenticators(List.of());
- waitForRegistration();
-
- mContext.getTestablePermissions().setPermission(
- USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_DENIED);
- mContext.getTestablePermissions().setPermission(
- USE_BACKGROUND_FACE_AUTHENTICATION, PackageManager.PERMISSION_GRANTED);
-
- final long operationId = 5;
- mFaceService.mServiceWrapper.authenticateInBackground(mToken, operationId,
- mFaceServiceReceiver, faceAuthenticateOptions);
-
- assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
- }
-
- @Test
public void testOptionsForDetect() throws Exception {
FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
.setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index 9ad2652..6731403 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -235,7 +235,7 @@
}
@Test
- public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() {
+ public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_requestsAndUpdatesAudioStatus() {
enableAdjustOnlyAbsoluteVolumeBehavior();
mNativeWrapper.clearResultMessages();
@@ -250,7 +250,22 @@
);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).isEmpty();
+ // We can't sent <Set Audio Volume Level> when using adjust-only AVB.
+ // Instead, we send <Give Audio Status>, to get the System Audio device's volume level.
+ // This ensures that we end up with a correct audio status in AudioService, even if it
+ // set it incorrectly because it assumed that we could send <Set Audio Volume Level>
+ assertThat(mNativeWrapper.getResultMessages().size()).isEqualTo(1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress())
+ );
+
+ // When we receive <Report Audio Status>, we notify AudioService of the volume level.
+ receiveReportAudioStatus(50,
+ true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(50 * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
+ anyInt());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 0aa72d0..98e119c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -184,13 +184,13 @@
@Test
public void isValid_setMenuLanguage() {
- assertMessageValidity("4F:32:53:50:41").isEqualTo(OK);
+ assertMessageValidity("0F:32:53:50:41").isEqualTo(OK);
assertMessageValidity("0F:32:45:4E:47:8C:49:D3:48").isEqualTo(OK);
- assertMessageValidity("40:32:53:50:41").isEqualTo(ERROR_DESTINATION);
- assertMessageValidity("F0:32").isEqualTo(ERROR_SOURCE);
- assertMessageValidity("4F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
- assertMessageValidity("4F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
+ assertMessageValidity("04:32:53:50:41").isEqualTo(ERROR_DESTINATION);
+ assertMessageValidity("40:32").isEqualTo(ERROR_SOURCE);
+ assertMessageValidity("0F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
+ assertMessageValidity("0F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 507b3fe..1591a96 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -316,6 +316,10 @@
.that(userTypeDetails).isNotNull();
final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+ // Only run the test if private profile creation is enabled on the device
+ assumeTrue("Private profile not enabled on the device",
+ mUserManager.canAddPrivateProfile());
+
// Test that only one private profile can be created
final int mainUserId = mainUser.getIdentifier();
UserInfo userInfo = createProfileForUser("Private profile1",
@@ -1231,6 +1235,20 @@
@MediumTest
@Test
+ public void testPrivateProfileCreationRestrictions() {
+ assumeTrue(mUserManager.canAddPrivateProfile());
+ final int mainUserId = ActivityManager.getCurrentUser();
+ try {
+ UserInfo privateProfileInfo = createProfileForUser("Private",
+ UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId);
+ assertThat(privateProfileInfo).isNotNull();
+ } catch (Exception e) {
+ fail("Creation of private profile failed due to " + e.getMessage());
+ }
+ }
+
+ @MediumTest
+ @Test
public void testAddRestrictedProfile() throws Exception {
if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return;
assertWithMessage("There should be no associated restricted profiles before the test")
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 2f29d10..515898a 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -48,6 +48,8 @@
"notification_flags_lib",
"platform-test-rules",
"SettingsLib",
+ "libprotobuf-java-lite",
+ "platformprotoslite",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 4dded1d..05b6c90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -885,6 +886,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -915,6 +917,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -945,6 +948,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -975,6 +979,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1152,6 +1157,58 @@
}
@Test
+ public void testUpgradeAppNoPermissionNoRebind() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm,
+ APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses bind permission
+ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+ (Answer<ServiceInfo>) invocation -> {
+ ComponentName invocationCn = invocation.getArgument(0);
+ if (invocationCn != null) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationCn.getPackageName();
+ serviceInfo.name = invocationCn.getClassName();
+ if (invocationCn.equals(unapprovedComponent)) {
+ serviceInfo.permission = "none";
+ } else {
+ serviceInfo.permission = service.getConfig().bindPermission;
+ }
+ serviceInfo.metaData = null;
+ return serviceInfo;
+ }
+ return null;
+ }
+ );
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+ assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
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 e3ea55a..03f2749 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -14092,7 +14092,8 @@
@Test
public void testProfileUnavailableIntent() throws RemoteException {
- mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
verify(mWorkerHandler).post(any(Runnable.class));
verify(mSnoozeHelper).clearData(anyInt());
@@ -14101,7 +14102,8 @@
@Test
public void testManagedProfileUnavailableIntent() throws RemoteException {
- mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
verify(mWorkerHandler).post(any(Runnable.class));
verify(mSnoozeHelper).clearData(anyInt());
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 78fb570..bfc47fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -324,7 +324,11 @@
when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
.thenReturn(appPermissions);
- when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
+ IntArray currentProfileIds = IntArray.wrap(new int[]{0});
+ if (UserManager.isHeadlessSystemUserMode()) {
+ currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
+ }
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
new file mode 100644
index 0000000..f724510
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.provider.Settings;
+import android.service.notification.ZenPolicy;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChannelPolicy;
+import com.android.os.dnd.ConversationType;
+import com.android.os.dnd.PeopleType;
+import com.android.os.dnd.State;
+import com.android.os.dnd.ZenMode;
+
+import com.google.protobuf.Internal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Test to validate that logging enums used in Zen classes match their API definitions. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ZenEnumTest {
+
+ @Test
+ public void testEnum_zenMode() {
+ testEnum(Settings.Global.class, "ZEN_MODE", ZenMode.class, "ZEN_MODE");
+ }
+
+ @Test
+ public void testEnum_activeRuleType() {
+ testEnum(AutomaticZenRule.class, "TYPE", ActiveRuleType.class, "TYPE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyState() {
+ testEnum(ZenPolicy.class, "STATE", State.class, "STATE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyChannelPolicy() {
+ testEnum(ZenPolicy.class, "CHANNEL_POLICY", ChannelPolicy.class, "CHANNEL_POLICY");
+ }
+
+ @Test
+ public void testEnum_zenPolicyConversationType() {
+ testEnum(ZenPolicy.class, "CONVERSATION_SENDERS", ConversationType.class, "CONV");
+ }
+
+ @Test
+ public void testEnum_zenPolicyPeopleType() {
+ testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
+ }
+
+ /**
+ * Verifies that any constants (i.e. {@code public static final int} fields) named {@code
+ * <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
+ * in the enum values defined in {@code loggingProtoEnumClass}.
+ *
+ * <p>Note that <em>extra</em> values in the logging enum are accepted (since we have one of
+ * those, and the main goal of this test is that we don't forget to update the logging enum
+ * if new API enum values are added).
+ */
+ private static void testEnum(Class<?> apiClass, String apiPrefix,
+ Class<? extends Internal.EnumLite> loggingProtoEnumClass,
+ String loggingPrefix) {
+ Map<String, Integer> apiConstants =
+ Arrays.stream(apiClass.getDeclaredFields())
+ .filter(f -> Modifier.isPublic(f.getModifiers()))
+ .filter(f -> Modifier.isStatic(f.getModifiers()))
+ .filter(f -> Modifier.isFinal(f.getModifiers()))
+ .filter(f -> f.getType().equals(int.class))
+ .filter(f -> f.getName().startsWith(apiPrefix + "_"))
+ .collect(Collectors.toMap(
+ Field::getName,
+ ZenEnumTest::getStaticFieldIntValue));
+
+ Map<String, Integer> loggingConstants =
+ Arrays.stream(loggingProtoEnumClass.getEnumConstants())
+ .collect(Collectors.toMap(
+ v -> v.toString(),
+ v -> v.getNumber()));
+
+ Map<String, Integer> renamedApiConstants = apiConstants.entrySet().stream()
+ .collect(Collectors.toMap(
+ entry -> entry.getKey().replace(apiPrefix + "_", loggingPrefix + "_"),
+ Map.Entry::getValue));
+
+ assertThat(loggingConstants).containsAtLeastEntriesIn(renamedApiConstants);
+ }
+
+ private static int getStaticFieldIntValue(Field f) {
+ try {
+ return f.getInt(null);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 7c1adbc..c4d2460 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -4808,6 +4808,53 @@
@Test
@EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_ruleChanged_deactivatesRule() {
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
+ .setTriggerDescription("Whenever")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() {
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
+ CONDITION_TRUE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
index 038f1db..54ab367 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
@@ -135,7 +135,7 @@
AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
records.add(createRecord(GROUP_1, KEY_2, createTime++));
assertThat(droppedRecord).isNotNull();
- assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+ assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
dumpRecords(records);
assertGroupHeadersWrittenOnce(records, GROUP_1);
@@ -155,7 +155,7 @@
AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
records.add(createRecord(GROUP_1, KEY_1, createTime + AGGREGATION_TIME_LIMIT));
assertThat(droppedRecord).isNotNull();
- assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+ assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
dumpRecords(records);
assertGroupHeadersWrittenOnce(records, GROUP_1);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index f54c7e5..88a9483 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -604,7 +604,8 @@
@RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
+ setHasFixedKeyboardAmplitudeIntensity(true);
// Keyboard touch ignored.
assertVibrationIgnoredForAttributes(
@@ -628,7 +629,8 @@
@RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+ setHasFixedKeyboardAmplitudeIntensity(true);
// General touch ignored.
assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -642,6 +644,25 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+ public void shouldIgnoreVibration_noFixedKeyboardAmplitude_ignoresKeyboardTouchVibration() {
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+ setHasFixedKeyboardAmplitudeIntensity(false);
+
+ // General touch ignored.
+ assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+ // Keyboard touch ignored.
+ assertVibrationIgnoredForAttributes(
+ new VibrationAttributes.Builder()
+ .setUsage(USAGE_TOUCH)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .build(),
+ Vibration.Status.IGNORED_FOR_SETTINGS);
+ }
+
+ @Test
public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() {
// Vibrations from the primary device is never ignored.
for (int usage : ALL_USAGES) {
@@ -953,6 +974,10 @@
when(mVibrationConfigMock.ignoreVibrationsOnWirelessCharger()).thenReturn(ignore);
}
+ private void setHasFixedKeyboardAmplitudeIntensity(boolean hasFixedAmplitude) {
+ when(mVibrationConfigMock.hasFixedKeyboardAmplitude()).thenReturn(hasFixedAmplitude);
+ }
+
private void deleteUserSetting(String settingName) {
Settings.System.putStringForUser(
mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 09e7b91..45a2ba4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3147,12 +3147,12 @@
// By default, activity is visible.
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be visible. Although the activity is already visible, app
// transition animation should be applied on this activity. This might be unnecessary, but
// until we verify no logic relies on this behavior, we'll keep this as is.
+ mDisplayContent.prepareAppTransition(0);
activity.setVisibility(true);
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
@@ -3167,11 +3167,11 @@
// By default, activity is visible.
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be invisible. Since the visibility changes, app transition
// animation should be applied on this activity.
+ mDisplayContent.prepareAppTransition(0);
activity.setVisibility(false);
assertTrue(activity.isVisible());
assertFalse(activity.isVisibleRequested());
@@ -3187,7 +3187,6 @@
// activity.
assertFalse(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be visible. Since the visibility changes, app transition
@@ -3389,6 +3388,7 @@
// frozen until the input started.
mDisplayContent.setImeLayeringTarget(app1);
mDisplayContent.updateImeInputAndControlTarget(app1);
+ mDisplayContent.computeImeTarget(true /* updateImeTarget */);
performSurfacePlacementAndWaitForWindowAnimator();
assertEquals(app1, mDisplayContent.getImeInputTarget());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 527ea0d..ce90504 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1239,7 +1239,7 @@
final ActivityRecord activity1 = finishTopActivity(rootTask1);
assertEquals(DESTROYING, activity1.getState());
verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
- eq(display.mDisplayId), anyBoolean());
+ eq(display), anyBoolean());
}
private ActivityRecord finishTopActivity(Task task) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index da11e6a..649f520 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -835,8 +835,6 @@
new TestDisplayContent.Builder(mAtm, 1000, 1500)
.setSystemDecorations(true).build();
- doReturn(true).when(mRootWindowContainer)
- .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean());
doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
anyBoolean());
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 8b44579..36adeec 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -380,6 +380,11 @@
return false;
}
+ if (descriptors == null) {
+ Slog.e(TAG, "Failed to add device as the descriptor is null");
+ return false;
+ }
+
UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
if (deviceClass == UsbConstants.USB_CLASS_PER_INTERFACE
&& !checkUsbInterfacesDenyListed(parser)) {
@@ -462,8 +467,7 @@
}
// Tracking
- addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
- parser.getRawDescriptors());
+ addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, descriptors);
// Stats collection
FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED,
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index b84ff29..12e04c2 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -195,7 +195,8 @@
// whether or not ImsFeature.FEATURE_EMERGENCY_MMTEL feature is set and should
// not be set by users of ImsService.
CAPABILITY_SIP_DELEGATE_CREATION,
- CAPABILITY_TERMINAL_BASED_CALL_WAITING
+ CAPABILITY_TERMINAL_BASED_CALL_WAITING,
+ CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface ImsServiceCapability {}
@@ -206,7 +207,9 @@
*/
private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
- CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
+ CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION",
+ CAPABILITY_TERMINAL_BASED_CALL_WAITING, "TERMINAL_BASED_CALL_WAITING",
+ CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING, "SIMULTANEOUS_CALLING");
/**
* The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 1d71f95..d658d59 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -63,17 +63,20 @@
],
}
-android_library_import {
- name: "wm-flicker-window-extensions_nodeps",
- aars: ["libs/window-extensions-release.aar"],
- sdk_version: "current",
-}
-
java_library {
name: "wm-flicker-window-extensions",
sdk_version: "current",
static_libs: [
- "wm-flicker-window-extensions_nodeps",
+ "androidx.window.extensions_extensions-nodeps",
+ ],
+ installable: false,
+}
+
+java_library {
+ name: "wm-flicker-window-extensions-core",
+ sdk_version: "current",
+ static_libs: [
+ "androidx.window.extensions.core_core-nodeps",
],
installable: false,
}
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
deleted file mode 100644
index 918e514..0000000
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index a64996c..d9a4c26 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,6 +59,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
+import java.util.TreeMap;
/**
* Test class for {@link ProtoLogImpl}.
@@ -89,7 +90,7 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024);
+ 1024 * 1024, mReader, 1024, new TreeMap<>());
}
@After
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 270f595..548adef 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -64,6 +64,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Random;
+import java.util.TreeMap;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -152,7 +153,8 @@
.thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
- mProtoLog = new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader);
+ mProtoLog =
+ new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader, new TreeMap<>());
}
@After
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
index a359155..2690bc5 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -25,6 +25,7 @@
const val READ_LOG_CMD = "read-log"
private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
+ // TODO: This is always the same. I don't think it's required
private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index ea2c8da..837dae9 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -24,11 +24,17 @@
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.InitializerDeclaration
+import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.ObjectCreationExpr
import com.github.javaparser.ast.expr.SimpleName
import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.stmt.BlockStmt
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
@@ -96,7 +102,8 @@
outJar.putNextEntry(zipEntry(protologImplPath))
outJar.write(generateProtoLogImpl(protologImplName, command.viewerConfigFilePathArg,
- command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath).toByteArray())
+ command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath,
+ groups, command.protoLogGroupsClassNameArg).toByteArray())
val executor = newThreadPool()
@@ -140,6 +147,8 @@
viewerConfigFilePath: String,
legacyViewerConfigFilePath: String?,
legacyOutputFilePath: String?,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String,
): String {
val file = File(PROTOLOG_IMPL_SRC_PATH)
@@ -160,7 +169,8 @@
classNameNode.setId(protoLogImplGenName)
injectConstants(classDeclaration,
- viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath)
+ viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath, groups,
+ protoLogGroupsClassName)
return code.toString()
}
@@ -169,7 +179,9 @@
classDeclaration: ClassOrInterfaceDeclaration,
viewerConfigFilePath: String,
legacyViewerConfigFilePath: String?,
- legacyOutputFilePath: String?
+ legacyOutputFilePath: String?,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String
) {
classDeclaration.fields.forEach { field ->
field.getAnnotationByClass(ProtoLogToolInjected::class.java)
@@ -197,6 +209,35 @@
StringLiteralExpr(it)
} ?: NullLiteralExpr())
}
+ ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
+ val initializerBlockStmt = BlockStmt()
+ for (group in groups) {
+ initializerBlockStmt.addStatement(
+ MethodCallExpr()
+ .setName("put")
+ .setArguments(
+ NodeList(StringLiteralExpr(group.key),
+ FieldAccessExpr()
+ .setScope(
+ NameExpr(
+ protoLogGroupsClassName
+ ))
+ .setName(group.value.name)))
+ )
+ group.key
+ }
+
+ val treeMapCreation = ObjectCreationExpr()
+ .setType("TreeMap<String, IProtoLogGroup>")
+ .setAnonymousClassBody(NodeList(
+ InitializerDeclaration().setBody(
+ initializerBlockStmt
+ )
+ ))
+
+ field.setFinal(true)
+ field.variables.first().setInitializer(treeMapCreation)
+ }
else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
}
}
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index b18bdff..b1b314fc 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,6 +17,7 @@
// ==========================================================
// Build the host executable: protoc-gen-javastream
// ==========================================================
+
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -41,6 +42,32 @@
static_libs: ["libprotoc"],
}
+// ==========================================================
+// Build the host static library: java_streaming_proto_lib
+// ==========================================================
+
+cc_library_host_static {
+ name: "java_streaming_proto_lib",
+ defaults: ["protoc-gen-stream-defaults"],
+ target: {
+ darwin: {
+ cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
+ },
+ },
+ cflags: [
+ "-Wno-format-y2k",
+ "-DSTATIC_ANDROIDFW_FOR_TOOLS",
+ ],
+
+ srcs: [
+ "java/java_proto_stream_code_generator.cpp",
+ ],
+}
+
+// ==========================================================
+// Build the host executable: protoc-gen-javastream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-javastream",
srcs: [
@@ -48,8 +75,13 @@
],
defaults: ["protoc-gen-stream-defaults"],
+ static_libs: ["java_streaming_proto_lib"],
}
+// ==========================================================
+// Build the host executable: protoc-gen-cppstream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-cppstream",
srcs: [
@@ -60,13 +92,31 @@
}
// ==========================================================
+// Build the host tests: StreamingProtoTest
+// ==========================================================
+
+cc_test_host {
+ name: "StreamingProtoTest",
+ defaults: ["protoc-gen-stream-defaults"],
+ srcs: [
+ "test/unit/**/*.cpp",
+ ],
+ static_libs: [
+ "java_streaming_proto_lib",
+ "libgmock",
+ "libgtest",
+ ],
+}
+
+// ==========================================================
// Build the java test
// ==========================================================
+
java_library {
- name: "StreamingProtoTest",
+ name: "StreamingProtoJavaIntegrationTest",
srcs: [
- "test/**/*.java",
- "test/**/*.proto",
+ "test/integration/**/*.java",
+ "test/integration/**/*.proto",
],
proto: {
type: "stream",
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
new file mode 100644
index 0000000..9d61111
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java_proto_stream_code_generator.h"
+
+#include <stdio.h>
+
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += class_name;
+ result += ".java";
+
+ return result;
+}
+
+static string indent_more(const string& indent) {
+ return indent + INDENT;
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i = 0; i < N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int " << make_constant_name(value.name()) << " = "
+ << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Write a field.
+ */
+static void write_field(stringstream& text, const FieldDescriptorProto& field,
+ const string& indent) {
+ string optional_comment =
+ field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
+ string repeated_comment =
+ field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed() ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void write_message(stringstream& text, const DescriptorProto& message,
+ const string& indent) {
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i = 0; i < N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+ const string& filename, bool generate_outer,
+ const vector<EnumDescriptorProto>& enums,
+ const vector<DescriptorProto>& messages) {
+ stringstream text;
+
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API
+ text << "/** @hide */" << endl;
+ // text << "@android.annotation.TestApi" << endl;
+
+ if (generate_outer) {
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+ }
+
+ size_t N;
+ const string indented = generate_outer ? indent_more("") : string();
+
+ N = enums.size();
+ for (size_t i = 0; i < N; i++) {
+ write_enum(text, enums[i], indented);
+ }
+
+ N = messages.size();
+ for (size_t i = 0; i < N; i++) {
+ write_message(text, messages[i], indented);
+ }
+
+ if (generate_outer) {
+ text << "}" << endl;
+ }
+
+ CodeGeneratorResponse::File* file_response = response->add_file();
+ file_response->set_name(filename);
+ file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class. Put all of the enums into the "outer" class.
+ */
+static void write_multiple_files(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ // If there is anything to put in the outer class file, create one
+ if (file_descriptor.enum_type_size() > 0) {
+ vector<EnumDescriptorProto> enums;
+ int N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name =
+ file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+
+ if (messages_to_compile.empty() || !enums.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+ }
+ }
+
+ // For each of the message types, make a file
+ int N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ vector<EnumDescriptorProto> enums;
+
+ vector<DescriptorProto> messages;
+
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+ messages.push_back(file_descriptor.message_type(i));
+
+ if (messages_to_compile.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+ false, enums, messages);
+ }
+ }
+}
+
+static void write_single_file(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ int N;
+
+ vector<EnumDescriptorProto> enums;
+ N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+ N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+
+ messages.push_back(file_descriptor.message_type(i));
+ }
+
+ if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
+ enums, messages);
+ }
+}
+
+static void parse_args_string(stringstream args_string_stream,
+ set<string>* messages_to_compile_out) {
+ string line;
+ while (getline(args_string_stream, line, ';')) {
+ stringstream line_ss(line);
+ string arg_name;
+ getline(line_ss, arg_name, ':');
+ if (arg_name == "include_filter") {
+ string full_message_name;
+ while (getline(line_ss, full_message_name, ',')) {
+ messages_to_compile_out->insert(full_message_name);
+ }
+ } else {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
+ }
+ }
+}
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
+ CodeGeneratorResponse response;
+
+ set<string> messages_to_compile;
+ auto request_params = request.parameter();
+ if (!request_params.empty()) {
+ parse_args_string(stringstream(request_params), &messages_to_compile);
+ }
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i = 0; i < N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ if (file_descriptor.options().java_multiple_files()) {
+ write_multiple_files(&response, file_descriptor, messages_to_compile);
+ } else {
+ write_single_file(&response, file_descriptor, messages_to_compile);
+ }
+ }
+ }
+
+ return response;
+}
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.h b/tools/streaming_proto/java/java_proto_stream_code_generator.h
new file mode 100644
index 0000000..d2492f7
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+#define AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+
+#include "stream_proto_utils.h"
+#include "string_utils.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request);
+
+#endif // AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
\ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index c9c50a5..5b35504 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,268 +1,21 @@
-#include "Errors.h"
-#include "stream_proto_utils.h"
-#include "string_utils.h"
-
#include <stdio.h>
+
#include <iomanip>
#include <iostream>
-#include <sstream>
#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+#include "java_proto_stream_code_generator.h"
+#include "stream_proto_utils.h"
using namespace android::stream_proto;
using namespace google::protobuf::io;
using namespace std;
/**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
- string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
- }
- return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
- if (file_descriptor.options().has_java_package()) {
- return file_descriptor.options().java_package();
- } else {
- return file_descriptor.package();
- }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
- string const package = make_java_package(file_descriptor);
- string result;
- if (package.size() > 0) {
- result = replace_string(package, '.', '/');
- result += '/';
- }
-
- result += class_name;
- result += ".java";
-
- return result;
-}
-
-static string
-indent_more(const string& indent)
-{
- return indent + INDENT;
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
- const int N = enu.value_size();
- text << indent << "// enum " << enu.name() << endl;
- for (int i=0; i<N; i++) {
- const EnumValueDescriptorProto& value = enu.value(i);
- text << indent << "public static final int "
- << make_constant_name(value.name())
- << " = " << value.number() << ";" << endl;
- }
- text << endl;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
- string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
- ? "optional " : "";
- string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
- ? "repeated " : "";
- string proto_type = get_proto_type(field);
- string packed_comment = field.options().packed()
- ? " [packed=true]" : "";
- text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
- << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
- text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
- ios::fmtflags fmt(text.flags());
- text << setfill('0') << setw(16) << hex << get_field_id(field);
- text.flags(fmt);
-
- text << "L;" << endl;
-
- text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
- int N;
- const string indented = indent_more(indent);
-
- text << indent << "// message " << message.name() << endl;
- text << indent << "public final class " << message.name() << " {" << endl;
- text << endl;
-
- // Enums
- N = message.enum_type_size();
- for (int i=0; i<N; i++) {
- write_enum(text, message.enum_type(i), indented);
- }
-
- // Nested classes
- N = message.nested_type_size();
- for (int i=0; i<N; i++) {
- write_message(text, message.nested_type(i), indented);
- }
-
- // Fields
- N = message.field_size();
- for (int i=0; i<N; i++) {
- write_field(text, message.field(i), indented);
- }
-
- text << indent << "}" << endl;
- text << endl;
-}
-
-/**
- * Write the contents of a file.
*
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
- const string& filename, bool generate_outer,
- const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
- stringstream text;
-
- string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
-
- text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
- text << "// source: " << file_descriptor.name() << endl << endl;
-
- if (package_name.size() > 0) {
- if (package_name.size() > 0) {
- text << "package " << package_name << ";" << endl;
- text << endl;
- }
- }
-
- // This bit of policy is android api rules specific: Raw proto classes
- // must never be in the API
- text << "/** @hide */" << endl;
-// text << "@android.annotation.TestApi" << endl;
-
- if (generate_outer) {
- text << "public final class " << outer_class_name << " {" << endl;
- text << endl;
- }
-
- size_t N;
- const string indented = generate_outer ? indent_more("") : string();
-
- N = enums.size();
- for (size_t i=0; i<N; i++) {
- write_enum(text, enums[i], indented);
- }
-
- N = messages.size();
- for (size_t i=0; i<N; i++) {
- write_message(text, messages[i], indented);
- }
-
- if (generate_outer) {
- text << "}" << endl;
- }
-
- CodeGeneratorResponse::File* file_response = response->add_file();
- file_response->set_name(filename);
- file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class. Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- // If there is anything to put in the outer class file, create one
- if (file_descriptor.enum_type_size() > 0) {
- vector<EnumDescriptorProto> enums;
- int N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
- }
-
- // For each of the message types, make a file
- int N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- vector<EnumDescriptorProto> enums;
-
- vector<DescriptorProto> messages;
- messages.push_back(file_descriptor.message_type(i));
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
- false, enums, messages);
- }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- int N;
-
- vector<EnumDescriptorProto> enums;
- N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
- N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- messages.push_back(file_descriptor.message_type(i));
- }
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
-}
-
-/**
* Main.
*/
int
@@ -273,24 +26,11 @@
GOOGLE_PROTOBUF_VERIFY_VERSION;
- CodeGeneratorRequest request;
- CodeGeneratorResponse response;
-
// Read the request
+ CodeGeneratorRequest request;
request.ParseFromIstream(&cin);
- // Build the files we need.
- const int N = request.proto_file_size();
- for (int i=0; i<N; i++) {
- const FileDescriptorProto& file_descriptor = request.proto_file(i);
- if (should_generate_for_file(request, file_descriptor.name())) {
- if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor);
- } else {
- write_single_file(&response, file_descriptor);
- }
- }
- }
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
// If we had errors, don't write the response. Print the errors and exit.
if (ERRORS.HasErrors()) {
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/integration/imported.proto
similarity index 100%
rename from tools/streaming_proto/test/imported.proto
rename to tools/streaming_proto/test/integration/imported.proto
diff --git a/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
new file mode 100644
index 0000000..2a7001b
--- /dev/null
+++ b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.streaming_proto_test;
+
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/integration/test.proto
similarity index 97%
rename from tools/streaming_proto/test/test.proto
rename to tools/streaming_proto/test/integration/test.proto
index de80ed6..3cf81b4 100644
--- a/tools/streaming_proto/test/test.proto
+++ b/tools/streaming_proto/test/integration/test.proto
@@ -16,7 +16,7 @@
syntax = "proto2";
-import "frameworks/base/tools/streaming_proto/test/imported.proto";
+import "frameworks/base/tools/streaming_proto/test/integration/imported.proto";
package com.android.streaming_proto_test;
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
deleted file mode 100644
index 1246f53..0000000
--- a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.streaming_proto_test;
-
-public class Main {
- public void main(String[] argv) {
- System.out.println("hello world");
- }
-}
diff --git a/tools/streaming_proto/test/unit/streaming_proto_java.cpp b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
new file mode 100644
index 0000000..8df9716
--- /dev/null
+++ b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "java/java_proto_stream_code_generator.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+static void add_my_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+}
+
+static void add_my_other_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyOtherTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyOtherTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+}
+
+static CodeGeneratorRequest create_simple_two_file_request() {
+ CodeGeneratorRequest request;
+
+ add_my_test_proto_file(&request);
+ add_my_other_test_proto_file(&request);
+
+ return request;
+}
+
+static CodeGeneratorRequest create_simple_multi_file_request() {
+ CodeGeneratorRequest request;
+
+ request.add_file_to_generate("MyMultiMessageTestProtoFile");
+
+ FileDescriptorProto* file_desc = request.add_proto_file();
+ file_desc->set_name("MyMultiMessageTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(true);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+
+ message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+
+ return request;
+}
+
+TEST(StreamingProtoJavaTest, NoFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestProtoFile.java");
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestProtoFile"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithoutFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestMessage.java");
+ EXPECT_THAT(response.file(1).content(), Not(HasSubstr("class MyOtherTestProtoFile")));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}