diff options
361 files changed, 11498 insertions, 8383 deletions
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS index eb5842ba58ce..45719a7ced09 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/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index e96d07f44b34..ee9400fb8408 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -46,8 +46,11 @@ import android.os.Message; import android.os.PowerManager; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.telephony.TelephonyManager; +import android.telephony.UiccSlotMapping; import android.util.ArraySet; import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; @@ -68,6 +71,8 @@ import com.android.server.utils.AlarmQueue; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Set; import java.util.function.Predicate; /** @@ -1620,9 +1625,21 @@ public final class FlexibilityController extends StateController { private final Object mSatLock = new Object(); private DeviceIdleInternal mDeviceIdleInternal; + private TelephonyManager mTelephonyManager; + + private final boolean mHasFeatureTelephonySubscription; /** Set of all apps that have been deemed special, keyed by user ID. */ private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>(); + /** + * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged + * for. + */ + @GuardedBy("mSatLock") + private final SparseSetArray<String> mCarrierPrivilegedApps = new SparseSetArray<>(); + @GuardedBy("mSatLock") + private final SparseArray<LogicalIndexCarrierPrivilegesCallback> + mCarrierPrivilegedCallbacks = new SparseArray<>(); @GuardedBy("mSatLock") private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>(); @@ -1630,6 +1647,10 @@ public final class FlexibilityController extends StateController { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { + case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED: + updateCarrierPrivilegedCallbackRegistration(); + break; + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache); break; @@ -1637,6 +1658,11 @@ public final class FlexibilityController extends StateController { } }; + SpecialAppTracker() { + mHasFeatureTelephonySubscription = mContext.getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION); + } + public boolean isSpecialApp(final int userId, @NonNull String packageName) { synchronized (mSatLock) { if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) { @@ -1654,6 +1680,12 @@ public final class FlexibilityController extends StateController { if (mPowerAllowlistedApps.contains(packageName)) { return true; } + for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) { + if (mCarrierPrivilegedApps.contains( + mCarrierPrivilegedApps.keyAt(l), packageName)) { + return true; + } + } } return false; } @@ -1669,9 +1701,12 @@ public final class FlexibilityController extends StateController { private void onSystemServicesReady() { mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); synchronized (mLock) { if (mFlexibilityEnabled) { + mHandler.post( + SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration); mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache); } } @@ -1686,6 +1721,13 @@ public final class FlexibilityController extends StateController { private void startTracking() { IntentFilter filter = new IntentFilter( PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); + + if (mHasFeatureTelephonySubscription) { + filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); + + updateCarrierPrivilegedCallbackRegistration(); + } + mContext.registerReceiver(mBroadcastReceiver, filter); updatePowerAllowlistCache(); @@ -1695,9 +1737,61 @@ public final class FlexibilityController extends StateController { mContext.unregisterReceiver(mBroadcastReceiver); synchronized (mSatLock) { + mCarrierPrivilegedApps.clear(); mPowerAllowlistedApps.clear(); mSpecialApps.clear(); + + for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) { + mTelephonyManager.unregisterCarrierPrivilegesCallback( + mCarrierPrivilegedCallbacks.valueAt(i)); + } + mCarrierPrivilegedCallbacks.clear(); + } + } + + private void updateCarrierPrivilegedCallbackRegistration() { + if (mTelephonyManager == null) { + return; + } + if (!mHasFeatureTelephonySubscription) { + return; + } + + Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping(); + final ArraySet<String> changedPkgs = new ArraySet<>(); + synchronized (mSatLock) { + final IntArray callbacksToRemove = new IntArray(); + for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) { + callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i)); + } + for (UiccSlotMapping mapping : simSlotMapping) { + final int logicalIndex = mapping.getLogicalSlotIndex(); + if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) { + // Callback already exists. No need to create a new one or remove it. + callbacksToRemove.remove(logicalIndex); + continue; + } + final LogicalIndexCarrierPrivilegesCallback callback = + new LogicalIndexCarrierPrivilegesCallback(logicalIndex); + mCarrierPrivilegedCallbacks.put(logicalIndex, callback); + // Upon registration, the callbacks will be called with the current list of + // apps, so there's no need to query the app list synchronously. + mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex, + AppSchedulingModuleThread.getExecutor(), callback); + } + + for (int i = callbacksToRemove.size() - 1; i >= 0; --i) { + final int logicalIndex = callbacksToRemove.get(i); + final LogicalIndexCarrierPrivilegesCallback callback = + mCarrierPrivilegedCallbacks.get(logicalIndex); + mTelephonyManager.unregisterCarrierPrivilegesCallback(callback); + mCarrierPrivilegedCallbacks.remove(logicalIndex); + changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex)); + mCarrierPrivilegedApps.remove(logicalIndex); + } } + + updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); } /** @@ -1762,18 +1856,65 @@ public final class FlexibilityController extends StateController { updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); } + class LogicalIndexCarrierPrivilegesCallback implements + TelephonyManager.CarrierPrivilegesCallback { + public final int logicalIndex; + + LogicalIndexCarrierPrivilegesCallback(int logicalIndex) { + this.logicalIndex = logicalIndex; + } + + @Override + public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames, + @NonNull Set<Integer> privilegedUids) { + final ArraySet<String> changedPkgs = new ArraySet<>(); + synchronized (mSatLock) { + final ArraySet<String> oldPrivilegedSet = + mCarrierPrivilegedApps.get(logicalIndex); + if (oldPrivilegedSet != null) { + changedPkgs.addAll(oldPrivilegedSet); + mCarrierPrivilegedApps.remove(logicalIndex); + } + for (String pkgName : privilegedPackageNames) { + mCarrierPrivilegedApps.add(logicalIndex, pkgName); + if (!changedPkgs.remove(pkgName)) { + // The package wasn't in the previous set of privileged apps. Add it + // since its state has changed. + changedPkgs.add(pkgName); + } + } + } + + // The carrier privileged list doesn't provide a simple userId correlation, + // so for now, use USER_ALL for these packages. + // TODO(141645789): use the UID list to narrow down to specific userIds + updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs); + } + } + public void dump(@NonNull IndentingPrintWriter pw) { pw.println("Special apps:"); pw.increaseIndent(); synchronized (mSatLock) { for (int u = 0; u < mSpecialApps.size(); ++u) { + pw.print("User "); pw.print(mSpecialApps.keyAt(u)); pw.print(": "); pw.println(mSpecialApps.valuesAt(u)); } pw.println(); + pw.println("Carrier privileged packages:"); + pw.increaseIndent(); + for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) { + pw.print(mCarrierPrivilegedApps.keyAt(i)); + pw.print(": "); + pw.println(mCarrierPrivilegedApps.valuesAt(i)); + } + pw.decreaseIndent(); + + pw.println(); pw.print("Power allowlisted packages: "); pw.println(mPowerAllowlistedApps); } diff --git a/api/Android.bp b/api/Android.bp index 8e063667826c..093ee4beab50 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -298,6 +298,28 @@ packages_to_document = [ "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 @@ stubs_defaults { // 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 be027105ae98..494d2ae37d53 100644 --- a/cmds/telecom/Android.bp +++ b/cmds/telecom/Android.bp @@ -21,5 +21,8 @@ license { 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 1488e14cfb8f..50af5a7a29b7 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 @@ public final class Telecom extends BaseCommand { // 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 42ac6b73f61f..b9ad92b77c6f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5395,7 +5395,7 @@ package android.app { 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 @@ package android.app { 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 @@ package android.appwidget { 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 @@ package android.graphics { 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 @@ package android.graphics { 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 @@ package android.graphics { 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 @@ package android.service.notification { 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 d190c62413e0..b73f1993ee4c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -70,7 +70,7 @@ package android { 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 @@ package android { 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 @@ package android.app.ondeviceintelligence { } 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 @@ package android.app.ondeviceintelligence { 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 @@ package android.app.ondeviceintelligence { @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 @@ package android.app.ondeviceintelligence { 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 @@ package android.app.ondeviceintelligence { 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 @@ package android.content { 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.display { } -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 @@ package android.permission { 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 @@ package android.service.notification { } 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 @@ package android.service.ondeviceintelligence { @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 @@ package android.service.ondeviceintelligence { 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 1923641e2d4e..1e72a061d35a 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -511,9 +511,9 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize(): 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 @@ MissingNullability: android.service.contentcapture.ContentCaptureService#dump(ja 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 f6ec370478a9..5e2397d41424 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -173,8 +173,8 @@ public final class AutomaticZenRule implements Parcelable { * 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 @@ public final class AutomaticZenRule implements Parcelable { * 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 @@ public final class AutomaticZenRule implements Parcelable { /** * 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 @@ public final class AutomaticZenRule implements Parcelable { * 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 1129f9dbdfeb..79cb09d5baea 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6068,11 +6068,19 @@ public class Notification implements Parcelable } /** - * 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 @@ public class Notification implements Parcelable } /** - * 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 @@ public class Notification implements Parcelable } /** - * 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 283e21aab39c..b82a1e3d8dfa 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -270,13 +270,16 @@ public class NotificationManager { * 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 @@ public class NotificationManager { = "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 @@ public class NotificationManager { /** * 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 @@ public class NotificationManager { /** * 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 @@ public class NotificationManager { } /** - * 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 @@ public class NotificationManager { * 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 fb1b17bd23d4..89199ca0d493 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.net.Uri; 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.text.TextUtils; 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 @@ public class AssistStructure implements Parcelable { @Nullable OutcomeReceiver<GetCredentialResponse, GetCredentialException> mGetCredentialCallback; + @Nullable ResultReceiver mGetCredentialResultReceiver; + + AutofillValue mAutofillValue; CharSequence[] mAutofillOptions; boolean mSanitized; @@ -916,6 +925,7 @@ public class AssistStructure implements Parcelable { mExtras = in.readBundle(); } mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR); + mGetCredentialResultReceiver = in.readTypedObject(ResultReceiver.CREATOR); } /** @@ -1153,6 +1163,7 @@ public class AssistStructure implements Parcelable { out.writeBundle(mExtras); } out.writeTypedObject(mGetCredentialRequest, flags); + out.writeTypedObject(mGetCredentialResultReceiver, flags); return flags; } @@ -1295,9 +1306,8 @@ public class AssistStructure implements Parcelable { */ @FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION) @Nullable - public OutcomeReceiver<GetCredentialResponse, - GetCredentialException> getCredentialManagerCallback() { - return mGetCredentialCallback; + public ResultReceiver getCredentialManagerCallback() { + return mGetCredentialResultReceiver; } /** @@ -1894,6 +1904,7 @@ public class AssistStructure implements Parcelable { 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 @@ public class AssistStructure implements Parcelable { 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 510735461553..4a38c9224a3d 100644 --- a/core/java/android/app/ondeviceintelligence/Feature.java +++ b/core/java/android/app/ondeviceintelligence/Feature.java @@ -199,24 +199,13 @@ public final class Feature implements Parcelable { 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 92f351362f70..f3cbd2621694 100644 --- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java +++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java @@ -41,7 +41,7 @@ import java.text.MessageFormat; @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 final class FeatureDetails implements Parcelable { } 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 @@ public final class FeatureDetails implements Parcelable { /** * 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 final class FeatureDetails implements Parcelable { public String toString() { return MessageFormat.format("FeatureDetails '{' status = {0}, " + "persistableBundle = {1} '}'", - mStatus, + mFeatureStatus, mFeatureDetailParams); } @@ -121,21 +121,21 @@ public final class FeatureDetails implements Parcelable { 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 @@ public final class FeatureDetails implements Parcelable { 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 e9fb5f2cb440..000000000000 --- 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 b925f4863de2..360a8094723c 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 9848e1ddda5a..0adf305f2920 100644 --- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl +++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl @@ -3,6 +3,7 @@ package android.app.ondeviceintelligence; 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 @@ import android.os.PersistableBundle; 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 a6805749fa04..132e53e1ae2e 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.Content; import android.app.ondeviceintelligence.IResponseCallback; import android.app.ondeviceintelligence.IProcessingSignal; import android.os.PersistableBundle; +import android.os.RemoteCallback; /** @@ -15,4 +16,5 @@ interface IStreamingResponseCallback { 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 b724e03fbca4..000000000000 --- 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 000000000000..9219a89128df --- /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 4d8e0d55e355..d195c4d52c22 100644 --- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java +++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java @@ -26,8 +26,10 @@ import android.annotation.Nullable; 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 android.os.RemoteException; 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 @@ import java.util.function.LongConsumer; @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 class OnDeviceIntelligenceManager { 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 @@ public class OnDeviceIntelligenceManager { } } + + /** + * 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 @@ public class OnDeviceIntelligenceManager { } /** - * 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 @@ public class OnDeviceIntelligenceManager { 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 @@ public class OnDeviceIntelligenceManager { * 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 class OnDeviceIntelligenceManager { 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 @@ public class OnDeviceIntelligenceManager { } /** - * 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 @@ public class OnDeviceIntelligenceManager { 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 class OnDeviceIntelligenceManager { 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 @@ public class OnDeviceIntelligenceManager { 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 class OnDeviceIntelligenceManager { */ 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 @@ public class OnDeviceIntelligenceManager { /** 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 000000000000..b0b6e1948cf0 --- /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 index ebcf61c8c0c2..ac2b0329496c 100644 --- a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java +++ b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java @@ -21,23 +21,19 @@ import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE 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 000000000000..2c19c1eb246c --- /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 000000000000..035cc4b365b5 --- /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 eb82e1fbb16c..cda4d89b828f 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -1417,13 +1417,15 @@ public class AppWidgetManager { * @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 70d2c7a3a41a..c7e5d88c299d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5081,8 +5081,6 @@ public abstract class Context { * @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/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index 4dcc51729a61..a9084565180a 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 @@ public final class DomainVerificationManager { } /** - * 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 08b906439b08..4cdaaddd05bf 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -727,7 +727,7 @@ public final class SensorPrivacyManager { * 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 8165d44b251a..3ba8be4cc2ab 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -28,10 +28,3 @@ flag { 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 066c45f03ec4..210ce2b78fca 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -18,23 +18,18 @@ package android.hardware.face; 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.Binder; 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.util.Slog; 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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan @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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan 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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * 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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * 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 class FaceManager implements BiometricAuthenticator, BiometricFaceConstan 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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan 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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * 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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } /** - * 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 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan PowerManager.PARTIAL_WAKE_LOCK, "faceLockoutResetCallback"); wakeLock.acquire(); - mExecutor.execute(() -> { + mHandler.post(() -> { try { callback.onLockoutReset(sensorId); } finally { @@ -1352,6 +1270,70 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan } } + 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 b98c0cb41ac9..553d9f76bd01 100644 --- a/core/java/android/hardware/face/IFaceService.aidl +++ b/core/java/android/hardware/face/IFaceService.aidl @@ -47,7 +47,7 @@ interface IFaceService { 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 @@ interface IFaceService { 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 @@ interface IFaceService { 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/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java index a14a2c7e54ca..555a1204c8a7 100644 --- a/core/java/android/os/vibrator/VibrationConfig.java +++ b/core/java/android/os/vibrator/VibrationConfig.java @@ -70,6 +70,8 @@ public class VibrationConfig { private final boolean mDefaultKeyboardVibrationEnabled; + private final boolean mHasFixedKeyboardAmplitude; + /** @hide */ public VibrationConfig(@Nullable Resources resources) { mHapticChannelMaxVibrationAmplitude = loadFloat(resources, @@ -87,6 +89,8 @@ public class VibrationConfig { 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 @@ public class VibrationConfig { 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 410f51045b9d..3c7692d03410 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -1800,7 +1800,11 @@ public final class PermissionManager { */ @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 eea6464c9047..e26dc73f7172 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3583,6 +3583,12 @@ public final class Settings { + " 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 @@ public final class Settings { + " 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 @@ public final class Settings { @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 7658af53a7f8..bd9ab86fa8d1 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; 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 @@ public abstract class NotificationListenerService extends Service { * 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 @@ public abstract class NotificationListenerService extends Service { * @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 @@ public abstract class NotificationListenerService extends Service { * 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 @@ public abstract class NotificationListenerService extends Service { * for active notifications. * */ + @UiThread public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { onNotificationRemoved(sbn); } @@ -541,6 +546,7 @@ public abstract class NotificationListenerService extends Service { * @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 @@ public abstract class NotificationListenerService extends Service { * * @hide */ + @UiThread @SystemApi public void onNotificationRemoved(@NonNull StatusBarNotification sbn, @NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) { @@ -563,6 +570,7 @@ public abstract class NotificationListenerService extends Service { * the notification manager. You are safe to call {@link #getActiveNotifications()} * at this time. */ + @UiThread public void onListenerConnected() { // optional } @@ -572,6 +580,7 @@ public abstract class NotificationListenerService extends Service { * 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 @@ public abstract class NotificationListenerService extends Service { * @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 @@ public abstract class NotificationListenerService extends Service { * * @param hints The current {@link #getCurrentListenerHints() listener hints}. */ + @UiThread public void onListenerHintsChanged(int hints) { // optional } @@ -603,6 +614,7 @@ public abstract class NotificationListenerService extends Service { * @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 @@ public abstract class NotificationListenerService extends Service { * {@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 @@ public abstract class NotificationListenerService extends Service { * {@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 @@ public abstract class NotificationListenerService extends Service { * @param interruptionFilter The current * {@link #getCurrentInterruptionFilter() interruption filter}. */ + @UiThread public void onInterruptionFilterChanged(int interruptionFilter) { // optional } @@ -1197,6 +1212,11 @@ public abstract class NotificationListenerService extends Service { * <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 e44c69c4df28..6dbff7185f6f 100644 --- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl +++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl @@ -36,11 +36,13 @@ import android.service.ondeviceintelligence.IRemoteProcessingService; */ 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 index e3fda04e6592..73257ed7680a 100644 --- a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl +++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl @@ -18,7 +18,7 @@ package android.service.ondeviceintelligence; 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.IRemoteStorageService; 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 46ba25d40bec..fce3689bb8b3 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java @@ -65,7 +65,7 @@ import java.util.function.LongConsumer; /** * 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 @@ public abstract class OnDeviceIntelligenceService extends Service { @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 @@ public abstract class OnDeviceIntelligenceService extends Service { } @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 @@ public abstract class OnDeviceIntelligenceService extends Service { 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 @@ public abstract class OnDeviceIntelligenceService extends Service { * 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 @@ public abstract class OnDeviceIntelligenceService extends Service { * @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 @@ public abstract class OnDeviceIntelligenceService extends Service { * 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 @@ public abstract class OnDeviceIntelligenceService extends Service { * 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 index 86001975cc09..7f7f9c28c60e 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.FlaggedApi; 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.Feature; 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 @@ import java.util.function.Consumer; * * <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 @@ import java.util.function.Consumer; */ @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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { @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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { } @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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { Objects.requireNonNull(processingState); Objects.requireNonNull(callback); - OnDeviceTrustedInferenceService.this.onUpdateProcessingState(processingState, + OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(processingState, wrapOutcomeReceiver(callback) ); } @@ -178,35 +182,37 @@ public abstract class OnDeviceTrustedInferenceService extends Service { } /** - * 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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { */ @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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { * 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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { */ @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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { } } + + /** + * 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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { }); } - 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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { 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 @@ public abstract class OnDeviceTrustedInferenceService extends Service { 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 abstract class OnDeviceTrustedInferenceService extends Service { 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 67a397828fa0..0ae3e598f8b9 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1775,9 +1775,9 @@ public final class MotionEvent extends InputEvent implements Parcelable { @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 @@ public final class MotionEvent extends InputEvent implements Parcelable { 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 d29963c6a82b..a6f380d4d483 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -33760,7 +33760,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @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 131fca7d923a..1efd37591ee4 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -315,7 +315,8 @@ public abstract class ViewStructure { /** * 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 fe4ac4e2f0f1..a2d8d80096c5 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -4299,7 +4299,7 @@ public class RemoteViews implements Parcelable, Filter { } 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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { * @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 @@ public class RemoteViews implements Parcelable, Filter { 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 @@ public class RemoteViews implements Parcelable, Filter { 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/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index 85bdbb908205..e55cdef82235 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -80,11 +80,10 @@ interface IAppWidgetService { 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 bd806bfb3e24..91678c793b44 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 final class SystemUiDeviceConfigFlags { */ 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/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp index 071f933db065..83b6afa8f8f6 100644 --- a/core/jni/LayoutlibLoader.cpp +++ b/core/jni/LayoutlibLoader.cpp @@ -428,7 +428,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_ERR; } - if (!systemProperties["register_properties_during_load"].empty()) { + if (!systemProperties["icu.data.path"].empty()) { // Set the location of ICU data bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str()); if (!icuInitialized) { diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp index 23adb8f7e2a3..1a86363b6ed9 100644 --- a/core/jni/android_view_MotionEvent.cpp +++ b/core/jni/android_view_MotionEvent.cpp @@ -411,8 +411,8 @@ static void android_view_MotionEvent_nativeAddBatch(JNIEnv* env, jclass clazz, 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 @@ static void android_view_MotionEvent_nativeOffsetLocation(jlong nativePtr, jfloa 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 @@ static const JNINativeMethod gMotionEventMethods[] = { {"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 52bad21e156d..1acdc75a600e 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 df8158dbb6c8..6e804c0b428a 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 @@ a similar way. <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 4f20fceef8d1..9d902c94ffe4 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 59066eb83f1c..238772f5961e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6434,4 +6434,6 @@ ul.</string> <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 150951fd57f8..4b716543c726 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 15bb66b3e303..8d9fad999624 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 @@ public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { 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 37a499adf682..6cc54850dce6 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 @@ public class BugreportManagerTest { 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 @@ public class BugreportManagerTest { 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 @@ public class BugreportManagerTest { 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 @@ public class BugreportManagerTest { 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 34f5841aef72..3a872b50af75 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 @@ package android.hardware.face; 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 static org.mockito.Mockito.when; 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 org.mockito.junit.MockitoRule; 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 @@ public class FaceManagerTest { @Mock private FaceManager.AuthenticationCallback mAuthCallback; @Mock - private BiometricPrompt.AuthenticationCallback mBioAuthCallback; - @Mock private FaceManager.EnrollmentCallback mEnrollmentCallback; @Mock private FaceManager.FaceDetectionCallback mFaceDetectionCallback; @@ -98,16 +91,13 @@ public class FaceManagerTest { 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 @@ public class FaceManagerTest { } @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 128c54b77e57..d437f7bc4060 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 static org.junit.Assert.assertTrue; 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 @@ public class ViewVelocityTest { @UiThreadTest @Test + @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API) public void frameRateChangesWhenContentMoves() { mMovingView.offsetLeftAndRight(100); float frameRate = mViewRoot.getPreferredFrameRate(); @@ -72,11 +76,13 @@ public class ViewVelocityTest { @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/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 051e73f4d4c8..0f12438613cf 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -455,7 +455,7 @@ applications that come with the platform <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 b6ce9b64323b..f9ac02a0055a 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -789,10 +789,8 @@ public class Canvas extends BaseCanvas { * @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 7cc0eb7a6728..a99e20101c3b 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 @@ public class Matrix44 { /** * 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 @@ public class Matrix44 { /** * 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 948264579e1d..294b1f0e21fd 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 02b707568cd0..e5fe1b5431eb 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 7c4f49979455..000000000000 --- 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 a5605a7ff50a..fa18e2bc5add 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 c6f85a0b4ed4..fca2fe4eddc5 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 e0057fe64fd2..296c89568386 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 4d47ca998d8b..139cde2c66f7 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 @@ public class PipController implements PipTransitionController.PipTransitionCallb // 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 f4ccd689f938..500e6f4d6369 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { 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 b37dd0d6fd2d..3d0dd31cc686 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.graphics.PointF; 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 @@ import com.android.wm.shell.R; */ 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 @@ class HandleMenu { * 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 @@ class HandleMenu { * 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 000000000000..4b008a7b4815 --- /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 000000000000..829a39617012 --- /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 000000000000..417fec72be6a --- /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 000000000000..7e13db536fc9 --- /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 000000000000..90cdf3513be9 --- /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 000000000000..745ded4810f3 --- /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 8f227b1604b7..89d14fb982d9 100644 --- a/location/java/android/location/provider/ForwardGeocodeRequest.java +++ b/location/java/android/location/provider/ForwardGeocodeRequest.java @@ -260,7 +260,11 @@ public final class ForwardGeocodeRequest implements Parcelable { 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 57c9047f07ca..210770740fcc 100644 --- a/location/java/android/location/provider/ReverseGeocodeRequest.java +++ b/location/java/android/location/provider/ReverseGeocodeRequest.java @@ -207,7 +207,11 @@ public final class ReverseGeocodeRequest implements Parcelable { 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 53699bc706ea..0a223142954f 100644 --- a/native/android/input.cpp +++ b/native/android/input.cpp @@ -124,11 +124,11 @@ int64_t AMotionEvent_getEventTime(const AInputEvent* motion_event) { } 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 b6bc40d5fc19..7dd16ba6c18e 100644 --- a/nfc/Android.bp +++ b/nfc/Android.bp @@ -38,6 +38,7 @@ java_sdk_library { 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 c0db089253e7..54f1421e463d 100644 --- a/nfc/api/current.txt +++ b/nfc/api/current.txt @@ -194,13 +194,13 @@ package android.nfc { 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 ea58504063d7..e55f5403ed83 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -27,6 +27,7 @@ import android.annotation.SystemApi; 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 @@ public final class CardEmulation { * @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 @@ public final class CardEmulation { * 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 @@ public final class CardEmulation { * (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 @@ public final class CardEmulation { * 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 4e1f4ee2e565..d7a2e3624eab 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.app.assist.AssistStructure 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 @@ class CredentialAutofillService : AutofillService() { // 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 @@ class CredentialAutofillService : AutofillService() { 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 @@ class CredentialAutofillService : AutofillService() { 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 a7b5c36215cf..e43b09ede1cb 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.common.material.rememberModalBottomSheetSta 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 @@ fun ModalBottomSheet( 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 c68ae8b168fb..006a2d9858c4 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 @@ fun SheetContainerCard( 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 3ebdd204b640..ff421bc47511 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 @@ package com.android.credentialmanager.common.ui 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/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 33086f98efa0..e489bc552b25 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -1918,16 +1918,6 @@ public class ApplicationsState { } }; - 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 da1fd55c4ccb..0c7d6f093289 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 @@ object BluetoothLeBroadcastMetadataExt { } 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 5f026c451152..e34c50eb5ce6 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 android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; 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.bluetooth.LocalBluetoothManager; 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 @@ import java.util.stream.Stream; /** 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 @@ public abstract class InfoMediaManager extends MediaManager { } } + 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 abstract class InfoMediaManager extends MediaManager { 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 @@ public abstract class InfoMediaManager extends MediaManager { 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 @@ public abstract class InfoMediaManager extends MediaManager { 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 59254925dbcf..63056b6dc8c3 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 @@ public class LocalMediaManager implements BluetoothCallback { } mInfoMediaManager = - InfoMediaManager.createInstance( - context, packageName, notification, mLocalBluetoothManager); + InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager); } /** @@ -505,9 +504,9 @@ public class LocalMediaManager implements BluetoothCallback { 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 @@ public class LocalMediaManager implements BluetoothCallback { } @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 453e807947cf..c4fac358c01a 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 @@ public class ManagerInfoMediaManager extends InfoMediaManager { /* 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 d562c8a82f2d..000000000000 --- 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 ea4de392e139..ff4d4dd837d2 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 @@ import java.util.List; 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 df03167cd0f9..9c82cb1ef57d 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 @@ public final class RouterInfoMediaManager extends InfoMediaManager { /* 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/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java index c647cbb5a0b1..f0185b95e8f4 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 @@ public class InfoMediaManagerIntegTest { @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 @@ public class InfoMediaManagerIntegTest { @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 c159d5ee37f0..d85d2534f856 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 @@ public class InfoMediaManagerTest { @Mock private LocalBluetoothManager mLocalBluetoothManager; @Mock - private MediaManager.MediaDeviceCallback mCallback; + private InfoMediaManager.MediaDeviceCallback mCallback; @Mock private MediaSessionManager mMediaSessionManager; @Mock @@ -109,8 +109,7 @@ public class InfoMediaManagerTest { 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 9a7d4f1540df..693b7d0faa79 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.never; 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.AudioDeviceAttributes; 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 @@ public class LocalMediaManagerTest { 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 @@ public class LocalMediaManagerTest { @Mock private LocalBluetoothProfileManager mLocalProfileManager; @Mock - private MediaRouter2Manager mMediaRouter2Manager; - @Mock private MediaRoute2Info mRouteInfo1; @Mock private MediaRoute2Info mRouteInfo2; @@ -97,6 +93,7 @@ public class LocalMediaManagerTest { private Context mContext; private LocalMediaManager mLocalMediaManager; + private InfoMediaManager mInfoMediaManager; private ShadowBluetoothAdapter mShadowBluetoothAdapter; private InfoMediaDevice mInfoMediaDevice1; private InfoMediaDevice mInfoMediaDevice2; @@ -116,10 +113,16 @@ public class LocalMediaManagerTest { 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 @@ public class LocalMediaManagerTest { 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 c3237f0e72eb..000000000000 --- 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 add313419c7d..8f8445d7a40b 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -115,6 +115,10 @@ public class GlobalSettings { 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, + Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index c0a076095434..6def40be7977 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 @@ public class GlobalSettingsValidators { 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/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 28cdc6db192b..09d076ee9c26 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -626,9 +626,6 @@ public class SettingsBackupTest { Settings.Global.Wearable.BEDTIME_MODE, Settings.Global.Wearable.BEDTIME_HARD_MODE, Settings.Global.Wearable.LOCK_SCREEN_STATE, - Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED, - Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE, - Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED, Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED, Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN, Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 0c02f56ab294..eb2d13dc9eb5 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 baa139727d72..d05d40d969de 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -25,6 +25,16 @@ flag { } 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 8dc74951d332..b1240252796f 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.graphics.fonts.FontVariationAxis 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 @@ class TextAnimator( } sealed class PositionedGlyph { - /** Mutable X coordinate of the glyph position relative from drawing offset. */ var x: Float = 0f @@ -269,41 +269,53 @@ class TextAnimator( 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() - } - - if (textSize >= 0) { - textInterpolator.targetPaint.textSize = textSize - } - - if (!fvar.isNullOrBlank()) { - textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar) - } + try { + if (animate) { + animator.cancel() + textInterpolator.rebase() + } - 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 (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 (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 @@ class TextAnimator( 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() + } + } 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 } - animator.start() - } else { - // No animation is requested, thus set base and target state to the same state. - textInterpolator.progress = 1f - textInterpolator.rebase() - invalidateCallback() } } @@ -355,15 +378,13 @@ class TextAnimator( 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 @@ class TextAnimator( 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 515c8169f1c4..078da1c863ce 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.Image 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.LocalContext 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.firstItemAtOffset 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 @@ fun CommunalHub( } }, ) { - 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 @@ fun CommunalHub( @OptIn(ExperimentalFoundationApi::class) @Composable -private fun ColumnScope.CommunalHubLazyGrid( +private fun BoxScope.CommunalHubLazyGrid( communalContent: List<CommunalContentModel>, viewModel: BaseCommunalViewModel, contentPadding: PaddingValues, @@ -295,7 +282,7 @@ private fun ColumnScope.CommunalHubLazyGrid( 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 @@ private fun ColumnScope.CommunalHubLazyGrid( } } -@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/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 92eb8f8c36c2..4950b96b077f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -45,32 +45,32 @@ import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayView import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.LockscreenShadeTransitionController -import com.android.systemui.statusbar.phone.ScreenOffAnimationController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -102,6 +102,7 @@ private const val SENSOR_HEIGHT = 60 @RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class UdfpsControllerOverlayTest : SysuiTestCase() { + private val kosmos = testKosmos() @JvmField @Rule var rule = MockitoJUnit.rule() @@ -148,28 +149,11 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Before fun setup() { - testScope = TestScope(StandardTestDispatcher()) - powerRepository = FakePowerRepository() - powerInteractor = - PowerInteractor( - powerRepository, - mock(FalsingCollector::class.java), - mock(ScreenOffAnimationController::class.java), - statusBarStateController, - ) - keyguardTransitionRepository = FakeKeyguardTransitionRepository() - keyguardTransitionInteractor = - KeyguardTransitionInteractor( - scope = testScope.backgroundScope, - repository = keyguardTransitionRepository, - fromLockscreenTransitionInteractor = { - mock(FromLockscreenTransitionInteractor::class.java) - }, - fromPrimaryBouncerTransitionInteractor = { - mock(FromPrimaryBouncerTransitionInteractor::class.java) - }, - fromAodTransitionInteractor = { mock(FromAodTransitionInteractor::class.java) }, - ) + testScope = kosmos.testScope + powerRepository = kosmos.fakePowerRepository + powerInteractor = kosmos.powerInteractor + keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView) whenever(inflater.inflate(R.layout.udfps_bp_view, null)) .thenReturn(mock(UdfpsBpView::class.java)) 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 8f802b80781f..563aad1920f7 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.shared.model.CommunalWidgetContentModel 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 @@ class CommunalViewModelTest : SysuiTestCase() { kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, - kosmos.deviceEntryInteractor, mediaHost, logcatLogBuffer("CommunalViewModelTest"), ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt index fb46ed9d54ed..b3380ff6409c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt @@ -25,18 +25,17 @@ import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource -import com.android.systemui.power.data.repository.FakePowerRepository -import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest -import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealEffect +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import junit.framework.Assert.assertEquals import junit.framework.Assert.assertFalse import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch -import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -49,9 +48,10 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) class LightRevealScrimRepositoryTest : SysuiTestCase() { - private lateinit var fakeKeyguardRepository: FakeKeyguardRepository - private lateinit var powerRepository: FakePowerRepository - private lateinit var powerInteractor: PowerInteractor + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository + private val powerInteractor = kosmos.powerInteractor private lateinit var underTest: LightRevealScrimRepositoryImpl @get:Rule val animatorTestRule = AnimatorTestRule(this) @@ -59,13 +59,13 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - fakeKeyguardRepository = FakeKeyguardRepository() - powerRepository = FakePowerRepository() - powerInteractor = - PowerInteractorFactory.create(repository = powerRepository).powerInteractor - underTest = - LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context, powerInteractor, mock()) + LightRevealScrimRepositoryImpl( + kosmos.fakeKeyguardRepository, + context, + kosmos.powerInteractor, + mock() + ) } @Test @@ -168,8 +168,9 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) fun revealAmount_emitsTo1AfterAnimationStarted() = - runTest(UnconfinedTestDispatcher()) { + testScope.runTest { val value by collectLastValue(underTest.revealAmount) + runCurrent() underTest.startRevealAmountAnimator(true) assertEquals(0.0f, value) animatorTestRule.advanceTimeBy(500L) @@ -179,8 +180,9 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) fun revealAmount_startingRevealTwiceWontRerunAnimator() = - runTest(UnconfinedTestDispatcher()) { + testScope.runTest { val value by collectLastValue(underTest.revealAmount) + runCurrent() underTest.startRevealAmountAnimator(true) assertEquals(0.0f, value) animatorTestRule.advanceTimeBy(250L) @@ -193,12 +195,14 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) fun revealAmount_emitsTo0AfterAnimationStartedReversed() = - runTest(UnconfinedTestDispatcher()) { - val value by collectLastValue(underTest.revealAmount) + testScope.runTest { + val lastValue by collectLastValue(underTest.revealAmount) + runCurrent() + underTest.startRevealAmountAnimator(true) + animatorTestRule.advanceTimeBy(500L) underTest.startRevealAmountAnimator(false) - assertEquals(1.0f, value) animatorTestRule.advanceTimeBy(500L) - assertEquals(0.0f, value) + assertEquals(0.0f, lastValue) } /** diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index f9ec3d161bb0..24c651f3c702 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -18,9 +18,11 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager +import android.platform.test.annotations.DisableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository @@ -120,6 +122,7 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testKeyguardGuardVisibilityStopsSecureCamera() = testScope.runTest { val secureCameraActive = collectLastValue(underTest.isSecureCameraActive) @@ -144,6 +147,7 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testBouncerShowingResetsSecureCameraState() = testScope.runTest { val secureCameraActive = collectLastValue(underTest.isSecureCameraActive) @@ -166,6 +170,7 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest { val isVisible = collectLastValue(underTest.isKeyguardVisible) repository.setKeyguardShowing(true) 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 000000000000..c80eb135f1a5 --- /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 000000000000..62c51e6252ce --- /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 000000000000..6e9db2cbef07 --- /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 000000000000..39755bf8d764 --- /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 000000000000..37ef6ad17a64 --- /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 000000000000..e1f3d97eb35c --- /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/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml index 61f69c04c174..f6042e467987 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 e181d079fc6d..35f6a08795bc 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 b71341791ee6..5dbdd18bc81b 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 f28d4052b5a8..8a2245d3d14c 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -404,7 +404,9 @@ constructor( 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 31698a35c811..01c2cc4fe874 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 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } } - 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 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { 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 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { } }); } else { + if (mBatteryPercentView != null) { + mEstimateText = null; + mBatteryPercentView.setText(null); + } updateContentDescription(); } } @@ -485,21 +505,18 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { return; } - if (mUnifiedBattery == null) { - return; - } + if (!mShowPercentAvailable || mUnifiedBattery == null) return; - // 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; + 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)); + } setBatteryDrawableState( new BatteryDrawableState( @@ -534,17 +551,8 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { 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 8c9ae62db036..10a0d9584d05 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 @@ interface BatterySaverModule { @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 1b8495ace243..f3652b89cc50 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.Rect 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 @@ import kotlin.math.roundToInt */ @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 @@ class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) { 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 6d3206767d2b..5e34d2909d81 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.PixelFormat 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 @@ class BatteryFillDrawable(private val framePath: Path) : Drawable() { // 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 199dd1f18a42..706b9ec563c9 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.Drawable 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 @@ class BatteryLayersDrawable( ) : 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 @@ class BatteryLayersDrawable( 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 @@ class BatteryLayersDrawable( 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 @@ class BatteryLayersDrawable( 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 @@ class BatteryLayersDrawable( */ 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 @@ class BatteryLayersDrawable( 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 123d6ba57900..aa0e37348f1e 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.PixelFormat 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 @@ class BatteryPercentTextOnlyDrawable(font: Typeface) : Drawable() { } 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 @@ class BatteryPercentTextOnlyDrawable(font: Typeface) : Drawable() { 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 0c418b9caa7d..3b4c77936cff 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.PixelFormat 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 @@ class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() { } 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 @@ class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() { canvas.drawText( percentText, - (ViewportInsetLeft * hScale) + offsetX, + ((if (rtl) ViewportInsetLeftRtl else ViewportInsetLeft) * hScale) + offsetX, (ViewportInsetTop * vScale) + offsetY, textPaint ) @@ -128,6 +130,7 @@ class BatterySpaceSharingPercentTextDrawable(font: Typeface) : Drawable() { 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 6af0fa069dbc..66aeda63e222 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.settingslib.media.MediaOutputConstants; 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 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate { 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 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate { @AssistedInject BroadcastDialogDelegate( Context context, - MediaOutputDialogFactory mediaOutputDialogFactory, + MediaOutputDialogManager mediaOutputDialogManager, @Nullable LocalBluetoothManager localBluetoothManager, UiEventLogger uiEventLogger, @Background Executor bgExecutor, @@ -166,7 +166,7 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate { @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 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate { 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 35b27aaeb6bc..fc9a7df4744d 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.interactor.CommunalTutorialInteracto 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.flow.onEach 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 @@ constructor( 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 @@ constructor( /** 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/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt index 79455ebb624c..289dbd9f66e0 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt @@ -24,9 +24,12 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.shared.model.BiometricMessage import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.plugins.ActivityStarter import com.android.systemui.power.domain.interactor.PowerInteractor @@ -60,17 +63,23 @@ constructor( private val context: Context, activityStarter: ActivityStarter, powerInteractor: PowerInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { private val keyguardOccludedByApp: Flow<Boolean> = - combine( - keyguardInteractor.isKeyguardOccluded, - keyguardInteractor.isKeyguardShowing, - primaryBouncerInteractor.isShowing, - alternateBouncerInteractor.isVisible, - ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible -> - occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible - } - .distinctUntilChanged() + if (KeyguardWmStateRefactor.isEnabled) { + keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED } + } else { + combine( + keyguardInteractor.isKeyguardOccluded, + keyguardInteractor.isKeyguardShowing, + primaryBouncerInteractor.isShowing, + alternateBouncerInteractor.isVisible, + ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible -> + occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible + } + .distinctUntilChanged() + } + private val fingerprintUnlockSuccessEvents: Flow<Unit> = fingerprintAuthRepository.authenticationStatus .ifKeyguardOccludedByApp() 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 931a86938592..ed82278a7346 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 @@ constructor( } companion object { - const val KEY_UP_TIMEOUT = 100L + const val KEY_UP_TIMEOUT = 60L } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 2e233d8e5dd2..3134e35a92e3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -289,6 +289,8 @@ public class KeyguardService extends Service { }; } + private final WindowManagerOcclusionManager mWmOcclusionManager; + @Inject public KeyguardService( KeyguardViewMediator keyguardViewMediator, @@ -302,7 +304,8 @@ public class KeyguardService extends Service { KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator, @Application CoroutineScope scope, FeatureFlags featureFlags, - PowerInteractor powerInteractor) { + PowerInteractor powerInteractor, + WindowManagerOcclusionManager windowManagerOcclusionManager) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -323,6 +326,8 @@ public class KeyguardService extends Service { keyguardSurfaceBehindAnimator, scope); } + + mWmOcclusionManager = windowManagerOcclusionManager; } @Override @@ -414,7 +419,11 @@ public class KeyguardService extends Service { Trace.beginSection("KeyguardService.mBinder#setOccluded"); checkPermission(); - mKeyguardViewMediator.setOccluded(isOccluded, animate); + if (!KeyguardWmStateRefactor.isEnabled()) { + mKeyguardViewMediator.setOccluded(isOccluded, animate); + } else { + mWmOcclusionManager.onKeyguardServiceSetOccluded(isOccluded); + } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 6d917bbde82b..a37397db81f8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1365,7 +1365,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager; + private WindowManagerOcclusionManager mWmOcclusionManager; /** + * Injected constructor. See {@link KeyguardModule}. */ public KeyguardViewMediator( @@ -1411,7 +1413,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, SelectedUserInteractor selectedUserInteractor, - KeyguardInteractor keyguardInteractor) { + KeyguardInteractor keyguardInteractor, + WindowManagerOcclusionManager wmOcclusionManager) { mContext = context; mUserTracker = userTracker; mFalsingCollector = falsingCollector; @@ -1486,6 +1489,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard"); mShowKeyguardWakeLock.setReferenceCounted(false); + + mWmOcclusionManager = wmOcclusionManager; } public void userActivity() { @@ -2103,15 +2108,27 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } public IRemoteAnimationRunner getOccludeAnimationRunner() { - return validatingRemoteAnimationRunner(mOccludeAnimationRunner); + if (KeyguardWmStateRefactor.isEnabled()) { + return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner()); + } else { + return validatingRemoteAnimationRunner(mOccludeAnimationRunner); + } } + /** + * TODO(b/326464548): Move this to WindowManagerOcclusionManager + */ public IRemoteAnimationRunner getOccludeByDreamAnimationRunner() { return validatingRemoteAnimationRunner(mOccludeByDreamAnimationRunner); } public IRemoteAnimationRunner getUnoccludeAnimationRunner() { - return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner); + if (KeyguardWmStateRefactor.isEnabled()) { + return validatingRemoteAnimationRunner( + mWmOcclusionManager.getUnoccludeAnimationRunner()); + } else { + return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner); + } } public boolean isHiding() { @@ -2145,8 +2162,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, if (mOccluded != isOccluded) { mOccluded = isOccluded; - mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate - && mDeviceInteractive); + if (!KeyguardWmStateRefactor.isEnabled()) { + mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate + && mDeviceInteractive); + } adjustStatusBarLocked(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 8ebcece940c2..00f50023b263 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -140,8 +140,14 @@ constructor( nonApps: Array<RemoteAnimationTarget>, finishedCallback: IRemoteAnimationFinishedCallback ) { - goingAwayRemoteAnimationFinishedCallback = finishedCallback - keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0]) + if (apps.isNotEmpty()) { + goingAwayRemoteAnimationFinishedCallback = finishedCallback + keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0]) + } else { + // Nothing to do here if we have no apps, end the animation, which will cancel it and WM + // will make *something* visible. + finishedCallback.onAnimationFinished() + } } fun onKeyguardGoingAwayRemoteAnimationCancelled() { @@ -174,13 +180,19 @@ constructor( * if so, true should be the right choice. */ private fun setWmLockscreenState( - lockscreenShowing: Boolean = this.isLockscreenShowing ?: true.also { - Log.d(TAG, "Using isLockscreenShowing=true default in setWmLockscreenState, " + - "because setAodVisible was called before the first setLockscreenShown " + - "call during boot. This is not typical, but is theoretically possible. " + - "If you're investigating the lockscreen showing unexpectedly, start here.") - }, - aodVisible: Boolean = this.isAodVisible + lockscreenShowing: Boolean = + this.isLockscreenShowing + ?: true.also { + Log.d( + TAG, + "Using isLockscreenShowing=true default in setWmLockscreenState, " + + "because setAodVisible was called before the first " + + "setLockscreenShown call during boot. This is not typical, but is " + + "theoretically possible. If you're investigating the lockscreen " + + "showing unexpectedly, start here." + ) + }, + aodVisible: Boolean = this.isAodVisible ) { Log.d( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt new file mode 100644 index 000000000000..aab90c378a19 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt @@ -0,0 +1,327 @@ +/* + * 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.keyguard + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Matrix +import android.os.RemoteException +import android.util.Log +import android.view.IRemoteAnimationFinishedCallback +import android.view.IRemoteAnimationRunner +import android.view.RemoteAnimationTarget +import android.view.SyncRtSurfaceTransactionApplier +import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED +import androidx.annotation.VisibleForTesting +import com.android.app.animation.Interpolators +import com.android.internal.jank.InteractionJankMonitor +import com.android.internal.policy.ScreenDecorationsUtils +import com.android.keyguard.KeyguardViewController +import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.TransitionAnimator +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.res.R +import java.util.concurrent.Executor +import javax.inject.Inject + +private val UNOCCLUDE_ANIMATION_DURATION = 250 +private val UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT = 0.1f + +/** + * Keeps track of Window Manager's occlusion state and RemoteAnimations related to changes in + * occlusion state. Occlusion is when a [FLAG_SHOW_WHEN_LOCKED] activity is displaying over the + * lockscreen - we're still locked, but the user can interact with the activity. + * + * Typical "occlusion" use cases include launching the camera over the lockscreen, tapping a quick + * affordance to bring up Google Pay/Wallet/whatever it's called by the time you're reading this, + * and Maps Navigation. + * + * Window Manager considers the keyguard to be 'occluded' whenever a [FLAG_SHOW_WHEN_LOCKED] + * activity is on top of the task stack, even if the device is unlocked and the keyguard is not + * visible. System UI considers the keyguard to be [KeyguardState.OCCLUDED] only when we're on the + * keyguard and an activity is displaying over it. + * + * For all System UI use cases, you should use [KeyguardTransitionInteractor] to determine if we're + * in the [KeyguardState.OCCLUDED] state and react accordingly. If you are sure that you need to + * check whether Window Manager considers OCCLUDED=true even though the lockscreen is not showing, + * use [KeyguardShowWhenLockedActivityInteractor.isShowWhenLockedActivityOnTop] in combination with + * [KeyguardTransitionInteractor] state. + * + * This is a very sensitive piece of state that has caused many headaches in the past. Please be + * careful. + */ +@SysUISingleton +class WindowManagerOcclusionManager +@Inject +constructor( + val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + val activityTransitionAnimator: ActivityTransitionAnimator, + val keyguardViewController: dagger.Lazy<KeyguardViewController>, + val powerInteractor: PowerInteractor, + val context: Context, + val interactionJankMonitor: InteractionJankMonitor, + @Main executor: Executor, + val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, + val occlusionInteractor: KeyguardOcclusionInteractor, +) { + val powerButtonY = + context.resources.getDimensionPixelSize( + R.dimen.physical_power_button_center_screen_location_y + ) + val windowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) + + var occludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null + + /** + * Animation runner provided to WindowManager, which will be used if an occluding activity is + * launched and Window Manager wants us to animate it in. This is used as a signal that we are + * now occluded, and should update our state accordingly. + */ + val occludeAnimationRunner: IRemoteAnimationRunner = + object : IRemoteAnimationRunner.Stub() { + override fun onAnimationStart( + transit: Int, + apps: Array<RemoteAnimationTarget>, + wallpapers: Array<RemoteAnimationTarget>, + nonApps: Array<RemoteAnimationTarget>, + finishedCallback: IRemoteAnimationFinishedCallback? + ) { + Log.d(TAG, "occludeAnimationRunner#onAnimationStart") + // Wrap the callback so that it's guaranteed to be nulled out once called. + occludeAnimationFinishedCallback = + object : IRemoteAnimationFinishedCallback.Stub() { + override fun onAnimationFinished() { + finishedCallback?.onAnimationFinished() + occludeAnimationFinishedCallback = null + } + } + keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + showWhenLockedActivityOnTop = true, + taskInfo = apps.firstOrNull()?.taskInfo, + ) + activityTransitionAnimator + .createRunner(occludeAnimationController) + .onAnimationStart( + transit, + apps, + wallpapers, + nonApps, + occludeAnimationFinishedCallback, + ) + } + + override fun onAnimationCancelled() { + Log.d(TAG, "occludeAnimationRunner#onAnimationCancelled") + } + } + + var unoccludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null + + /** + * Animation runner provided to WindowManager, which will be used if an occluding activity is + * finished and Window Manager wants us to animate it out. This is used as a signal that we are + * no longer occluded, and should update our state accordingly. + * + * TODO(b/326464548): Restore dream specific animation. + */ + val unoccludeAnimationRunner: IRemoteAnimationRunner = + object : IRemoteAnimationRunner.Stub() { + var unoccludeAnimator: ValueAnimator? = null + val unoccludeMatrix = Matrix() + + /** TODO(b/326470033): Extract this logic into ViewModels. */ + override fun onAnimationStart( + transit: Int, + apps: Array<RemoteAnimationTarget>, + wallpapers: Array<RemoteAnimationTarget>, + nonApps: Array<RemoteAnimationTarget>, + finishedCallback: IRemoteAnimationFinishedCallback? + ) { + Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart") + // Wrap the callback so that it's guaranteed to be nulled out once called. + unoccludeAnimationFinishedCallback = + object : IRemoteAnimationFinishedCallback.Stub() { + override fun onAnimationFinished() { + finishedCallback?.onAnimationFinished() + unoccludeAnimationFinishedCallback = null + } + } + keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + showWhenLockedActivityOnTop = false, + taskInfo = apps.firstOrNull()?.taskInfo, + ) + interactionJankMonitor.begin( + createInteractionJankMonitorConf( + InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION, + "UNOCCLUDE" + ) + ) + if (apps.isEmpty()) { + Log.d( + TAG, + "No apps provided to unocclude runner; " + + "skipping animation and unoccluding." + ) + unoccludeAnimationFinishedCallback?.onAnimationFinished() + return + } + val target = apps[0] + val localView: View = keyguardViewController.get().getViewRootImpl().getView() + val applier = SyncRtSurfaceTransactionApplier(localView) + // TODO( + executor.execute { + unoccludeAnimator?.cancel() + unoccludeAnimator = + ValueAnimator.ofFloat(1f, 0f).apply { + duration = UNOCCLUDE_ANIMATION_DURATION.toLong() + interpolator = Interpolators.TOUCH_RESPONSE + addUpdateListener { animation: ValueAnimator -> + val animatedValue = animation.animatedValue as Float + val surfaceHeight: Float = + target.screenSpaceBounds.height().toFloat() + + unoccludeMatrix.setTranslate( + 0f, + (1f - animatedValue) * + surfaceHeight * + UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT + ) + + SurfaceParams.Builder(target.leash) + .withAlpha(animatedValue) + .withMatrix(unoccludeMatrix) + .withCornerRadius(windowCornerRadius) + .build() + .also { applier.scheduleApply(it) } + } + addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + try { + unoccludeAnimationFinishedCallback + ?.onAnimationFinished() + unoccludeAnimator = null + interactionJankMonitor.end( + InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION + ) + } catch (e: RemoteException) { + e.printStackTrace() + } + } + } + ) + start() + } + } + } + + override fun onAnimationCancelled() { + Log.d(TAG, "unoccludeAnimationRunner#onAnimationCancelled") + context.mainExecutor.execute { unoccludeAnimator?.cancel() } + Log.d(TAG, "Unocclude animation cancelled.") + interactionJankMonitor.cancel(InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION) + } + } + + /** + * Called when Window Manager tells the KeyguardService directly that we're occluded or not + * occluded, without starting an occlude/unocclude remote animation. This happens if occlusion + * state changes without an animation (such as if a SHOW_WHEN_LOCKED activity is launched while + * we're unlocked), or if an animation has been cancelled/interrupted and Window Manager wants + * to make sure that we're in the correct state. + */ + fun onKeyguardServiceSetOccluded(occluded: Boolean) { + Log.d(TAG, "#onKeyguardServiceSetOccluded($occluded)") + keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(occluded) + } + + @VisibleForTesting + val occludeAnimationController: ActivityTransitionAnimator.Controller = + object : ActivityTransitionAnimator.Controller { + + override var transitionContainer: ViewGroup + get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup + set(_) { + // Should never be set. + } + + /** TODO(b/326470033): Extract this logic into ViewModels. */ + override fun createAnimatorState(): TransitionAnimator.State { + val fullWidth = transitionContainer.width + val fullHeight = transitionContainer.height + + if ( + keyguardOcclusionInteractor.showWhenLockedActivityLaunchedFromPowerGesture.value + ) { + val initialHeight = fullHeight / 3f + val initialWidth = fullWidth / 3f + + // Start the animation near the power button, at one-third size, since the + // camera was launched from the power button. + return TransitionAnimator.State( + top = (powerButtonY - initialHeight / 2f).toInt(), + bottom = (powerButtonY + initialHeight / 2f).toInt(), + left = (fullWidth - initialWidth).toInt(), + right = fullWidth, + topCornerRadius = windowCornerRadius, + bottomCornerRadius = windowCornerRadius, + ) + } else { + val initialHeight = fullHeight / 2f + val initialWidth = fullWidth / 2f + + // Start the animation in the center of the screen, scaled down to half + // size. + return TransitionAnimator.State( + top = (fullHeight - initialHeight).toInt() / 2, + bottom = (initialHeight + (fullHeight - initialHeight) / 2).toInt(), + left = (fullWidth - initialWidth).toInt() / 2, + right = (initialWidth + (fullWidth - initialWidth) / 2).toInt(), + topCornerRadius = windowCornerRadius, + bottomCornerRadius = windowCornerRadius, + ) + } + } + } + + private fun createInteractionJankMonitorConf( + cuj: Int, + tag: String? + ): InteractionJankMonitor.Configuration.Builder { + val builder = + InteractionJankMonitor.Configuration.Builder.withView( + cuj, + keyguardViewController.get().getViewRootImpl().view + ) + return if (tag != null) builder.setTag(tag) else builder + } + + companion object { + val TAG = "WindowManagerOcclusion" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java index 968c3e3a6792..5306645bf69f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java @@ -50,6 +50,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager; +import com.android.systemui.keyguard.WindowManagerOcclusionManager; import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule; import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule; import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; @@ -163,7 +164,8 @@ public interface KeyguardModule { SystemPropertiesHelper systemPropertiesHelper, Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager, SelectedUserInteractor selectedUserInteractor, - KeyguardInteractor keyguardInteractor) { + KeyguardInteractor keyguardInteractor, + WindowManagerOcclusionManager windowManagerOcclusionManager) { return new KeyguardViewMediator( context, uiEventLogger, @@ -209,7 +211,8 @@ public interface KeyguardModule { systemPropertiesHelper, wmLockscreenVisibilityManager, selectedUserInteractor, - keyguardInteractor); + keyguardInteractor, + windowManagerOcclusionManager); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt new file mode 100644 index 000000000000..e3654b415017 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt @@ -0,0 +1,86 @@ +/* + * 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.keyguard.data.repository + +import android.app.ActivityManager.RunningTaskInfo +import android.app.WindowConfiguration +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +/** + * Information about the SHOW_WHEN_LOCKED activity that is either newly on top of the task stack, or + * newly not on top of the task stack. + */ +data class ShowWhenLockedActivityInfo( + /** Whether the activity is on top. If not, we're unoccluding and will be animating it out. */ + val isOnTop: Boolean, + + /** + * Information about the activity, which we use for transition internals and also to customize + * animations. + */ + val taskInfo: RunningTaskInfo? = null +) { + fun isDream(): Boolean { + return taskInfo?.topActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM + } +} + +/** + * Maintains state about "occluding" activities - activities with FLAG_SHOW_WHEN_LOCKED, which are + * capable of displaying over the lockscreen while the device is still locked (such as Google Maps + * navigation). + * + * Window Manager considers the device to be in the "occluded" state whenever such an activity is on + * top of the task stack, including while we're unlocked, while keyguard code considers us to be + * occluded only when we're locked, with an occluding activity currently displaying over the + * lockscreen. + * + * This dual definition is confusing, so this repository collects all of the signals WM gives us, + * and consolidates them into [showWhenLockedActivityInfo.isOnTop], which is the actual question WM + * is answering when they say whether we're 'occluded'. Keyguard then uses this signal to + * conditionally transition to [KeyguardState.OCCLUDED] where appropriate. + */ +@SysUISingleton +class KeyguardOcclusionRepository @Inject constructor() { + val showWhenLockedActivityInfo = MutableStateFlow(ShowWhenLockedActivityInfo(isOnTop = false)) + + /** + * Sets whether there's a SHOW_WHEN_LOCKED activity on top of the task stack, and optionally, + * information about the activity itself. + * + * If no value is provided for [taskInfo], we'll default to the current [taskInfo]. + * + * The [taskInfo] is always present when this method is called from the occlude/unocclude + * animation runners. We use the default when calling from [KeyguardService.isOccluded], since + * we only receive a true/false value there. isOccluded is mostly redundant - it's almost always + * called with true after an occlusion animation has started, and with false after an unocclude + * animation has started. In those cases, we don't want to clear out the taskInfo just because + * it wasn't available at that call site. + */ + fun setShowWhenLockedActivityInfo( + onTop: Boolean, + taskInfo: RunningTaskInfo? = showWhenLockedActivityInfo.value.taskInfo + ) { + showWhenLockedActivityInfo.value = + ShowWhenLockedActivityInfo( + isOnTop = onTop, + taskInfo = taskInfo, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 64e28700aa74..3a6423d680c6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -95,6 +95,7 @@ interface KeyguardRepository { val isKeyguardShowing: Flow<Boolean> /** Is an activity showing over the keyguard? */ + @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED") val isKeyguardOccluded: Flow<Boolean> /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt index 9b3f13d16911..d9479de7e25b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt @@ -75,7 +75,6 @@ constructor( powerInteractor: PowerInteractor, private val scrimLogger: ScrimLogger, ) : LightRevealScrimRepository { - companion object { val TAG = LightRevealScrimRepository::class.simpleName!! } @@ -156,7 +155,11 @@ constructor( override fun startRevealAmountAnimator(reveal: Boolean) { if (reveal == willBeOrIsRevealed) return willBeOrIsRevealed = reveal - if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse() + if (reveal && !revealAmountAnimator.isRunning) { + revealAmountAnimator.start() + } else { + revealAmountAnimator.reverse() + } scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 6aed944ac809..88ddfd4f4347 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor @@ -46,13 +47,16 @@ constructor( @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, private val communalInteractor: CommunalInteractor, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.ALTERNATE_BOUNCER, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { @@ -112,6 +116,11 @@ constructor( } private fun listenForAlternateBouncerToGone() { + if (KeyguardWmStateRefactor.isEnabled) { + // Handled via #dismissAlternateBouncer. + return + } + scope.launch { keyguardInteractor.isKeyguardGoingAway .sampleUtil(finishedKeyguardState, ::Pair) @@ -149,6 +158,10 @@ constructor( } } + fun dismissAlternateBouncer() { + scope.launch { startTransitionTo(KeyguardState.GONE) } + } + companion object { const val TAG = "FromAlternateBouncerTransitionInteractor" val TRANSITION_DURATION_MS = 300.milliseconds diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index dbd5e26eacfc..9040e031d54e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -27,14 +27,17 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample import com.android.systemui.util.kotlin.sample +import java.util.UUID import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @SysUISingleton @@ -47,29 +50,118 @@ constructor( @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.AOD, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { - listenForAodToLockscreen() + listenForAodToAwake() + listenForAodToOccluded() listenForAodToPrimaryBouncer() listenForAodToGone() - listenForAodToOccluded() listenForTransitionToCamera(scope, keyguardInteractor) } /** + * Listen for the signal that we're waking up and figure what state we need to transition to. + */ + private fun listenForAodToAwake() { + val transitionToLockscreen: suspend (TransitionStep) -> UUID? = + { startedStep: TransitionStep -> + val modeOnCanceled = + if (startedStep.from == KeyguardState.LOCKSCREEN) { + TransitionModeOnCanceled.REVERSE + } else if (startedStep.from == KeyguardState.GONE) { + TransitionModeOnCanceled.RESET + } else { + TransitionModeOnCanceled.LAST_VALUE + } + startTransitionTo( + toState = KeyguardState.LOCKSCREEN, + modeOnCanceled = modeOnCanceled, + ) + } + + if (KeyguardWmStateRefactor.isEnabled) { + // The refactor uses PowerInteractor's wakefulness, which is the earliest wake signal + // available. We have all of the information we need at this time to make a decision + // about where to transition. + scope.launch { + powerInteractor.detailedWakefulness + // React only to wake events. + .filter { it.isAwake() } + .sample( + startedKeyguardTransitionStep, + keyguardInteractor.biometricUnlockState, + keyguardInteractor.primaryBouncerShowing, + ) + // Make sure we've at least STARTED a transition to AOD. + .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.AOD } + .collect { (_, startedStep, biometricUnlockState, primaryBouncerShowing) -> + // Check with the superclass to see if an occlusion transition is needed. + // Also, don't react to wake and unlock events, as we'll be receiving a call + // to #dismissAod() shortly when the authentication completes. + if ( + !maybeStartTransitionToOccludedOrInsecureCamera() && + !isWakeAndUnlock(biometricUnlockState) && + !primaryBouncerShowing + ) { + transitionToLockscreen(startedStep) + } + } + } + } else { + scope.launch { + keyguardInteractor + .dozeTransitionTo(DozeStateModel.FINISH) + .sample( + keyguardInteractor.isKeyguardShowing, + startedKeyguardTransitionStep, + keyguardInteractor.isKeyguardOccluded, + keyguardInteractor.biometricUnlockState, + keyguardInteractor.primaryBouncerShowing, + ) + .collect { + ( + _, + isKeyguardShowing, + lastStartedStep, + occluded, + biometricUnlockState, + primaryBouncerShowing) -> + if ( + lastStartedStep.to == KeyguardState.AOD && + !occluded && + !isWakeAndUnlock(biometricUnlockState) && + isKeyguardShowing && + !primaryBouncerShowing + ) { + transitionToLockscreen(lastStartedStep) + } + } + } + } + } + + /** * There are cases where the transition to AOD begins but never completes, such as tapping power * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to * run AOD->OCCLUDED. */ private fun listenForAodToOccluded() { + if (KeyguardWmStateRefactor.isEnabled) { + // Handled by calls to maybeStartTransitionToOccludedOrInsecureCamera on waking. + return + } + scope.launch { keyguardInteractor.isKeyguardOccluded .sample(startedKeyguardTransitionStep, ::Pair) @@ -84,49 +176,6 @@ constructor( } } - private fun listenForAodToLockscreen() { - scope.launch { - keyguardInteractor - .dozeTransitionTo(DozeStateModel.FINISH) - .sample( - keyguardInteractor.isKeyguardShowing, - startedKeyguardTransitionStep, - keyguardInteractor.isKeyguardOccluded, - keyguardInteractor.biometricUnlockState, - keyguardInteractor.primaryBouncerShowing, - ) - .collect { - ( - _, - isKeyguardShowing, - lastStartedStep, - occluded, - biometricUnlockState, - primaryBouncerShowing) -> - if ( - lastStartedStep.to == KeyguardState.AOD && - !occluded && - !isWakeAndUnlock(biometricUnlockState) && - isKeyguardShowing && - !primaryBouncerShowing - ) { - val modeOnCanceled = - if (lastStartedStep.from == KeyguardState.LOCKSCREEN) { - TransitionModeOnCanceled.REVERSE - } else if (lastStartedStep.from == KeyguardState.GONE) { - TransitionModeOnCanceled.RESET - } else { - TransitionModeOnCanceled.LAST_VALUE - } - startTransitionTo( - toState = KeyguardState.LOCKSCREEN, - modeOnCanceled = modeOnCanceled, - ) - } - } - } - } - /** * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the * PRIMARY_BOUNCER. @@ -145,6 +194,7 @@ constructor( private fun listenForAodToGone() { if (KeyguardWmStateRefactor.isEnabled) { + // Handled via #dismissAod. return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index 8591fe782d3a..57b2a632008a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock import com.android.systemui.keyguard.shared.model.KeyguardState @@ -34,6 +35,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @SysUISingleton @@ -46,18 +48,22 @@ constructor( @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.DOZING, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { listenForDozingToAny() + listenForWakeFromDozing() listenForTransitionToCamera(scope, keyguardInteractor) } @@ -70,6 +76,10 @@ constructor( } private fun listenForDozingToAny() { + if (KeyguardWmStateRefactor.isEnabled) { + return + } + scope.launch { powerInteractor.isAwake .debounce(50L) @@ -112,6 +122,58 @@ constructor( } } + /** Figure out what state to transition to when we awake from DOZING. */ + private fun listenForWakeFromDozing() { + if (!KeyguardWmStateRefactor.isEnabled) { + return + } + + scope.launch { + powerInteractor.detailedWakefulness + .filter { it.isAwake() } + .sample( + startedKeyguardTransitionStep, + communalInteractor.isIdleOnCommunal, + keyguardInteractor.biometricUnlockState, + canDismissLockScreen, + keyguardInteractor.primaryBouncerShowing, + ) + // If we haven't at least STARTED a transition to DOZING, ignore. + .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.DOZING } + .collect { + ( + _, + _, + isIdleOnCommunal, + biometricUnlockState, + canDismissLockscreen, + primaryBouncerShowing) -> + if ( + !maybeStartTransitionToOccludedOrInsecureCamera() && + // Handled by dismissFromDozing(). + !isWakeAndUnlock(biometricUnlockState) + ) { + startTransitionTo( + if (canDismissLockscreen) { + KeyguardState.GONE + } else if (primaryBouncerShowing) { + KeyguardState.PRIMARY_BOUNCER + } else if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + ) + } + } + } + } + + /** Dismisses keyguard from the DOZING state. */ + fun dismissFromDozing() { + scope.launch { startTransitionTo(KeyguardState.GONE) } + } + override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { interpolator = Interpolators.LINEAR diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt index a6cdaa8c6761..6433d0ede796 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt @@ -25,6 +25,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositor import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -46,12 +47,16 @@ constructor( @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, + powerInteractor: PowerInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 28282c630b8f..31371384e338 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -23,10 +23,13 @@ import com.android.systemui.Flags.communalHub import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.util.kotlin.Utils import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample @@ -35,6 +38,7 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @SysUISingleton @@ -48,18 +52,23 @@ constructor( @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, + powerInteractor: PowerInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.DREAMING, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { listenForDreamingToOccluded() listenForDreamingToGoneWhenDismissable() listenForDreamingToGoneFromBiometricUnlock() + listenForDreamingToLockscreen() listenForDreamingToAodOrDozing() listenForTransitionToCamera(scope, keyguardInteractor) listenForDreamingToGlanceableHub() @@ -78,6 +87,7 @@ constructor( fun startToLockscreenTransition() { scope.launch { + KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode() if ( transitionInteractor.startedKeyguardState.replayCache.last() == KeyguardState.DREAMING @@ -88,16 +98,52 @@ constructor( } private fun listenForDreamingToOccluded() { + if (KeyguardWmStateRefactor.isEnabled) { + scope.launch { + combine( + keyguardInteractor.isDreaming, + keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, + ::Pair + ) + .sample(startedKeyguardTransitionStep, ::toTriple) + .filter { (isDreaming, _, startedStep) -> + !isDreaming && startedStep.to == KeyguardState.DREAMING + } + .collect { maybeStartTransitionToOccludedOrInsecureCamera() } + } + } else { + scope.launch { + combine( + keyguardInteractor.isKeyguardOccluded, + keyguardInteractor.isDreaming, + ::Pair + ) + .sample(startedKeyguardTransitionStep, Utils.Companion::toTriple) + .collect { (isOccluded, isDreaming, lastStartedTransition) -> + if ( + isOccluded && + !isDreaming && + lastStartedTransition.to == KeyguardState.DREAMING + ) { + startTransitionTo(KeyguardState.OCCLUDED) + } + } + } + } + } + + private fun listenForDreamingToLockscreen() { + if (!KeyguardWmStateRefactor.isEnabled) { + return + } + scope.launch { - combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair) - .sample(startedKeyguardTransitionStep, ::toTriple) - .collect { (isOccluded, isDreaming, lastStartedTransition) -> - if ( - isOccluded && - !isDreaming && - lastStartedTransition.to == KeyguardState.DREAMING - ) { - startTransitionTo(KeyguardState.OCCLUDED) + keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop + .filter { onTop -> !onTop } + .sample(startedKeyguardState) + .collect { startedState -> + if (startedState == KeyguardState.DREAMING) { + startTransitionTo(KeyguardState.LOCKSCREEN) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index 786c3c6697d9..51bc3ae778e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled @@ -35,6 +36,7 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -49,14 +51,18 @@ constructor( private val keyguardInteractor: KeyguardInteractor, override val transitionRepository: KeyguardTransitionRepository, transitionInteractor: KeyguardTransitionInteractor, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.GLANCEABLE_HUB, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { + override fun start() { if (!Flags.communalHub()) { return @@ -151,14 +157,27 @@ constructor( } private fun listenForHubToOccluded() { - scope.launch { - and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming)) - .sample(startedKeyguardState, ::Pair) - .collect { (isOccludedAndNotDreaming, keyguardState) -> - if (isOccludedAndNotDreaming && keyguardState == fromState) { - startTransitionTo(KeyguardState.OCCLUDED) + if (KeyguardWmStateRefactor.isEnabled) { + scope.launch { + keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop + .filter { onTop -> onTop } + .sample(startedKeyguardState) + .collect { + if (it == KeyguardState.GLANCEABLE_HUB) { + maybeStartTransitionToOccludedOrInsecureCamera() + } } - } + } + } else { + scope.launch { + and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming)) + .sample(startedKeyguardState, ::Pair) + .collect { (isOccludedAndNotDreaming, keyguardState) -> + if (isOccludedAndNotDreaming && keyguardState == fromState) { + startTransitionTo(KeyguardState.OCCLUDED) + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 7593ac252543..d5a9bd19d766 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -22,6 +22,8 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardWmStateRefactor +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled @@ -34,6 +36,8 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @SysUISingleton @@ -46,14 +50,18 @@ constructor( @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + private val biometricSettingsRepository: BiometricSettingsRepository, ) : TransitionInteractor( fromState = KeyguardState.GONE, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { @@ -65,23 +73,46 @@ constructor( // Primarily for when the user chooses to lock down the device private fun listenForGoneToLockscreenOrHub() { - scope.launch { - keyguardInteractor.isKeyguardShowing - .sample( - startedKeyguardState, - communalInteractor.isIdleOnCommunal, - ) - .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) -> - if (isKeyguardShowing && startedState == KeyguardState.GONE) { - val to = - if (isIdleOnCommunal) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(to) + if (KeyguardWmStateRefactor.isEnabled) { + scope.launch { + biometricSettingsRepository.isCurrentUserInLockdown + .distinctUntilChanged() + .filter { inLockdown -> inLockdown } + .sample( + startedKeyguardState, + communalInteractor.isIdleOnCommunal, + ) + .collect { (_, startedState, isIdleOnCommunal) -> + if (startedState == KeyguardState.GONE) { + val to = + if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(to, ownerReason = "User initiated lockdown") + } } - } + } + } else { + scope.launch { + keyguardInteractor.isKeyguardShowing + .sample( + startedKeyguardState, + communalInteractor.isIdleOnCommunal, + ) + .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) -> + if (isKeyguardShowing && startedState == KeyguardState.GONE) { + val to = + if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(to) + } + } + } } } @@ -122,24 +153,10 @@ constructor( private fun listenForGoneToAodOrDozing() { scope.launch { - powerInteractor.isAsleep - .sample( - combine( - startedKeyguardTransitionStep, - keyguardInteractor.isAodAvailable, - ::Pair - ), - ::toTriple - ) - .collect { (isAsleep, lastStartedStep, isAodAvailable) -> - if (lastStartedStep.to == KeyguardState.GONE && isAsleep) { - startTransitionTo( - toState = - if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING, - modeOnCanceled = TransitionModeOnCanceled.RESET, - ) - } - } + listenForSleepTransition( + from = KeyguardState.GONE, + modeOnCanceledFromStartedStep = { TransitionModeOnCanceled.RESET }, + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index cb1571e7d702..bcad3324b258 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -33,7 +33,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.Utils.Companion.toQuad -import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import java.util.UUID import javax.inject.Inject @@ -44,6 +43,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -62,21 +62,24 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val flags: FeatureFlags, private val shadeRepository: ShadeRepository, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, private val swipeToDismissInteractor: SwipeToDismissInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.LOCKSCREEN, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { listenForLockscreenToGone() listenForLockscreenToGoneDragging() - listenForLockscreenToOccluded() + listenForLockscreenToOccludedOrDreaming() listenForLockscreenToAodOrDozing() listenForLockscreenToPrimaryBouncer() listenForLockscreenToDreaming() @@ -115,6 +118,10 @@ constructor( } private fun listenForLockscreenToDreaming() { + if (KeyguardWmStateRefactor.isEnabled) { + return + } + val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING) scope.launch { keyguardInteractor.isAbleToDream @@ -157,7 +164,10 @@ constructor( if ( isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN ) { - startTransitionTo(KeyguardState.PRIMARY_BOUNCER) + startTransitionTo( + KeyguardState.PRIMARY_BOUNCER, + ownerReason = "#listenForLockscreenToPrimaryBouncer" + ) } } } @@ -254,7 +264,8 @@ constructor( transitionId = startTransitionTo( toState = KeyguardState.PRIMARY_BOUNCER, - animator = null, // transition will be manually controlled + animator = null, // transition will be manually controlled, + ownerReason = "#listenForLockscreenToPrimaryBouncerDragging" ) } } @@ -309,47 +320,51 @@ constructor( } } - private fun listenForLockscreenToOccluded() { - scope.launch { - keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect { - (isOccluded, keyguardState) -> - if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) { - startTransitionTo(KeyguardState.OCCLUDED) - } + private fun listenForLockscreenToOccludedOrDreaming() { + if (KeyguardWmStateRefactor.isEnabled) { + scope.launch { + keyguardOcclusionInteractor.showWhenLockedActivityInfo + .filter { it.isOnTop } + .sample(startedKeyguardState, ::Pair) + .collect { (taskInfo, startedState) -> + if (startedState == KeyguardState.LOCKSCREEN) { + startTransitionTo( + if (taskInfo.isDream()) { + KeyguardState.DREAMING + } else { + KeyguardState.OCCLUDED + } + ) + } + } + } + } else { + scope.launch { + keyguardInteractor.isKeyguardOccluded + .sample(startedKeyguardState, ::Pair) + .collect { (isOccluded, keyguardState) -> + if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) { + startTransitionTo(KeyguardState.OCCLUDED) + } + } } } } private fun listenForLockscreenToAodOrDozing() { scope.launch { - powerInteractor.isAsleep - .sample( - combine( - startedKeyguardTransitionStep, - keyguardInteractor.isAodAvailable, - ::Pair - ), - ::toTriple - ) - .collect { (isAsleep, lastStartedStep, isAodAvailable) -> - if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) { - val toState = - if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING - val modeOnCanceled = - if ( - toState == KeyguardState.AOD && - lastStartedStep.from == KeyguardState.AOD - ) { - TransitionModeOnCanceled.REVERSE - } else { - TransitionModeOnCanceled.LAST_VALUE - } - startTransitionTo( - toState = toState, - modeOnCanceled = modeOnCanceled, - ) + listenForSleepTransition( + from = KeyguardState.LOCKSCREEN, + modeOnCanceledFromStartedStep = { startedStep -> + if ( + startedStep.to == KeyguardState.AOD && startedStep.from == KeyguardState.AOD + ) { + TransitionModeOnCanceled.REVERSE + } else { + TransitionModeOnCanceled.LAST_VALUE } } + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index efb604d5dadc..f10327e02240 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -22,17 +22,17 @@ import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample -import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @SysUISingleton @@ -45,20 +45,23 @@ constructor( @Background bgDispatcher: CoroutineDispatcher, @Main mainDispatcher: CoroutineDispatcher, private val keyguardInteractor: KeyguardInteractor, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, private val communalInteractor: CommunalInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.OCCLUDED, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { listenForOccludedToLockscreenOrHub() listenForOccludedToDreaming() - listenForOccludedToAodOrDozing() + listenForOccludedToAsleep() listenForOccludedToGone() listenForOccludedToAlternateBouncer() listenForOccludedToPrimaryBouncer() @@ -90,43 +93,72 @@ constructor( } private fun listenForOccludedToLockscreenOrHub() { - scope.launch { - keyguardInteractor.isKeyguardOccluded - .sample( - keyguardInteractor.isKeyguardShowing, - startedKeyguardTransitionStep, - communalInteractor.isIdleOnCommunal, - ) - .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) -> - // Occlusion signals come from the framework, and should interrupt any - // existing transition - if ( - !isOccluded && - isShowing && - lastStartedKeyguardState.to == KeyguardState.OCCLUDED - ) { - val to = - if (isIdleOnCommunal) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(to) + if (KeyguardWmStateRefactor.isEnabled) { + scope.launch { + keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop + .filter { onTop -> !onTop } + .sample( + startedKeyguardState, + communalInteractor.isIdleOnCommunal, + ) + .collect { (_, startedState, isIdleOnCommunal) -> + // Occlusion signals come from the framework, and should interrupt any + // existing transition + if (startedState == KeyguardState.OCCLUDED) { + val to = + if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(to) + } } - } + } + } else { + scope.launch { + keyguardInteractor.isKeyguardOccluded + .sample( + keyguardInteractor.isKeyguardShowing, + startedKeyguardTransitionStep, + communalInteractor.isIdleOnCommunal, + ) + .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) + -> + // Occlusion signals come from the framework, and should interrupt any + // existing transition + if ( + !isOccluded && + isShowing && + lastStartedKeyguardState.to == KeyguardState.OCCLUDED + ) { + val to = + if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(to) + } + } + } } } private fun listenForOccludedToGone() { + if (KeyguardWmStateRefactor.isEnabled) { + // We don't think OCCLUDED to GONE is possible. You should always have to go via a + // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard + // dismisses it, and even "extend unlock" doesn't unlock the device in the background. + // If we're wrong - sorry, add it back here. + return + } + scope.launch { keyguardInteractor.isKeyguardOccluded .sample( - combine( - keyguardInteractor.isKeyguardShowing, - startedKeyguardTransitionStep, - ::Pair - ), - ::toTriple + keyguardInteractor.isKeyguardShowing, + startedKeyguardTransitionStep, ) .collect { (isOccluded, isShowing, lastStartedKeyguardState) -> // Occlusion signals come from the framework, and should interrupt any @@ -142,25 +174,12 @@ constructor( } } - private fun listenForOccludedToAodOrDozing() { - scope.launch { - powerInteractor.isAsleep - .sample( - combine( - startedKeyguardTransitionStep, - keyguardInteractor.isAodAvailable, - ::Pair - ), - ::toTriple - ) - .collect { (isAsleep, lastStartedStep, isAodAvailable) -> - if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) { - startTransitionTo( - if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING - ) - } - } - } + fun dismissToGone() { + scope.launch { startTransitionTo(KeyguardState.GONE) } + } + + private fun listenForOccludedToAsleep() { + scope.launch { listenForSleepTransition(from = KeyguardState.OCCLUDED) } } private fun listenForOccludedToAlternateBouncer() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index c5a28463bf7e..391dccc7f444 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -31,7 +31,6 @@ import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.Utils.Companion.sample -import com.android.systemui.util.kotlin.Utils.Companion.toQuad import com.android.systemui.util.kotlin.Utils.Companion.toTriple import com.android.systemui.util.kotlin.sample import com.android.wm.shell.animation.Interpolators @@ -42,6 +41,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch @@ -59,18 +59,21 @@ constructor( private val flags: FeatureFlags, private val keyguardSecurityModel: KeyguardSecurityModel, private val selectedUserInteractor: SelectedUserInteractor, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : TransitionInteractor( fromState = KeyguardState.PRIMARY_BOUNCER, transitionInteractor = transitionInteractor, mainDispatcher = mainDispatcher, bgDispatcher = bgDispatcher, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) { override fun start() { listenForPrimaryBouncerToGone() - listenForPrimaryBouncerToAodOrDozing() + listenForPrimaryBouncerToAsleep() listenForPrimaryBouncerToLockscreenHubOrOccluded() listenForPrimaryBouncerToDreamingLockscreenHosted() listenForTransitionToCamera(scope, keyguardInteractor) @@ -128,74 +131,88 @@ constructor( } private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() { - scope.launch { - keyguardInteractor.primaryBouncerShowing - .sample( - powerInteractor.isAwake, - startedKeyguardTransitionStep, - keyguardInteractor.isKeyguardOccluded, - keyguardInteractor.isDreaming, - keyguardInteractor.isActiveDreamLockscreenHosted, - communalInteractor.isIdleOnCommunal, - ) - .collect { - ( - isBouncerShowing, - isAwake, - lastStartedTransitionStep, - occluded, - isDreaming, - isActiveDreamLockscreenHosted, - isIdleOnCommunal) -> - if ( - !isBouncerShowing && - lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER && - isAwake && - !isActiveDreamLockscreenHosted - ) { - val toState = - if (occluded && !isDreaming) { - KeyguardState.OCCLUDED - } else if (isIdleOnCommunal) { - KeyguardState.GLANCEABLE_HUB - } else if (isDreaming) { - KeyguardState.DREAMING - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(toState) + if (KeyguardWmStateRefactor.isEnabled) { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample( + startedKeyguardTransitionStep, + powerInteractor.isAwake, + keyguardInteractor.isActiveDreamLockscreenHosted, + communalInteractor.isIdleOnCommunal + ) + .filter { (_, startedStep, _, _) -> + startedStep.to == KeyguardState.PRIMARY_BOUNCER } - } - } - } - - private fun listenForPrimaryBouncerToAodOrDozing() { - scope.launch { - keyguardInteractor.primaryBouncerShowing - .sample( - combine( - powerInteractor.isAsleep, + .collect { + ( + isBouncerShowing, + _, + isAwake, + isActiveDreamLockscreenHosted, + isIdleOnCommunal) -> + if ( + !maybeStartTransitionToOccludedOrInsecureCamera() && + !isBouncerShowing && + isAwake && + !isActiveDreamLockscreenHosted + ) { + val toState = + if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(toState) + } + } + } + } else { + scope.launch { + keyguardInteractor.primaryBouncerShowing + .sample( + powerInteractor.isAwake, startedKeyguardTransitionStep, - keyguardInteractor.isAodAvailable, - ::Triple - ), - ::toQuad - ) - .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable) - -> - if ( - !isBouncerShowing && - lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER && - isAsleep - ) { - startTransitionTo( - if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING - ) + keyguardInteractor.isKeyguardOccluded, + keyguardInteractor.isDreaming, + keyguardInteractor.isActiveDreamLockscreenHosted, + communalInteractor.isIdleOnCommunal, + ) + .collect { + ( + isBouncerShowing, + isAwake, + lastStartedTransitionStep, + occluded, + isDreaming, + isActiveDreamLockscreenHosted, + isIdleOnCommunal) -> + if ( + !isBouncerShowing && + lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER && + isAwake && + !isActiveDreamLockscreenHosted + ) { + val toState = + if (occluded && !isDreaming) { + KeyguardState.OCCLUDED + } else if (isIdleOnCommunal) { + KeyguardState.GLANCEABLE_HUB + } else if (isDreaming) { + KeyguardState.DREAMING + } else { + KeyguardState.LOCKSCREEN + } + startTransitionTo(toState) + } } - } + } } } + private fun listenForPrimaryBouncerToAsleep() { + scope.launch { listenForSleepTransition(from = KeyguardState.PRIMARY_BOUNCER) } + } + private fun listenForPrimaryBouncerToDreamingLockscreenHosted() { scope.launch { keyguardInteractor.primaryBouncerShowing diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 5410b10a4b93..f321bd7e13a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -163,15 +163,18 @@ constructor( .distinctUntilChanged() /** Whether the keyguard is showing or not. */ + @Deprecated("Use KeyguardTransitionInteractor + KeyguardState") val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing /** Whether the keyguard is dismissible or not. */ val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible /** Whether the keyguard is occluded (covered by an activity). */ + @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED") val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded /** Whether the keyguard is going away. */ + @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE") val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway /** Keyguard can be clipped at the top as the shade is dragged */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt new file mode 100644 index 000000000000..9aa2202b4100 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt @@ -0,0 +1,122 @@ +/* + * 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.keyguard.domain.interactor + +import android.app.ActivityManager.RunningTaskInfo +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.stateIn + +/** + * Logic related to keyguard occlusion. The keyguard is occluded when an activity with + * FLAG_SHOW_WHEN_LOCKED is on top of the activity task stack, with that activity displaying on top + * of ("occluding") the lockscreen UI. Common examples of this are Google Maps Navigation and the + * secure camera. + * + * This should usually be used only by keyguard internal classes. Most System UI use cases should + * use [KeyguardTransitionInteractor] to see if we're in [KeyguardState.OCCLUDED] instead. + */ +@SysUISingleton +class KeyguardOcclusionInteractor +@Inject +constructor( + @Application scope: CoroutineScope, + val repository: KeyguardOcclusionRepository, + val powerInteractor: PowerInteractor, + val transitionInteractor: KeyguardTransitionInteractor, + val keyguardInteractor: KeyguardInteractor, +) { + val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow() + + /** + * Whether a SHOW_WHEN_LOCKED activity is on top of the task stack. This does not necessarily + * mean we're OCCLUDED, as we could be GONE (unlocked), with an activity that can (but is not + * currently) displaying over the lockscreen. + * + * Transition interactors use this to determine when we should transition to the OCCLUDED state. + * + * Outside of the transition/occlusion interactors, you almost certainly don't want to use this. + * Instead, use KeyguardTransitionInteractor to figure out if we're in KeyguardState.OCCLUDED. + */ + val isShowWhenLockedActivityOnTop = showWhenLockedActivityInfo.map { it.isOnTop } + + /** Whether we should start a transition due to the power button launch gesture. */ + fun shouldTransitionFromPowerButtonGesture(): Boolean { + // powerButtonLaunchGestureTriggered remains true while we're awake from a power button + // gesture. Check that we were asleep or transitioning to asleep before starting a + // transition, to ensure we don't transition while moving between, for example, + // *_BOUNCER -> LOCKSCREEN. + return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered && + KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState()) + } + + /** + * Whether the SHOW_WHEN_LOCKED activity was launched from the double tap power button gesture. + * This remains true while the activity is running and emits false once it is killed. + */ + val showWhenLockedActivityLaunchedFromPowerGesture = + merge( + // Emit true when the power launch gesture is triggered, since this means a + // SHOW_WHEN_LOCKED activity will be launched from the gesture (unless we're + // currently + // GONE, in which case we're going back to GONE and launching the insecure camera). + powerInteractor.detailedWakefulness + .sample(transitionInteractor.currentKeyguardState, ::Pair) + .map { (wakefulness, currentKeyguardState) -> + wakefulness.powerButtonLaunchGestureTriggered && + currentKeyguardState != KeyguardState.GONE + }, + // Emit false once that activity goes away. + isShowWhenLockedActivityOnTop.filter { !it }.map { false } + ) + .stateIn(scope, SharingStarted.Eagerly, false) + + /** + * Whether launching an occluding activity will automatically dismiss keyguard. This happens if + * the keyguard is dismissable. + */ + val occludingActivityWillDismissKeyguard = + keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false) + + /** + * Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer + * on top). + * + * This signal arrives from WM when a SHOW_WHEN_LOCKED activity is started or killed - it is + * never set directly by System UI. While we might be the reason the activity was started + * (launching the camera from the power button gesture), we ultimately only receive this signal + * once that activity starts. It's up to us to start the appropriate keyguard transitions, + * because that activity is going to be visible (or not) regardless. + */ + fun setWmNotifiedShowWhenLockedActivityOnTop( + showWhenLockedActivityOnTop: Boolean, + taskInfo: RunningTaskInfo? = null + ) { + repository.setShowWhenLockedActivityInfo(showWhenLockedActivityOnTop, taskInfo) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index d81f1f14158c..c28e49db9d37 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -41,6 +41,7 @@ constructor( private val powerInteractor: PowerInteractor, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, private val shadeInteractor: ShadeInteractor, + private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) { fun start() { @@ -91,6 +92,12 @@ constructor( } scope.launch { + keyguardInteractor.isKeyguardDismissible.collect { + logger.log(TAG, VERBOSE, "isKeyguardDismissable", it) + } + } + + scope.launch { keyguardInteractor.isAbleToDream.collect { logger.log(TAG, VERBOSE, "isAbleToDream", it) } @@ -125,5 +132,11 @@ constructor( logger.log(TAG, VERBOSE, "onCameraLaunchDetected", it) } } + + scope.launch { + keyguardOcclusionInteractor.showWhenLockedActivityInfo.collect { + logger.log(TAG, VERBOSE, "showWhenLockedActivityInfo", it) + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index 37b331cd8455..00902b419a97 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER @@ -42,6 +43,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map @@ -56,11 +58,15 @@ class KeyguardTransitionInteractor @Inject constructor( @Application val scope: CoroutineScope, + private val keyguardRepository: KeyguardRepository, private val repository: KeyguardTransitionRepository, private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>, private val fromPrimaryBouncerTransitionInteractor: dagger.Lazy<FromPrimaryBouncerTransitionInteractor>, private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>, + private val fromAlternateBouncerTransitionInteractor: + dagger.Lazy<FromAlternateBouncerTransitionInteractor>, + private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>, ) { private val TAG = this::class.simpleName @@ -207,6 +213,12 @@ constructor( .map { step -> step.to } .shareIn(scope, SharingStarted.Eagerly, replay = 1) + /** Which keyguard state to use when the device goes to sleep. */ + val asleepKeyguardState: StateFlow<KeyguardState> = + keyguardRepository.isAodAvailable + .map { aodAvailable -> if (aodAvailable) AOD else DOZING } + .stateIn(scope, SharingStarted.Eagerly, DOZING) + /** * A pair of the most recent STARTED step, and the transition step immediately preceding it. The * transition framework enforces that the previous step is either a CANCELED or FINISHED step, @@ -368,7 +380,10 @@ constructor( when (val startedState = startedKeyguardState.replayCache.last()) { LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard() PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer() + ALTERNATE_BOUNCER -> + fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer() AOD -> fromAodTransitionInteractor.get().dismissAod() + DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing() else -> Log.e( "KeyguardTransitionInteractor", @@ -421,12 +436,17 @@ constructor( fromStatePredicate: (KeyguardState) -> Boolean, toStatePredicate: (KeyguardState) -> Boolean, ): Flow<Boolean> { + return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) } + } + + fun isInTransitionWhere( + fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean + ): Flow<Boolean> { return repository.transitions .filter { it.transitionState != TransitionState.CANCELED } .mapLatest { it.transitionState != TransitionState.FINISHED && - fromStatePredicate(it.from) && - toStatePredicate(it.to) + fromToStatePredicate(it.from, it.to) } .distinctUntilChanged() } @@ -447,4 +467,16 @@ constructor( */ fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) = stateMatcher(finishedKeyguardState.replayCache.last()) + + fun getCurrentState(): KeyguardState { + return currentKeyguardState.replayCache.last() + } + + fun getStartedState(): KeyguardState { + return startedKeyguardState.replayCache.last() + } + + fun getFinishedState(): KeyguardState { + return finishedKeyguardState.replayCache.last() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt index 4d731eccd9bb..8905c9e752de 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -43,7 +43,6 @@ constructor( private val scrimLogger: ScrimLogger, private val powerInteractor: PowerInteractor, ) { - init { listenForStartedKeyguardTransitionStep() } @@ -52,9 +51,7 @@ constructor( scope.launch { transitionInteractor.startedKeyguardTransitionStep.collect { scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it) - lightRevealScrimRepository.startRevealAmountAnimator( - willBeRevealedInState(it.to), - ) + lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to)) } } } @@ -89,25 +86,25 @@ constructor( companion object { - /** - * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given - * state after the transition is complete. If false, scrim will be fully hidden. - */ - private fun willBeRevealedInState(state: KeyguardState): Boolean { - return when (state) { - KeyguardState.OFF -> false - KeyguardState.DOZING -> false - KeyguardState.AOD -> false - KeyguardState.DREAMING -> true - KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true - KeyguardState.GLANCEABLE_HUB -> true - KeyguardState.ALTERNATE_BOUNCER -> true - KeyguardState.PRIMARY_BOUNCER -> true - KeyguardState.LOCKSCREEN -> true - KeyguardState.GONE -> true - KeyguardState.OCCLUDED -> true + /** + * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given + * state after the transition is complete. If false, scrim will be fully hidden. + */ + private fun willBeRevealedInState(state: KeyguardState): Boolean { + return when (state) { + KeyguardState.OFF -> false + KeyguardState.DOZING -> false + KeyguardState.AOD -> false + KeyguardState.DREAMING -> true + KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true + KeyguardState.GLANCEABLE_HUB -> true + KeyguardState.ALTERNATE_BOUNCER -> true + KeyguardState.PRIMARY_BOUNCER -> true + KeyguardState.LOCKSCREEN -> true + KeyguardState.GONE -> true + KeyguardState.OCCLUDED -> true + } } - } val TAG = LightRevealScrimInteractor::class.simpleName!! } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index 3ccbdba6d58e..375df3e8f5f5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -18,15 +18,20 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.Log +import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.util.kotlin.sample import java.util.UUID import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -46,6 +51,8 @@ sealed class TransitionInteractor( val transitionInteractor: KeyguardTransitionInteractor, val mainDispatcher: CoroutineDispatcher, val bgDispatcher: CoroutineDispatcher, + val powerInteractor: PowerInteractor, + val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) { val name = this::class.simpleName ?: "UnknownTransitionInteractor" abstract val transitionRepository: KeyguardTransitionRepository @@ -65,7 +72,11 @@ sealed class TransitionInteractor( suspend fun startTransitionTo( toState: KeyguardState, animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), - modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE + modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE, + // Even more information about why the owner started this transition, if this is a dangerous + // transition (*cough* occlusion) where you'd be sad to not have all the info you can get in + // a bugreport. + ownerReason: String = "", ): UUID? { if ( fromState != transitionInteractor.startedKeyguardState.replayCache.last() && @@ -85,7 +96,7 @@ sealed class TransitionInteractor( return withContext(mainDispatcher) { transitionRepository.startTransition( TransitionInfo( - name, + name + if (ownerReason.isNotBlank()) "($ownerReason)" else "", fromState, toState, animator, @@ -95,24 +106,107 @@ sealed class TransitionInteractor( } } + /** + * Check whether we need to transition to [KeyguardState.OCCLUDED], based on the presence of a + * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch + * gesture cases. If so, start the transition. + * + * Returns true if a transition was started, false otherwise. + */ + suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean { + if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) { + if (transitionInteractor.getCurrentState() == KeyguardState.GONE) { + // If the current state is GONE when the launch gesture is triggered, it means we + // were in transition from GONE -> DOZING/AOD due to the first power button tap. The + // second tap indicates that the user's intent was actually to launch the unlocked + // (insecure) camera, so we should transition back to GONE. + startTransitionTo( + KeyguardState.GONE, + ownerReason = "Power button gesture while GONE" + ) + } else if (keyguardOcclusionInteractor.occludingActivityWillDismissKeyguard.value) { + // The double tap gesture occurred while not GONE (AOD/LOCKSCREEN/etc.), but the + // keyguard is dismissable. The activity launch will dismiss the keyguard, so we + // should transition to GONE. + startTransitionTo( + KeyguardState.GONE, + ownerReason = "Power button gesture on dismissable keyguard" + ) + } else { + // Otherwise, the double tap gesture occurred while not GONE and not dismissable, + // which means we will launch the secure camera, which OCCLUDES the keyguard. + startTransitionTo( + KeyguardState.OCCLUDED, + ownerReason = "Power button gesture on lockscreen" + ) + } + + return true + } else if (keyguardOcclusionInteractor.showWhenLockedActivityInfo.value.isOnTop) { + // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so + // it's visible. + // TODO(b/307976454) - Centralize transition to DREAMING here. + startTransitionTo( + KeyguardState.OCCLUDED, + ownerReason = "SHOW_WHEN_LOCKED activity on top" + ) + + return true + } else { + // No transition needed, let the interactor figure out where to go. + return false + } + } + + /** + * Transition to the appropriate state when the device goes to sleep while in [from]. + * + * We could also just use [fromState], but it's more readable in the From*TransitionInteractor + * if you're explicitly declaring which state you're listening from. If you passed in the wrong + * state, [startTransitionTo] would complain anyway. + */ + suspend fun listenForSleepTransition( + from: KeyguardState, + modeOnCanceledFromStartedStep: (TransitionStep) -> TransitionModeOnCanceled = { + TransitionModeOnCanceled.LAST_VALUE + } + ) { + powerInteractor.isAsleep + .filter { isAsleep -> isAsleep } + .sample(startedKeyguardTransitionStep) + .filter { startedStep -> startedStep.to == from } + .map(modeOnCanceledFromStartedStep) + .collect { modeOnCanceled -> + startTransitionTo( + toState = transitionInteractor.asleepKeyguardState.value, + modeOnCanceled = modeOnCanceled, + ownerReason = "Sleep transition triggered" + ) + } + } + /** This signal may come in before the occlusion signal, and can provide a custom transition */ fun listenForTransitionToCamera( scope: CoroutineScope, keyguardInteractor: KeyguardInteractor, ) { - scope.launch { - keyguardInteractor.onCameraLaunchDetected - .sample(transitionInteractor.finishedKeyguardState) - .collect { finishedKeyguardState -> - // Other keyguard state transitions may trigger on the first power button push, - // so use the last finishedKeyguardState to determine the overriding FROM state - if (finishedKeyguardState == fromState) { - startTransitionTo( - toState = KeyguardState.OCCLUDED, - modeOnCanceled = TransitionModeOnCanceled.RESET, - ) + if (!KeyguardWmStateRefactor.isEnabled) { + scope.launch { + keyguardInteractor.onCameraLaunchDetected + .sample(transitionInteractor.finishedKeyguardState) + .collect { finishedKeyguardState -> + // Other keyguard state transitions may trigger on the first power button + // push, + // so use the last finishedKeyguardState to determine the overriding FROM + // state + if (finishedKeyguardState == fromState) { + startTransitionTo( + toState = KeyguardState.OCCLUDED, + modeOnCanceled = TransitionModeOnCanceled.RESET, + ) + } } - } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt index a9eec18319c3..7e39a884a69e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt @@ -43,6 +43,10 @@ constructor( to = KeyguardState.GONE, ) + /** + * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake + * and unlock, and also during insecure camera launch (which is GONE -> AOD (canceled) -> GONE). + */ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { var startAlpha = 1f return transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt index 105a7ed52311..445575f7e55d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -16,12 +16,15 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow /** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */ @SysUISingleton @@ -37,5 +40,23 @@ constructor( to = KeyguardState.OCCLUDED, ) + /** + * Fade out the lockscreen during a transition to OCCLUDED. + * + * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top + * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the + * first tap transitions to AOD, the second cancels that transition and starts AOD -> OCCLUDED. + */ + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var currentAlpha = 0f + return transitionAnimation.sharedFlow( + duration = 250.milliseconds, + startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements. + onStart = { currentAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(currentAlpha, 0f, it) }, + onCancel = { 0f }, + ) + } + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt new file mode 100644 index 000000000000..c0b11959cbd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.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.keyguard.ui.viewmodel + +import android.util.MathUtils +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlinx.coroutines.flow.Flow + +/** + * Breaks down DOZING->OCCLUDED transition into discrete steps for corresponding views to consume. + */ +@SysUISingleton +class DozingToOccludedTransitionViewModel +@Inject +constructor( + animationFlow: KeyguardTransitionAnimationFlow, +) : DeviceEntryIconTransition { + private val transitionAnimation = + animationFlow.setup( + duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, + from = KeyguardState.DOZING, + to = KeyguardState.OCCLUDED, + ) + + /** + * Fade out the lockscreen during a transition to OCCLUDED. + * + * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top + * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the + * first tap transitions to DOZING, the second cancels that transition and starts DOZING -> + * OCCLUDED. + */ + fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { + var currentAlpha = 0f + return transitionAnimation.sharedFlow( + duration = 250.milliseconds, + startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements. + onStart = { currentAlpha = viewState.alpha() }, + onStep = { MathUtils.lerp(currentAlpha, 0f, it) }, + onCancel = { 0f }, + ) + } + + override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 1760b927a6cd..f848717c2170 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -73,8 +73,10 @@ constructor( AlternateBouncerToGoneTransitionViewModel, private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, + private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, @@ -170,8 +172,10 @@ constructor( alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, aodToGoneTransitionViewModel.lockscreenAlpha(viewState), aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState), + aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), dozingToGoneTransitionViewModel.lockscreenAlpha(viewState), dozingToLockscreenTransitionViewModel.lockscreenAlpha, + dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), dreamingToLockscreenTransitionViewModel.lockscreenAlpha, glanceableHubToLockscreenTransitionViewModel.keyguardAlpha, goneToAodTransitionViewModel.enterFromTopAnimationAlpha, 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 840b309aee39..26c63f31fa46 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.MediaDataUtils; 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { MediaViewController mediaViewController, SeekBarViewModel seekBarViewModel, Lazy<MediaDataManager> lazyMediaDataManager, - MediaOutputDialogFactory mediaOutputDialogFactory, + MediaOutputDialogManager mediaOutputDialogManager, MediaCarouselController mediaCarouselController, FalsingManager falsingManager, SystemClock systemClock, @@ -324,7 +324,7 @@ public class MediaControlPanel { mSeekBarViewModel = seekBarViewModel; mMediaViewController = mediaViewController; mMediaDataManagerLazy = lazyMediaDataManager; - mMediaOutputDialogFactory = mediaOutputDialogFactory; + mMediaOutputDialogManager = mediaOutputDialogManager; mMediaCarouselController = mediaCarouselController; mFalsingManager = falsingManager; mSystemClock = systemClock; @@ -737,7 +737,7 @@ public class MediaControlPanel { mPackageName, mMediaViewHolder.getSeamlessButton()); } else { mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId); - mMediaOutputDialogFactory.create(mPackageName, true, + mMediaOutputDialogManager.createAndShow(mPackageName, true, mMediaViewHolder.getSeamlessButton()); } } else { @@ -761,7 +761,7 @@ public class MediaControlPanel { } } } 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 5d113a97c42f..452cb7e29f20 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 @@ constructor( ) { /** 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 b6e39372e34c..000000000000 --- 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 000000000000..54d175c6a110 --- /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 b3b7bceea0e0..adee7f2c86be 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.View; 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.NotificationEntry; 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.CopyOnWriteArrayList; import java.util.concurrent.Executor; import java.util.stream.Collectors; -import javax.inject.Inject; - /** * Controller for media output dialog */ @@ -170,10 +173,13 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, 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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, 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 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, 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 index 02be0c1a6c2d..e7816a40bb5e 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 @@ constructor( } /** 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 @@ constructor( } /** 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 @@ constructor( ) } - 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 @@ constructor( ) } - private fun create( + private fun createAndShow( packageName: String?, aboveStatusBar: Boolean, dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?, @@ -109,23 +92,9 @@ constructor( // 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 @@ constructor( 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 38d31ed92141..774792f5030d 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 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) * 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 @@ class MediaOutputDialogReceiver @Inject constructor( 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 @@ class MediaOutputDialogReceiver @Inject constructor( 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 @@ class MediaOutputDialogReceiver @Inject constructor( 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 b5b1f0ffe23d..6e7e0f241dd8 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 @@ public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue. 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 @@ public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue. @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 c0e688f0f82f..c7aae3ca788e 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.Service 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 @@ import dagger.multibindings.IntoMap 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 2d63dbcb82fa..7d3f2a532aae 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 @@ import dagger.multibindings.IntoSet interface NoteTaskQuickAffordanceModule { @[Binds IntoSet] - fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig + fun bindNoteTaskQuickAffordance( + impl: NoteTaskQuickAffordanceConfig + ): KeyguardQuickAffordanceConfig } diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt index e1d1ec207938..5432793d8117 100644 --- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt @@ -20,20 +20,24 @@ data class WakefulnessModel( * the [KeyguardTransitionInteractor]. */ internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE, - val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER, val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER, - /** + /** * Whether the power button double tap gesture was triggered since the last time went to sleep. * If this value is true while [isAsleep]=true, it means we'll be waking back up shortly. If it * is true while [isAwake]=true, it means we're awake because of the button gesture. * - * This value remains true until the next time [isAsleep]=true. + * This value remains true until the next time [isAsleep]=true, since it would otherwise be + * totally arbitrary at what point we decide the gesture was no longer "triggered". Since a + * sleep event is guaranteed to arrive prior to the next power button gesture (as the first tap + * of the double tap always begins a sleep transition), this will always be reset to false prior + * to a subsequent power gesture. */ val powerButtonLaunchGestureTriggered: Boolean = false, ) { - fun isAwake() = internalWakefulnessState == WakefulnessState.AWAKE || + fun isAwake() = + internalWakefulnessState == WakefulnessState.AWAKE || internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE fun isAsleep() = !isAwake() @@ -48,11 +52,10 @@ data class WakefulnessModel( fun isAsleepFrom(wakeSleepReason: WakeSleepReason) = isAsleep() && lastSleepReason == wakeSleepReason - fun isAwakeOrAsleepFrom(reason: WakeSleepReason) = - isAsleepFrom(reason) || isAwakeFrom(reason) + fun isAwakeOrAsleepFrom(reason: WakeSleepReason) = isAsleepFrom(reason) || isAwakeFrom(reason) fun isAwakeFromTapOrGesture(): Boolean { - return isAwake() && (lastWakeReason == WakeSleepReason.TAP || - lastWakeReason == WakeSleepReason.GESTURE) + return isAwake() && + (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE) } -}
\ No newline at end of file +} 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 000000000000..22bbbbb62019 --- /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 000000000000..1e4eb38008bd --- /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 000000000000..dbec50dacf0e --- /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 000000000000..0c08fbacfcfc --- /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 000000000000..caae4d26634d --- /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 000000000000..fdc596b3030c --- /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 000000000000..2620cd555d8e --- /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 000000000000..ece904611782 --- /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 d0585d3782c2..20bd7c65c0ec 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 @@ public class ScrimView extends View { 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 @@ public class ScrimView extends View { mDrawable.invalidateSelf(); } - if (mChangeRunnable != null) { - mChangeRunnableExecutor.execute(mChangeRunnable); - } } public int getTint() { @@ -300,9 +295,6 @@ public class ScrimView extends View { mViewAlpha = alpha; mDrawable.setAlpha((int) (255 * alpha)); - if (mChangeRunnable != null) { - mChangeRunnableExecutor.execute(mChangeRunnable); - } } }); } @@ -311,14 +303,6 @@ public class ScrimView extends View { 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/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index ef4e5308e18e..a12b9709a063 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -84,6 +84,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -457,14 +458,43 @@ public final class KeyboardShortcutListSearch { List<KeyboardShortcutMultiMappingGroup> keyboardShortcutMultiMappingGroups = new ArrayList<>(); for (KeyboardShortcutGroup group : keyboardShortcutGroups) { - CharSequence categoryTitle = group.getLabel(); - List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>(); + KeyboardShortcutMultiMappingGroup mappedGroup = + new KeyboardShortcutMultiMappingGroup( + group.getLabel(), + new ArrayList<>()); + Map<String, List<ShortcutMultiMappingInfo>> shortcutMap = new LinkedHashMap<>(); for (KeyboardShortcutInfo info : group.getItems()) { - shortcutMultiMappingInfos.add(new ShortcutMultiMappingInfo(info)); + String label = info.getLabel().toString(); + Icon icon = info.getIcon(); + if (shortcutMap.containsKey(label)) { + List<ShortcutMultiMappingInfo> shortcuts = shortcutMap.get(label); + boolean foundSameIcon = false; + for (ShortcutMultiMappingInfo shortcut : shortcuts) { + Icon shortcutIcon = shortcut.getIcon(); + if ((shortcutIcon != null + && icon != null + && shortcutIcon.sameAs(icon)) + || (shortcutIcon == null && icon == null)) { + foundSameIcon = true; + shortcut.addShortcutKeyGroup(new ShortcutKeyGroup(info, null)); + break; + } + } + if (!foundSameIcon) { + shortcuts.add(new ShortcutMultiMappingInfo(info)); + } + } else { + List<ShortcutMultiMappingInfo> shortcuts = new ArrayList<>(); + shortcuts.add(new ShortcutMultiMappingInfo(info)); + shortcutMap.put(label, shortcuts); + } } - keyboardShortcutMultiMappingGroups.add( - new KeyboardShortcutMultiMappingGroup( - categoryTitle, shortcutMultiMappingInfos)); + for (List<ShortcutMultiMappingInfo> shortcutInfos : shortcutMap.values()) { + for (ShortcutMultiMappingInfo shortcutInfo : shortcutInfos) { + mappedGroup.addItem(shortcutInfo); + } + } + keyboardShortcutMultiMappingGroups.add(mappedGroup); } return keyboardShortcutMultiMappingGroups; } @@ -1377,7 +1407,9 @@ public final class KeyboardShortcutListSearch { ShortcutMultiMappingInfo(KeyboardShortcutInfo info) { mLabel = info.getLabel(); mIcon = info.getIcon(); - mShortcutKeyGroups = Arrays.asList(new ShortcutKeyGroup(info, null)); + mShortcutKeyGroups = new ArrayList<>( + Arrays.asList(new ShortcutKeyGroup(info, null)) + ); } CharSequence getLabel() { @@ -1388,6 +1420,10 @@ public final class KeyboardShortcutListSearch { return mIcon; } + void addShortcutKeyGroup(ShortcutKeyGroup group) { + mShortcutKeyGroups.add(group); + } + List<ShortcutKeyGroup> getShortcutKeyGroups() { return mShortcutKeyGroups; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 4ee83497b368..81f644f8acbb 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 @@ constructor( /** 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 @@ constructor( * * @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/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index 642eaccc3c99..c4d9cbfcd55c 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.AirplaneModeMapper 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 @@ interface ConnectivityModule { 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 @@ interface ConnectivityModule { 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/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt new file mode 100644 index 000000000000..1bebbfd34ff4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt @@ -0,0 +1,108 @@ +/* + * 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.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.util.kotlin.sample +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * Whether to set the status bar keyguard view occluded or not, and whether to animate that change. + */ +data class OccludedState( + val occluded: Boolean, + val animate: Boolean = false, +) + +/** Handles logic around calls to [StatusBarKeyguardViewManager] in legacy code. */ +@Deprecated("Will be removed once all of SBKVM's responsibilies are refactored.") +@SysUISingleton +class StatusBarKeyguardViewManagerInteractor +@Inject +constructor( + keyguardTransitionInteractor: KeyguardTransitionInteractor, + keyguardOcclusionInteractor: KeyguardOcclusionInteractor, + powerInteractor: PowerInteractor, +) { + /** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */ + private val occlusionStateFromStartedStep: Flow<OccludedState> = + keyguardTransitionInteractor.startedKeyguardTransitionStep + .sample(powerInteractor.detailedWakefulness, ::Pair) + .map { (startedStep, wakefulness) -> + val transitioningFromPowerButtonGesture = + KeyguardState.deviceIsAsleepInState(startedStep.from) && + startedStep.to == KeyguardState.OCCLUDED && + wakefulness.powerButtonLaunchGestureTriggered + + if ( + startedStep.to == KeyguardState.OCCLUDED && !transitioningFromPowerButtonGesture + ) { + // Set occluded upon STARTED, *unless* we're transitioning from the power + // button, in which case we're going to play an animation over the lockscreen UI + // and need to remain unoccluded until the transition finishes. + return@map OccludedState(occluded = true, animate = false) + } + + if ( + startedStep.from == KeyguardState.OCCLUDED && + startedStep.to == KeyguardState.LOCKSCREEN + ) { + // Set unoccluded immediately ONLY if we're transitioning back to the lockscreen + // since we need the views visible to animate them back down. This is a special + // case due to the way unocclusion remote animations are run. We can remove this + // once the unocclude animation uses the return animation framework. + return@map OccludedState(occluded = false, animate = false) + } + + // Otherwise, wait for the transition to FINISH to decide. + return@map null + } + .filterNotNull() + + /** Occlusion state to apply whenever a keyguard transition is FINISHED. */ + private val occlusionStateFromFinishedStep = + keyguardTransitionInteractor.finishedKeyguardTransitionStep + .sample(keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, ::Pair) + .map { (finishedStep, showWhenLockedOnTop) -> + // If we're FINISHED in OCCLUDED, we want to render as occluded. We also need to + // remain occluded if a SHOW_WHEN_LOCKED activity is on the top of the task stack, + // and we're in any state other than GONE. This is necessary, for example, when we + // transition from OCCLUDED to a bouncer state. Otherwise, we should not be + // occluded. + val occluded = + finishedStep.to == KeyguardState.OCCLUDED || + (showWhenLockedOnTop && finishedStep.to != KeyguardState.GONE) + OccludedState(occluded = occluded, animate = false) + } + + /** Occlusion state to apply to SKBVM's setOccluded call. */ + val keyguardViewOcclusionState = + merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep) + .distinctUntilChangedBy { + // Don't switch 'animate' values mid-transition. + it.occluded + } +} 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 ea9df9af8cff..05e8717d0005 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 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro 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 dab89c5235b0..b90aa107d617 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 @@ package com.android.systemui.statusbar.notification.row 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 @@ constructor( 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 @@ constructor( 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 c17ee3991713..f835cca1a60c 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.View; 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder ExpandableNotificationRow row, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, NotificationContentInflaterLogger logger) { - 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); - } + 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_HEADS_UP) != 0) { - logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view"); - result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight); - } + if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) { + logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view"); + result.newExpandedView = createExpandedView(builder, isLowPriority); + } - if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) { - logger.logAsyncTaskProgress(entryForLogging, "creating public remote view"); - result.newPublicView = builder.makePublicContentView(isLowPriority); - } + if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) { + logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view"); + result.newHeadsUpView = builder.createHeadsUpContentView( + usesIncreasedHeadsUpHeight); + } - 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_PUBLIC) != 0) { + logger.logAsyncTaskProgress(entryForLogging, "creating public remote view"); + result.newPublicView = builder.makePublicContentView(isLowPriority); } - 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 (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; + 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder cancellationSignal.setOnCancelListener( () -> { logger.logAsyncTaskProgress(entry, "apply cancelled"); + Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row)); runningInflations.values().forEach(CancellationSignal::cancel); }); @@ -769,17 +779,17 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder } @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()); - - 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); + return TraceUtils.trace("NotificationContentInflater.AsyncInflationTask#doInBackground", + () -> { + try { + return doInBackgroundInternal(); + } catch (Exception e) { + mError = e; + mLogger.logAsyncTaskException(mEntry, "inflating", e); + return null; + } + }); + } - 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 - ); - } + 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()); + + 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); + 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; - } + 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder } } + @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 class NotificationContentInflater implements NotificationRowContentBinder 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 000000000000..0594c1227a44 --- /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 000000000000..133d3e730eff --- /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 000000000000..7694e58296ff --- /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 000000000000..cea6a2b197be --- /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 c90aceef6934..ab2f664fee88 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 @@ public class AmbientState implements Dumpable { */ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private int mScrollY; - private boolean mDimmed; private float mOverScrollTopAmount; private float mOverScrollBottomAmount; private boolean mDozing; @@ -344,14 +343,6 @@ public class AmbientState implements Dumpable { 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 @@ public class AmbientState implements Dumpable { 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 @@ public class AmbientState implements Dumpable { 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 5343cbf4f9fa..03a108287087 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 @@ public class AnimationFilter { boolean animateZ; boolean animateHeight; boolean animateTopInset; - boolean animateDimmed; boolean animateHideSensitive; boolean hasDelays; boolean hasGoToFullShadeEvent; @@ -83,11 +82,6 @@ public class AnimationFilter { return this; } - public AnimationFilter animateDimmed() { - animateDimmed = true; - return this; - } - public AnimationFilter animateHideSensitive() { animateHideSensitive = true; return this; @@ -128,7 +122,6 @@ public class AnimationFilter { 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 @@ public class AnimationFilter { 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 d0c5c82b50ee..d1e5ab04a630 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 @@ public class ExpandableViewState extends ViewState { | ExpandableViewState.LOCATION_MAIN_AREA; public int height; - public boolean dimmed; public boolean hideSensitive; public boolean belowSpeedBump; public boolean inShelf; @@ -128,7 +127,6 @@ public class ExpandableViewState extends ViewState { 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 @@ public class ExpandableViewState extends ViewState { 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 @@ public class ExpandableViewState extends ViewState { 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 fa973001cec7..28f874da0c74 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 @@ public class NotificationChildrenContainer extends ViewGroup } 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 27db84f6715e..b47b18d4512d 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 com.android.systemui.util.DumpUtilsKt.visibilityString; 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 android.widget.ScrollView; 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } }; 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mSplitShadeStateController = splitShadeStateController; updateSplitNotificationShade(); } - private FeatureFlags mFeatureFlags; + private final FeatureFlags mFeatureFlags; private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener = new ExpandableView.OnHeightChangedListener() { @@ -667,8 +629,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private void onPreDrawDuringAnimation() { mShelf.updateAppearance(); - if (!mNeedsAnimation && !mChildrenUpdateRequested) { - updateBackground(); - } } private void updateScrollStateForAddedChildren() { @@ -2565,125 +2363,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable setAnimationRunning(true); mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay); mAnimationEvents.clear(); - updateBackground(); updateViewShadows(); } else { applyCurrentState(); @@ -3359,7 +3031,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable generatePositionChangeEvents(); generateTopPaddingEvent(); generateActivateEvent(); - generateDimmedEvent(); generateHideSensitiveEvent(); generateGoToFullShadeEvent(); generateViewResizeEvent(); @@ -3577,14 +3248,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } break; - case MotionEvent.ACTION_UP: + case ACTION_UP: if (mIsBeingDragged) { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); @@ -3854,7 +3516,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable runAnimationFinishedRunnables(); setAnimationRunning(false); - updateBackground(); updateViewShadows(); } @@ -4714,7 +4333,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable invalidateOutline(); } updateAlgorithmHeightAndPadding(); - updateBackgroundDimming(); requestChildrenUpdate(); updateOwnTranslationZ(); } @@ -4747,21 +4365,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } } - 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 class NotificationStackScrollLayout extends ViewGroup implements Dumpable */ public void setDozeAmount(float dozeAmount) { mAmbientState.setDozeAmount(dozeAmount); - updateContinuousBackgroundDrawing(); updateStackPosition(); requestChildrenUpdate(); } @@ -5711,7 +5310,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable view.setTranslationY(wakeUplocation); } } - mDimmedNeedsAnimation = true; } void setAnimateBottomOnLayout(boolean animateBottomOnLayout) { @@ -5763,7 +5361,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable updateFirstAndLastBackgroundViews(); requestDisallowInterceptTouchEvent(true); updateContinuousShadowDrawing(); - updateContinuousBackgroundDrawing(); requestChildrenUpdate(); } @@ -5786,7 +5383,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable * @param numHeadsUp the number of active alerting notifications. */ public void setNumHeadsUp(long numHeadsUp) { - mNumHeadsUp = numHeadsUp; mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0); } @@ -6160,19 +5756,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable .animateHeight() .animateTopInset() .animateY() - .animateDimmed() .animateZ(), // ANIMATION_TYPE_ACTIVATED_CHILD @@ -6267,8 +5849,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable .animateZ(), // ANIMATION_TYPE_DIMMED - new AnimationFilter() - .animateDimmed(), + new AnimationFilter(), // ANIMATION_TYPE_CHANGE_POSITION new AnimationFilter() @@ -6283,7 +5864,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable .animateHeight() .animateTopInset() .animateY() - .animateDimmed() .animateZ() .hasDelays(), @@ -6339,7 +5919,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable // 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 7c138776d5a5..3bdd0e9920c0 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 @@ public class NotificationStackScrollLayoutController implements Dumpable { mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed); mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener); - mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming); - mLockscreenShadeTransitionController.setStackScroller(this); mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener); @@ -1743,13 +1741,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { } /** - * 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 1ef9a8f3d7ec..9b1952ba63fd 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 @@ public class StackScrollAlgorithm { /** 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/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index f5237938ebfa..78fc1471053d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -42,8 +42,10 @@ import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel @@ -99,7 +101,9 @@ constructor( private val alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel, private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel, + private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel, private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel, + private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel, private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, private val glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, @@ -155,8 +159,8 @@ constructor( .distinctUntilChanged() .dumpWhileCollecting("isShadeLocked") - private val shadeCollapseFadeInComplete = MutableStateFlow(false) - .dumpValue("shadeCollapseFadeInComplete") + private val shadeCollapseFadeInComplete = + MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete") val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> = interactor.configurationBasedDimensions @@ -187,9 +191,8 @@ constructor( statesForConstrainedNotifications.contains(it) }, keyguardTransitionInteractor - .transitionValue(LOCKSCREEN) - .onStart { emit(0f) } - .map { it > 0 } + .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN } + .onStart { emit(false) } ) { constrainedNotificationState, transitioningToOrFromLockscreen -> constrainedNotificationState || transitioningToOrFromLockscreen } @@ -362,16 +365,16 @@ constructor( private val alphaWhenGoneAndShadeState: Flow<Float> = combineTransform( - keyguardTransitionInteractor.transitions - .map { step -> step.to == GONE && step.transitionState == FINISHED } - .distinctUntilChanged(), - keyguardInteractor.statusBarState, - ) { isGoneTransitionFinished, statusBarState -> - if (isGoneTransitionFinished && statusBarState == SHADE) { - emit(1f) + keyguardTransitionInteractor.transitions + .map { step -> step.to == GONE && step.transitionState == FINISHED } + .distinctUntilChanged(), + keyguardInteractor.statusBarState, + ) { isGoneTransitionFinished, statusBarState -> + if (isGoneTransitionFinished && statusBarState == SHADE) { + emit(1f) + } } - } - .dumpWhileCollecting("alphaWhenGoneAndShadeState") + .dumpWhileCollecting("alphaWhenGoneAndShadeState") fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> { // All transition view models are mututally exclusive, and safe to merge @@ -379,7 +382,9 @@ constructor( merge( alternateBouncerToGoneTransitionViewModel.lockscreenAlpha, aodToLockscreenTransitionViewModel.notificationAlpha, + aodToOccludedTransitionViewModel.lockscreenAlpha(viewState), dozingToLockscreenTransitionViewModel.lockscreenAlpha, + dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState), dreamingToLockscreenTransitionViewModel.lockscreenAlpha, goneToAodTransitionViewModel.notificationAlpha, goneToDreamingTransitionViewModel.lockscreenAlpha, @@ -433,30 +438,35 @@ constructor( * alpha sources. */ val glanceableHubAlpha: Flow<Float> = - isOnGlanceableHubWithoutShade.flatMapLatest { isOnGlanceableHubWithoutShade -> - combineTransform( - lockscreenToGlanceableHubRunning, - glanceableHubToLockscreenRunning, - merge( - lockscreenToGlanceableHubTransitionViewModel.notificationAlpha, - glanceableHubToLockscreenTransitionViewModel.notificationAlpha, - ) - ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha -> - if (isOnGlanceableHubWithoutShade) { - // Notifications should not be visible on the glanceable hub. - // TODO(b/321075734): implement a way to actually set the notifications to gone - // while on the hub instead of just adjusting alpha - emit(0f) - } else if (lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning) { - emit(alpha) - } else { - // Not on the hub and no transitions running, return full visibility so we don't - // block the notifications from showing. - emit(1f) + isOnGlanceableHubWithoutShade + .flatMapLatest { isOnGlanceableHubWithoutShade -> + combineTransform( + lockscreenToGlanceableHubRunning, + glanceableHubToLockscreenRunning, + merge( + lockscreenToGlanceableHubTransitionViewModel.notificationAlpha, + glanceableHubToLockscreenTransitionViewModel.notificationAlpha, + ) + ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha -> + if (isOnGlanceableHubWithoutShade) { + // Notifications should not be visible on the glanceable hub. + // TODO(b/321075734): implement a way to actually set the notifications to + // gone + // while on the hub instead of just adjusting alpha + emit(0f) + } else if ( + lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning + ) { + emit(alpha) + } else { + // Not on the hub and no transitions running, return full visibility so we + // don't + // block the notifications from showing. + emit(1f) + } } } - } - .dumpWhileCollecting("glanceableHubAlpha") + .dumpWhileCollecting("glanceableHubAlpha") /** * Under certain scenarios, such as swiping up on the lockscreen, the container will need to be @@ -464,20 +474,20 @@ constructor( */ fun translationY(params: BurnInParameters): Flow<Float> { return combine( - aodBurnInViewModel.translationY(params).onStart { emit(0f) }, - isOnLockscreenWithoutShade, - merge( - keyguardInteractor.keyguardTranslationY, - occludedToLockscreenTransitionViewModel.lockscreenTranslationY, - ) - ) { burnInY, isOnLockscreenWithoutShade, translationY -> - if (isOnLockscreenWithoutShade) { - burnInY + translationY - } else { - 0f + aodBurnInViewModel.translationY(params).onStart { emit(0f) }, + isOnLockscreenWithoutShade, + merge( + keyguardInteractor.keyguardTranslationY, + occludedToLockscreenTransitionViewModel.lockscreenTranslationY, + ) + ) { burnInY, isOnLockscreenWithoutShade, translationY -> + if (isOnLockscreenWithoutShade) { + burnInY + translationY + } else { + 0f + } } - } - .dumpWhileCollecting("translationY") + .dumpWhileCollecting("translationY") } /** @@ -486,10 +496,10 @@ constructor( */ val translationX: Flow<Float> = merge( - lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX, - glanceableHubToLockscreenTransitionViewModel.notificationTranslationX, - ) - .dumpWhileCollecting("translationX") + lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX, + glanceableHubToLockscreenTransitionViewModel.notificationTranslationX, + ) + .dumpWhileCollecting("translationX") /** * When on keyguard, there is limited space to display notifications so calculate how many could 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 d2e36b88fd9d..088f8949525b 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump 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/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index ee844345d2ec..ba89d4ac22cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -95,6 +95,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.unfold.FoldAodAnimationController; @@ -351,8 +352,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor; private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor; private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor; - private final JavaAdapter mJavaAdapter; + private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor; @Inject public StatusBarKeyguardViewManager( @@ -386,7 +387,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb SelectedUserInteractor selectedUserInteractor, Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor, JavaAdapter javaAdapter, - Lazy<SceneInteractor> sceneInteractorLazy + Lazy<SceneInteractor> sceneInteractorLazy, + StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor ) { mContext = context; mViewMediatorCallback = callback; @@ -421,6 +423,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mSurfaceBehindInteractor = surfaceBehindInteractor; mJavaAdapter = javaAdapter; mSceneInteractorLazy = sceneInteractorLazy; + mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor; } KeyguardTransitionInteractor mKeyguardTransitionInteractor; @@ -503,6 +506,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb lockscreenVis || animatingSurface ), this::consumeShowStatusBarKeyguardView); + + mJavaAdapter.alwaysCollectFlow( + mStatusBarKeyguardViewManagerInteractor.getKeyguardViewOcclusionState(), + (occlusionState) -> setOccluded( + occlusionState.getOccluded(), occlusionState.getAnimate())); } } @@ -1453,6 +1461,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb hideAlternateBouncer(false); executeAfterKeyguardGoneAction(); } + + if (KeyguardWmStateRefactor.isEnabled()) { + mKeyguardTransitionInteractor.startDismissKeyguardTransition(); + } } /** Display security message to relevant KeyguardMessageArea. */ 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 0128eb762296..80ccd646f6be 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.channels.awaitClose 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 @@ fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> { } .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/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java index 0d6c0f59b2d0..10cf08221fb3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java @@ -25,8 +25,11 @@ import android.net.Uri; import android.os.UserHandle; import android.provider.Settings; +import com.android.app.tracing.TraceUtils; import com.android.systemui.settings.UserTracker; +import kotlin.Unit; + /** * Used to interact with per-user Settings.Secure and Settings.System settings (but not * Settings.Global, since those do not vary per-user) @@ -123,8 +126,16 @@ public interface UserSettingsProxy extends SettingsProxy { default void registerContentObserverForUser( Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, int userHandle) { - getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle)); + TraceUtils.trace( + () -> { + // The limit for trace tags length is 127 chars, which leaves us 90 for Uri. + return "USP#registerObserver#[" + uri.toString() + "]"; + }, () -> { + getContentResolver().registerContentObserver( + uri, notifyForDescendants, settingsObserver, + getRealUserHandle(userHandle)); + return Unit.INSTANCE; + }); } /** diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 404563087041..deec215865b2 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.dump.DumpManager; 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 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, 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 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, ConfigurationController configurationController, - MediaOutputDialogFactory mediaOutputDialogFactory, + MediaOutputDialogManager mediaOutputDialogManager, InteractionJankMonitor interactionJankMonitor, VolumePanelNavigationInteractor volumePanelNavigationInteractor, VolumeNavigator volumeNavigator, @@ -340,7 +340,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mAccessibilityMgr = accessibilityManagerWrapper; mDeviceProvisionedController = deviceProvisionedController; mConfigurationController = configurationController; - mMediaOutputDialogFactory = mediaOutputDialogFactory; + mMediaOutputDialogManager = mediaOutputDialogManager; mCsdWarningDialogFactory = csdWarningDialogFactory; mShowActiveStreamOnly = showActiveStreamOnly(); mHasSeenODICaptionsTooltip = @@ -1199,7 +1199,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, 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 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } 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 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, 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 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, /* 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 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, 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 64a5644bc452..1f87ec8ffe02 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 android.os.Looper; 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 @@ public interface VolumeModule { AccessibilityManagerWrapper accessibilityManagerWrapper, DeviceProvisionedController deviceProvisionedController, ConfigurationController configurationController, - MediaOutputDialogFactory mediaOutputDialogFactory, + MediaOutputDialogManager mediaOutputDialogManager, InteractionJankMonitor interactionJankMonitor, VolumePanelNavigationInteractor volumePanelNavigationInteractor, VolumeNavigator volumeNavigator, @@ -116,7 +116,7 @@ public interface VolumeModule { 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 2ab59984d06f..cb16abe7e575 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.internal.jank.InteractionJankMonitor 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 @@ import javax.inject.Inject 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 @@ constructor( 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 f07932c0de69..e3be3822fb94 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 @@ package com.android.systemui.animation 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 @@ import org.junit.runner.RunWith @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 043dcaa0d919..3c073d5e7a3b 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 @@ class BatteryMeterViewTest : SysuiTestCase() { } @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 7a18477bffdd..a569ceec32b2 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.settingslib.bluetooth.LocalBluetoothProfileManager; 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 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase { @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 @@ public class BroadcastDialogDelegateTest extends SysuiTestCase { mBroadcastDialogDelegate = new BroadcastDialogDelegate( mContext, - mMediaOutputDialogFactory, + mMediaOutputDialogManager, mLocalBluetoothManager, new UiEventLoggerFake(), mFakeExecutor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 489665cd130a..51828c91de4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -6,6 +6,7 @@ import android.app.WindowConfiguration import android.graphics.Point import android.graphics.Rect import android.os.PowerManager +import android.platform.test.annotations.DisableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.RemoteAnimationTarget @@ -15,6 +16,7 @@ import android.view.View import android.view.ViewRootImpl import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController +import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController @@ -130,6 +132,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { * surface, or the user will see the wallpaper briefly as the app animates in. */ @Test + @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun noSurfaceAnimation_ifWakeAndUnlocking() { whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true) @@ -320,6 +323,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { * If we are not wake and unlocking, we expect the unlock animation to play normally. */ @Test + @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun surfaceAnimation_multipleTargets() { keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation( arrayOf(remoteTarget1, remoteTarget2), @@ -358,6 +362,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun surfaceBehindAlphaOverriddenTo0_ifNotInteractive() { whenever(powerManager.isInteractive).thenReturn(false) @@ -389,6 +394,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { } @Test + @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun surfaceBehindAlphaNotOverriddenTo0_ifInteractive() { whenever(powerManager.isInteractive).thenReturn(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 0957748c9938..0bd4cbec64dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -1263,7 +1263,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mSystemPropertiesHelper, () -> mock(WindowManagerLockscreenVisibilityManager.class), mSelectedUserInteractor, - mKeyguardInteractor); + mKeyguardInteractor, + mock(WindowManagerOcclusionManager.class)); mViewMediator.start(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt new file mode 100644 index 000000000000..7bef01a7a5ce --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt @@ -0,0 +1,286 @@ +/* + * 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. + */ + +/* + * 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.keyguard.domain.interactor + +import android.os.PowerManager +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.testKosmos +import junit.framework.Assert.assertEquals +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mockito.reset +import org.mockito.Mockito.spy + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class FromAodTransitionInteractorTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) + } + + private val testScope = kosmos.testScope + private val underTest = kosmos.fromAodTransitionInteractor + + private val powerInteractor = kosmos.powerInteractor + private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + + @Before + fun setup() { + underTest.start() + + // Transition to AOD and set the power interactor asleep. + powerInteractor.setAsleepForTest() + runBlocking { + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope + ) + kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + reset(transitionRepository) + } + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToLockscreen_onWakeup() = + testScope.runTest { + powerInteractor.setAwakeForTest() + runCurrent() + + // Under default conditions, we should transition to LOCKSCREEN when waking up. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() = + testScope.runTest { + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true) + powerInteractor.setAwakeForTest() + runCurrent() + + // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.AOD, + to = KeyguardState.OCCLUDED, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() = + testScope.runTest { + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() = + testScope.runTest { + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + testScope, + ) + runCurrent() + + // Make sure we're GONE. + assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState()) + + // Get part way to AOD. + powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN) + runCurrent() + + transitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + testScope = testScope, + throughTransitionState = TransitionState.RUNNING, + ) + + // Detect a power gesture and then wake up. + reset(transitionRepository) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + testScope, + ) + runCurrent() + + // Make sure we're GONE. + assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState()) + + // Get all the way to AOD + powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN) + transitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + testScope = testScope, + ) + + // Detect a power gesture and then wake up. + reset(transitionRepository) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no + // longer an insecure camera launch and it would be bad if we unlocked now. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + runCurrent() + + // Make sure we're in LOCKSCREEN. + assertEquals( + KeyguardState.LOCKSCREEN, + kosmos.keyguardTransitionInteractor.getFinishedState() + ) + + // Get part way to AOD. + powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN) + runCurrent() + + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = testScope, + throughTransitionState = TransitionState.RUNNING, + ) + + // Detect a power gesture and then wake up. + reset(transitionRepository) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testWakeAndUnlock_transitionsToGone_onlyAfterDismissCallPostWakeup() = + testScope.runTest { + kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + powerInteractor.setAwakeForTest() + runCurrent() + + // Waking up from wake and unlock should not start any transitions, we'll wait for the + // dismiss call. + assertThat(transitionRepository).noTransitionsStarted() + + underTest.dismissAod() + runCurrent() + + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt new file mode 100644 index 000000000000..258dbf3efbae --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -0,0 +1,312 @@ +/* + * 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. + */ + +/* + * 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.keyguard.domain.interactor + +import android.os.PowerManager +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.testKosmos +import junit.framework.Assert.assertEquals +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mockito.reset +import org.mockito.Mockito.spy + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class FromDozingTransitionInteractorTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) + } + + private val testScope = kosmos.testScope + private val underTest = kosmos.fromDozingTransitionInteractor + + private val powerInteractor = kosmos.powerInteractor + private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + + @Before + fun setup() { + underTest.start() + + // Transition to DOZING and set the power interactor asleep. + powerInteractor.setAsleepForTest() + runBlocking { + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + testScope + ) + kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + reset(transitionRepository) + } + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToLockscreen_onWakeup() = + testScope.runTest { + powerInteractor.setAwakeForTest() + runCurrent() + + // Under default conditions, we should transition to LOCKSCREEN when waking up. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() = + testScope.runTest { + kosmos.fakeCommunalRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) + ) + runCurrent() + + powerInteractor.setAwakeForTest() + runCurrent() + + // Under default conditions, we should transition to LOCKSCREEN when waking up. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DOZING, + to = KeyguardState.GLANCEABLE_HUB, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() = + testScope.runTest { + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true) + powerInteractor.setAwakeForTest() + runCurrent() + + // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DOZING, + to = KeyguardState.OCCLUDED, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop_evenIfIdleOnCommunal() = + testScope.runTest { + kosmos.fakeCommunalRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) + ) + runCurrent() + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true) + powerInteractor.setAwakeForTest() + runCurrent() + + // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED. + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DOZING, + to = KeyguardState.OCCLUDED, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() = + testScope.runTest { + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() = + testScope.runTest { + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.GONE, + testScope, + ) + runCurrent() + + // Make sure we're GONE. + assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState()) + + // Get part way to AOD. + powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN) + runCurrent() + + transitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.DOZING, + testScope = testScope, + throughTransitionState = TransitionState.RUNNING, + ) + + // Detect a power gesture and then wake up. + reset(transitionRepository) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.GONE, + testScope, + ) + runCurrent() + + // Make sure we're GONE. + assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState()) + + // Get all the way to AOD + powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN) + transitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.DOZING, + testScope = testScope, + ) + + // Detect a power gesture and then wake up. + reset(transitionRepository) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no + // longer an insecure camera launch and it would be bad if we unlocked now. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.DOZING, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + runCurrent() + + // Make sure we're in LOCKSCREEN. + assertEquals( + KeyguardState.LOCKSCREEN, + kosmos.keyguardTransitionInteractor.getFinishedState() + ) + + // Get part way to AOD. + powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN) + runCurrent() + + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + testScope = testScope, + throughTransitionState = TransitionState.RUNNING, + ) + + // Detect a power gesture and then wake up. + reset(transitionRepository) + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + // We should head back to GONE since we started there. + assertThat(transitionRepository) + .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt new file mode 100644 index 000000000000..f534ba5bc68c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt @@ -0,0 +1,163 @@ +/* + * 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. + */ + +/* + * 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.keyguard.domain.interactor + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.testKosmos +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mockito.reset +import org.mockito.Mockito.spy + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class FromDreamingTransitionInteractorTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) + } + + private val testScope = kosmos.testScope + private val underTest = kosmos.fromDreamingTransitionInteractor + + private val powerInteractor = kosmos.powerInteractor + private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + + @Before + fun setup() { + underTest.start() + + // Transition to DOZING and set the power interactor asleep. + powerInteractor.setAsleepForTest() + runBlocking { + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + testScope + ) + kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + reset(transitionRepository) + } + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() = + testScope.runTest { + kosmos.fakeKeyguardRepository.setDreaming(true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + testScope, + ) + runCurrent() + + reset(transitionRepository) + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true) + kosmos.fakeKeyguardRepository.setDreaming(false) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DREAMING, + to = KeyguardState.OCCLUDED, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testDoesNotTransitionToOccluded_occludingActivityOnTop_whileStillDreaming() = + testScope.runTest { + kosmos.fakeKeyguardRepository.setDreaming(true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + testScope, + ) + runCurrent() + + reset(transitionRepository) + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true) + runCurrent() + + assertThat(transitionRepository).noTransitionsStarted() + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionsToLockscreen_whenOccludingActivityEnds() = + testScope.runTest { + kosmos.fakeKeyguardRepository.setDreaming(true) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + testScope, + ) + runCurrent() + + reset(transitionRepository) + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.DREAMING, + to = KeyguardState.LOCKSCREEN, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt index 6aebe365dc8c..c3e24d579491 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.ActivityManager +import android.app.WindowConfiguration import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -26,6 +28,7 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -42,6 +45,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.never +import org.mockito.Mockito.reset import org.mockito.Mockito.spy import org.mockito.Mockito.verify @@ -171,4 +175,49 @@ class FromLockscreenTransitionInteractorTest : SysuiTestCase() { assertThatRepository(transitionRepository).noTransitionsStarted() } + + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionsToOccluded_whenShowWhenLockedActivityOnTop() = + testScope.runTest { + underTest.start() + runCurrent() + + reset(transitionRepository) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo( + true, + ActivityManager.RunningTaskInfo().apply { + topActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD + } + ) + runCurrent() + + assertThatRepository(transitionRepository) + .startedTransition( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionsToDream_whenDreamActivityOnTop() = + testScope.runTest { + underTest.start() + runCurrent() + + reset(transitionRepository) + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo( + true, + ActivityManager.RunningTaskInfo().apply { + topActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM + } + ) + runCurrent() + + assertThatRepository(transitionRepository) + .startedTransition( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DREAMING, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt new file mode 100644 index 000000000000..d3c48483d100 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt @@ -0,0 +1,127 @@ +/* + * 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. + */ + +/* + * 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.keyguard.domain.interactor + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.testKosmos +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.runner.RunWith +import org.mockito.Mockito.reset +import org.mockito.Mockito.spy + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class FromOccludedTransitionInteractorTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) + } + + private val testScope = kosmos.testScope + private val underTest = kosmos.fromOccludedTransitionInteractor + + private val powerInteractor = kosmos.powerInteractor + private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + + @Before + fun setup() { + underTest.start() + + // Transition to OCCLUDED and set up PowerInteractor and the occlusion repository. + powerInteractor.setAwakeForTest() + runBlocking { + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + testScope + ) + reset(transitionRepository) + } + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testShowWhenLockedActivity_noLongerOnTop_transitionsToLockscreen() = + testScope.runTest { + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + ) + } + + @Test + @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() = + testScope.runTest { + kosmos.fakeCommunalRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) + ) + runCurrent() + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.OCCLUDED, + to = KeyguardState.GLANCEABLE_HUB, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt index f33a5c9484ee..7ee8963aaa15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt @@ -16,14 +16,23 @@ package com.android.systemui.keyguard.domain.interactor +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.communal.data.repository.fakeCommunalRepository +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.selectedUserInteractor @@ -31,20 +40,27 @@ import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue import junit.framework.Assert.fail import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.reset +import org.mockito.Mockito.spy @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() { - val kosmos = testKosmos() + val kosmos = + testKosmos().apply { + this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository()) + } val underTest = kosmos.fromPrimaryBouncerTransitionInteractor val testScope = kosmos.testScope val selectedUserInteractor = kosmos.selectedUserInteractor val transitionRepository = kosmos.fakeKeyguardTransitionRepository + val bouncerRepository = kosmos.fakeKeyguardBouncerRepository @Test fun testSurfaceBehindVisibility() = @@ -193,4 +209,85 @@ class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() { fail("surfaceBehindModel was unexpectedly null.") } } + + @Test + @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testReturnToLockscreen_whenBouncerHides() = + testScope.runTest { + underTest.start() + bouncerRepository.setPrimaryShow(true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + testScope + ) + + reset(transitionRepository) + + bouncerRepository.setPrimaryShow(false) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.LOCKSCREEN + ) + } + + @Test + @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testReturnToGlanceableHub_whenBouncerHides_ifIdleOnCommunal() = + testScope.runTest { + underTest.start() + kosmos.fakeCommunalRepository.setTransitionState( + flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal)) + ) + bouncerRepository.setPrimaryShow(true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + testScope + ) + + reset(transitionRepository) + + bouncerRepository.setPrimaryShow(false) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.GLANCEABLE_HUB + ) + } + + @Test + @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) + fun testTransitionToOccluded_bouncerHide_occludingActivityOnTop() = + testScope.runTest { + underTest.start() + bouncerRepository.setPrimaryShow(true) + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.PRIMARY_BOUNCER, + testScope + ) + + reset(transitionRepository) + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true) + runCurrent() + + // Shouldn't transition to OCCLUDED until the bouncer hides. + assertThat(transitionRepository).noTransitionsStarted() + + bouncerRepository.setPrimaryShow(false) + runCurrent() + + assertThat(transitionRepository) + .startedTransition( + from = KeyguardState.PRIMARY_BOUNCER, + to = KeyguardState.OCCLUDED + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt new file mode 100644 index 000000000000..8a77ed2130a9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt @@ -0,0 +1,224 @@ +/* + * 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. + */ + +/* + * 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.keyguard.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue +import kotlin.test.Test +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardOcclusionInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.keyguardOcclusionInteractor + private val powerInteractor = kosmos.powerInteractor + private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + + @Test + fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = testScope, + throughTransitionState = TransitionState.RUNNING + ) + + powerInteractor.onCameraLaunchGestureDetected() + runCurrent() + + assertTrue(underTest.shouldTransitionFromPowerButtonGesture()) + } + + @Test + fun testTransitionFromPowerGesture_whileAsleep_isTrue() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = testScope, + ) + + powerInteractor.onCameraLaunchGestureDetected() + runCurrent() + + assertTrue(underTest.shouldTransitionFromPowerButtonGesture()) + } + + @Test + fun testTransitionFromPowerGesture_whileWaking_isFalse() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = testScope, + ) + transitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + throughTransitionState = TransitionState.RUNNING + ) + + powerInteractor.onCameraLaunchGestureDetected() + runCurrent() + + assertFalse(underTest.shouldTransitionFromPowerButtonGesture()) + } + + @Test + fun testTransitionFromPowerGesture_whileAwake_isFalse() = + testScope.runTest { + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = testScope, + ) + transitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + ) + + powerInteractor.onCameraLaunchGestureDetected() + runCurrent() + + assertFalse(underTest.shouldTransitionFromPowerButtonGesture()) + } + + @Test + fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() = + testScope.runTest { + val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture) + powerInteractor.setAsleepForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + testScope = testScope, + ) + + powerInteractor.onCameraLaunchGestureDetected() + powerInteractor.setAwakeForTest() + runCurrent() + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true) + runCurrent() + + assertThat(values) + .containsExactly( + false, + true, + ) + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false) + runCurrent() + + assertThat(values) + .containsExactly( + false, + true, + false, + ) + + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true) + runCurrent() + + assertThat(values) + .containsExactly( + false, + true, + // Power button gesture was not triggered a second time, so this should remain + // false. + false, + ) + } + + @Test + fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() = + testScope.runTest { + val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture) + powerInteractor.setAwakeForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + + powerInteractor.setAsleepForTest() + transitionRepository.sendTransitionSteps( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + testScope = testScope, + throughTransitionState = TransitionState.RUNNING + ) + + powerInteractor.onCameraLaunchGestureDetected() + kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true) + powerInteractor.setAwakeForTest() + runCurrent() + + transitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + testScope = testScope, + ) + + assertThat(values) + .containsExactly( + false, + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 92aad692ac52..95606ae81e5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -21,8 +21,8 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN +import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_COMMUNAL_HUB -import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.communal.domain.interactor.communalInteractor @@ -40,7 +40,6 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat -import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest @@ -48,7 +47,6 @@ import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.statusbar.commandQueue import com.android.systemui.testKosmos -import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -92,30 +90,26 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { private var commandQueue = kosmos.fakeCommandQueue private val shadeRepository = kosmos.fakeShadeRepository private val transitionRepository = kosmos.fakeKeyguardTransitionRepository - private val transitionInteractor = kosmos.keyguardTransitionInteractor private lateinit var featureFlags: FakeFeatureFlags // Used to verify transition requests for test output @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel - @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor - private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor - private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor - private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor - private lateinit var fromGoneTransitionInteractor: FromGoneTransitionInteractor - private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor - private lateinit var fromAlternateBouncerTransitionInteractor: - FromAlternateBouncerTransitionInteractor + private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor + private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor + private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor + private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor + private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor + private val fromAlternateBouncerTransitionInteractor = + kosmos.fromAlternateBouncerTransitionInteractor private val fromPrimaryBouncerTransitionInteractor = kosmos.fromPrimaryBouncerTransitionInteractor - private lateinit var fromDreamingLockscreenHostedTransitionInteractor: - FromDreamingLockscreenHostedTransitionInteractor - private lateinit var fromGlanceableHubTransitionInteractor: - FromGlanceableHubTransitionInteractor + private val fromDreamingLockscreenHostedTransitionInteractor = + kosmos.fromDreamingLockscreenHostedTransitionInteractor + private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor private val powerInteractor = kosmos.powerInteractor - private val keyguardInteractor = kosmos.keyguardInteractor private val communalInteractor = kosmos.communalInteractor @Before @@ -125,122 +119,21 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) + mSetFlagsRule.disableFlags( + Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR, + ) featureFlags = FakeFeatureFlags() - val glanceableHubTransitions = - GlanceableHubTransitions( - bgDispatcher = kosmos.testDispatcher, - transitionInteractor = transitionInteractor, - transitionRepository = transitionRepository, - communalInteractor = communalInteractor - ) - fromLockscreenTransitionInteractor.start() fromPrimaryBouncerTransitionInteractor.start() - - fromDreamingTransitionInteractor = - FromDreamingTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - glanceableHubTransitions = glanceableHubTransitions, - ) - .apply { start() } - - fromDreamingLockscreenHostedTransitionInteractor = - FromDreamingLockscreenHostedTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ) - .apply { start() } - - fromAodTransitionInteractor = - FromAodTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - powerInteractor = powerInteractor, - ) - .apply { start() } - - fromGoneTransitionInteractor = - FromGoneTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - powerInteractor = powerInteractor, - communalInteractor = communalInteractor, - ) - .apply { start() } - - fromDozingTransitionInteractor = - FromDozingTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - powerInteractor = powerInteractor, - communalInteractor = communalInteractor, - ) - .apply { start() } - - fromOccludedTransitionInteractor = - FromOccludedTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - powerInteractor = powerInteractor, - communalInteractor = communalInteractor, - ) - .apply { start() } - - fromAlternateBouncerTransitionInteractor = - FromAlternateBouncerTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - communalInteractor = communalInteractor, - powerInteractor = powerInteractor, - ) - .apply { start() } - - fromGlanceableHubTransitionInteractor = - FromGlanceableHubTransitionInteractor( - scope = testScope, - bgDispatcher = kosmos.testDispatcher, - mainDispatcher = kosmos.testDispatcher, - glanceableHubTransitions = glanceableHubTransitions, - keyguardInteractor = keyguardInteractor, - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - powerInteractor = powerInteractor, - ) - .apply { start() } - - mSetFlagsRule.disableFlags( - FLAG_KEYGUARD_WM_STATE_REFACTOR, - ) + fromDreamingTransitionInteractor.start() + fromDreamingLockscreenHostedTransitionInteractor.start() + fromAodTransitionInteractor.start() + fromGoneTransitionInteractor.start() + fromDozingTransitionInteractor.start() + fromOccludedTransitionInteractor.start() + fromAlternateBouncerTransitionInteractor.start() + fromGlanceableHubTransitionInteractor.start() } @Test @@ -257,7 +150,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { .startedTransition( to = KeyguardState.PRIMARY_BOUNCER, from = KeyguardState.LOCKSCREEN, - ownerName = "FromLockscreenTransitionInteractor", + ownerName = + "FromLockscreenTransitionInteractor" + + "(#listenForLockscreenToPrimaryBouncer)", animatorAssertion = { it.isNotNull() } ) @@ -282,7 +177,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { .startedTransition( to = KeyguardState.DOZING, from = KeyguardState.OCCLUDED, - ownerName = "FromOccludedTransitionInteractor", + ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)", animatorAssertion = { it.isNotNull() } ) @@ -307,7 +202,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { .startedTransition( to = KeyguardState.AOD, from = KeyguardState.OCCLUDED, - ownerName = "FromOccludedTransitionInteractor", + ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)", animatorAssertion = { it.isNotNull() } ) @@ -389,7 +284,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { .startedTransition( to = KeyguardState.DOZING, from = KeyguardState.LOCKSCREEN, - ownerName = "FromLockscreenTransitionInteractor", + ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)", animatorAssertion = { it.isNotNull() } ) @@ -414,7 +309,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { .startedTransition( to = KeyguardState.AOD, from = KeyguardState.LOCKSCREEN, - ownerName = "FromLockscreenTransitionInteractor", + ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)", animatorAssertion = { it.isNotNull() } ) @@ -778,7 +673,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { .startedTransition( to = KeyguardState.DOZING, from = KeyguardState.GONE, - ownerName = "FromGoneTransitionInteractor", + ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)", animatorAssertion = { it.isNotNull() } ) @@ -803,7 +698,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { .startedTransition( to = KeyguardState.AOD, from = KeyguardState.GONE, - ownerName = "FromGoneTransitionInteractor", + ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)", animatorAssertion = { it.isNotNull() } ) @@ -1070,12 +965,14 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Test fun primaryBouncerToAod() = testScope.runTest { + // GIVEN aod available + keyguardRepository.setAodAvailable(true) + runCurrent() + // GIVEN a prior transition has run to PRIMARY_BOUNCER bouncerRepository.setPrimaryShow(true) runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) - // GIVEN aod available and starting to sleep - keyguardRepository.setAodAvailable(true) powerInteractor.setAsleepForTest() // WHEN the primaryBouncer stops showing @@ -1085,7 +982,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // THEN a transition to AOD should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromPrimaryBouncerTransitionInteractor", + ownerName = + "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)", from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.AOD, animatorAssertion = { it.isNotNull() }, @@ -1112,7 +1010,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // THEN a transition to DOZING should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromPrimaryBouncerTransitionInteractor", + ownerName = + "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)", from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.DOZING, animatorAssertion = { it.isNotNull() }, @@ -1642,7 +1541,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromLockscreenTransitionInteractor", + ownerName = + "FromLockscreenTransitionInteractor" + + "(#listenForLockscreenToPrimaryBouncerDragging)", from = KeyguardState.LOCKSCREEN, to = KeyguardState.PRIMARY_BOUNCER, animatorAssertion = { it.isNull() }, // dragging should be manually animated 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 2f92afa39b52..83e4d3130b67 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.view.RecommendationViewHolder 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 087988469a49..83def8e47651 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { 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 dbfab64b004a..bda0e1ed5c46 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 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.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 fdd434acdc9f..6043ede66b31 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 fakeMediaProjectionManager = FakeMediaProjectionManager() - private val fakeActivityTaskManager = 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 kosmos = taskSwitcherKosmos() + private val testScope = kosmos.testScope + + private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager + private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager + + 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 dfb688bbde4b..33e65f26716a 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 c4e939339fa1..9382c5882b25 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 @@ package com.android.systemui.mediaprojection.taskswitcher.ui 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.ArgumentCaptor 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 687f97082ce4..a468953b971e 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 a59ba071d7e8..eb692eb1b4ac 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.util.ArrayMap; 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.ArrayList; import java.util.List; import java.util.Map; + @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -65,7 +66,7 @@ public class DynamicChildBindControllerTest extends SysuiTestCase { @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/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt index b3fc25c47912..24195fe0640c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt @@ -16,8 +16,10 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel +import android.platform.test.annotations.DisableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule @@ -238,6 +240,7 @@ class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR) fun animationsEnabled_isTrue_whenKeyguardIsShowing() = testComponent.runTest { keyguardTransitionRepository.sendTransitionStep( 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 fb49499fc29d..718f99841292 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.classifier.FalsingManagerFake; 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 @@ public class NotificationTestHelper { 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 000000000000..f88bd7ec60bb --- /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 3d752880f423..4715b33aa40a 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 @@ class AmbientStateTest : SysuiTestCase() { ) } - // 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 @@ class AmbientStateTest : SysuiTestCase() { } // 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 f326ceaf1950..220305cc6bda 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.any; 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.keyguard.BouncerPanelExpansionCalculator; 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.phone.StatusBarKeyguardViewManager; 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { } @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 + ); + + mStackScroller.dispatchTouchEvent(moveEvent2); - verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent); + 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { ); } - private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> { - private final MotionEvent mLeftEvent; + private MotionEventSubject assertThatMotionEvent(MotionEvent actual) { + return new MotionEventSubject(actual); + } + + private static class MotionEventSubject { + private final MotionEvent mActual; - MotionEventMatcher(MotionEvent leftEvent) { - mLeftEvent = leftEvent; + 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/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index f050857d5df2..562aa6a4f497 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -95,6 +95,7 @@ import com.android.systemui.shade.ShadeLockscreenInteractor; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; +import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; @@ -226,7 +227,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), mock(JavaAdapter.class), - () -> mock(SceneInteractor.class)) { + () -> mock(SceneInteractor.class), + mock(StatusBarKeyguardViewManagerInteractor.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -736,7 +738,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mSelectedUserInteractor, () -> mock(KeyguardSurfaceBehindInteractor.class), mock(JavaAdapter.class), - () -> mock(SceneInteractor.class)) { + () -> mock(SceneInteractor.class), + mock(StatusBarKeyguardViewManagerInteractor.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; 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 3a6324d3de53..d0261ae7d256 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.Prefs; 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 @@ public class VolumeDialogImplTest extends SysuiTestCase { @Mock DeviceProvisionedController mDeviceProvisionedController; @Mock - MediaOutputDialogFactory mMediaOutputDialogFactory; + MediaOutputDialogManager mMediaOutputDialogManager; @Mock InteractionJankMonitor mInteractionJankMonitor; @Mock @@ -196,7 +196,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { mAccessibilityMgr, mDeviceProvisionedController, mConfigurationController, - mMediaOutputDialogFactory, + mMediaOutputDialogManager, mInteractionJankMonitor, mVolumePanelNavigationInteractor, mVolumeNavigator, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt index c3f677e8d0d3..d5411ad77ce4 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.plugins.activityStarter @@ -40,5 +41,6 @@ val Kosmos.occludingAppDeviceEntryInteractor by context = mockedContext, activityStarter = activityStarter, powerInteractor = powerInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index a9a2d91c0815..dcbd5777489a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import android.annotation.FloatRange +import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionInfo @@ -65,55 +66,79 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio } /** - * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling - * [runCurrent] after each step. + * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step. + * + * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part + * way using [throughTransitionState]. */ suspend fun sendTransitionSteps( from: KeyguardState, to: KeyguardState, testScope: TestScope, + throughTransitionState: TransitionState = TransitionState.FINISHED, ) { - sendTransitionSteps(from, to, testScope.testScheduler) + sendTransitionSteps(from, to, testScope.testScheduler, throughTransitionState) } /** - * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling - * [runCurrent] after each step. + * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step. + * + * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part + * way using [throughTransitionState]. */ suspend fun sendTransitionSteps( from: KeyguardState, to: KeyguardState, testScheduler: TestCoroutineScheduler, + throughTransitionState: TransitionState = TransitionState.FINISHED, ) { sendTransitionStep( - TransitionStep( - transitionState = TransitionState.STARTED, - from = from, - to = to, - value = 0f, - ) + step = + TransitionStep( + transitionState = TransitionState.STARTED, + from = from, + to = to, + value = 0f, + ) ) testScheduler.runCurrent() - sendTransitionStep( - TransitionStep( - transitionState = TransitionState.RUNNING, - from = from, - to = to, - value = 0.5f + if ( + throughTransitionState == TransitionState.RUNNING || + throughTransitionState == TransitionState.FINISHED + ) { + sendTransitionStep( + step = + TransitionStep( + transitionState = TransitionState.RUNNING, + from = from, + to = to, + value = 0.5f + ) ) - ) - testScheduler.runCurrent() + testScheduler.runCurrent() + } - sendTransitionStep( - TransitionStep( - transitionState = TransitionState.FINISHED, - from = from, - to = to, - value = 1f, + if (throughTransitionState == TransitionState.FINISHED) { + sendTransitionStep( + step = + TransitionStep( + transitionState = TransitionState.FINISHED, + from = from, + to = to, + value = 1f, + ) ) + testScheduler.runCurrent() + } + } + + suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) { + this.sendTransitionStep( + step = step, + validateStep = validateStep, + ownerName = step.ownerName ) - testScheduler.runCurrent() } /** @@ -132,7 +157,22 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio * If you're testing something involving transitions themselves and are sure you want to send * only a FINISHED step, override [validateStep]. */ - suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) { + suspend fun sendTransitionStep( + from: KeyguardState = KeyguardState.OFF, + to: KeyguardState = KeyguardState.OFF, + value: Float = 0f, + transitionState: TransitionState = TransitionState.FINISHED, + ownerName: String = "", + step: TransitionStep = + TransitionStep( + from = from, + to = to, + value = value, + transitionState = transitionState, + ownerName = ownerName + ), + validateStep: Boolean = true + ) { _transitions.replayCache.last().let { lastStep -> if ( validateStep && @@ -159,7 +199,9 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio step: TransitionStep, validateStep: Boolean = true ): Job { - return coroutineScope.launch { sendTransitionStep(step, validateStep) } + return coroutineScope.launch { + sendTransitionStep(step = step, validateStep = validateStep) + } } suspend fun sendTransitionSteps( @@ -168,12 +210,13 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio validateStep: Boolean = true ) { steps.forEach { - sendTransitionStep(it, validateStep = validateStep) + sendTransitionStep(step = it, validateStep = validateStep) testScope.testScheduler.runCurrent() } } override fun startTransition(info: TransitionInfo): UUID? { + Log.i("TEST", "Start transition: ", Exception()) return if (info.animator == null) UUID.randomUUID() else null } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt new file mode 100644 index 000000000000..4c8bf9054106 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * 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.keyguard.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.keyguardOcclusionRepository by Kosmos.Fixture { KeyguardOcclusionRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..530cbedbdd0c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.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.keyguard.domain.interactor + +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor + +val Kosmos.fromAlternateBouncerTransitionInteractor by + Kosmos.Fixture { + FromAlternateBouncerTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + communalInteractor = communalInteractor, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt index 2477415cc06e..bbe37c18dd08 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt @@ -18,19 +18,21 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor val Kosmos.fromAodTransitionInteractor by Kosmos.Fixture { FromAodTransitionInteractor( transitionRepository = fakeKeyguardTransitionRepository, transitionInteractor = keyguardTransitionInteractor, - scope = testScope, + scope = applicationCoroutineScope, bgDispatcher = testDispatcher, mainDispatcher = testDispatcher, keyguardInteractor = keyguardInteractor, powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..23dcd965c028 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.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.keyguard.domain.interactor + +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor + +var Kosmos.fromDozingTransitionInteractor by + Kosmos.Fixture { + FromDozingTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + communalInteractor = communalInteractor, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..f7a9d59eac26 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt @@ -0,0 +1,38 @@ +/* + * 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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor + +var Kosmos.fromDreamingLockscreenHostedTransitionInteractor by + Kosmos.Fixture { + FromDreamingLockscreenHostedTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..135644cfac3e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor + +var Kosmos.fromDreamingTransitionInteractor by + Kosmos.Fixture { + FromDreamingTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + glanceableHubTransitions = glanceableHubTransitions, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..1695327d75bc --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor + +var Kosmos.fromGlanceableHubTransitionInteractor by + Kosmos.Fixture { + FromGlanceableHubTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + glanceableHubTransitions = glanceableHubTransitions, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt index 25fc67a9691b..604d9e435e8e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt @@ -17,11 +17,13 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor val Kosmos.fromGoneTransitionInteractor by Kosmos.Fixture { @@ -34,5 +36,7 @@ val Kosmos.fromGoneTransitionInteractor by keyguardInteractor = keyguardInteractor, powerInteractor = powerInteractor, communalInteractor = communalInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + biometricSettingsRepository = biometricSettingsRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt index 3b52676fc0ef..162fd9029bb0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt @@ -23,6 +23,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor var Kosmos.fromLockscreenTransitionInteractor by Kosmos.Fixture { @@ -38,5 +39,6 @@ var Kosmos.fromLockscreenTransitionInteractor by powerInteractor = powerInteractor, glanceableHubTransitions = glanceableHubTransitions, swipeToDismissInteractor = swipeToDismissInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt new file mode 100644 index 000000000000..fc740a180dc4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.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.keyguard.domain.interactor + +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor + +val Kosmos.fromOccludedTransitionInteractor by + Kosmos.Fixture { + FromOccludedTransitionInteractor( + transitionRepository = keyguardTransitionRepository, + transitionInteractor = keyguardTransitionInteractor, + scope = applicationCoroutineScope, + bgDispatcher = testDispatcher, + mainDispatcher = testDispatcher, + keyguardInteractor = keyguardInteractor, + powerInteractor = powerInteractor, + communalInteractor = communalInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt index 6b764491f32a..98babffb50d3 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt @@ -24,6 +24,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor import com.android.systemui.user.domain.interactor.selectedUserInteractor var Kosmos.fromPrimaryBouncerTransitionInteractor by @@ -40,5 +41,6 @@ var Kosmos.fromPrimaryBouncerTransitionInteractor by keyguardSecurityModel = keyguardSecurityModel, selectedUserInteractor = selectedUserInteractor, powerInteractor = powerInteractor, + keyguardOcclusionInteractor = keyguardOcclusionInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt index 6df7493be200..6cc1e8eba73d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt @@ -16,19 +16,21 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope -import dagger.Lazy val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by Kosmos.Fixture { KeyguardTransitionInteractor( scope = applicationCoroutineScope, repository = keyguardTransitionRepository, - fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor }, - fromPrimaryBouncerTransitionInteractor = - Lazy { fromPrimaryBouncerTransitionInteractor }, - fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor }, + keyguardRepository = keyguardRepository, + fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor }, + fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor }, + fromAodTransitionInteractor = { fromAodTransitionInteractor }, + fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor }, + fromDozingTransitionInteractor = { fromDozingTransitionInteractor }, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt new file mode 100644 index 000000000000..8162520e5d88 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.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.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +val Kosmos.dozingToOccludedTransitionViewModel by + Kosmos.Fixture { + DozingToOccludedTransitionViewModel( + animationFlow = keyguardTransitionAnimationFlow, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt index c2300a1ed1ad..75e3ac24e381 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - @file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.keyguard.ui.viewmodel @@ -41,8 +40,10 @@ val Kosmos.keyguardRootViewModel by Fixture { alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, aodToGoneTransitionViewModel = aodToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, + dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel, dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel, goneToAodTransitionViewModel = goneToAodTransitionViewModel, 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 e1b1966aed6c..e78866904dfe 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 index 920e5ee94cca..41d2d600e627 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 index 28393e837b93..2b6032ccafe5 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 000000000000..d344b75c9eb7 --- /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 000000000000..a2d1d93bbecb --- /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 000000000000..6772ba317da4 --- /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/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt new file mode 100644 index 000000000000..d79374021968 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt @@ -0,0 +1,36 @@ +/* + * 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.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.powerInteractor + +val Kosmos.keyguardOcclusionInteractor by + Kosmos.Fixture { + KeyguardOcclusionInteractor( + scope = testScope.backgroundScope, + repository = keyguardOcclusionRepository, + powerInteractor = powerInteractor, + transitionInteractor = keyguardTransitionInteractor, + keyguardInteractor = keyguardInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt new file mode 100644 index 000000000000..9e34fe8d7c61 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt @@ -0,0 +1,30 @@ +/* + * 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.domain.interactor + +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.power.domain.interactor.powerInteractor + +val Kosmos.statusBarKeyguardViewManagerInteractor by + Kosmos.Fixture { + StatusBarKeyguardViewManagerInteractor( + keyguardTransitionInteractor = this.keyguardTransitionInteractor, + keyguardOcclusionInteractor = this.keyguardOcclusionInteractor, + powerInteractor = this.powerInteractor, + ) + } 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 000000000000..7ffa262d55a4 --- /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/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt index 106e85cc8d85..c01366489c69 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt @@ -23,7 +23,9 @@ import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInterac import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel +import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel @@ -57,7 +59,9 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture { communalInteractor = communalInteractor, alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel, aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel, + aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel, dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel, + dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel, dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel, goneToAodTransitionViewModel = goneToAodTransitionViewModel, goneToDozingTransitionViewModel = goneToDozingTransitionViewModel, 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 5ae033c9870d..d798b3b13341 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 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal 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 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal } @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 a3b1a0eb260d..3938f77b9c54 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 android.testing.TestableLooper 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 @@ val Kosmos.localMediaRepositoryFactory: LocalMediaRepositoryFactory by 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 f31eb44f23f5..23e269a67283 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.Context; 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.Pair; 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 @@ public class WallpaperBackupAgent extends BackupAgent { @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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { private boolean mSystemHasLiveComponent; private boolean mLockHasLiveComponent; + private DisplayManager mDisplayManager; + @Override public void onCreate() { if (DEBUG) { @@ -137,6 +155,8 @@ public class WallpaperBackupAgent extends BackupAgent { mBackupManager = new BackupManager(getBaseContext()); mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this); + + mDisplayManager = getSystemService(DisplayManager.class); } @Override @@ -175,9 +195,11 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { } } + /** + * 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 @@ public class WallpaperBackupAgent extends BackupAgent { 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 @@ public class WallpaperBackupAgent extends BackupAgent { infoStage.delete(); imageStage.delete(); lockImageStage.delete(); + deviceDimensionsStage.delete(); SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); prefs.edit() @@ -409,6 +493,66 @@ public class WallpaperBackupAgent extends BackupAgent { } } + /** + * 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 @@ public class WallpaperBackupAgent extends BackupAgent { }; } + /** + * 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 3ecdf3f101a5..ec9223c7d667 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.Context; 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 @@ public class WallpaperBackupAgentTest { 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 29b9d441cf38..fbd6709fd461 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.Binder; 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.provider.DeviceConfig; 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.IOException; 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.Map; 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // 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 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Mark widget lifecycle broadcasts as 'interactive' private Bundle mInteractiveBroadcast; + private ApiCounter mGeneratedPreviewsApiCounter; + AppWidgetServiceImpl(Context context) { mContext = context; } @@ -294,6 +307,17 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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); @@ -2480,6 +2504,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 +4029,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } @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 +4051,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku 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 +4097,26 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } + 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 +4590,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } - 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 +4837,96 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } } + /** + * 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 551297b253d1..7afb78033fb5 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ @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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 6d731b21ac8a..b1672ed9c732 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 f82a6aabfefb..748253fc9194 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -106,7 +106,7 @@ public final class DropBoxManagerService extends SystemService { 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 8dc15ade532e..25337a434329 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 663ba8a38d77..5e367097b78c 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_ALLOWLISTS; 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub ? 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } // 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub // 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", @@ -4779,36 +4727,47 @@ public class ActivityManagerService extends IActivityManager.Stub // being bound to an application. thread.runIsolatedEntryPoint( app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs()); - } else if (instr2 != null) { - thread.bindApplication(processName, appInfo, - app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage, - instr2.mIsSdkInSandbox, - providerList, - instr2.mClass, - profilerInfo, instr2.mArguments, - instr2.mWatcher, - instr2.mUiAutomationConnection, testMode, - mBinderTransactionTrackingEnabled, enableTrackAllocation, - isRestrictedBackupMode || !normalMode, app.isPersistent(), - new Configuration(app.getWindowProcessController().getConfiguration()), - app.getCompat(), getCommonServicesLocked(app.isolated), - mCoreSettingsObserver.getCoreSettingsLocked(), - buildSerial, autofillOptions, contentCaptureOptions, - app.getDisabledCompatChanges(), serializedSystemFontMap, - app.getStartElapsedTime(), app.getStartUptime()); } else { - thread.bindApplication(processName, appInfo, - app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage, - /* isSdkInSandbox= */ false, - providerList, null, profilerInfo, null, null, null, testMode, - mBinderTransactionTrackingEnabled, enableTrackAllocation, - isRestrictedBackupMode || !normalMode, app.isPersistent(), + boolean isSdkInSandbox = false; + ComponentName instrumentationName = null; + Bundle instrumentationArgs = null; + IInstrumentationWatcher instrumentationWatcher = null; + IUiAutomationConnection instrumentationUiConnection = null; + if (instr2 != null) { + isSdkInSandbox = instr2.mIsSdkInSandbox; + instrumentationName = instr2.mClass; + instrumentationArgs = instr2.mArguments; + instrumentationWatcher = instr2.mWatcher; + instrumentationUiConnection = instr2.mUiAutomationConnection; + } + thread.bindApplication( + processName, + appInfo, + app.sdkSandboxClientAppVolumeUuid, + app.sdkSandboxClientAppPackage, + isSdkInSandbox, + providerList, + instrumentationName, + profilerInfo, + instrumentationArgs, + instrumentationWatcher, + instrumentationUiConnection, + testMode, + mBinderTransactionTrackingEnabled, + enableTrackAllocation, + isRestrictedBackupMode || !normalMode, + app.isPersistent(), new Configuration(app.getWindowProcessController().getConfiguration()), - app.getCompat(), getCommonServicesLocked(app.isolated), + app.getCompat(), + getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(), - buildSerial, autofillOptions, contentCaptureOptions, - app.getDisabledCompatChanges(), serializedSystemFontMap, - app.getStartElapsedTime(), app.getStartUptime()); + buildSerial, + autofillOptions, + contentCaptureOptions, + app.getDisabledCompatChanges(), + serializedSystemFontMap, + app.getStartElapsedTime(), + app.getStartUptime()); } Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG); @@ -4948,9 +4907,7 @@ public class ActivityManagerService extends IActivityManager.Stub // 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) { @@ -9090,9 +9047,7 @@ public class ActivityManagerService extends IActivityManager.Stub } private void startBroadcastObservers() { - for (BroadcastQueue queue : mBroadcastQueues) { - queue.start(mContext.getContentResolver()); - } + mBroadcastQueue.start(mContext.getContentResolver()); } private void updateForceBackgroundCheck(boolean enabled) { @@ -9363,7 +9318,9 @@ public class ActivityManagerService extends IActivityManager.Stub sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n"); } if (info.broadcastIntentAction != null) { - sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n"); + sb.append("Broadcast-Intent-Action: ") + .append(info.broadcastIntentAction) + .append("\n"); } if (info.durationMillis != -1) { sb.append("Duration-Millis: ").append(info.durationMillis).append("\n"); @@ -9723,82 +9680,126 @@ public class ActivityManagerService extends IActivityManager.Stub // If process is null, we are being called from some internal code // and may be about to die -- run this synchronously. final boolean runSynchronously = process == null; - Thread worker = new Thread("Error dump: " + dropboxTag) { - @Override - public void run() { - if (report != null) { - sb.append(report); - } - - String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; - String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag; - int lines = Build.IS_USER - ? 0 - : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0); - int dropboxMaxSize = Settings.Global.getInt( - mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE); - - if (dataFile != null) { - // Attach the stack traces file to the report so collectors can load them - // by file if they have access. - sb.append(DATA_FILE_PATH_HEADER) - .append(dataFile.getAbsolutePath()).append('\n'); - - int maxDataFileSize = dropboxMaxSize - - sb.length() - - lines * RESERVED_BYTES_PER_LOGCAT_LINE - - DATA_FILE_PATH_FOOTER.length(); - - if (maxDataFileSize > 0) { - // Inline dataFile contents if there is room. - try { - sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize, - "\n\n[[TRUNCATED]]\n")); - } catch (IOException e) { - Slog.e(TAG, "Error reading " + dataFile, e); + Thread worker = + new Thread("Error dump: " + dropboxTag) { + @Override + public void run() { + if (report != null) { + sb.append(report); } - } - // Always append the footer, even there wasn't enough space to inline the - // dataFile contents. - sb.append(DATA_FILE_PATH_FOOTER); - } + String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; + String maxBytesSetting = + Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag; + int lines = + Build.IS_USER + ? 0 + : Settings.Global.getInt( + mContext.getContentResolver(), logcatSetting, 0); + int dropboxMaxSize = + Settings.Global.getInt( + mContext.getContentResolver(), + maxBytesSetting, + DROPBOX_DEFAULT_MAX_SIZE); + + if (dataFile != null) { + // Attach the stack traces file to the report so collectors can load + // them + // by file if they have access. + sb.append(DATA_FILE_PATH_HEADER) + .append(dataFile.getAbsolutePath()) + .append('\n'); + + int maxDataFileSize = + dropboxMaxSize + - sb.length() + - lines * RESERVED_BYTES_PER_LOGCAT_LINE + - DATA_FILE_PATH_FOOTER.length(); + + if (maxDataFileSize > 0) { + // Inline dataFile contents if there is room. + try { + sb.append( + FileUtils.readTextFile( + dataFile, + maxDataFileSize, + "\n\n[[TRUNCATED]]\n")); + } catch (IOException e) { + Slog.e(TAG, "Error reading " + dataFile, e); + } + } - if (crashInfo != null && crashInfo.stackTrace != null) { - sb.append(crashInfo.stackTrace); - } + // Always append the footer, even there wasn't enough space to inline + // the + // dataFile contents. + sb.append(DATA_FILE_PATH_FOOTER); + } + + if (crashInfo != null && crashInfo.stackTrace != null) { + sb.append(crashInfo.stackTrace); + } - if (lines > 0 && !runSynchronously) { - sb.append("\n"); + if (lines > 0 && !runSynchronously) { + sb.append("\n"); - InputStreamReader input = null; - try { - java.lang.Process logcat = new ProcessBuilder( - // Time out after 10s of inactivity, but kill logcat with SEGV - // so we can investigate why it didn't finish. - "/system/bin/timeout", "-i", "-s", "SEGV", "10s", - // Merge several logcat streams, and take the last N lines. - "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system", - "-b", "main", "-b", "crash", "-t", String.valueOf(lines)) - .redirectErrorStream(true).start(); - - try { logcat.getOutputStream().close(); } catch (IOException e) {} - try { logcat.getErrorStream().close(); } catch (IOException e) {} - input = new InputStreamReader(logcat.getInputStream()); - - int num; - char[] buf = new char[8192]; - while ((num = input.read(buf)) > 0) sb.append(buf, 0, num); - } catch (IOException e) { - Slog.e(TAG, "Error running logcat", e); - } finally { - if (input != null) try { input.close(); } catch (IOException e) {} - } - } + InputStreamReader input = null; + try { + java.lang.Process logcat = + new ProcessBuilder( + // Time out after 10s of inactivity, but + // kill logcat with SEGV + // so we can investigate why it didn't + // finish. + "/system/bin/timeout", + "-i", + "-s", + "SEGV", + "10s", + // Merge several logcat streams, and take + // the last N lines. + "/system/bin/logcat", + "-v", + "threadtime", + "-b", + "events", + "-b", + "system", + "-b", + "main", + "-b", + "crash", + "-t", + String.valueOf(lines)) + .redirectErrorStream(true) + .start(); - dbox.addText(dropboxTag, sb.toString()); - } - }; + try { + logcat.getOutputStream().close(); + } catch (IOException e) { + } + try { + logcat.getErrorStream().close(); + } catch (IOException e) { + } + input = new InputStreamReader(logcat.getInputStream()); + + int num; + char[] buf = new char[8192]; + while ((num = input.read(buf)) > 0) sb.append(buf, 0, num); + } catch (IOException e) { + Slog.e(TAG, "Error running logcat", e); + } finally { + if (input != null) + try { + input.close(); + } catch (IOException e) { + } + } + } + + dbox.addText(dropboxTag, sb.toString()); + } + }; if (runSynchronously) { final int oldMask = StrictMode.allowThreadDiskWritesMask(); @@ -10166,43 +10167,48 @@ public class ActivityManagerService extends IActivityManager.Stub mOomAdjuster.dumpCacheOomRankerSettings(pw); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); - + pw.println( + "-------------------------------------------------------------------------------"); } dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); - + pw.println( + "-------------------------------------------------------------------------------"); } mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } if (dumpAll || dumpPackage != null) { dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } } mCpHelper.dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } dumpPermissions(fd, pw, args, opti, dumpAll, dumpPackage); pw.println(); sdumper = mServices.newServiceDumperLocked(fd, pw, args, opti, dumpAll, dumpPackage); if (!dumpClient) { if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } sdumper.dumpLocked(); } @@ -10217,7 +10223,8 @@ public class ActivityManagerService extends IActivityManager.Stub // method with the lock held. if (dumpClient) { if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } sdumper.dumpWithClient(); } @@ -10230,33 +10237,38 @@ public class ActivityManagerService extends IActivityManager.Stub // proxies in the first place. pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } dumpBinderProxies(pw, BINDER_PROXY_HIGH_WATERMARK /* minToDump */); } synchronized(this) { pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } mAtmInternal.dump(DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, displayIdFilter); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } mAtmInternal.dump(DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, displayIdFilter); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } mAtmInternal.dump(DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, displayIdFilter); if (dumpPackage == null) { pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } mAtmInternal.dump(DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, displayIdFilter); @@ -10266,7 +10278,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (!dumpNormalPriority) { pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } mAtmInternal.dump(DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, displayIdFilter); @@ -10274,45 +10287,53 @@ public class ActivityManagerService extends IActivityManager.Stub if (mAssociations.size() > 0) { pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } dumpAssociationsLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); } pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage); - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage); } if (dumpPackage == null) { pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } mOomAdjProfiler.dump(pw); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } dumpLmkLocked(pw); } pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } synchronized (mProcLock) { mProcessList.dumpProcessesLSP(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId); } pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } dumpUsers(pw); pw.println(); if (dumpAll) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); } mComponentAliasResolver.dump(pw); } @@ -10322,7 +10343,8 @@ public class ActivityManagerService extends IActivityManager.Stub * Dump the app restriction controller, it's required not to hold the global lock here. */ private void dumpAppRestrictionController(PrintWriter pw) { - pw.println("-------------------------------------------------------------------------------"); + pw.println( + "-------------------------------------------------------------------------------"); mAppRestrictionController.dump(pw, ""); } @@ -11431,9 +11453,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } 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( @@ -11462,7 +11482,9 @@ public class ActivityManagerService extends IActivityManager.Stub void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { - pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)"); + pw.println( + "ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity" + + " allowed-associations)"); boolean printed = false; if (mAllowedAssociations != null) { for (int i = 0; i < mAllowedAssociations.size(); i++) { @@ -11581,11 +11603,9 @@ public class ActivityManagerService extends IActivityManager.Stub } 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; @@ -11641,9 +11661,8 @@ public class ActivityManagerService extends IActivityManager.Stub 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; @@ -12816,7 +12835,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (!opts.isCompact) { pw.print(" Used RAM: "); pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss + kernelUsed)); pw.print(" ("); - pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss)); pw.print(" used pss + "); + pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss)); + pw.print(" used pss + "); pw.print(stringifyKBSize(kernelUsed)); pw.print(" kernel)\n"); pw.print(" Lost RAM: "); pw.println(stringifyKBSize(lostRAM)); } else { @@ -13490,9 +13510,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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); @@ -13686,8 +13704,15 @@ public class ActivityManagerService extends IActivityManager.Stub } validateServiceInstanceName(instanceName); - if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, - "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground); + if (DEBUG_SERVICE) + Slog.v( + TAG_SERVICE, + "*** startService: " + + service + + " type=" + + resolvedType + + " fg=" + + requireForeground); final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); @@ -14599,7 +14624,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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), @@ -15448,9 +15473,14 @@ public class ActivityManagerService extends IActivityManager.Stub if (checkPermission(android.Manifest.permission.BROADCAST_STICKY, callingPid, callingUid) != PackageManager.PERMISSION_GRANTED) { - String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid=" - + callingPid + ", uid=" + callingUid - + " requires " + android.Manifest.permission.BROADCAST_STICKY; + String msg = + "Permission Denial: broadcastIntent() requesting a sticky broadcast from" + + " pid=" + + callingPid + + ", uid=" + + callingUid + + " requires " + + android.Manifest.permission.BROADCAST_STICKY; Slog.w(TAG, msg); throw new SecurityException(msg); } @@ -15593,7 +15623,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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, @@ -15686,7 +15716,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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, @@ -15974,9 +16004,7 @@ public class ActivityManagerService extends IActivityManager.Stub } void backgroundServicesFinishedLocked(int userId) { - for (BroadcastQueue queue : mBroadcastQueues) { - queue.backgroundServicesFinishedLocked(userId); - } + mBroadcastQueue.backgroundServicesFinishedLocked(userId); } public void finishReceiver(IBinder caller, int resultCode, String resultData, @@ -15997,8 +16025,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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); @@ -16589,10 +16616,7 @@ public class ActivityManagerService extends IActivityManager.Stub // ========================================================= 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; } @@ -16716,10 +16740,8 @@ public class ActivityManagerService extends IActivityManager.Stub */ @GuardedBy("this") final boolean canGcNowLocked() { - for (BroadcastQueue q : mBroadcastQueues) { - if (!q.isIdleLocked()) { - return false; - } + if (!mBroadcastQueue.isIdleLocked()) { + return false; } return mAtmInternal.canGcNow(); } @@ -17929,9 +17951,7 @@ public class ActivityManagerService extends IActivityManager.Stub } void onProcessFreezableChangedLocked(ProcessRecord app) { - if (mEnableModernQueue) { - mBroadcastQueues[0].onProcessFreezableChangedLocked(app); - } + mBroadcastQueue.onProcessFreezableChangedLocked(app); } @VisibleForTesting @@ -19622,9 +19642,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (flushBroadcastLoopers) { BroadcastLoopers.waitForIdle(pw); } - for (BroadcastQueue queue : mBroadcastQueues) { - queue.waitForIdle(pw); - } + mBroadcastQueue.waitForIdle(pw); pw.println("All broadcast queues are idle!"); pw.flush(); } @@ -19640,9 +19658,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (flushBroadcastLoopers) { BroadcastLoopers.waitForBarrier(pw); } - for (BroadcastQueue queue : mBroadcastQueues) { - queue.waitForBarrier(pw); - } + mBroadcastQueue.waitForBarrier(pw); if (flushApplicationThreads) { waitForApplicationBarrier(pw); } @@ -19718,9 +19734,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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) { @@ -19765,9 +19779,7 @@ public class ActivityManagerService extends IActivityManager.Stub return; } - for (BroadcastQueue queue : mBroadcastQueues) { - queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs); - } + mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs); } @Override @@ -20321,7 +20333,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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); @@ -20337,26 +20349,8 @@ public class ActivityManagerService extends IActivityManager.Stub // 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 */ @@ -20678,14 +20672,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } - 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 8aa3921d3f2f..000000000000 --- 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 3c56752d08e3..000000000000 --- 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 98263df3c4e8..4422608a8893 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.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; 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.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.BundleMerger; import android.os.Handler; @@ -85,9 +89,12 @@ import com.android.internal.annotations.VisibleForTesting; 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 @@ class BroadcastQueueModernImpl extends BroadcastQueue { 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 4ef31bff9a42..f2b9b25d6097 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 @@ import java.io.PrintWriter; /** * 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 @@ final class ConnectionRecord { 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 825b938cdafa..3988277ab525 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 @@ package com.android.server.am; 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 @@ import com.android.internal.app.procstats.ProcessStats; /** * 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 @@ public final class ContentProviderConnection extends Binder { 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 31328ae83f71..cd6964ea2631 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 @@ public class OomAdjuster { } @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 @@ public class OomAdjuster { } @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 @@ public class OomAdjuster { * 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 @@ public class OomAdjuster { 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 @@ public class OomAdjuster { 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 @@ public class OomAdjuster { } @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 @@ public class OomAdjuster { 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 @@ public class OomAdjuster { 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 1bf779adcce1..dd75bc0442d0 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_TOP_SLEEPING; 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.annotation.Nullable; 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.Retention; 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 @@ public class OomAdjusterModernImpl extends OomAdjuster { mProcessRecordNodes[i] = new LinkedProcessRecordList(type); } mLastNode = new ProcessRecordNode[size]; + resetLastNodes(); } int size() { @@ -285,7 +288,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { void reset() { for (int i = 0; i < mProcessRecordNodes.length; i++) { mProcessRecordNodes[i].reset(); - mLastNode[i] = null; + setLastNodeToHead(i); } } @@ -509,26 +512,118 @@ public class OomAdjusterModernImpl extends OomAdjuster { } /** - * 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 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; - private final UnreachedProcessCollector mUnreachedProcessCollector = - new UnreachedProcessCollector(); + // 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 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } + 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 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } + 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; + fullUpdateLSP(oomAdjReason); - 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); + mService.mOomAdjProfiler.oomAdjEnded(); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } - // Copy all of the reachable processes into the target process set. - targetProcesses.addAll(reachableProcesses); - reachableProcesses.clear(); + @GuardedBy({"mService", "mProcLock"}) + @Override + protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) { + mPendingProcessSet.add(app); + performUpdateOomAdjPendingTargetsLocked(oomAdjReason); + return true; + } - final boolean result = performNewUpdateOomAdjLSP(oomAdjReason, - topApp, targetProcesses, uids, false, now, cachedAdj); + @GuardedBy("mService") + @Override + protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); + mService.mOomAdjProfiler.oomAdjStarted(); - 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); + synchronized (mProcLock) { + partialUpdateLSP(oomAdjReason, mPendingProcessSet); } - targetProcesses.clear(); - reachableProcesses.clear(); + mPendingProcessSet.clear(); mService.mOomAdjProfiler.oomAdjEnded(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - return result; } + /** + * Perform a full update on the entire process list. + */ @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(); - } + 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; - 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(); + mNewNumServiceProcs = 0; + mNewNumAServiceProcs = 0; - // 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); + // Clear the priority queues. + mProcessRecordProcStateNodes.reset(); + mProcessRecordAdjNodes.reset(); - 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(); + 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(); } - } - updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids, - cachedAdj, now, fullUpdate); + // 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); + } - clientProcesses.clear(); + // 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(); - return true; + assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP()); + postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime); } /** - * 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}. + * Traverse the process graph and update processes based on changes in connection importances. */ - @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); - } - - clientProcesses.clear(); - mUnreachedProcessCollector.processList = clientProcesses; - for (int i = 0; i < appsSize; i++) { - final ProcessRecord app = apps.valueAt(i); - app.forEachClient(mUnreachedProcessCollector); + @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, mComputeConnectionsConsumer); } - mUnreachedProcessCollector.processList = null; - // 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); + // 2nd pass, scan each slot in the adj node list. + for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) { + mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer); } } + /** + * Perform a partial update on the target processes and their reachable processes. + */ @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(); + 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; - final int procStateTarget = mProcessRecordProcStateNodes.size() - 1; - final int adjTarget = mProcessRecordAdjNodes.size() - 1; + ActiveUids activeUids = mTmpUidRecords; + activeUids.clear(); + mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, activeUids, false); 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); - } - // 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); - } - // 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. - } + final ArrayList<ProcessRecord> reachables = mTmpProcessList; + reachables.clear(); - // 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. + 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); + } - mProcessRecordAdjNodes.resetLastNodes(); + // Collect all processes that are reachable. + // Any process not found in this step will not change in importance during this update. + collectAndMarkReachableProcessesLSP(reachables); - // 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); - } + // 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); - // 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); + // 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; + } } - } - @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 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()); } - if (uids != null) { - final UidRecord uidRec = app.getUidRecord(); - - if (uidRec != null) { - uids.put(uidRec.getUid(), uidRec); + // 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); } } - computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason, - false); + postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime); } /** - * @return The proposed change to the schedGroup. + * 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 int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj, - int schedGroup) { - schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup); - - updateAdjSlotIfNecessary(app, prevRawAppAdj); - - return schedGroup; + 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); + } } - @GuardedBy({"mService", "mProcLock"}) - @Override - protected void setIntermediateProcStateLSP(ProcessRecord app, int procState, - int prevProcState) { - super.setIntermediateProcStateLSP(app, procState, prevProcState); + /** + * 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(); - updateProcStateSlotIfNecessary(app, prevProcState); + 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 @@ public class OomAdjusterModernImpl extends OomAdjuster { ? 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 @@ public class OomAdjusterModernImpl extends OomAdjuster { 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 @@ public class OomAdjusterModernImpl extends OomAdjuster { 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); + } + } + + /** + * 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); + } + } + } - computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false, - oomAdjReason, cachedAdj, false, false); + 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 2ef433cad8ce..0aa1a69334d7 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 @@ class ProcessErrorStateRecord { 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 89c89944e92e..a1fdd5070527 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 @@ public final class ProcessList { // 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 7009bd0b4695..7356588b408a 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.WindowProcessController; 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 @@ class ProcessRecord implements WindowProcessListener { && !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 562beaf50a7f..3d695bcdb767 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 @@ final class ProcessServiceRecord { 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/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 68b4e3fb51ba..7ee2a7ababb3 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 @@ public class FaceService extends SystemService { 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 @@ public class FaceService extends SystemService { 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 @@ public class FaceService extends SystemService { 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 000000000000..413020eb9bd9 --- /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 49f607095b90..4c3020f58870 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { 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 @@ public class ClipboardService extends SystemService { } } }); + } else if (Build.IS_ARC) { + mClipboardMonitor = new ArcClipboardMonitor((clip, uid) -> { + setPrimaryClipInternal(clip, uid); + }); } else { - mEmulatorClipboardMonitor = (clip) -> {}; + mClipboardMonitor = (clip) -> {}; } updateConfig(); @@ -937,7 +941,7 @@ public class ClipboardService extends SystemService { 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 f50831964303..55601bc5a596 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 a43f93a9beac..91e560e19f0d 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 static com.android.server.display.AutomaticBrightnessController.AUTO_BRIG import android.annotation.Nullable; import android.hardware.display.DisplayManagerInternal; -import android.os.PowerManager; import android.os.Trace; /** @@ -110,7 +109,6 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display 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 2010aca72494..7f014f6dae28 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 f6d02dbc46df..34d53be6933a 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 android.view.Display; 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 @@ public final class DisplayBrightnessController { /** * 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 8e844501ab34..71cc8725f85b 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 @@ public class DisplayBrightnessStrategySelector { } 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 d1ca49b8bf79..8b54b22082fd 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 @@ public class AutomaticBrightnessStrategy { 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 a23c08a9e5c6..d876a381ca7a 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 @@ public class HdmiCecMessageValidator { 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 c8c662387d16..d0532b995deb 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 class HdmiControlService extends SystemService { 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 fc994719c85d..0ca48084f4e1 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } /** - * 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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub @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 @@ public class ContextHubClientBroker extends IContextHubClient.Stub // 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/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING index 8db59055c1f4..4fc1a17032e9 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 f645eaa28632..3ecc58e2aef2 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 @@ abstract public class ManagedServices { 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 @@ abstract public class ManagedServices { for (int uid : uidList) { if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) { anyServicesInvolved = true; + trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid)); } } } @@ -1135,8 +1153,7 @@ abstract public class ManagedServices { 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 @@ abstract public class ManagedServices { 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 @@ abstract public class ManagedServices { 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/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index bc86c8216952..289faf45a253 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 android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; 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 class ZenModeHelper { 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 @@ public class ZenModeHelper { 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()) { + if (!Objects.equals(rule.conditionId, azr.getConditionId())) { + rule.conditionId = azr.getConditionId(); + modified = true; + } + 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; } - 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(); // 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 @@ public class ZenModeHelper { 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 @@ public class ZenModeHelper { // 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 @@ public class ZenModeHelper { * 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 @@ public class ZenModeHelper { } zenRule.zenPolicyUserModifiedFields = userModifiedFields; } + + return !newPolicy.equals(oldPolicy); } /** @@ -1283,12 +1337,14 @@ public class ZenModeHelper { * <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 @@ public class ZenModeHelper { } zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields; } + + return !newEffects.equals(oldEffects); } private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) { @@ -2505,7 +2563,7 @@ public class ZenModeHelper { 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 0abe50f12772..71800efae292 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.IOnDeviceIntelligenceManager; 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.RemoteException; 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 @@ import java.util.Set; * 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 @@ public class OnDeviceIntelligenceManagerService extends SystemService { protected final Object mLock = new Object(); - private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService; + private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService; private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService; volatile boolean mIsServiceEnabled; @@ -165,7 +167,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); mRemoteOnDeviceIntelligenceService.post( - service -> service.getFeature(id, featureCallback)); + service -> service.getFeature(Binder.getCallingUid(), id, featureCallback)); } @Override @@ -185,7 +187,7 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); mRemoteOnDeviceIntelligenceService.post( - service -> service.listFeatures(listFeaturesCallback)); + service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback)); } @Override @@ -207,7 +209,8 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); mRemoteOnDeviceIntelligenceService.post( - service -> service.getFeatureDetails(feature, featureDetailsCallback)); + service -> service.getFeatureDetails(Binder.getCallingUid(), feature, + featureDetailsCallback)); } @Override @@ -227,33 +230,35 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } 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 @@ public class OnDeviceIntelligenceManagerService extends SystemService { 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 @@ public class OnDeviceIntelligenceManagerService extends SystemService { "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 @@ public class OnDeviceIntelligenceManagerService extends SystemService { 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 @@ public class OnDeviceIntelligenceManagerService extends SystemService { "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 @@ public class OnDeviceIntelligenceManagerService extends SystemService { Bundle processingState, IProcessingUpdateStatusCallback callback) { try { - ensureRemoteTrustedInferenceServiceInitialized(); + ensureRemoteInferenceServiceInitialized(); mRemoteInferenceService.post( service -> service.updateProcessingState( processingState, callback)); @@ -363,22 +368,24 @@ public class OnDeviceIntelligenceManagerService extends SystemService { }; } - 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 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } 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 index cc8e78804bb6..69ba1d2fb599 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 static android.content.Context.BIND_INCLUDE_CAPABILITIES; 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 @@ public class RemoteOnDeviceTrustedInferenceService extends * {@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/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java index ef8453dcee67..29de26e41df4 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.PackageInstaller.UNARCHIVAL_STATUS_UNSET; 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 @@ public class PackageArchiver { 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 @@ public class PackageArchiver { } 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/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 305b087190d6..5c8215e4bfdb 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 @@ public class DomainVerificationService extends SystemService 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 @@ public class DomainVerificationService extends SystemService 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 @@ public class DomainVerificationService extends SystemService @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 3fd00c6993cf..b8c4d22f186a 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 @@ import java.util.regex.Matcher; 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 @@ public final class DomainVerificationUtils { 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 24d7acd772c1..53607880ff48 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 @@ public class ThermalManagerService extends SystemService { return runOverrideStatus(); case "reset": return runReset(); + case "headroom": + return runHeadroom(); default: return handleDefaultCommands(cmd); } @@ -862,6 +864,36 @@ public class ThermalManagerService extends SystemService { } } + 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 @@ public class ThermalManagerService extends SystemService { 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 ffce50e5bd5e..79adcb438e6b 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 @@ public class TvInteractiveAppManagerService extends SystemService { } } - 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 5b77433fa6d9..2fc183d9113f 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 @@ final class VibrationSettings { 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 9616c286f1b3..5175b74116f4 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 @@ public class WallpaperCropper { (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 @@ public class WallpaperCropper { 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 @@ public class WallpaperCropper { } } } 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 @@ public class WallpaperCropper { 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 88e9672cd0a1..0165d65283dc 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 @@ public class WallpaperDataParser { } else { wallpaper.cropHint.set(totalCropHint); } + wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f); } else { wallpaper.cropHint.set(totalCropHint); } @@ -493,6 +494,7 @@ public class WallpaperDataParser { 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 d30a2167a183..7ba953dae55c 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * 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 6ad056f5a902..2c39c5875389 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 @@ class ActivityStarter { 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 2cda1f55b038..826e332b5f3c 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { // 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { 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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index b88d7f7e50a8..83f44d23dbb1 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -520,37 +520,11 @@ class InsetsSourceProvider { updateVisibility(); mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash, mClientVisible, surfacePosition, getInsetsHint()); - mStateController.notifySurfaceTransactionReady(this, getSurfaceTransactionId(leash), true); ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource Control %s for target %s", mControl, mControlTarget); } - private long getSurfaceTransactionId(SurfaceControl leash) { - // Here returns mNativeObject (long) as the ID instead of the leash itself so that - // InsetsStateController won't keep referencing the leash unexpectedly. - return leash != null ? leash.mNativeObject : 0; - } - - /** - * This is called when the surface transaction of the leash initialization has been committed. - * - * @param id Indicates which transaction is committed so that stale callbacks can be dropped. - */ - void onSurfaceTransactionCommitted(long id) { - if (mIsLeashReadyForDispatching) { - return; - } - if (mControl == null) { - return; - } - if (id != getSurfaceTransactionId(mControl.getLeash())) { - return; - } - mIsLeashReadyForDispatching = true; - mStateController.notifySurfaceTransactionReady(this, 0, false); - } - void startSeamlessRotation() { if (!mSeamlessRotating) { mSeamlessRotating = true; @@ -571,6 +545,10 @@ class InsetsSourceProvider { return true; } + void onSurfaceTransactionApplied() { + mIsLeashReadyForDispatching = true; + } + void setClientVisible(boolean clientVisible) { if (mClientVisible == clientVisible) { return; @@ -755,7 +733,6 @@ class InsetsSourceProvider { public void onAnimationCancelled(SurfaceControl animationLeash) { if (mAdapter == this) { mStateController.notifyControlRevoked(mControlTarget, InsetsSourceProvider.this); - mStateController.notifySurfaceTransactionReady(InsetsSourceProvider.this, 0, false); mControl = null; mControlTarget = null; mAdapter = null; diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index ba578f642429..6b9fcf411ce1 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -34,7 +34,6 @@ import android.os.Trace; import android.util.ArrayMap; import android.util.ArraySet; import android.util.SparseArray; -import android.util.SparseLongArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; import android.view.InsetsSourceControl; @@ -59,7 +58,6 @@ class InsetsStateController { private final DisplayContent mDisplayContent; private final SparseArray<InsetsSourceProvider> mProviders = new SparseArray<>(); - private final SparseLongArray mSurfaceTransactionIds = new SparseLongArray(); private final ArrayMap<InsetsControlTarget, ArrayList<InsetsSourceProvider>> mControlTargetProvidersMap = new ArrayMap<>(); private final SparseArray<InsetsControlTarget> mIdControlTargetMap = new SparseArray<>(); @@ -362,32 +360,14 @@ class InsetsStateController { notifyPendingInsetsControlChanged(); } - void notifySurfaceTransactionReady(InsetsSourceProvider provider, long id, boolean ready) { - if (ready) { - mSurfaceTransactionIds.put(provider.getSource().getId(), id); - } else { - mSurfaceTransactionIds.delete(provider.getSource().getId()); - } - } - private void notifyPendingInsetsControlChanged() { if (mPendingControlChanged.isEmpty()) { return; } - final int size = mSurfaceTransactionIds.size(); - final SparseLongArray surfaceTransactionIds = new SparseLongArray(size); - for (int i = 0; i < size; i++) { - surfaceTransactionIds.append( - mSurfaceTransactionIds.keyAt(i), mSurfaceTransactionIds.valueAt(i)); - } mDisplayContent.mWmService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { - for (int i = 0; i < size; i++) { - final int sourceId = surfaceTransactionIds.keyAt(i); - final InsetsSourceProvider provider = mProviders.get(sourceId); - if (provider == null) { - continue; - } - provider.onSurfaceTransactionCommitted(surfaceTransactionIds.valueAt(i)); + for (int i = mProviders.size() - 1; i >= 0; i--) { + final InsetsSourceProvider provider = mProviders.valueAt(i); + provider.onSurfaceTransactionApplied(); } final ArraySet<InsetsControlTarget> newControlTargets = new ArraySet<>(); int displayId = mDisplayContent.getDisplayId(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 07a03ebcc9b2..a75d3b6ef16d 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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * * @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 @@ class RootWindowContainer extends WindowContainer<DisplayContent> 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 e16d869fdcd9..73aa3078d12b 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 @@ class Task extends TaskFragment { } effectiveUid = info.applicationInfo.uid; mIsEffectivelySystemApp = info.applicationInfo.isSystemApp(); - stringName = null; if (info.targetActivity == null) { if (_intent != null) { @@ -1045,6 +1044,7 @@ class Task extends TaskFragment { updateTaskDescription(); } mSupportsPictureInPicture = info.supportsPictureInPicture(); + stringName = null; // Re-adding the task to Recents once updated if (inRecents) { @@ -4948,13 +4948,6 @@ class Task extends TaskFragment { } } - 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 @@ class Task extends TaskFragment { } 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 a818a721f964..597e901f62ef 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { // 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.isVisibleRequested() || next.mAppStopped) { - next.setVisibility(true); + 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; } - next.completeResumeLocked(); - return true; } try { @@ -1655,17 +1638,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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 7fc61e1ac7f3..a84a99a9fbc5 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { // 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { @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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { @Override public void dump(PrintWriter pw, String prefix) { - pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash="); - pw.print(mAnimationLeash); - pw.println(); } @Override @@ -616,16 +609,16 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { 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/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 5e7f1cbdd06e..b43a4540bbde 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -28,7 +28,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.content.Context; -import android.os.HandlerExecutor; import android.os.Trace; import android.util.Slog; import android.util.TimeUtils; @@ -70,8 +69,6 @@ public class WindowAnimator { private Choreographer mChoreographer; - private final HandlerExecutor mExecutor; - /** * Indicates whether we have an animation frame callback scheduled, which will happen at * vsync-app and then schedule the animation tick at the right time (vsync-sf). @@ -83,7 +80,8 @@ public class WindowAnimator { * A list of runnable that need to be run after {@link WindowContainer#prepareSurfaces} is * executed and the corresponding transaction is closed and applied. */ - private ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>(); + private final ArrayList<Runnable> mAfterPrepareSurfacesRunnables = new ArrayList<>(); + private boolean mInExecuteAfterPrepareSurfacesRunnables; private final SurfaceControl.Transaction mTransaction; @@ -94,7 +92,6 @@ public class WindowAnimator { mTransaction = service.mTransactionFactory.get(); service.mAnimationHandler.runWithScissors( () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */); - mExecutor = new HandlerExecutor(service.mAnimationHandler); mAnimationFrameCallback = frameTimeNs -> { synchronized (mService.mGlobalLock) { @@ -200,19 +197,6 @@ public class WindowAnimator { updateRunningExpensiveAnimationsLegacy(); } - final ArrayList<Runnable> afterPrepareSurfacesRunnables = mAfterPrepareSurfacesRunnables; - if (!afterPrepareSurfacesRunnables.isEmpty()) { - mAfterPrepareSurfacesRunnables = new ArrayList<>(); - mTransaction.addTransactionCommittedListener(mExecutor, () -> { - synchronized (mService.mGlobalLock) { - // Traverse in order they were added. - for (int i = 0, size = afterPrepareSurfacesRunnables.size(); i < size; i++) { - afterPrepareSurfacesRunnables.get(i).run(); - } - afterPrepareSurfacesRunnables.clear(); - } - }); - } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "applyTransaction"); mTransaction.apply(); Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); @@ -220,6 +204,7 @@ public class WindowAnimator { ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate"); mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); + executeAfterPrepareSurfacesRunnables(); if (DEBUG_WINDOW_TRACE) { Slog.i(TAG, "!!! animate: exit" @@ -301,10 +286,34 @@ public class WindowAnimator { /** * Adds a runnable to be executed after {@link WindowContainer#prepareSurfaces} is called and - * the corresponding transaction is closed, applied, and committed. + * the corresponding transaction is closed and applied. */ void addAfterPrepareSurfacesRunnable(Runnable r) { + // If runnables are already being handled in executeAfterPrepareSurfacesRunnable, then just + // immediately execute the runnable passed in. + if (mInExecuteAfterPrepareSurfacesRunnables) { + r.run(); + return; + } + mAfterPrepareSurfacesRunnables.add(r); scheduleAnimation(); } + + void executeAfterPrepareSurfacesRunnables() { + + // Don't even think about to start recursing! + if (mInExecuteAfterPrepareSurfacesRunnables) { + return; + } + mInExecuteAfterPrepareSurfacesRunnables = true; + + // Traverse in order they were added. + final int size = mAfterPrepareSurfacesRunnables.size(); + for (int i = 0; i < size; i++) { + mAfterPrepareSurfacesRunnables.get(i).run(); + } + mAfterPrepareSurfacesRunnables.clear(); + mInExecuteAfterPrepareSurfacesRunnables = false; + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 1106a95fcdce..46bac161f0a6 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ 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 0b58543e076d..b32c54496002 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 @@ class PermissionService(private val service: AccessCheckingService) : ) 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 66e07175e7f5..c54a94eada0b 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 @@ class DomainVerificationManagerApiTest { } 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 fbb14c3db9f9..440905137cf9 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.verify; 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 @@ public class DisplayOffloadSessionImplTest { 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 e9315c8ed8e6..14d8a9c5f0f2 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_BRIG 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.LocalServices; 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 @@ public final class DisplayPowerControllerTest { 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 b99ecf3978ae..14de527aa1f7 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.Binder; 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 @@ public class LocalDisplayAdapterTest { 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 289d54b86c7d..9b6cc0a4b377 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 @@ public final class DisplayBrightnessControllerTest { 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 1c681ce21f02..0e89d8383a8f 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 @@ public final class DisplayBrightnessStrategySelectorTest { 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 ba462e363b4e..a5dc668944df 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 @@ public class AutomaticBrightnessStrategyTest { } @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 fb47aa8b9983..9d3caa555e60 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 @@ public abstract class BaseBroadcastQueueTest { @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1]; - @Mock AppOpsService mAppOpsService; @Mock @@ -120,6 +118,7 @@ public abstract class BaseBroadcastQueueTest { HandlerThread mHandlerThread; TestLooperManager mLooper; AtomicInteger mNextPid; + BroadcastHistory mEmptyHistory; /** * Map from PID to registered registered runtime receivers. @@ -137,6 +136,13 @@ public abstract class BaseBroadcastQueueTest { .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 @@ public abstract class BaseBroadcastQueueTest { 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 @@ public abstract class BaseBroadcastQueueTest { } @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 cc6fc80d9fa1..bcf297f37243 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 @@ public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest { 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 e141faf599fa..56e5bd6b0937 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.filters.MediumTest; 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.PrintWriter; 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 @@ import java.util.function.UnaryOperator; * 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { */ 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { }).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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { } 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { .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 we added package to process - verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName), - anyLong(), any()); - } + // 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've reported package as being used verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName), @@ -868,9 +824,6 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { */ @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { */ @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { // 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { */ @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { } 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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { */ @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { */ @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { @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 @@ public class BroadcastQueueTest extends BaseBroadcastQueueTest { @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 f0efb795f5a1..878b945766af 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 android.util.SparseArray; 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 @@ public class BroadcastRecordTest { } } - /** - * 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 1226f0c0b315..872ac40a4d76 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 @@ public class MockingOomAdjusterTests { } 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 @@ public class MockingOomAdjusterTests { 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/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 6bcd778c234b..c6a6865f1cf1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -60,10 +60,13 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.Nullable; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.job.JobInfo; @@ -71,12 +74,15 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; import android.net.NetworkRequest; import android.os.Looper; import android.os.PowerManager; +import android.os.UserHandle; import android.provider.DeviceConfig; +import android.telephony.TelephonyManager; +import android.telephony.UiccSlotMapping; import android.util.ArraySet; import android.util.EmptyArray; import android.util.SparseArray; @@ -104,6 +110,9 @@ import java.time.Duration; import java.time.Instant; import java.time.ZoneOffset; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Executor; public class FlexibilityControllerTest { @@ -113,6 +122,9 @@ public class FlexibilityControllerTest { private MockitoSession mMockingSession; private BroadcastReceiver mBroadcastReceiver; + private final SparseArray<ArraySet<String>> mCarrierPrivilegedApps = new SparseArray<>(); + private final SparseArray<TelephonyManager.CarrierPrivilegesCallback> + mCarrierPrivilegedCallbacks = new SparseArray<>(); private FlexibilityController mFlexibilityController; private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; private JobStore mJobStore; @@ -130,6 +142,10 @@ public class FlexibilityControllerTest { @Mock private PrefetchController mPrefetchController; @Mock + private TelephonyManager mTelephonyManager; + @Mock + private IPackageManager mIPackageManager; + @Mock private PackageManager mPackageManager; @Before @@ -138,6 +154,7 @@ public class FlexibilityControllerTest { .initMocks(this) .strictness(Strictness.LENIENT) .spyStatic(DeviceConfig.class) + .mockStatic(AppGlobals.class) .mockStatic(LocalServices.class) .startMocking(); // Called in StateController constructor. @@ -167,17 +184,23 @@ public class FlexibilityControllerTest { -> mDeviceConfigPropertiesBuilder.build()) .when(() -> DeviceConfig.getProperties( eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any())); + // Used in FlexibilityController.SpecialAppTracker. + when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) + .thenReturn(true); //used to get jobs by UID mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir()); doReturn(mJobStore).when(mJobSchedulerService).getJobStore(); // Used in JobStatus. - doReturn(mock(PackageManagerInternal.class)) - .when(() -> LocalServices.getService(PackageManagerInternal.class)); + doReturn(mIPackageManager).when(AppGlobals::getPackageManager); // Freeze the clocks at a moment in time JobSchedulerService.sSystemClock = Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC); JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC); + // Set empty set of privileged apps. + setSimSlotMappings(null); + setPowerWhitelistExceptIdle(); // Initialize real objects. doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any()); ArgumentCaptor<BroadcastReceiver> receiverCaptor = @@ -249,9 +272,13 @@ public class FlexibilityControllerTest { } private JobStatus createJobStatus(String testTag, JobInfo.Builder job) { + return createJobStatus(testTag, job, SOURCE_PACKAGE); + } + + private JobStatus createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage) { JobInfo jobInfo = job.build(); JobStatus js = JobStatus.createFromJobInfo( - jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag); + jobInfo, 1000, sourcePackage, SOURCE_USER_ID, "FCTest", testTag); js.enqueueTime = FROZEN_TIME; js.setStandbyBucket(ACTIVE_INDEX); if (js.hasFlexibilityConstraint()) { @@ -1084,7 +1111,6 @@ public class FlexibilityControllerTest { @Test public void testAllowlistedAppBypass() { - setPowerWhitelistExceptIdle(); mFlexibilityController.onSystemServicesReady(); JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass", @@ -1118,6 +1144,148 @@ public class FlexibilityControllerTest { } @Test + public void testCarrierPrivilegedAppBypass() throws Exception { + mFlexibilityController.onSystemServicesReady(); + + final String carrier1Pkg1 = "com.test.carrier.1.pkg.1"; + final String carrier1Pkg2 = "com.test.carrier.1.pkg.2"; + final String carrier2Pkg = "com.test.carrier.2.pkg"; + final String nonCarrierPkg = "com.test.normal.pkg"; + + setPackageUid(carrier1Pkg1, 1); + setPackageUid(carrier1Pkg2, 11); + setPackageUid(carrier2Pkg, 2); + setPackageUid(nonCarrierPkg, 3); + + // Set the second carrier's privileged list before SIM configuration is sent to test + // initialization. + setCarrierPrivilegedAppList(2, carrier2Pkg); + + UiccSlotMapping sim1 = mock(UiccSlotMapping.class); + UiccSlotMapping sim2 = mock(UiccSlotMapping.class); + doReturn(1).when(sim1).getLogicalSlotIndex(); + doReturn(2).when(sim2).getLogicalSlotIndex(); + setSimSlotMappings(List.of(sim1, sim2)); + + JobStatus jsHighC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg1); + JobStatus jsDefaultC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg1); + JobStatus jsLowC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg1); + JobStatus jsMinC1P1 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg1); + JobStatus jsHighC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg2); + JobStatus jsDefaultC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg2); + JobStatus jsLowC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg2); + JobStatus jsMinC1P2 = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg2); + JobStatus jsHighC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier2Pkg); + JobStatus jsDefaultC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier2Pkg); + JobStatus jsLowC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier2Pkg); + JobStatus jsMinC2P = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier2Pkg); + JobStatus jsHighNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_HIGH), nonCarrierPkg); + JobStatus jsDefaultNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), nonCarrierPkg); + JobStatus jsLowNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_LOW), nonCarrierPkg); + JobStatus jsMinNCP = createJobStatus("testCarrierPrivilegedAppBypass", + createJob(0).setPriority(JobInfo.PRIORITY_MIN), nonCarrierPkg); + + setCarrierPrivilegedAppList(1); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + + // Only mark the first package of carrier 1 as privileged. Only that app's jobs should + // be exempted. + setCarrierPrivilegedAppList(1, carrier1Pkg1); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + + // Add the second package of carrier 1. Both apps' jobs should be exempted. + setCarrierPrivilegedAppList(1, carrier1Pkg1, carrier1Pkg2); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + + // Remove a SIM slot. The relevant app's should no longer have exempted jobs. + setSimSlotMappings(List.of(sim1)); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2)); + assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP)); + assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP)); + } + } + + @Test public void testForegroundAppBypass() { JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)); @@ -1753,6 +1921,24 @@ public class FlexibilityControllerTest { } } + private void setCarrierPrivilegedAppList(int logicalIndex, String... packages) { + final ArraySet<String> packageSet = packages == null + ? new ArraySet<>() : new ArraySet<>(packages); + mCarrierPrivilegedApps.put(logicalIndex, packageSet); + + TelephonyManager.CarrierPrivilegesCallback callback = + mCarrierPrivilegedCallbacks.get(logicalIndex); + if (callback != null) { + callback.onCarrierPrivilegesChanged(packageSet, Collections.emptySet()); + waitForQuietModuleThread(); + } + } + + private void setPackageUid(final String pkgName, final int uid) throws Exception { + doReturn(uid).when(mIPackageManager) + .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid))); + } + private void setPowerWhitelistExceptIdle(String... packages) { doReturn(packages == null ? EmptyArray.STRING : packages) .when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle(); @@ -1763,6 +1949,47 @@ public class FlexibilityControllerTest { } } + private void setSimSlotMappings(@Nullable Collection<UiccSlotMapping> simSlotMapping) { + clearInvocations(mTelephonyManager); + final Collection<UiccSlotMapping> returnedMapping = simSlotMapping == null + ? Collections.emptyList() : simSlotMapping; + doReturn(returnedMapping).when(mTelephonyManager).getSimSlotMapping(); + if (mBroadcastReceiver != null) { + final Intent intent = new Intent(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED); + mBroadcastReceiver.onReceive(mContext, intent); + waitForQuietModuleThread(); + } + if (returnedMapping.size() > 0) { + ArgumentCaptor<TelephonyManager.CarrierPrivilegesCallback> callbackCaptor = + ArgumentCaptor.forClass(TelephonyManager.CarrierPrivilegesCallback.class); + ArgumentCaptor<Integer> logicalIndexCaptor = ArgumentCaptor.forClass(Integer.class); + + final int minExpectedNewRegistrations = Math.max(0, + returnedMapping.size() - mCarrierPrivilegedCallbacks.size()); + verify(mTelephonyManager, atLeast(minExpectedNewRegistrations)) + .registerCarrierPrivilegesCallback( + logicalIndexCaptor.capture(), any(), callbackCaptor.capture()); + + final List<Integer> registeredIndices = logicalIndexCaptor.getAllValues(); + final List<TelephonyManager.CarrierPrivilegesCallback> registeredCallbacks = + callbackCaptor.getAllValues(); + for (int i = 0; i < registeredIndices.size(); ++i) { + final int logicalIndex = registeredIndices.get(i); + final TelephonyManager.CarrierPrivilegesCallback callback = + registeredCallbacks.get(i); + + mCarrierPrivilegedCallbacks.put(logicalIndex, callback); + + // The API contract promises a callback upon registration with the current list. + final ArraySet<String> cpApps = mCarrierPrivilegedApps.get(logicalIndex); + callback.onCarrierPrivilegesChanged( + cpApps == null ? Collections.emptySet() : cpApps, + Collections.emptySet()); + } + waitForQuietModuleThread(); + } + } + private void setUidBias(int uid, int bias) { int prevBias = mJobSchedulerService.getUidBias(uid); doReturn(bias).when(mJobSchedulerService).getUidBias(uid); diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 5e5181bdfeeb..0089d4cafaad 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/appwidget/ApiCounterTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt new file mode 100644 index 000000000000..79766f8a46b7 --- /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 c8a5583de0b2..3aaac2e9cf1b 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 @@ public class FaceServiceTest { } @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 9ad2652e9c63..673140390ae7 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 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav } @Test - public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() { + public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_requestsAndUpdatesAudioStatus() { enableAdjustOnlyAbsoluteVolumeBehavior(); mNativeWrapper.clearResultMessages(); @@ -250,7 +250,22 @@ public abstract class BaseTvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehav ); 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 0aa72d00be1b..98e119cf0dad 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 @@ public class HdmiCecMessageValidatorTest { @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/utils/com/android/server/testutils/StubTransaction.java b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java index b9ece9360980..e27bb4c8c3b6 100644 --- a/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java +++ b/services/tests/servicestests/utils/com/android/server/testutils/StubTransaction.java @@ -40,19 +40,12 @@ import java.util.concurrent.Executor; public class StubTransaction extends SurfaceControl.Transaction { private HashSet<Runnable> mWindowInfosReportedListeners = new HashSet<>(); - private HashSet<SurfaceControl.TransactionCommittedListener> mTransactionCommittedListeners = - new HashSet<>(); @Override public void apply() { for (Runnable listener : mWindowInfosReportedListeners) { listener.run(); } - for (SurfaceControl.TransactionCommittedListener listener - : mTransactionCommittedListeners) { - listener.onTransactionCommitted(); - } - mTransactionCommittedListeners.clear(); } @Override @@ -246,9 +239,6 @@ public class StubTransaction extends SurfaceControl.Transaction { @Override public SurfaceControl.Transaction addTransactionCommittedListener(Executor executor, SurfaceControl.TransactionCommittedListener listener) { - SurfaceControl.TransactionCommittedListener listenerInner = - () -> executor.execute(listener::onTransactionCommitted); - mTransactionCommittedListeners.add(listenerInner); return this; } diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp index 2f29d10ec2f9..515898a883e8 100644 --- a/services/tests/uiservicestests/Android.bp +++ b/services/tests/uiservicestests/Android.bp @@ -48,6 +48,8 @@ android_test { "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 4dded1d0342d..05b6c907069b 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.any; 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 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, true); service.reregisterService(cn, 0); @@ -915,6 +917,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a", 0, false); service.reregisterService(cn, 0); @@ -945,6 +948,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, true); service.reregisterService(cn, 0); @@ -975,6 +979,7 @@ public class ManagedServicesTest extends UiServiceTestCase { return true; }); + mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>()); service.addApprovedList("a/a", 0, false); service.reregisterService(cn, 0); @@ -1152,6 +1157,58 @@ public class ManagedServicesTest extends UiServiceTestCase { } @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/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 78fb5705d5ac..bfc47fdef5cb 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 @@ public class PreferencesHelperTest extends UiServiceTestCase { 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 000000000000..f724510eeb73 --- /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 7c1adbc39033..c4d246052580 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 @@ public class ZenModeHelperTest extends UiServiceTestCase { @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 038f1db32d18..54ab367df381 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 @@ public class GroupedAggregatedLogRecordsTest { 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 @@ public class GroupedAggregatedLogRecordsTest { 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 f54c7e57828b..88a94830e98a 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 @@ public class VibrationSettingsTest { @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 @@ public class VibrationSettingsTest { @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 @@ public class VibrationSettingsTest { } @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 @@ public class VibrationSettingsTest { 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 09e7b9141e04..45a2ba4bfcca 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 @@ public class ActivityRecordTests extends WindowTestsBase { // 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 @@ public class ActivityRecordTests extends WindowTestsBase { // 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 @@ public class ActivityRecordTests extends WindowTestsBase { // 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 @@ public class ActivityRecordTests extends WindowTestsBase { // 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/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 1a1fe95756d7..0c1fbf3cb3d7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -94,7 +94,6 @@ public class AppTransitionControllerTest extends WindowTestsBase { public void setUp() throws Exception { assumeFalse(WindowManagerService.sEnableShellTransitions); mAppTransitionController = new AppTransitionController(mWm, mDisplayContent); - mWm.mAnimator.ready(); } @Test @@ -856,7 +855,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -887,7 +886,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, null /* changingTaskFragment */); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation is not run by the remote handler because the activity is filling the Task. assertFalse(remoteAnimationRunner.isAnimationStarted()); @@ -922,7 +921,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, null /* changingTaskFragment */); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -947,7 +946,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -974,7 +973,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment1); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation run by the remote handler. assertTrue(remoteAnimationRunner.isAnimationStarted()); @@ -998,7 +997,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation not run by the remote handler. assertFalse(remoteAnimationRunner.isAnimationStarted()); @@ -1025,7 +1024,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(openingActivity, closingActivity, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation should not run by the remote handler when there are non-embedded activities of // different UID. @@ -1052,7 +1051,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // Animation should not run by the remote handler when there is wallpaper in the transition. assertFalse(remoteAnimationRunner.isAnimationStarted()); @@ -1086,7 +1085,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity1, null /* closingActivity */, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // The animation will be animated remotely by client and all activities are input disabled // for untrusted animation. @@ -1137,7 +1136,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // The animation will be animated remotely by client and all activities are input disabled // for untrusted animation. @@ -1179,7 +1178,7 @@ public class AppTransitionControllerTest extends WindowTestsBase { // Prepare and start transition. prepareAndTriggerAppTransition(activity, null /* closingActivity */, taskFragment); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); // The animation will be animated remotely by client, but input should not be dropped for // fully trusted. diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 1f15ec3be3a8..2085d6140f68 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -371,7 +371,6 @@ public class InsetsStateControllerTest extends WindowTestsBase { mDisplayContent.getInsetsPolicy().updateBarControlTarget(app); mDisplayContent.getInsetsPolicy().showTransient(statusBars(), true /* isGestureOnSystemBar */); - mWm.mAnimator.ready(); waitUntilWindowAnimatorIdle(); assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars())); diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index a1638019359b..11d9629cf25e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -43,6 +43,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; @@ -112,7 +113,6 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0); mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter, mHandler, false /*isActivityEmbedding*/); - mWm.mAnimator.ready(); } private WindowState createAppOverlayWindow() { @@ -136,7 +136,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -168,7 +168,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -290,7 +290,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -336,7 +336,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { task.applyAnimationUnchecked(null /* lp */, true /* enter */, TRANSIT_OLD_TASK_OPEN, false /* isVoiceInteraction */, null /* sources */); mController.goodToGo(TRANSIT_OLD_TASK_OPEN); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); try { @@ -363,7 +363,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -417,7 +417,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -471,7 +471,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { ((AnimationAdapter) record.mThumbnailAdapter).startAnimation(mMockThumbnailLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION, mThumbnailFinishedCallback); mController.goodToGo(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -526,7 +526,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -559,7 +559,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -595,7 +595,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -645,7 +645,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor = ArgumentCaptor.forClass(RemoteAnimationTarget[].class); final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor = @@ -782,7 +782,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { mDisplayContent.applySurfaceChangesTransaction(); mController.goodToGo(TRANSIT_OLD_TASK_OPEN); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN), any(), any(), any(), any()); @@ -810,7 +810,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, mFinishedCallback); mController.goodToGo(transit); - waitUntilWindowAnimatorIdle(); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); return adapter; } 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 527ea0d35f02..ce9050456681 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 @@ public class RootTaskTests extends WindowTestsBase { 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 da11e6ad613a..649f5207e88f 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 @@ public class RootWindowContainerTests extends WindowTestsBase { 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/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index 7ab093d0ae13..a8f6fe86c823 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -546,7 +546,7 @@ public class SystemServicesTestRule implements TestRule { // This makes sure all previous messages in the handler are fully processed vs. just popping // them from the message queue. final AtomicBoolean currentMessagesProcessed = new AtomicBoolean(false); - wm.mAnimator.addAfterPrepareSurfacesRunnable(() -> { + wm.mAnimator.getChoreographer().postFrameCallback(time -> { synchronized (currentMessagesProcessed) { currentMessagesProcessed.set(true); currentMessagesProcessed.notifyAll(); diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 8b44579eda33..36adeec74e36 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 @@ public class UsbHostManager { 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 @@ public class UsbHostManager { } // 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 b84ff2977b34..12e04c24fa64 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -195,7 +195,8 @@ public class ImsService extends Service { // 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 @@ public class ImsService extends Service { */ 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 1d71f95ef64f..d658d5991a57 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -63,17 +63,20 @@ java_library { ], } -android_library_import { - name: "wm-flicker-window-extensions_nodeps", - aars: ["libs/window-extensions-release.aar"], +java_library { + name: "wm-flicker-window-extensions", sdk_version: "current", + static_libs: [ + "androidx.window.extensions_extensions-nodeps", + ], + installable: false, } java_library { - name: "wm-flicker-window-extensions", + name: "wm-flicker-window-extensions-core", sdk_version: "current", static_libs: [ - "wm-flicker-window-extensions_nodeps", + "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 Binary files differdeleted file mode 100644 index 918e514f4c89..000000000000 --- a/tests/FlickerTests/libs/window-extensions-release.aar +++ /dev/null diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp index b18bdff7263f..b1b314fcdb19 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 @@ cc_defaults { 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 @@ cc_binary_host { ], 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 @@ cc_binary_host { } // ========================================================== +// 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 000000000000..9d61111fb5bd --- /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 000000000000..d2492f75d383 --- /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 c9c50a561a04..5b35504865f8 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 @@ main(int argc, char const*const* argv) 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 index 05c8f0c54fac..05c8f0c54fac 100644 --- a/tools/streaming_proto/test/imported.proto +++ b/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 000000000000..2a7001b294ea --- /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 index de80ed6612fc..3cf81b4ffd56 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 1246f539b44b..000000000000 --- 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 000000000000..8df9716b312d --- /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")); +} |