diff options
308 files changed, 6993 insertions, 6466 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/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp index 8b8d361edbd4..a142450ac0c6 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.cpp +++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp @@ -134,8 +134,9 @@ JNIEnv* DeviceCallback::getJNIEnv() { return env; } -std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid, - uint16_t bus, const std::vector<uint8_t>& descriptor, +std::unique_ptr<Device> Device::open(int32_t id, const char* name, const char* uniq, int32_t vid, + int32_t pid, uint16_t bus, + const std::vector<uint8_t>& descriptor, std::unique_ptr<DeviceCallback> callback) { size_t size = descriptor.size(); if (size > HID_MAX_DESCRIPTOR_SIZE) { @@ -152,8 +153,7 @@ std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, struct uhid_event ev = {}; ev.type = UHID_CREATE2; strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name)); - std::string uniq = android::base::StringPrintf("Id: %d", id); - strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq)); + strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq, sizeof(ev.u.create2.uniq)); memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0])); ev.u.create2.rd_size = size; ev.u.create2.bus = bus; @@ -314,19 +314,31 @@ std::vector<uint8_t> getData(JNIEnv* env, jbyteArray javaArray) { return data; } -static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid, - jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) { +static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jstring rawUniq, jint id, + jint vid, jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) { ScopedUtfChars name(env, rawName); if (name.c_str() == nullptr) { return 0; } + std::string uniq; + if (rawUniq != nullptr) { + uniq = ScopedUtfChars(env, rawUniq); + } else { + uniq = android::base::StringPrintf("Id: %d", id); + } + + if (uniq.c_str() == nullptr) { + return 0; + } + std::vector<uint8_t> desc = getData(env, rawDescriptor); std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback)); std::unique_ptr<uhid::Device> d = - uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc, + uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), + reinterpret_cast<const char*>(uniq.c_str()), vid, pid, bus, desc, std::move(cb)); return reinterpret_cast<jlong>(d.release()); } @@ -370,7 +382,7 @@ static void closeDevice(JNIEnv* /* env */, jclass /* clazz */, jlong ptr) { static JNINativeMethod sMethods[] = { {"nativeOpenDevice", - "(Ljava/lang/String;IIII[B" + "(Ljava/lang/String;Ljava/lang/String;IIII[B" "Lcom/android/commands/hid/Device$DeviceCallback;)J", reinterpret_cast<void*>(openDevice)}, {"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)}, diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h index 9c6060d837e0..bc7a9092cc4e 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.h +++ b/cmds/hid/jni/com_android_commands_hid_Device.h @@ -42,8 +42,9 @@ private: class Device { public: - static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid, - uint16_t bus, const std::vector<uint8_t>& descriptor, + static std::unique_ptr<Device> open(int32_t id, const char* name, const char* uniq, int32_t vid, + int32_t pid, uint16_t bus, + const std::vector<uint8_t>& descriptor, std::unique_ptr<DeviceCallback> callback); ~Device(); diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java index 0415037cfc9f..4e8adc3af55c 100644 --- a/cmds/hid/src/com/android/commands/hid/Device.java +++ b/cmds/hid/src/com/android/commands/hid/Device.java @@ -71,6 +71,7 @@ public class Device { private static native long nativeOpenDevice( String name, + String uniq, int id, int vid, int pid, @@ -89,6 +90,7 @@ public class Device { public Device( int id, String name, + String uniq, int vid, int pid, int bus, @@ -113,8 +115,9 @@ public class Device { } else { args.arg1 = id + ":" + vid + ":" + pid; } - args.arg2 = descriptor; - args.arg3 = report; + args.arg2 = uniq; + args.arg3 = descriptor; + args.arg4 = report; mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget(); mTimeToSend = SystemClock.uptimeMillis(); } @@ -167,11 +170,12 @@ public class Device { mPtr = nativeOpenDevice( (String) args.arg1, + (String) args.arg2, args.argi1, args.argi2, args.argi3, args.argi4, - (byte[]) args.arg2, + (byte[]) args.arg3, new DeviceCallback()); pauseEvents(); break; diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java index 3efb79766b94..09ed5281a83d 100644 --- a/cmds/hid/src/com/android/commands/hid/Event.java +++ b/cmds/hid/src/com/android/commands/hid/Event.java @@ -56,6 +56,7 @@ public class Event { private int mId; private String mCommand; private String mName; + private String mUniq; private byte[] mDescriptor; private int mVid; private int mPid; @@ -78,6 +79,10 @@ public class Event { return mName; } + public String getUniq() { + return mUniq; + } + public byte[] getDescriptor() { return mDescriptor; } @@ -118,6 +123,7 @@ public class Event { return "Event{id=" + mId + ", command=" + String.valueOf(mCommand) + ", name=" + String.valueOf(mName) + + ", uniq=" + String.valueOf(mUniq) + ", descriptor=" + Arrays.toString(mDescriptor) + ", vid=" + mVid + ", pid=" + mPid @@ -149,6 +155,10 @@ public class Event { mEvent.mName = name; } + public void setUniq(String uniq) { + mEvent.mUniq = uniq; + } + public void setDescriptor(byte[] descriptor) { mEvent.mDescriptor = descriptor; } @@ -247,6 +257,9 @@ public class Event { case "name": eb.setName(mReader.nextString()); break; + case "uniq": + eb.setUniq(mReader.nextString()); + break; case "vid": eb.setVid(readInt()); break; diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java index 2db791fe90bd..5ebfd959ef33 100644 --- a/cmds/hid/src/com/android/commands/hid/Hid.java +++ b/cmds/hid/src/com/android/commands/hid/Hid.java @@ -117,8 +117,17 @@ public class Hid { "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!"); } int id = e.getId(); - Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(), - e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs()); + Device d = new Device( + id, + e.getName(), + e.getUniq(), + e.getVendorId(), + e.getProductId(), + e.getBus(), + e.getDescriptor(), + e.getReport(), + e.getFeatureReports(), + e.getOutputs()); mDevices.append(id, d); } 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 04c7873c3b48..592cbfdd4829 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4445,7 +4445,7 @@ package android.app { method public final android.media.session.MediaController getMediaController(); method @NonNull public android.view.MenuInflater getMenuInflater(); method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher(); - method public final android.app.Activity getParent(); + method @Deprecated public final android.app.Activity getParent(); method @Nullable public android.content.Intent getParentActivityIntent(); method public android.content.SharedPreferences getPreferences(int); method @Nullable public android.net.Uri getReferrer(); @@ -4463,7 +4463,7 @@ package android.app { method public void invalidateOptionsMenu(); method public boolean isActivityTransitionRunning(); method public boolean isChangingConfigurations(); - method public final boolean isChild(); + method @Deprecated public final boolean isChild(); method public boolean isDestroyed(); method public boolean isFinishing(); method public boolean isImmersive(); @@ -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); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index adc7ef1990a5..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); @@ -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/api/test-current.txt b/core/api/test-current.txt index 53d0c032dbaf..cfdabc88b073 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3930,6 +3930,7 @@ package android.view.inputmethod { } public final class InputMethodInfo implements android.os.Parcelable { + ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String); ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String); ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String); ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 63cafdc6c855..44dc8e2f4cb7 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1254,12 +1254,23 @@ public class Activity extends ContextThemeWrapper return mApplication; } - /** Is this activity embedded inside of another activity? */ + /** + * Whether this is a child {@link Activity} of an {@link ActivityGroup}. + * + * @deprecated {@link ActivityGroup} is deprecated. + */ + @Deprecated public final boolean isChild() { return mParent != null; } - /** Return the parent activity if this view is an embedded child. */ + /** + * Returns the parent {@link Activity} if this is a child {@link Activity} of an + * {@link ActivityGroup}. + * + * @deprecated {@link ActivityGroup} is deprecated. + */ + @Deprecated public final Activity getParent() { return mParent; } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index ab5395e81aa3..9f2e4739e8e6 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -562,6 +562,12 @@ public final class NotificationChannel implements Parcelable { * audio attributes. Notification channels with an {@link #getImportance() importance} of at * least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound. * + * Note: An app-specific sound can be provided in the Uri parameter, but because channels are + * persistent for the duration of the app install, and are backed up and restored, the Uri + * should be stable. For this reason it is not recommended to use a + * {@link ContentResolver#SCHEME_ANDROID_RESOURCE} uri, as resource ids can change on app + * upgrade. + * * Only modifiable before the channel is submitted to * {@link NotificationManager#createNotificationChannel(NotificationChannel)}. */ 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/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index 6bb9c33afb6a..41c1f17ce978 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -697,8 +697,9 @@ public class LauncherApps { public List<UserHandle> getProfiles() { if (mUserManager.isManagedProfile() || (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks() - && android.os.Flags.allowPrivateProfile() - && mUserManager.isPrivateProfile())) { + && android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && mUserManager.isPrivateProfile())) { // If it's a managed or private profile, only return the current profile. final List result = new ArrayList(1); result.add(android.os.Process.myUserHandle()); diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index ac80561c3b50..48a7cc920f1d 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -184,3 +184,11 @@ flag { description: "Enable Private Space telephony and SMS intent redirection to the main user" bug: "325576602" } + +flag { + name: "block_private_space_creation" + namespace: "profile_experiences" + description: "Allow blocking private space creation based on specific conditions" + bug: "290333800" + is_fixed_read_only: true +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index 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/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 6b2814ed2146..58aafbc648f7 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -778,6 +778,16 @@ public abstract class DisplayManagerInternal { */ float[] getAutoBrightnessLuxLevels(int mode); + /** + * @return The current brightness setting + */ + float getBrightness(); + + /** + * @return The brightness value that is used when the device is in doze + */ + float getDozeBrightness(); + /** Returns whether displayoffload supports the given display state. */ static boolean isSupportedOffloadState(int displayState) { return Display.isSuspendedState(displayState); 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/IUserManager.aidl b/core/java/android/os/IUserManager.aidl index 800ba6d3a542..23d6007e3d8b 100644 --- a/core/java/android/os/IUserManager.aidl +++ b/core/java/android/os/IUserManager.aidl @@ -85,6 +85,7 @@ interface IUserManager { boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId); boolean isRestricted(int userId); boolean canHaveRestrictedProfile(int userId); + boolean canAddPrivateProfile(int userId); int getUserSerialNumber(int userId); int getUserHandle(int userSerialNumber); int getUserRestrictionSource(String restrictionKey, int userId); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 9757a1096a30..fdaa0b467566 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2353,6 +2353,17 @@ public class UserManager { public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7; /** + * Indicates user operation failed because user is disabled on the device. + * @hide + */ + public static final int USER_OPERATION_ERROR_DISABLED_USER = 8; + /** + * Indicates user operation failed because user is disabled on the device. + * @hide + */ + public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9; + + /** * Result returned from various user operations. * * @hide @@ -2366,7 +2377,9 @@ public class UserManager { USER_OPERATION_ERROR_CURRENT_USER, USER_OPERATION_ERROR_LOW_STORAGE, USER_OPERATION_ERROR_MAX_USERS, - USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS + USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS, + USER_OPERATION_ERROR_DISABLED_USER, + USER_OPERATION_ERROR_PRIVATE_PROFILE, }) public @interface UserOperationResult {} @@ -2563,6 +2576,17 @@ public class UserManager { } /** + * Returns whether the device supports Private Profile + * @hide + */ + public static boolean isPrivateProfileEnabled() { + if (android.multiuser.Flags.blockPrivateSpaceCreation()) { + return !ActivityManager.isLowRamDeviceStatic(); + } + return true; + } + + /** * Returns whether multiple admins are enabled on the device * @hide */ @@ -3155,6 +3179,27 @@ public class UserManager { } /** + * Checks if it's possible to add a private profile to the context user + * @return whether the context user can add a private profile. + * @hide + */ + @RequiresPermission(anyOf = { + Manifest.permission.MANAGE_USERS, + Manifest.permission.CREATE_USERS}, + conditional = true) + @UserHandleAware + public boolean canAddPrivateProfile() { + if (android.multiuser.Flags.blockPrivateSpaceCreation()) { + try { + return mService.canAddPrivateProfile(mUserId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + return true; + } + + /** * Returns whether the context user has at least one restricted profile associated with it. * @return whether the user has a restricted profile associated with it * @hide diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java index 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/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/View.java b/core/java/android/view/View.java index 828004b6b235..a6f380d4d483 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -25,6 +25,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS; @@ -5629,7 +5630,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @Nullable private ViewTranslationCallback mViewTranslationCallback; - private float mFrameContentVelocity = 0; + private float mFrameContentVelocity = -1; @Nullable @@ -5660,6 +5661,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected long mMinusTwoFrameIntervalMillis = 0; private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + private float mLastFrameX = Float.NaN; + private float mLastFrameY = Float.NaN; + @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) @@ -24597,7 +24601,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void draw(@NonNull Canvas canvas) { final int privateFlags = mPrivateFlags; mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; - mFrameContentVelocity = 0; + + mFrameContentVelocity = -1; + mLastFrameX = mLeft + mRenderNode.getTranslationX(); + mLastFrameY = mTop + mRenderNode.getTranslationY(); /* * Draw traversal performs several drawing steps which must be executed @@ -33673,6 +33680,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (sToolkitMetricsForFrameRateDecisionFlagValue) { viewRootImpl.recordViewPercentage(sizePercentage); } + if (viewVelocityApi()) { + float velocity = mFrameContentVelocity; + if (velocity < 0f) { + velocity = calculateVelocity(); + } + if (velocity > 0f) { + float frameRate = convertVelocityToFrameRate(velocity); + viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE); + return; + } + } if (!Float.isNaN(mPreferredFrameRate)) { if (mPreferredFrameRate < 0) { if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) { @@ -33695,6 +33713,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private float convertVelocityToFrameRate(float velocityPps) { + float density = getResources().getDisplayMetrics().density; + float velocityDps = velocityPps / density; + // Choose a frame rate in increments of 10fps + return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f))); + } + + private float calculateVelocity() { + // This current calculation is very simple. If something on the screen moved, then + // it votes for the highest velocity. If it doesn't move, then return 0. + float x = mLeft + mRenderNode.getTranslationX(); + float y = mTop + mRenderNode.getTranslationY(); + + return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY)) + ? 100_000f : 0f; + } + /** * Set the current velocity of the View, we only track positive value. * We will use the velocity information to adjust the frame rate when applicable. @@ -33725,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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 02f8e6e9b810..69f16572585d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -31,6 +31,7 @@ import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE; +import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; @@ -7571,7 +7572,8 @@ public final class ViewRootImpl implements ViewParent, } // For the variable refresh rate project - if (handled && shouldTouchBoost(action, mWindowAttributes.type)) { + if (handled && shouldTouchBoost(action & MotionEvent.ACTION_MASK, + mWindowAttributes.type)) { // set the frame rate to the maximum value. mIsTouchBoosting = true; setPreferredFrameRateCategory(mPreferredFrameRateCategory); @@ -12358,7 +12360,9 @@ public final class ViewRootImpl implements ViewParent, // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction. // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction // (e.g., Window Initialization). - if (mIsFrameRateBoosting || mInsetsAnimationRunning) { + if (mIsFrameRateBoosting || mInsetsAnimationRunning + || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE + && mPreferredFrameRate > 0)) { frameRateCategory = FRAME_RATE_CATEGORY_HIGH; } @@ -12381,7 +12385,8 @@ public final class ViewRootImpl implements ViewParent, } private void setPreferredFrameRate(float preferredFrameRate) { - if (!shouldSetFrameRate()) { + if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE + && preferredFrameRate > 0)) { return; } @@ -12398,6 +12403,17 @@ public final class ViewRootImpl implements ViewParent, mFrameRateCompatibility).applyAsyncUnsafe(); mLastPreferredFrameRate = preferredFrameRate; } + if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) { + // We've received a velocity, so we'll let the velocity control the + // frame rate unless we receive additional motion events. + mIsTouchBoosting = false; + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.instant( + Trace.TRACE_TAG_VIEW, + "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame" + ); + } + } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate", e); } finally { @@ -12423,9 +12439,8 @@ public final class ViewRootImpl implements ViewParent, } private boolean shouldTouchBoost(int motionEventAction, int windowType) { - boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN - || motionEventAction == MotionEvent.ACTION_MOVE - || motionEventAction == MotionEvent.ACTION_UP; + // boost for almost all input + boolean desiredAction = motionEventAction != MotionEvent.ACTION_OUTSIDE; boolean undesiredType = windowType == TYPE_INPUT_METHOD && sToolkitFrameRateTypingReadOnlyFlagValue; // use toolkitSetFrameRate flag to gate the change @@ -12531,6 +12546,22 @@ public final class ViewRootImpl implements ViewParent, } /** + * Get the value of mLastPreferredFrameRate + */ + @VisibleForTesting + public float getLastPreferredFrameRate() { + return mLastPreferredFrameRate; + } + + /** + * Returns whether touch boost is currently enabled. + */ + @VisibleForTesting + public boolean getIsTouchBoosting() { + return mIsTouchBoosting; + } + + /** * Get the value of mFrameRateCompatibility */ @VisibleForTesting 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/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 16fecc17d426..8ddc1788a353 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -499,6 +499,25 @@ public final class InputMethodInfo implements Parcelable { @TestApi public InputMethodInfo(@NonNull String packageName, @NonNull String className, @NonNull CharSequence label, @NonNull String settingsActivity, + boolean supportStylusHandwriting, + @NonNull String stylusHandwritingSettingsActivityAttr) { + this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, + settingsActivity, null /* languageSettingsActivity */, + null /* subtypes */, 0 /* isDefaultResId */, + false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, + false /* inlineSuggestionsEnabled */, false /* isVrOnly */, + false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, + supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */, + stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); + } + + /** + * Test API for creating a built-in input method to verify stylus handwriting. + * @hide + */ + @TestApi + public InputMethodInfo(@NonNull String packageName, @NonNull String className, + @NonNull CharSequence label, @NonNull String settingsActivity, @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, @NonNull String stylusHandwritingSettingsActivityAttr) { this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 7dcbbea7df90..78f06b6bddb3 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -2655,7 +2655,8 @@ public class ResolverActivity extends Activity implements private boolean privateSpaceEnabled() { return mIsIntentPicker && android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.allowResolverSheetForPrivateSpace(); + && android.multiuser.Flags.allowResolverSheetForPrivateSpace() + && android.multiuser.Flags.enablePrivateSpaceFeatures(); } /** diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java index 93fe37c974b2..360fcafe3318 100644 --- a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java +++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java @@ -75,7 +75,8 @@ public class SetScreenLockDialogActivity extends AlertActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (!(android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.showSetScreenLockDialog())) { + && android.multiuser.Flags.showSetScreenLockDialog() + && android.multiuser.Flags.enablePrivateSpaceFeatures())) { finish(); return; } diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index 4ef0a1baa4d1..97f8084d0031 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -77,6 +77,7 @@ public class UnlaunchableAppActivity extends Activity } if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() && !userManager.isManagedProfile(mUserId)) { Log.e(TAG, "Unlaunchable activity for target package " + targetPackageName + " called for a non-managed-profile " + mUserId); diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index 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/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index bdd9a913a06c..4ead82f22cfa 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -70,6 +70,8 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Map; import java.util.TreeMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData; @@ -90,6 +92,8 @@ public class PerfettoProtoLogImpl implements IProtoLog { private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider; private final TreeMap<String, IProtoLogGroup> mLogGroups; + private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool(); + public PerfettoProtoLogImpl(String viewerConfigFilePath, TreeMap<String, IProtoLogGroup> logGroups) { this(() -> { @@ -134,7 +138,8 @@ public class PerfettoProtoLogImpl implements IProtoLog { long tsNanos = SystemClock.elapsedRealtimeNanos(); try { - logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos); + mBackgroundLoggingService.submit(() -> + logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos)); if (group.isLogToLogcat()) { logToLogcat(group.getTag(), level, messageHash, messageString, args); } @@ -462,40 +467,6 @@ public class PerfettoProtoLogImpl implements IProtoLog { } /** - * Responds to a shell command. - */ - public int onShellCommand(ShellCommand shell) { - PrintWriter pw = shell.getOutPrintWriter(); - String cmd = shell.getNextArg(); - if (cmd == null) { - return unknownCommand(pw); - } - ArrayList<String> args = new ArrayList<>(); - String arg; - while ((arg = shell.getNextArg()) != null) { - args.add(arg); - } - final ILogger logger = (msg) -> logAndPrintln(pw, msg); - String[] groups = args.toArray(new String[args.size()]); - switch (cmd) { - case "enable-text": - return this.startLoggingToLogcat(groups, logger); - case "disable-text": - return this.stopLoggingToLogcat(groups, logger); - default: - return unknownCommand(pw); - } - } - - private int unknownCommand(PrintWriter pw) { - pw.println("Unknown command"); - pw.println("Window manager logging options:"); - pw.println(" enable-text [group...]: Enable logcat logging for given groups"); - pw.println(" disable-text [group...]: Disable logcat logging for given groups"); - return -1; - } - - /** * Returns {@code true} iff logging to proto is enabled. */ public boolean isProtoEnabled() { @@ -554,6 +525,49 @@ public class PerfettoProtoLogImpl implements IProtoLog { return 0; } + /** + * Responds to a shell command. + */ + public int onShellCommand(ShellCommand shell) { + PrintWriter pw = shell.getOutPrintWriter(); + String cmd = shell.getNextArg(); + if (cmd == null) { + return unknownCommand(pw); + } + ArrayList<String> args = new ArrayList<>(); + String arg; + while ((arg = shell.getNextArg()) != null) { + args.add(arg); + } + final ILogger logger = (msg) -> logAndPrintln(pw, msg); + String[] groups = args.toArray(new String[0]); + switch (cmd) { + case "start", "stop" -> { + pw.println("Command not supported. " + + "Please start and stop ProtoLog tracing with Perfetto."); + return -1; + } + case "enable-text" -> { + mViewerConfigReader.loadViewerConfig(logger); + return setTextLogging(true, logger, groups); + } + case "disable-text" -> { + return setTextLogging(false, logger, groups); + } + default -> { + return unknownCommand(pw); + } + } + } + + private int unknownCommand(PrintWriter pw) { + pw.println("Unknown command"); + pw.println("Window manager logging options:"); + pw.println(" enable-text [group...]: Enable logcat logging for given groups"); + pw.println(" disable-text [group...]: Disable logcat logging for given groups"); + return -1; + } + static void logAndPrintln(@Nullable PrintWriter pw, String msg) { Slog.i(LOG_TAG, msg); if (pw != null) { diff --git a/core/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..a0cb70518866 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1608,7 +1608,10 @@ <fraction name="config_autoBrightnessAdjustmentMaxGamma">300%</fraction> <!-- If we allow automatic adjustment of screen brightness while dozing, how many times we want - to reduce it to preserve the battery. Value of 100% means no scaling. --> + to reduce it to preserve the battery. Value of 100% means no scaling. Not used if there is + a designated auto-brightness doze mapping defined in Display Device Config. + Also used to scale the brightness for the doze mode when auto-brightness is disabled if + there is an offload session present. --> <fraction name="config_screenAutoBrightnessDozeScaleFactor">100%</fraction> <!-- When the screen is turned on, the previous estimate of the ambient light level at the time @@ -4673,8 +4676,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/symbols.xml b/core/res/res/values/symbols.xml index 06bcd26417e0..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" /> diff --git a/core/tests/coretests/res/layout/view_velocity_test.xml b/core/tests/coretests/res/layout/view_velocity_test.xml new file mode 100644 index 000000000000..98154a4b6b78 --- /dev/null +++ b/core/tests/coretests/res/layout/view_velocity_test.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/frameLayout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <View + android:id="@+id/moving_view" + android:layout_width="50dp" + android:layout_height="50dp" /> +</FrameLayout> 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/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 1a242eff73b1..2544fcb81692 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR; import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY; +import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; @@ -796,6 +797,38 @@ public class ViewRootImplTest { } /** + * When velocity of a View is not equal to 0, we call setFrameRateCategory with HIGH. + * Also, we shouldn't call setFrameRate. + */ + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, FLAG_VIEW_VELOCITY_API}) + public void votePreferredFrameRate_voteFrameRateCategory_velocityToHigh() { + View view = new View(sContext); + WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY); + wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check + wmlp.width = 1; + wmlp.height = 1; + + sInstrumentation.runOnMainSync(() -> { + WindowManager wm = sContext.getSystemService(WindowManager.class); + wm.addView(view, wmlp); + }); + sInstrumentation.waitForIdleSync(); + + ViewRootImpl viewRootImpl = view.getViewRootImpl(); + + sInstrumentation.runOnMainSync(() -> { + assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1); + view.setFrameContentVelocity(100); + view.invalidate(); + assertTrue(viewRootImpl.getPreferredFrameRate() > 0); + }); + sInstrumentation.waitForIdleSync(); + assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH); + assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0 , 0.1); + } + + /** * We should boost the frame rate if the value of mInsetsAnimationRunning is true. */ @Test diff --git a/core/tests/coretests/src/android/view/ViewVelocityTest.java b/core/tests/coretests/src/android/view/ViewVelocityTest.java new file mode 100644 index 000000000000..d437f7bc4060 --- /dev/null +++ b/core/tests/coretests/src/android/view/ViewVelocityTest.java @@ -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 android.view; + +import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; + +import static junit.framework.Assert.assertEquals; + +import static org.junit.Assert.assertFalse; +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; +import androidx.test.rule.ActivityTestRule; +import androidx.test.runner.AndroidJUnit4; + +import com.android.frameworks.coretests.R; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ViewVelocityTest { + + @Rule + public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>( + ViewCaptureTestActivity.class); + + private Activity mActivity; + private View mMovingView; + private ViewRootImpl mViewRoot; + + @Before + public void setUp() throws Throwable { + mActivity = mActivityRule.getActivity(); + mActivityRule.runOnUiThread(() -> { + mActivity.setContentView(R.layout.view_velocity_test); + mMovingView = mActivity.findViewById(R.id.moving_view); + }); + ViewParent parent = mActivity.getWindow().getDecorView().getParent(); + while (parent instanceof View) { + parent = parent.getParent(); + } + mViewRoot = (ViewRootImpl) parent; + } + + @UiThreadTest + @Test + @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API) + public void frameRateChangesWhenContentMoves() { + mMovingView.offsetLeftAndRight(100); + float frameRate = mViewRoot.getPreferredFrameRate(); + assertTrue(frameRate > 0); + } + + @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(); + MotionEvent down = MotionEvent.obtain( + /* downTime */ now, + /* eventTime */ now, + /* action */ MotionEvent.ACTION_DOWN, + /* x */ 0f, + /* y */ 0f, + /* metaState */ 0 + ); + mActivity.dispatchTouchEvent(down); + mMovingView.offsetLeftAndRight(10); + }); + mActivityRule.runOnUiThread(() -> { + mMovingView.invalidate(); + }); + + mActivityRule.runOnUiThread(() -> { + assertFalse(mViewRoot.getIsTouchBoosting()); + }); + } +} diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index b209c7c261ba..cb8754ae9962 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -1162,7 +1162,8 @@ public class ResolverActivityTest { @Test public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException { mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); markPrivateProfileUserAvailable(); Intent sendIntent = createSendImageIntent(); List<ResolvedComponentInfo> privateResolvedComponentInfos = @@ -1183,7 +1184,8 @@ public class ResolverActivityTest { @Test public void testTriggerFromPrivateProfile_withWorkProfilePresent(){ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); ResolverActivity.ENABLE_TABBED_VIEW = false; markPrivateProfileUserAvailable(); markWorkProfileUserAvailable(); @@ -1205,7 +1207,8 @@ public class ResolverActivityTest { @Test public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); markPrivateProfileUserAvailable(); markWorkProfileUserAvailable(); Intent sendIntent = createSendImageIntent(); @@ -1228,7 +1231,8 @@ public class ResolverActivityTest { @Test public void testPrivateProfile_triggerFromWorkProfile(){ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); markPrivateProfileUserAvailable(); markWorkProfileUserAvailable(); Intent sendIntent = createSendImageIntent(); diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index ce2543a47cf5..a621642f99df 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -88,5 +88,6 @@ <permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" /> <permission name="android.permission.READ_SEARCH_INDEXABLES" /> <permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/> + <permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" /> </privapp-permissions> </permissions> 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/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/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java index 22ba70860587..7b8486870a40 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java @@ -16,6 +16,10 @@ package com.android.wm.shell.desktopmode; +import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE; + +import android.annotation.NonNull; +import android.app.ActivityManager.RunningTaskInfo; import android.os.SystemProperties; import com.android.window.flags.Flags; @@ -66,6 +70,9 @@ public class DesktopModeStatus { private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean( "persist.wm.debug.desktop_use_rounded_corners", true); + private static final boolean ENFORCE_DISPLAY_RESTRICTIONS = SystemProperties.getBoolean( + "persist.wm.debug.desktop_mode_enforce_display_restrictions", true); + /** * Return {@code true} if desktop windowing is enabled */ @@ -104,4 +111,21 @@ public class DesktopModeStatus { public static boolean useRoundedCorners() { return USE_ROUNDED_CORNERS; } + + /** + * Return whether the display size restrictions should be enforced. + */ + public static boolean enforceDisplayRestrictions() { + return ENFORCE_DISPLAY_RESTRICTIONS; + } + + /** + * Return {@code true} if the display associated with the task is at least of size + * {@link android.content.res.Configuration#SCREENLAYOUT_SIZE_XLARGE} or has been overridden to + * ignore the size constraint. + */ + public static boolean meetsMinimumDisplayRequirements(@NonNull RunningTaskInfo taskInfo) { + return !enforceDisplayRestrictions() + || taskInfo.configuration.isLayoutSizeAtLeast(SCREENLAYOUT_SIZE_XLARGE); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 654409f4a637..2c66fd681f62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -305,6 +305,12 @@ class DesktopTasksController( task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction() ) { + if (!DesktopModeStatus.meetsMinimumDisplayRequirements(task)) { + KtProtoLog.w( + WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " + + "display does not meet minimum size requirements") + return + } KtProtoLog.v( WM_SHELL_DESKTOP_MODE, "DesktopTasksController: moveToDesktop taskId=%d", 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..bf22193566ed 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(); @@ -1058,8 +1052,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop() - && mDisplayController.getDisplayContext(taskInfo.displayId) - .getResources().getConfiguration().smallestScreenWidthDp >= 600; + && DesktopModeStatus.meetsMinimumDisplayRequirements(taskInfo); } private void createWindowDecoration( 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/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 35c803b78674..4c8a3088a79f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -23,6 +23,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED +import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL +import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE import android.os.Binder import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY @@ -123,7 +125,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Before fun setUp() { - mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking() + mockitoSession = mockitoSession().spyStatic(DesktopModeStatus::class.java).startMocking() whenever(DesktopModeStatus.isEnabled()).thenReturn(true) shellInit = Mockito.spy(ShellInit(testExecutor)) @@ -332,6 +334,45 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_screenSizeBelowXLarge_doesNothing() { + val task = setUpFullscreenTask() + + // Update screen layout to be below minimum size + task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL + + controller.moveToDesktop(task) + verifyWCTNotExecuted() + } + + @Test + fun moveToDesktop_screenSizeBelowXLarge_displayRestrictionsOverridden_taskIsMovedToDesktop() { + val task = setUpFullscreenTask() + + // Update screen layout to be below minimum size + task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL + + // Simulate enforce display restrictions system property overridden to false + whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false) + + controller.moveToDesktop(task) + + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test + fun moveToDesktop_screenSizeXLarge_taskIsMovedToDesktop() { + val task = setUpFullscreenTask() + + controller.moveToDesktop(task) + + val wct = getLatestMoveToDesktopWct() + assertThat(wct.changes[task.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + } + + @Test fun moveToDesktop_otherFreeformTasksBroughtToFront() { val homeTask = setUpHomeTask() val freeformTask = setUpFreeformTask() @@ -816,6 +857,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFullscreenTask(displayId) + task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) return task @@ -823,6 +865,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createSplitScreenTask(displayId) + task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) runningTasks.add(task) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 9bb5482de715..83519bbf624a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -23,10 +23,14 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.Context +import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL +import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE import android.graphics.Rect import android.hardware.display.DisplayManager import android.hardware.display.VirtualDisplay import android.os.Handler +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.SparseArray @@ -42,6 +46,9 @@ import android.view.SurfaceView import android.view.WindowInsets.Type.navigationBars import android.view.WindowInsets.Type.statusBars import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession +import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.window.flags.Flags import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase @@ -51,6 +58,7 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.DesktopModeStatus import com.android.wm.shell.desktopmode.DesktopTasksController import com.android.wm.shell.sysui.KeyguardChangeListener import com.android.wm.shell.sysui.ShellCommandHandler @@ -59,6 +67,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -71,6 +80,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness import java.util.Optional import java.util.function.Supplier import org.mockito.Mockito @@ -82,6 +92,10 @@ import org.mockito.kotlin.spy @RunWith(AndroidTestingRunner::class) @RunWithLooper class DesktopModeWindowDecorViewModelTests : ShellTestCase() { + @JvmField + @Rule + val setFlagsRule = SetFlagsRule() + @Mock private lateinit var mockDesktopModeWindowDecorFactory: DesktopModeWindowDecoration.Factory @Mock private lateinit var mockMainHandler: Handler @@ -351,6 +365,54 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun testWindowDecor_screenSizeBelowXLarge_decorNotCreated() { + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + // Update screen layout to be below minimum size + task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL + + onTaskOpening(task) + verify(mockDesktopModeWindowDecorFactory, never()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun testWindowDecor_screenSizeBelowXLarge_displayRestrictionsOverridden_decorCreated() { + val mockitoSession: StaticMockitoSession = mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .startMocking() + try { + // Simulate enforce display restrictions system property overridden to false + whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false) + + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + // Update screen layout to be below minimum size + task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL + setUpMockDecorationsForTasks(task) + + onTaskOpening(task) + verify(mockDesktopModeWindowDecorFactory) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + } finally { + mockitoSession.finishMocking() + } + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) + fun testWindowDecor_screenSizeXLarge_decorCreated() { + val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true) + task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE + setUpMockDecorationsForTasks(task) + + onTaskOpening(task) + verify(mockDesktopModeWindowDecorFactory) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + } + private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) { desktopModeWindowDecorViewModel.onTaskOpening( task, 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/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 6ced91f83ba4..0d0af1110ca4 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -625,8 +625,8 @@ class SharedSemaphoreInfo : public LightRefBase<SharedSemaphoreInfo> { SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device, VkSemaphore semaphore) - : mDestroyFunction(destroyFunction), mDevice(device), - mGrBackendSemaphore(GrBackendSemaphores::MakeVk(semaphore)) { + : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) { + mGrBackendSemaphore = GrBackendSemaphores::MakeVk(mSemaphore); } ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); } diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp index 6dc45a6aebec..6a32c5a71999 100644 --- a/libs/input/SpriteController.cpp +++ b/libs/input/SpriteController.cpp @@ -202,11 +202,13 @@ void SpriteController::doUpdateSprites() { && update.state.surfaceDrawn; bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible; bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible; - if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden - || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA - | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER - | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID - | DIRTY_ICON_STYLE))))) { + if (update.state.surfaceControl != NULL && + (becomingVisible || becomingHidden || + (wantSurfaceVisibleAndDrawn && + (update.state.dirty & + (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER | + DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE | + DIRTY_DRAW_DROP_SHADOW))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -235,13 +237,15 @@ void SpriteController::doUpdateSprites() { update.state.transformationMatrix.dtdy); } - if (wantSurfaceVisibleAndDrawn - && (becomingVisible - || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) { + if (wantSurfaceVisibleAndDrawn && + (becomingVisible || + (update.state.dirty & + (DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW)))) { Parcel p; p.writeInt32(static_cast<int32_t>(update.state.icon.style)); p.writeFloat(update.state.icon.hotSpotX); p.writeFloat(update.state.icon.hotSpotY); + p.writeBool(update.state.icon.drawNativeDropShadow); // Pass cursor metadata in the sprite surface so that when Android is running as a // client OS (e.g. ARC++) the host OS can get the requested cursor metadata and @@ -388,12 +392,13 @@ void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { uint32_t dirty; if (icon.isValid()) { mLocked.state.icon.bitmap = icon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888); - if (!mLocked.state.icon.isValid() - || mLocked.state.icon.hotSpotX != icon.hotSpotX - || mLocked.state.icon.hotSpotY != icon.hotSpotY) { + if (!mLocked.state.icon.isValid() || mLocked.state.icon.hotSpotX != icon.hotSpotX || + mLocked.state.icon.hotSpotY != icon.hotSpotY || + mLocked.state.icon.drawNativeDropShadow != icon.drawNativeDropShadow) { mLocked.state.icon.hotSpotX = icon.hotSpotX; mLocked.state.icon.hotSpotY = icon.hotSpotY; - dirty = DIRTY_BITMAP | DIRTY_HOTSPOT; + mLocked.state.icon.drawNativeDropShadow = icon.drawNativeDropShadow; + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_DRAW_DROP_SHADOW; } else { dirty = DIRTY_BITMAP; } @@ -404,7 +409,7 @@ void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { } } else if (mLocked.state.icon.isValid()) { mLocked.state.icon.bitmap.reset(); - dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE; + dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW; } else { return; // setting to invalid icon and already invalid so nothing to do } diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 04ecb3895aa2..35776e9961b3 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -151,6 +151,7 @@ private: DIRTY_HOTSPOT = 1 << 6, DIRTY_DISPLAY_ID = 1 << 7, DIRTY_ICON_STYLE = 1 << 8, + DIRTY_DRAW_DROP_SHADOW = 1 << 9, }; /* Describes the state of a sprite. diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h index 0939af46c258..7d45d02b4a6f 100644 --- a/libs/input/SpriteIcon.h +++ b/libs/input/SpriteIcon.h @@ -40,7 +40,7 @@ struct SpriteIcon { PointerIconStyle style{PointerIconStyle::TYPE_NULL}; float hotSpotX{}; float hotSpotY{}; - bool drawNativeDropShadow{false}; + bool drawNativeDropShadow{}; inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); } 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/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index 60ab1a4e42ac..a53a8ce79354 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -275,17 +275,23 @@ public class AudioMix implements Parcelable { if (o == null || getClass() != o.getClass()) return false; final AudioMix that = (AudioMix) o; + boolean tokenMatch = android.media.audiopolicy.Flags.audioMixOwnership() + ? Objects.equals(this.mToken, that.mToken) + : true; return Objects.equals(this.mRouteFlags, that.mRouteFlags) && Objects.equals(this.mRule, that.mRule) && Objects.equals(this.mMixType, that.mMixType) && Objects.equals(this.mFormat, that.mFormat) - && Objects.equals(this.mToken, that.mToken); + && tokenMatch; } /** @hide */ @Override public int hashCode() { - return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken); + if (android.media.audiopolicy.Flags.audioMixOwnership()) { + return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken); + } + return Objects.hash(mRouteFlags, mRule, mMixType, mFormat); } @Override 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 3363ac025f12..d7a2e3624eab 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt @@ -21,8 +21,6 @@ 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 @@ -124,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 @@ -528,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/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp index 98a5a674fcdd..79c810ca2611 100644 --- a/packages/PackageInstaller/Android.bp +++ b/packages/PackageInstaller/Android.bp @@ -54,6 +54,7 @@ android_app { "androidx.lifecycle_lifecycle-extensions", "android.content.pm.flags-aconfig-java", "android.os.flags-aconfig-java", + "android.multiuser.flags-aconfig-java", ], lint: { @@ -85,6 +86,7 @@ android_app { "androidx.lifecycle_lifecycle-extensions", "android.content.pm.flags-aconfig-java", "android.os.flags-aconfig-java", + "android.multiuser.flags-aconfig-java", ], aaptflags: ["--product tablet"], @@ -118,6 +120,7 @@ android_app { "androidx.lifecycle_lifecycle-extensions", "android.content.pm.flags-aconfig-java", "android.os.flags-aconfig-java", + "android.multiuser.flags-aconfig-java", ], aaptflags: ["--product tv"], diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java index 221ca4fd1c66..8f5d07c15b0b 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java @@ -166,6 +166,7 @@ public class UninstallAlertDialogFragment extends DialogFragment implements messageBuilder.append(getString( R.string.uninstall_application_text_current_user_clone_profile)); } else if (Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() && customUserManager.isPrivateProfile() && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) { messageBuilder.append( diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt index 0fc184506eaf..c6b6d36180f3 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt @@ -235,7 +235,9 @@ class UninstallRepository(private val context: Context) { messageString = context.getString( R.string.uninstall_application_text_current_user_clone_profile ) - } else if (Flags.allowPrivateProfile() && customUserManager!!.isPrivateProfile()) { + } else if (Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && customUserManager!!.isPrivateProfile()) { // TODO(b/324244123): Get these Strings from a User Property API. messageString = context.getString( R.string.uninstall_application_text_current_user_private_profile diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt index fc8de8035217..0a469b868562 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt @@ -47,7 +47,7 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) { onCheckedChange = model.onCheckedChange, paddingStart = 20.dp, paddingEnd = 20.dp, - paddingVertical = 18.dp, + paddingVertical = 24.dp, ) } } @@ -55,7 +55,7 @@ fun MainSwitchPreference(model: SwitchPreferenceModel) { @Preview @Composable -fun MainSwitchPreferencePreview() { +private fun MainSwitchPreferencePreview() { SettingsTheme { Column { MainSwitchPreference(object : SwitchPreferenceModel { diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index e489bc552b25..2889ce26d65d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -1703,6 +1703,7 @@ public class ApplicationsState { public boolean isPrivateProfile() { return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() && UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java index ff4d4dd837d2..2b8c2dd0d0e3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java @@ -37,6 +37,23 @@ import java.util.List; */ // TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients. /* package */ final class NoOpInfoMediaManager extends InfoMediaManager { + /** + * Placeholder routing session to return as active session of {@link NoOpInfoMediaManager}. + * + * <p>Returning this routing session avoids crashes in {@link InfoMediaManager} and maintains + * the same client-facing behaviour as if no routing session was found for the target package + * name. + * + * <p>Volume and max volume are set to {@code -1} to emulate a non-existing routing session in + * {@link #getSessionVolume()} and {@link #getSessionVolumeMax()}. + */ + private static final RoutingSessionInfo PLACEHOLDER_SESSION = + new RoutingSessionInfo.Builder( + /* id */ "FAKE_ROUTING_SESSION", /* clientPackageName */ "") + .addSelectedRoute(/* routeId */ "FAKE_SELECTED_ROUTE_ID") + .setVolumeMax(-1) + .setVolume(-1) + .build(); NoOpInfoMediaManager( Context context, @@ -118,7 +135,7 @@ import java.util.List; @NonNull @Override protected List<RoutingSessionInfo> getRoutingSessionsForPackage() { - return Collections.emptyList(); + return List.of(PLACEHOLDER_SESSION); } @Nullable diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt index a5c63be3c987..7e3f38b755d8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt @@ -18,23 +18,18 @@ package com.android.settingslib.media.data.repository import android.media.AudioDeviceAttributes import android.media.Spatializer -import androidx.concurrent.futures.DirectExecutor import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext interface SpatializerRepository { - /** Returns true when head tracking is enabled and false the otherwise. */ - val isHeadTrackingAvailable: StateFlow<Boolean> + /** + * Returns true when head tracking is available for the [audioDeviceAttributes] and false the + * otherwise. + */ + suspend fun isHeadTrackingAvailableForDevice( + audioDeviceAttributes: AudioDeviceAttributes + ): Boolean /** * Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and @@ -65,22 +60,14 @@ interface SpatializerRepository { class SpatializerRepositoryImpl( private val spatializer: Spatializer, - coroutineScope: CoroutineScope, private val backgroundContext: CoroutineContext, ) : SpatializerRepository { - override val isHeadTrackingAvailable: StateFlow<Boolean> = - callbackFlow { - val listener = - Spatializer.OnHeadTrackerAvailableListener { _, available -> - launch { send(available) } - } - spatializer.addOnHeadTrackerAvailableListener(DirectExecutor.INSTANCE, listener) - awaitClose { spatializer.removeOnHeadTrackerAvailableListener(listener) } - } - .onStart { emit(spatializer.isHeadTrackerAvailable) } - .flowOn(backgroundContext) - .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false) + override suspend fun isHeadTrackingAvailableForDevice( + audioDeviceAttributes: AudioDeviceAttributes + ): Boolean { + return withContext(backgroundContext) { spatializer.hasHeadTracker(audioDeviceAttributes) } + } override suspend fun isSpatialAudioAvailableForDevice( audioDeviceAttributes: AudioDeviceAttributes diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt index 0347403cb385..5589733b606d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt @@ -18,17 +18,17 @@ package com.android.settingslib.media.domain.interactor import android.media.AudioDeviceAttributes import com.android.settingslib.media.data.repository.SpatializerRepository -import kotlinx.coroutines.flow.StateFlow class SpatializerInteractor(private val repository: SpatializerRepository) { - /** Checks if head tracking is available. */ - val isHeadTrackingAvailable: StateFlow<Boolean> - get() = repository.isHeadTrackingAvailable - + /** Checks if spatial audio is available. */ suspend fun isSpatialAudioAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean = repository.isSpatialAudioAvailableForDevice(audioDeviceAttributes) + /** Checks if head tracking is available. */ + suspend fun isHeadTrackingAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean = + repository.isHeadTrackingAvailableForDevice(audioDeviceAttributes) + /** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */ suspend fun isSpatialAudioEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean = repository.getSpatialAudioCompatibleDevices().contains(audioDeviceAttributes) diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt index 794cf832f48b..7719c4b75e9c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt @@ -48,6 +48,9 @@ class NotificationsSoundPolicyInteractor( /** Checks if [notificationPolicy] allows media. */ val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() } + /** Checks if [notificationPolicy] allows system sounds. */ + val isSystemAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowSystem() } + /** Checks if [notificationPolicy] allows ringer. */ val isRingerAllowed: Flow<Boolean?> = notificationPolicy.map { policy -> @@ -62,31 +65,29 @@ class NotificationsSoundPolicyInteractor( areAlarmsAllowed.filterNotNull(), isMediaAllowed.filterNotNull(), isRingerAllowed.filterNotNull(), - ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed -> - if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) { - return@combine true - } - - val isNotificationOrRing = - stream.value == AudioManager.STREAM_RING || - stream.value == AudioManager.STREAM_NOTIFICATION - if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) { - return@combine true + isSystemAllowed.filterNotNull(), + ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed, isSystemAllowed -> + when (zenMode.zenMode) { + // Everything is muted + Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> return@combine true + Settings.Global.ZEN_MODE_ALARMS -> + return@combine stream.value == AudioManager.STREAM_RING || + stream.value == AudioManager.STREAM_NOTIFICATION || + stream.value == AudioManager.STREAM_SYSTEM + Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> { + when { + stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed -> + return@combine true + stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed -> + return@combine true + stream.value == AudioManager.STREAM_SYSTEM && !isSystemAllowed -> + return@combine true + (stream.value == AudioManager.STREAM_RING || + stream.value == AudioManager.STREAM_NOTIFICATION) && !isRingerAllowed -> + return@combine true + } + } } - if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) { - return@combine false - } - - if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) { - return@combine true - } - if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) { - return@combine true - } - if (isNotificationOrRing && !isRingerAllowed) { - return@combine true - } - return@combine false } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java index 1ad7d4930ecc..fe83ffb094e2 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java @@ -319,7 +319,8 @@ public class ApplicationsStateTest { @Test public void testPrivateProfileFilterDisplaysCorrectApps() { - mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mEntry.showInPersonalTab = true; mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM; @@ -334,7 +335,8 @@ public class ApplicationsStateTest { @Test public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() { - mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mEntry.showInPersonalTab = false; mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java new file mode 100644 index 000000000000..d630301a083b --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java @@ -0,0 +1,77 @@ +/* + * 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.settingslib.media; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** + * Tests for {@link NoOpInfoMediaManager} to avoid exceptions in {@link InfoMediaManager}. + * + * <p>While {@link NoOpInfoMediaManager} should not perform any actions, it should still return + * placeholder information in certain cases to not change the behaviour of {@link InfoMediaManager} + * and prevent crashes. + */ +@RunWith(RobolectricTestRunner.class) +public class NoOpInfoMediaManagerTest { + private InfoMediaManager mInfoMediaManager; + private Context mContext; + + @Before + public void setUp() { + mContext = ApplicationProvider.getApplicationContext(); + mInfoMediaManager = + new NoOpInfoMediaManager( + mContext, + /* packageName */ "FAKE_PACKAGE_NAME", + /* localBluetoothManager */ null); + } + + @Test + public void getSessionVolumeMax_returnsNotFound() { + assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1); + } + + @Test + public void getSessionVolume_returnsNotFound() { + assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1); + } + + @Test + public void getSessionName_returnsNull() { + assertThat(mInfoMediaManager.getSessionName()).isNull(); + } + + @Test + public void getRoutingSessionForPackage_returnsPlaceholderSession() { + // Make sure we return a placeholder routing session so that we avoid OOB exceptions. + assertThat(mInfoMediaManager.getRoutingSessionsForPackage()).hasSize(1); + } + + @Test + public void getSelectedMediaDevices_returnsEmptyList() { + assertThat(mInfoMediaManager.getSelectedMediaDevices()).isEmpty(); + } +} diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 3043d54b3025..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,7 @@ 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, 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/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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 12e8f574e906..98591e9b76e9 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -273,6 +273,9 @@ <!-- to control accessibility volume --> <uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" /> + <!-- to change spatial audio --> + <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" /> + <!-- to access ResolverRankerServices --> <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" /> diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt new file mode 100644 index 000000000000..a066b387d906 --- /dev/null +++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.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.volume.panel.component.spatialaudio + +import dagger.Module + +@Module interface SpatialAudioModule diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt index 0728daf28c25..2a99039fa306 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt @@ -26,6 +26,7 @@ import androidx.compose.ui.layout.onPlaced import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel +import kotlinx.coroutines.flow.map /** * Modifies the composable to account for anti-burn in translation, alpha, and scaling. @@ -38,9 +39,18 @@ fun Modifier.burnInAware( params: BurnInParameters, isClock: Boolean = false, ): Modifier { - val translationX by viewModel.translationX(params).collectAsState(initial = 0f) - val translationY by viewModel.translationY(params).collectAsState(initial = 0f) - val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel()) + val burnIn = viewModel.movement(params) + val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f) + val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f) + val scaleViewModel by + burnIn + .map { + BurnInScaleViewModel( + scale = it.scale, + scaleClockOnly = it.scaleClockOnly, + ) + } + .collectAsState(initial = BurnInScaleViewModel()) return this.graphicsLayer { val scale = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt new file mode 100644 index 000000000000..ae267e2b002a --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt @@ -0,0 +1,258 @@ +/* + * 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.volume.panel.component.selector.ui.composable + +import androidx.compose.animation.core.animateOffsetAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CornerSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp + +/** + * Radio button group for the Volume Panel. It allows selecting a single item + * + * @param indicatorBackgroundPadding is the distance between the edge of the indicator and the + * indicator background + * @param labelIndicatorBackgroundSpacing is the distance between indicator background and labels + * row + */ +@Composable +fun VolumePanelRadioButtonBar( + modifier: Modifier = Modifier, + indicatorBackgroundPadding: Dp = + VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundPadding, + spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing, + labelIndicatorBackgroundSpacing: Dp = + VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing, + indicatorCornerRadius: CornerRadius = + VolumePanelRadioButtonBarDefaults.defaultIndicatorCornerRadius(), + indicatorBackgroundCornerSize: CornerSize = + CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius), + colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(), + content: VolumePanelRadioButtonBarScope.() -> Unit +) { + val scope = + VolumePanelRadioButtonBarScopeImpl().apply(content).apply { + require(hasSelectedItem) { "At least one item should be selected" } + } + + val items = scope.items + + var selectedIndex by remember { mutableIntStateOf(items.indexOfFirst { it.isSelected }) } + + var size by remember { mutableStateOf(IntSize(0, 0)) } + val spacingPx = with(LocalDensity.current) { spacing.toPx() } + val indicatorWidth = size.width / items.size - (spacingPx * (items.size - 1) / items.size) + val offset by + animateOffsetAsState( + targetValue = + Offset( + selectedIndex * indicatorWidth + (spacingPx * selectedIndex), + 0f, + ), + label = "VolumePanelRadioButtonOffsetAnimation", + finishedListener = { + for (itemIndex in items.indices) { + val item = items[itemIndex] + if (itemIndex == selectedIndex) { + item.onItemSelected() + break + } + } + } + ) + + Column(modifier = modifier) { + Box(modifier = Modifier.height(IntrinsicSize.Max)) { + Canvas( + modifier = + Modifier.fillMaxSize() + .background( + colors.indicatorBackgroundColor, + RoundedCornerShape(indicatorBackgroundCornerSize), + ) + .padding(indicatorBackgroundPadding) + .onGloballyPositioned { size = it.size } + ) { + drawRoundRect( + color = colors.indicatorColor, + topLeft = offset, + size = Size(indicatorWidth, size.height.toFloat()), + cornerRadius = indicatorCornerRadius, + ) + } + Row( + modifier = Modifier.padding(indicatorBackgroundPadding), + horizontalArrangement = Arrangement.spacedBy(spacing) + ) { + for (itemIndex in items.indices) { + TextButton( + modifier = Modifier.weight(1f), + onClick = { selectedIndex = itemIndex }, + ) { + val item = items[itemIndex] + if (item.icon !== Empty) { + with(items[itemIndex]) { icon() } + } + } + } + } + } + + Row( + modifier = + Modifier.padding( + start = indicatorBackgroundPadding, + top = labelIndicatorBackgroundSpacing, + end = indicatorBackgroundPadding + ), + horizontalArrangement = Arrangement.spacedBy(spacing), + ) { + for (itemIndex in items.indices) { + TextButton( + modifier = Modifier.weight(1f), + onClick = { selectedIndex = itemIndex }, + ) { + val item = items[itemIndex] + if (item.icon !== Empty) { + with(items[itemIndex]) { label() } + } + } + } + } + } +} + +data class VolumePanelRadioButtonBarColors( + /** Color of the indicator. */ + val indicatorColor: Color, + /** Color of the indicator background. */ + val indicatorBackgroundColor: Color, +) + +object VolumePanelRadioButtonBarDefaults { + + val DefaultIndicatorBackgroundPadding = 8.dp + val DefaultSpacing = 24.dp + val DefaultLabelIndicatorBackgroundSpacing = 12.dp + val DefaultIndicatorCornerRadius = 20.dp + val DefaultIndicatorBackgroundCornerRadius = 20.dp + + @Composable + fun defaultIndicatorCornerRadius( + x: Dp = DefaultIndicatorCornerRadius, + y: Dp = DefaultIndicatorCornerRadius, + ): CornerRadius = with(LocalDensity.current) { CornerRadius(x.toPx(), y.toPx()) } + + /** + * Returns the default VolumePanelRadioButtonBar colors. + * + * @param indicatorColor is the color of the indicator + * @param indicatorBackgroundColor is the color of the indicator background + */ + @Composable + fun defaultColors( + indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer, + indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface, + ): VolumePanelRadioButtonBarColors = + VolumePanelRadioButtonBarColors( + indicatorColor = indicatorColor, + indicatorBackgroundColor = indicatorBackgroundColor, + ) +} + +/** [VolumePanelRadioButtonBar] content scope. Use [item] to add more items. */ +interface VolumePanelRadioButtonBarScope { + + /** + * Adds a single item to the radio button group. + * + * @param isSelected true when the item is selected and false the otherwise + * @param onItemSelected is called when the item is selected + * @param icon of the to show in the indicator bar + * @param label to show below the indicator bar for the corresponding [icon] + */ + fun item( + isSelected: Boolean, + onItemSelected: () -> Unit, + icon: @Composable RowScope.() -> Unit = Empty, + label: @Composable RowScope.() -> Unit = Empty, + ) +} + +private val Empty: @Composable RowScope.() -> Unit = {} + +private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope { + + var hasSelectedItem: Boolean = false + private set + + private val mutableItems: MutableList<Item> = mutableListOf() + val items: List<Item> = mutableItems + + override fun item( + isSelected: Boolean, + onItemSelected: () -> Unit, + icon: @Composable RowScope.() -> Unit, + label: @Composable RowScope.() -> Unit, + ) { + require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" } + hasSelectedItem = hasSelectedItem || isSelected + mutableItems.add( + Item( + isSelected = isSelected, + onItemSelected = onItemSelected, + icon = icon, + label = label, + ) + ) + } +} + +private class Item( + val isSelected: Boolean, + val onItemSelected: () -> Unit, + val icon: @Composable RowScope.() -> Unit, + val label: @Composable RowScope.() -> Unit, +) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt new file mode 100644 index 000000000000..da29d5810974 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.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.volume.panel.component.spatialaudio + +import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent +import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents +import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria +import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel +import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup +import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria +import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +/** Dagger module, that provides Spatial Audio Volume Panel UI functionality. */ +@Module +interface SpatialAudioModule { + + @Binds + @IntoMap + @StringKey(VolumePanelComponents.SPATIAL_AUDIO) + fun bindComponentAvailabilityCriteria( + criteria: SpatialAudioAvailabilityCriteria + ): ComponentAvailabilityCriteria + + companion object { + + @Provides + @IntoMap + @StringKey(VolumePanelComponents.SPATIAL_AUDIO) + fun provideVolumePanelUiComponent( + viewModel: SpatialAudioViewModel, + popup: SpatialAudioPopup, + ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt new file mode 100644 index 000000000000..bed0ae80e377 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt @@ -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 com.android.systemui.volume.panel.component.spatialaudio.ui.composable + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import com.android.systemui.animation.Expandable +import com.android.systemui.common.ui.compose.Icon +import com.android.systemui.common.ui.compose.toColor +import com.android.systemui.res.R +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup +import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar +import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel +import javax.inject.Inject + +class SpatialAudioPopup +@Inject +constructor( + private val viewModel: SpatialAudioViewModel, + private val volumePanelPopup: VolumePanelPopup, +) { + + /** Shows a popup with the [expandable] animation. */ + fun show(expandable: Expandable) { + volumePanelPopup.show(expandable, { Title() }, { Content(it) }) + } + + @Composable + private fun Title() { + Text( + text = stringResource(R.string.volume_panel_spatial_audio_title), + style = MaterialTheme.typography.titleMedium, + textAlign = TextAlign.Center, + maxLines = 1, + ) + } + + @Composable + private fun Content(dialog: SystemUIDialog) { + val isAvailable by viewModel.isAvailable.collectAsState() + + if (!isAvailable) { + SideEffect { dialog.dismiss() } + return + } + + val enabledModelStates by viewModel.spatialAudioButtonByEnabled.collectAsState() + if (enabledModelStates.isEmpty()) { + return + } + VolumePanelRadioButtonBar { + for (buttonViewModel in enabledModelStates) { + item( + isSelected = buttonViewModel.button.isChecked, + onItemSelected = { viewModel.setEnabled(buttonViewModel.model) }, + icon = { + Icon( + icon = buttonViewModel.button.icon, + tint = buttonViewModel.iconColor.toColor(), + ) + }, + label = { + Text( + text = buttonViewModel.button.label.toString(), + style = MaterialTheme.typography.labelMedium, + color = buttonViewModel.labelColor.toColor(), + ) + } + ) + } + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt index 0a3aea767733..723f6a2bfff4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt @@ -27,6 +27,8 @@ import com.android.systemui.log.logcatLogBuffer import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.wakelock.WakeLockFake +import com.google.common.truth.Truth.assertThat import java.util.Optional import kotlinx.coroutines.test.runTest import org.junit.Before @@ -44,6 +46,9 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope + private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder + private lateinit var fakeWakeLock: WakeLockFake + @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent @Mock private lateinit var activity: Activity @@ -57,6 +62,10 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { whenever(taskFragmentComponentFactory.create(any(), any(), any(), any())) .thenReturn(taskFragmentComponent) + fakeWakeLock = WakeLockFake() + fakeWakeLockBuilder = WakeLockFake.Builder(context) + fakeWakeLockBuilder.setWakeLock(fakeWakeLock) + whenever(controlsComponent.getControlsListingController()) .thenReturn(Optional.of(controlsListingController)) @@ -87,12 +96,29 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any()) } + @Test + fun testAttachWindow_wakeLockAcquired() = + testScope.runTest { + underTest.onAttachedToWindow() + assertThat(fakeWakeLock.isHeld).isTrue() + } + @Test + fun testDetachWindow_wakeLockCanBeReleased() = + testScope.runTest { + underTest.onAttachedToWindow() + assertThat(fakeWakeLock.isHeld).isTrue() + + underTest.onDetachedFromWindow() + assertThat(fakeWakeLock.isHeld).isFalse() + } + private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService = with(kosmos) { return HomeControlsDreamService( controlsSettingsRepository = FakeControlsSettingsRepository(), taskFragmentFactory = taskFragmentComponentFactory, homeControlsComponentInteractor = homeControlsComponentInteractor, + fakeWakeLockBuilder, dreamActivityProvider = activityProvider, bgDispatcher = testDispatcher, logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 128b46533a8b..19b80da62dc7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -25,7 +25,6 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository -import com.android.systemui.common.shared.model.Position import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback @@ -152,24 +151,6 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { } @Test - fun clockPosition() = - testScope.runTest { - assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0)) - - underTest.setClockPosition(0, 1) - assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1)) - - underTest.setClockPosition(1, 9) - assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9)) - - underTest.setClockPosition(1, 0) - assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0)) - - underTest.setClockPosition(3, 1) - assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1)) - } - - @Test fun dozeTimeTick() = testScope.runTest { val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt index b0f59fe68f11..de659cf17c05 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt @@ -71,7 +71,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) MockitoAnnotations.initMocks(this) - whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow) + whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) kosmos.burnInInteractor = burnInInteractor whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt())) .thenReturn(emptyFlow()) @@ -81,18 +81,18 @@ class AodBurnInViewModelTest : SysuiTestCase() { } @Test - fun translationY_initializedToZero() = + fun movement_initializedToZero() = testScope.runTest { - val translationY by collectLastValue(underTest.translationY(burnInParameters)) - assertThat(translationY).isEqualTo(0) + val movement by collectLastValue(underTest.movement(burnInParameters)) + assertThat(movement?.translationY).isEqualTo(0) + assertThat(movement?.translationX).isEqualTo(0) + assertThat(movement?.scale).isEqualTo(0f) } @Test fun translationAndScale_whenNotDozing() = testScope.runTest { - val translationX by collectLastValue(underTest.translationX(burnInParameters)) - val translationY by collectLastValue(underTest.translationY(burnInParameters)) - val scale by collectLastValue(underTest.scale(burnInParameters)) + val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to not dozing (on lockscreen) keyguardTransitionRepository.sendTransitionStep( @@ -113,24 +113,17 @@ class AodBurnInViewModelTest : SysuiTestCase() { scale = 0.5f, ) - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale) - .isEqualTo( - BurnInScaleViewModel( - scale = 1f, - scaleClockOnly = true, - ) - ) + assertThat(movement?.translationX).isEqualTo(0) + assertThat(movement?.translationY).isEqualTo(0) + assertThat(movement?.scale).isEqualTo(1f) + assertThat(movement?.scaleClockOnly).isEqualTo(true) } @Test fun translationAndScale_whenFullyDozing() = testScope.runTest { burnInParameters = burnInParameters.copy(minViewY = 100) - val translationX by collectLastValue(underTest.translationX(burnInParameters)) - val translationY by collectLastValue(underTest.translationY(burnInParameters)) - val scale by collectLastValue(underTest.scale(burnInParameters)) + val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -150,15 +143,10 @@ class AodBurnInViewModelTest : SysuiTestCase() { scale = 0.5f, ) - assertThat(translationX).isEqualTo(20) - assertThat(translationY).isEqualTo(30) - assertThat(scale) - .isEqualTo( - BurnInScaleViewModel( - scale = 0.5f, - scaleClockOnly = true, - ) - ) + assertThat(movement?.translationX).isEqualTo(20) + assertThat(movement?.translationY).isEqualTo(30) + assertThat(movement?.scale).isEqualTo(0.5f) + assertThat(movement?.scaleClockOnly).isEqualTo(true) // Set to the beginning of GONE->AOD transition keyguardTransitionRepository.sendTransitionStep( @@ -170,15 +158,10 @@ class AodBurnInViewModelTest : SysuiTestCase() { ), validateStep = false, ) - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale) - .isEqualTo( - BurnInScaleViewModel( - scale = 1f, - scaleClockOnly = true, - ) - ) + assertThat(movement?.translationX).isEqualTo(0) + assertThat(movement?.translationY).isEqualTo(0) + assertThat(movement?.scale).isEqualTo(1f) + assertThat(movement?.scaleClockOnly).isEqualTo(true) } @Test @@ -191,9 +174,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { minViewY = 100, topInset = 80, ) - val translationX by collectLastValue(underTest.translationX(burnInParameters)) - val translationY by collectLastValue(underTest.translationY(burnInParameters)) - val scale by collectLastValue(underTest.scale(burnInParameters)) + val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -213,16 +194,11 @@ class AodBurnInViewModelTest : SysuiTestCase() { translationY = -30, scale = 0.5f, ) - assertThat(translationX).isEqualTo(20) + assertThat(movement?.translationX).isEqualTo(20) // -20 instead of -30, due to inset of 80 - assertThat(translationY).isEqualTo(-20) - assertThat(scale) - .isEqualTo( - BurnInScaleViewModel( - scale = 0.5f, - scaleClockOnly = true, - ) - ) + assertThat(movement?.translationY).isEqualTo(-20) + assertThat(movement?.scale).isEqualTo(0.5f) + assertThat(movement?.scaleClockOnly).isEqualTo(true) // Set to the beginning of GONE->AOD transition keyguardTransitionRepository.sendTransitionStep( @@ -234,15 +210,10 @@ class AodBurnInViewModelTest : SysuiTestCase() { ), validateStep = false, ) - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale) - .isEqualTo( - BurnInScaleViewModel( - scale = 1f, - scaleClockOnly = true, - ) - ) + assertThat(movement?.translationX).isEqualTo(0) + assertThat(movement?.translationY).isEqualTo(0) + assertThat(movement?.scale).isEqualTo(1f) + assertThat(movement?.scaleClockOnly).isEqualTo(true) } @Test @@ -255,9 +226,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { minViewY = 100, topInset = 80, ) - val translationX by collectLastValue(underTest.translationX(burnInParameters)) - val translationY by collectLastValue(underTest.translationY(burnInParameters)) - val scale by collectLastValue(underTest.scale(burnInParameters)) + val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -277,16 +246,11 @@ class AodBurnInViewModelTest : SysuiTestCase() { translationY = -30, scale = 0.5f, ) - assertThat(translationX).isEqualTo(20) + assertThat(movement?.translationX).isEqualTo(20) // -20 instead of -30, due to inset of 80 - assertThat(translationY).isEqualTo(-20) - assertThat(scale) - .isEqualTo( - BurnInScaleViewModel( - scale = 0.5f, - scaleClockOnly = true, - ) - ) + assertThat(movement?.translationY).isEqualTo(-20) + assertThat(movement?.scale).isEqualTo(0.5f) + assertThat(movement?.scaleClockOnly).isEqualTo(true) // Set to the beginning of GONE->AOD transition keyguardTransitionRepository.sendTransitionStep( @@ -298,15 +262,10 @@ class AodBurnInViewModelTest : SysuiTestCase() { ), validateStep = false, ) - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale) - .isEqualTo( - BurnInScaleViewModel( - scale = 1f, - scaleClockOnly = true, - ) - ) + assertThat(movement?.translationX).isEqualTo(0) + assertThat(movement?.translationY).isEqualTo(0) + assertThat(movement?.scale).isEqualTo(1f) + assertThat(movement?.scaleClockOnly).isEqualTo(true) } @Test @@ -314,9 +273,7 @@ class AodBurnInViewModelTest : SysuiTestCase() { testScope.runTest { whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true) - val translationX by collectLastValue(underTest.translationX(burnInParameters)) - val translationY by collectLastValue(underTest.translationY(burnInParameters)) - val scale by collectLastValue(underTest.scale(burnInParameters)) + val movement by collectLastValue(underTest.movement(burnInParameters)) // Set to dozing (on AOD) keyguardTransitionRepository.sendTransitionStep( @@ -337,8 +294,9 @@ class AodBurnInViewModelTest : SysuiTestCase() { scale = 0.5f, ) - assertThat(translationX).isEqualTo(0) - assertThat(translationY).isEqualTo(0) - assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false)) + assertThat(movement?.translationX).isEqualTo(0) + assertThat(movement?.translationY).isEqualTo(0) + assertThat(movement?.scale).isEqualTo(0.5f) + assertThat(movement?.scaleClockOnly).isEqualTo(false) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index 864acfb1ddb9..04c270d07b0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.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. @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase @@ -24,9 +25,13 @@ import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory +import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -36,18 +41,23 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.MockitoAnnotations @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel + @Mock private lateinit var burnInInteractor: BurnInInteractor + private val burnInFlow = MutableStateFlow(BurnInModel()) + + private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor private lateinit var underTest: KeyguardIndicationAreaViewModel private lateinit var repository: FakeKeyguardRepository @@ -70,9 +80,11 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR) + mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) whenever(burnInHelperWrapper.burnInOffset(anyInt(), any())) .thenReturn(RETURNED_BURN_IN_OFFSET) + whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow) val withDeps = KeyguardInteractorFactory.create() val keyguardInteractor = withDeps.keyguardInteractor @@ -82,78 +94,85 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow) whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow) whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow) + bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository) underTest = KeyguardIndicationAreaViewModel( keyguardInteractor = keyguardInteractor, - bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), + bottomAreaInteractor = bottomAreaInteractor, keyguardBottomAreaViewModel = bottomAreaViewModel, burnInHelperWrapper = burnInHelperWrapper, + burnInInteractor = burnInInteractor, shortcutsCombinedViewModel = shortcutsCombinedViewModel, configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), ) } @Test - fun alpha() = runTest { - val value = collectLastValue(underTest.alpha) - - assertThat(value()).isEqualTo(1f) - alphaFlow.value = 0.1f - assertThat(value()).isEqualTo(0.1f) - alphaFlow.value = 0.5f - assertThat(value()).isEqualTo(0.5f) - alphaFlow.value = 0.2f - assertThat(value()).isEqualTo(0.2f) - alphaFlow.value = 0f - assertThat(value()).isEqualTo(0f) - } + fun alpha() = + testScope.runTest { + val value = collectLastValue(underTest.alpha) + + assertThat(value()).isEqualTo(1f) + alphaFlow.value = 0.1f + assertThat(value()).isEqualTo(0.1f) + alphaFlow.value = 0.5f + assertThat(value()).isEqualTo(0.5f) + alphaFlow.value = 0.2f + assertThat(value()).isEqualTo(0.2f) + alphaFlow.value = 0f + assertThat(value()).isEqualTo(0f) + } @Test - fun isIndicationAreaPadded() = runTest { - repository.setKeyguardShowing(true) - val value = collectLastValue(underTest.isIndicationAreaPadded) - - assertThat(value()).isFalse() - startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() - endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) - assertThat(value()).isTrue() - startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) - assertThat(value()).isTrue() - endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) - assertThat(value()).isFalse() - } + fun isIndicationAreaPadded() = + testScope.runTest { + repository.setKeyguardShowing(true) + val value = collectLastValue(underTest.isIndicationAreaPadded) + + assertThat(value()).isFalse() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = true) + assertThat(value()).isTrue() + startButtonFlow.value = startButtonFlow.value.copy(isVisible = false) + assertThat(value()).isTrue() + endButtonFlow.value = endButtonFlow.value.copy(isVisible = false) + assertThat(value()).isFalse() + } @Test - fun indicationAreaTranslationX() = runTest { - val value = collectLastValue(underTest.indicationAreaTranslationX) - - assertThat(value()).isEqualTo(0f) - repository.setClockPosition(100, 100) - assertThat(value()).isEqualTo(100f) - repository.setClockPosition(200, 100) - assertThat(value()).isEqualTo(200f) - repository.setClockPosition(200, 200) - assertThat(value()).isEqualTo(200f) - repository.setClockPosition(300, 100) - assertThat(value()).isEqualTo(300f) - } + fun indicationAreaTranslationX() = + testScope.runTest { + val value = collectLastValue(underTest.indicationAreaTranslationX) + + assertThat(value()).isEqualTo(0f) + bottomAreaInteractor.setClockPosition(100, 100) + assertThat(value()).isEqualTo(100f) + bottomAreaInteractor.setClockPosition(200, 100) + assertThat(value()).isEqualTo(200f) + bottomAreaInteractor.setClockPosition(200, 200) + assertThat(value()).isEqualTo(200f) + bottomAreaInteractor.setClockPosition(300, 100) + assertThat(value()).isEqualTo(300f) + } @Test - fun indicationAreaTranslationY() = runTest { - val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) - - // Negative 0 - apparently there's a difference in floating point arithmetic - FML - assertThat(value()).isEqualTo(-0f) - val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) - assertThat(value()).isEqualTo(expected1) - val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) - assertThat(value()).isEqualTo(expected2) - val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) - assertThat(value()).isEqualTo(expected3) - val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) - assertThat(value()).isEqualTo(expected4) - } + fun indicationAreaTranslationY() = + testScope.runTest { + val value = + collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)) + + // Negative 0 - apparently there's a difference in floating point arithmetic - FML + assertThat(value()).isEqualTo(-0f) + val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f) + assertThat(value()).isEqualTo(expected1) + val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f) + assertThat(value()).isEqualTo(expected2) + val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f) + assertThat(value()).isEqualTo(expected3) + val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f) + assertThat(value()).isEqualTo(expected4) + } private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float { repository.setDozeAmount(dozeAmount) 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/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index d505b27a9969..7fabe3338483 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -25,6 +25,7 @@ import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS; +import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES; import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE; import static android.os.UserHandle.USER_ALL; import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS; @@ -115,7 +116,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { public static List<FlagsParameterization> getParams() { return FlagsParameterization.allCombinationsOf( FLAG_ALLOW_PRIVATE_PROFILE, - FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS); + FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS, + FLAG_ENABLE_PRIVATE_SPACE_FEATURES); } public NotificationLockscreenUserManagerTest(FlagsParameterization flags) { @@ -872,7 +874,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE) + @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testProfileAvailabilityIntent() { mLockscreenUserManager.mCurrentProfiles.clear(); assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); @@ -883,7 +885,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE) + @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testProfileUnAvailabilityIntent() { mLockscreenUserManager.mCurrentProfiles.clear(); assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); @@ -894,7 +896,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE) + @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testManagedProfileAvailabilityIntent() { mLockscreenUserManager.mCurrentProfiles.clear(); mLockscreenUserManager.mCurrentManagedProfiles.clear(); @@ -908,7 +910,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE) + @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES}) public void testManagedProfileUnAvailabilityIntent() { mLockscreenUserManager.mCurrentProfiles.clear(); mLockscreenUserManager.mCurrentManagedProfiles.clear(); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt index e188f5bfc1c8..8e765f7bb689 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt @@ -196,10 +196,14 @@ class NotificationsSoundPolicyInteractorTest : SysuiTestCase() { } @Test - fun zenModeAlarms_ringAndNotifications_muted() { + fun zenModeAlarms_ringedStreams_muted() { with(kosmos) { val expectedToBeMuted = - setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION) + setOf( + AudioManager.STREAM_RING, + AudioManager.STREAM_NOTIFICATION, + AudioManager.STREAM_SYSTEM, + ) testScope.runTest { notificationsSoundPolicyRepository.updateNotificationPolicy() notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 0de15b8db665..deb19769372c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -34,6 +34,7 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState @@ -66,7 +67,7 @@ import org.mockito.Mockito.mock @RunWith(AndroidJUnit4::class) class SharedNotificationContainerViewModelTest : SysuiTestCase() { val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) - lateinit var translationYFlow: MutableStateFlow<Float> + lateinit var movementFlow: MutableStateFlow<BurnInModel> val kosmos = testKosmos().apply { @@ -95,8 +96,8 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { @Before fun setUp() { overrideResource(R.bool.config_use_split_notification_shade, false) - translationYFlow = MutableStateFlow(0f) - whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow) + movementFlow = MutableStateFlow(BurnInModel()) + whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow) underTest = kosmos.sharedNotificationContainerViewModel } @@ -608,7 +609,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { showLockscreen() assertThat(translationY).isEqualTo(0) - translationYFlow.value = 150f + movementFlow.value = BurnInModel(translationY = 150) assertThat(translationY).isEqualTo(150f) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt index 36be90ecbf7e..449e8bf6998f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt @@ -78,7 +78,7 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { with(kosmos) { testScope.runTest { localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice) - spatializerRepository.setIsHeadTrackingAvailable(false) + spatializerRepository.defaultHeadTrackingAvailable = false spatializerRepository.defaultSpatialAudioAvailable = false val isAvailable by collectLastValue(underTest.isAvailable()) @@ -94,7 +94,7 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { with(kosmos) { testScope.runTest { localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice) - spatializerRepository.setIsHeadTrackingAvailable(false) + spatializerRepository.defaultHeadTrackingAvailable = false spatializerRepository.defaultSpatialAudioAvailable = true val isAvailable by collectLastValue(underTest.isAvailable()) @@ -110,7 +110,7 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { with(kosmos) { testScope.runTest { localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice) - spatializerRepository.setIsHeadTrackingAvailable(true) + spatializerRepository.defaultHeadTrackingAvailable = true spatializerRepository.defaultSpatialAudioAvailable = true val isAvailable by collectLastValue(underTest.isAvailable()) @@ -125,7 +125,7 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { fun spatialAudio_headTracking_noDevice_unavailable() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsHeadTrackingAvailable(true) + spatializerRepository.defaultHeadTrackingAvailable = true spatializerRepository.defaultSpatialAudioAvailable = true val isAvailable by collectLastValue(underTest.isAvailable()) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt index eb6f0b2e32b3..06ae220876d6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt @@ -82,7 +82,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { ), true ) - spatializerRepository.setIsHeadTrackingAvailable(true) + spatializerRepository.defaultHeadTrackingAvailable = true underTest = SpatialAudioComponentInteractor( diff --git a/packages/SystemUI/res/drawable/ic_head_tracking.xml b/packages/SystemUI/res/drawable/ic_head_tracking.xml new file mode 100644 index 000000000000..d4a44fd9858e --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_head_tracking.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M480,520Q414,520 367,473Q320,426 320,360Q320,294 367,247Q414,200 480,200Q546,200 593,247Q640,294 640,360Q640,426 593,473Q546,520 480,520ZM160,840L160,728Q160,695 177,666Q194,637 224,622Q275,596 339,578Q403,560 480,560Q557,560 621,578Q685,596 736,622Q766,637 783,666Q800,695 800,728L800,840L160,840ZM240,760L720,760L720,728Q720,717 714.5,708Q709,699 700,694Q664,676 607.5,658Q551,640 480,640Q409,640 352.5,658Q296,676 260,694Q251,699 245.5,708Q240,717 240,728L240,760ZM480,440Q513,440 536.5,416.5Q560,393 560,360Q560,327 536.5,303.5Q513,280 480,280Q447,280 423.5,303.5Q400,327 400,360Q400,393 423.5,416.5Q447,440 480,440ZM39,200L39,120Q56,120 70,113.5Q84,107 95,96Q106,85 112,71Q118,57 118,40L199,40Q199,73 186.5,102Q174,131 152,153Q130,175 101,187.5Q72,200 39,200ZM39,361L39,281Q90,281 133.5,262Q177,243 209,210Q241,177 260,133.5Q279,90 279,40L360,40Q360,106 335,164.5Q310,223 266,267Q222,311 164,336Q106,361 39,361ZM920,361Q854,361 795.5,336Q737,311 693,267Q649,223 624,164.5Q599,106 599,40L679,40Q679,90 698,133.5Q717,177 750,210Q783,243 826.5,262Q870,281 920,281L920,361ZM920,200Q887,200 858,187.5Q829,175 807,153Q785,131 772.5,102Q760,73 760,40L840,40Q840,57 846.5,71Q853,85 864,96Q875,107 889,113.5Q903,120 920,120L920,200ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360ZM480,760L480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760L480,760L480,760Z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio.xml b/packages/SystemUI/res/drawable/ic_spatial_audio.xml new file mode 100644 index 000000000000..0ee609ab79f2 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_spatial_audio.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M920,401Q848,401 782,373.5Q716,346 665,295Q614,244 586.5,178Q559,112 559,40L639,40Q639,97 660,148Q681,199 721,239Q761,279 812,300.5Q863,322 920,322L920,401ZM920,242Q879,242 842.5,227Q806,212 777,183Q748,154 733,117.5Q718,81 718,40L797,40Q797,65 806.5,87.5Q816,110 833,127Q850,144 872.5,153Q895,162 920,162L920,242ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml new file mode 100644 index 000000000000..c7d3272b380d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml @@ -0,0 +1,26 @@ +<!-- + ~ 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. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M750,550L806,494Q766,454 743.5,402.5Q721,351 721,294Q721,237 743.5,186Q766,135 806,95L750,37Q699,88 670,155Q641,222 641,294Q641,366 670,432.5Q699,499 750,550ZM862,436L918,380Q901,363 891,341Q881,319 881,294Q881,269 891,247Q901,225 918,208L862,151Q833,180 817,216Q801,252 801,293Q801,334 817,371Q833,408 862,436ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" /> +</vector> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 71ae0d716429..035cfdc492d0 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -223,6 +223,7 @@ <item type="id" name="lock_icon" /> <item type="id" name="lock_icon_bg" /> <item type="id" name="burn_in_layer" /> + <item type="id" name="burn_in_layer_empty_view" /> <item type="id" name="communal_tutorial_indicator" /> <item type="id" name="nssl_placeholder_barrier_bottom" /> <item type="id" name="ambient_indication_container" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 5dbdd18bc81b..f71c4155771b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1538,8 +1538,16 @@ <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string> <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string> - <!-- Label for button to enabled/disable live caption [CHAR_LIMIT=30] --> + <!-- Label for button to enabled/disable active noise cancellation [CHAR_LIMIT=30] --> <string name="volume_panel_noise_control_title">Noise Control</string> + <!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] --> + <string name="volume_panel_spatial_audio_title">Spatial Audio</string> + <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] --> + <string name="volume_panel_spatial_audio_off">Off</string> + <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] --> + <string name="volume_panel_spatial_audio_fixed">Fixed</string> + <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] --> + <string name="volume_panel_spatial_audio_tracking">Head Tracking</string> <string name="volume_ringer_change">Tap to change ringer mode</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 9421f150a38a..c0ae4a1f4036 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -53,9 +53,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.animation.ViewHierarchyAnimator; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.shared.model.TransitionState; -import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.plugins.clocks.ClockController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; @@ -104,7 +101,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private final Rect mClipBounds = new Rect(); private final KeyguardInteractor mKeyguardInteractor; private final PowerInteractor mPowerInteractor; - private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; private final DozeParameters mDozeParameters; private View mStatusArea = null; @@ -112,7 +108,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV private Boolean mSplitShadeEnabled = false; private Boolean mStatusViewCentered = true; - private boolean mGoneToAodTransitionRunning = false; private DumpManager mDumpManager; private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener = @@ -181,7 +176,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV KeyguardLogger logger, InteractionJankMonitor interactionJankMonitor, KeyguardInteractor keyguardInteractor, - KeyguardTransitionInteractor keyguardTransitionInteractor, DumpManager dumpManager, PowerInteractor powerInteractor) { super(keyguardStatusView); @@ -197,7 +191,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mDumpManager = dumpManager; mKeyguardInteractor = keyguardInteractor; mPowerInteractor = powerInteractor; - mKeyguardTransitionInteractor = keyguardTransitionInteractor; } @Override @@ -232,6 +225,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mDumpManager.registerDumpable(getInstanceName(), this); if (migrateClocksToBlueprint()) { startCoroutines(EmptyCoroutineContext.INSTANCE); + mView.setVisibility(View.GONE); } } @@ -247,15 +241,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV dozeTimeTick(); } }, context); - - collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(), - (TransitionStep step) -> { - if (step.getTransitionState() == TransitionState.RUNNING) { - mGoneToAodTransitionRunning = true; - } else { - mGoneToAodTransitionRunning = false; - } - }, context); } public KeyguardStatusView getView() { @@ -326,7 +311,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV * Set keyguard status view alpha. */ public void setAlpha(float alpha) { - if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) { + if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) { mView.setAlpha(alpha); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 2000028dff41..f5a6cb35b545 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -88,6 +88,10 @@ public class KeyguardVisibilityHelper { boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState) { + if (migrateClocksToBlueprint()) { + log("Ignoring KeyguardVisibilityelper, migrateClocksToBlueprint flag on"); + return; + } Assert.isMainThread(); PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA); boolean isOccluded = mKeyguardStateController.isOccluded(); 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/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt index c0a873ac9a65..989b0de85e0c 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt @@ -18,6 +18,7 @@ package com.android.systemui.display.ui.view import android.content.Context import android.os.Bundle import android.view.View +import android.view.WindowInsets import android.widget.TextView import androidx.core.view.updatePadding import com.android.systemui.res.R @@ -44,7 +45,10 @@ class MirroringConfirmationDialog( private lateinit var mirrorButton: TextView private lateinit var dismissButton: TextView private lateinit var dualDisplayWarning: TextView + private lateinit var bottomSheet: View private var enabledPressed = false + private val defaultDialogBottomInset = + context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -63,6 +67,8 @@ class MirroringConfirmationDialog( visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE } + bottomSheet = requireViewById(R.id.cd_bottom_sheet) + setOnDismissListener { if (!enabledPressed) { onCancelMirroring.onClick(null) @@ -71,15 +77,17 @@ class MirroringConfirmationDialog( setupInsets() } - private fun setupInsets() { + private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) { // This avoids overlap between dialog content and navigation bars. - requireViewById<View>(R.id.cd_bottom_sheet).apply { - val navbarInsets = navbarBottomInsetsProvider() - val defaultDialogBottomInset = - context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding) - // we only care about the bottom inset as in all other configuration where navigations - // are in other display sides there is no overlap with the dialog. - updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset)) + // we only care about the bottom inset as in all other configuration where navigations + // are in other display sides there is no overlap with the dialog. + bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset)) + } + + override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) { + val navbarType = WindowInsets.Type.navigationBars() + if (changedTypes and navbarType != 0) { + setupInsets(insets.getInsets(navbarType).bottom) } } diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt index 190062cdcca1..fbf0538e4bae 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt @@ -32,9 +32,12 @@ import dagger.Module import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.launch @@ -57,6 +60,7 @@ constructor( private var dialog: Dialog? = null /** Starts listening for pending displays. */ + @OptIn(FlowPreview::class) override fun start() { val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay val concurrentDisplaysInProgessFlow = @@ -66,6 +70,13 @@ constructor( flow { emit(false) } } pendingDisplayFlow + // Let's debounce for 2 reasons: + // - prevent fast dialog flashes in case pending displays are available for just a few + // millis + // - Prevent jumps related to inset changes: when in 3 buttons navigation, device + // unlock triggers a change in insets that might result in a jump of the dialog (if a + // display was connected while on the lockscreen). + .debounce(200.milliseconds) .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress -> if (pendingDisplay == null) { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index e74814adb1b0..376d3129d8c3 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -17,6 +17,7 @@ package com.android.systemui.dreams.homecontrols import android.content.Intent +import android.os.PowerManager import android.service.controls.ControlsProviderService import android.service.dreams.DreamService import android.window.TaskFragmentInfo @@ -27,6 +28,8 @@ import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsCo import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.DreamLog +import com.android.systemui.util.wakelock.WakeLock +import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT import javax.inject.Inject import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher @@ -42,14 +45,23 @@ constructor( private val controlsSettingsRepository: ControlsSettingsRepository, private val taskFragmentFactory: TaskFragmentComponent.Factory, private val homeControlsComponentInteractor: HomeControlsComponentInteractor, + private val wakeLockBuilder: WakeLock.Builder, private val dreamActivityProvider: DreamActivityProvider, @Background private val bgDispatcher: CoroutineDispatcher, @DreamLog logBuffer: LogBuffer ) : DreamService() { + private val serviceJob = SupervisorJob() private val serviceScope = CoroutineScope(bgDispatcher + serviceJob) - private val logger = DreamLogger(logBuffer, "HomeControlsDreamService") + private val logger = DreamLogger(logBuffer, TAG) private lateinit var taskFragmentComponent: TaskFragmentComponent + private val wakeLock: WakeLock by lazy { + wakeLockBuilder + .setMaxTimeout(NO_TIMEOUT) + .setTag(TAG) + .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK) + .build() + } override fun onAttachedToWindow() { super.onAttachedToWindow() @@ -72,6 +84,8 @@ constructor( hide = { finish() } ) .apply { createTaskFragment() } + + wakeLock.acquire(TAG) } private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) { @@ -100,6 +114,7 @@ constructor( override fun onDetachedFromWindow() { super.onDetachedFromWindow() + wakeLock.release(TAG) taskFragmentComponent.destroy() serviceScope.launch { delay(CANCELLATION_DELAY_AFTER_DETACHED) @@ -115,5 +130,6 @@ constructor( * complete. */ val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds + const val TAG = "HomeControlsDreamService" } } 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 3a6423d680c6..1298fa5af033 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 @@ -24,7 +24,6 @@ import com.android.systemui.biometrics.AuthController import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow -import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -80,12 +79,6 @@ interface KeyguardRepository { val keyguardAlpha: StateFlow<Float> /** - * Observable of the relative offset of the lock-screen clock from its natural position on the - * screen. - */ - val clockPosition: StateFlow<Position> - - /** * Observable for whether the keyguard is showing. * * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in @@ -241,11 +234,6 @@ interface KeyguardRepository { fun setKeyguardAlpha(alpha: Float) /** - * Sets the relative offset of the lock-screen clock from its natural position on the screen. - */ - fun setClockPosition(x: Int, y: Int) - - /** * Returns whether the keyguard bottom area should be constrained to the top of the lock icon */ fun isUdfpsSupported(): Boolean @@ -324,9 +312,6 @@ constructor( private val _keyguardAlpha = MutableStateFlow(1f) override val keyguardAlpha = _keyguardAlpha.asStateFlow() - private val _clockPosition = MutableStateFlow(Position(0, 0)) - override val clockPosition = _clockPosition.asStateFlow() - private val _clockShouldBeCentered = MutableStateFlow(true) override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow() @@ -678,10 +663,6 @@ constructor( _keyguardAlpha.value = alpha } - override fun setClockPosition(x: Int, y: Int) { - _clockPosition.value = Position(x, y) - } - override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported override fun setQuickSettingsVisible(isVisible: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt index 7ae70a9a3e7c..ca862896efaa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context import androidx.annotation.DimenRes -import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.doze.util.BurnInHelperWrapper @@ -47,13 +47,15 @@ constructor( private val context: Context, private val burnInHelperWrapper: BurnInHelperWrapper, @Application private val scope: CoroutineScope, - private val configurationRepository: ConfigurationRepository, + private val configurationInteractor: ConfigurationInteractor, private val keyguardInteractor: KeyguardInteractor, ) { val deviceEntryIconXOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true) + .stateIn(scope, SharingStarted.WhileSubscribed(), 0) val deviceEntryIconYOffset: StateFlow<Int> = burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false) + .stateIn(scope, SharingStarted.WhileSubscribed(), 0) val udfpsProgress: StateFlow<Float> = keyguardInteractor.dozeTimeTick .mapLatest { burnInHelperWrapper.burnInProgressOffset() } @@ -63,18 +65,18 @@ constructor( burnInHelperWrapper.burnInProgressOffset() ) - val keyguardBurnIn: Flow<BurnInModel> = - combine( - burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true), - burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map { - it * 2 - - context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y) + /** Given the max x,y dimens, determine the current translation shifts. */ + fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> { + return combine( + burnInOffset(xDimenResourceId, isXAxis = true), + burnInOffset(yDimenResourceId, isXAxis = false).map { + it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId) } ) { translationX, translationY -> BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale()) } .distinctUntilChanged() - .stateIn(scope, SharingStarted.Lazily, BurnInModel()) + } /** * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the @@ -84,23 +86,14 @@ constructor( private fun burnInOffset( @DimenRes maxBurnInOffsetResourceId: Int, isXAxis: Boolean, - ): StateFlow<Int> { - return configurationRepository.onAnyConfigurationChange - .flatMapLatest { - val maxBurnInOffsetPixels = - context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) - keyguardInteractor.dozeTimeTick.mapLatest { - calculateOffset(maxBurnInOffsetPixels, isXAxis) - } + ): Flow<Int> { + return configurationInteractor.onAnyConfigurationChange.flatMapLatest { + val maxBurnInOffsetPixels = + context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) + keyguardInteractor.dozeTimeTick.mapLatest { + calculateOffset(maxBurnInOffsetPixels, isXAxis) } - .stateIn( - scope, - SharingStarted.Lazily, - calculateOffset( - context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId), - isXAxis, - ) - ) + } } /** @@ -111,24 +104,14 @@ constructor( private fun burnInOffsetDefinedInPixels( @DimenRes maxBurnInOffsetResourceId: Int, isXAxis: Boolean, - ): StateFlow<Int> { - return configurationRepository.scaleForResolution - .flatMapLatest { scale -> - val maxBurnInOffsetPixels = - context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) - keyguardInteractor.dozeTimeTick.mapLatest { - calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) - } + ): Flow<Int> { + return configurationInteractor.scaleForResolution.flatMapLatest { scale -> + val maxBurnInOffsetPixels = + context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId) + keyguardInteractor.dozeTimeTick.mapLatest { + calculateOffset(maxBurnInOffsetPixels, isXAxis, scale) } - .stateIn( - scope, - SharingStarted.WhileSubscribed(), - calculateOffset( - context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId), - isXAxis, - configurationRepository.getResolutionScale(), - ) - ) + } } private fun calculateOffset( 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 bcad3324b258..12b27eb195fb 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 @@ -357,7 +357,8 @@ constructor( from = KeyguardState.LOCKSCREEN, modeOnCanceledFromStartedStep = { startedStep -> if ( - startedStep.to == KeyguardState.AOD && startedStep.from == KeyguardState.AOD + transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD && + startedStep.from == KeyguardState.AOD ) { TransitionModeOnCanceled.REVERSE } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt index d2a7486eed0b..b9ec58ccb925 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt @@ -22,6 +22,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow /** Encapsulates business-logic specifically related to the keyguard bottom area. */ @SysUISingleton @@ -35,10 +37,13 @@ constructor( /** The amount of alpha for the UI components of the bottom area. */ val alpha: Flow<Float> = repository.bottomAreaAlpha /** The position of the keyguard clock. */ - val clockPosition: Flow<Position> = repository.clockPosition + private val _clockPosition = MutableStateFlow(Position(0, 0)) + /** See [ClockSection] */ + @Deprecated("with migrateClocksToBlueprint()") + val clockPosition: Flow<Position> = _clockPosition.asStateFlow() fun setClockPosition(x: Int, y: Int) { - repository.setClockPosition(x, y) + _clockPosition.value = Position(x, y) } fun setAlpha(alpha: Float) { 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 f321bd7e13a0..143edf972cb0 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 @@ -27,7 +27,6 @@ import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.shared.model.NotificationContainerBounds -import com.android.systemui.common.shared.model.Position import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -37,6 +36,7 @@ import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff import com.android.systemui.keyguard.shared.model.DozeTransitionModel +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.power.domain.interactor.PowerInteractor @@ -235,9 +235,6 @@ constructor( /** The approximate location on the screen of the face unlock sensor, if one is available. */ val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation - /** The position of the keyguard clock. */ - val clockPosition: Flow<Position> = repository.clockPosition - @Deprecated("Use the relevant TransitionViewModel") val keyguardAlpha: Flow<Float> = repository.keyguardAlpha @@ -272,8 +269,11 @@ constructor( configurationInteractor .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up) .flatMapLatest { translationDistance -> - shadeRepository.legacyShadeExpansion.map { - if (it == 0f) { + combine( + shadeRepository.legacyShadeExpansion.onStart { emit(0f) }, + keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, + ) { legacyShadeExpansion, goneValue -> + if (goneValue == 1f || legacyShadeExpansion == 0f) { // Reset the translation value 0f } else { @@ -281,11 +281,12 @@ constructor( MathUtils.lerp( translationDistance, 0, - Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it) + Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion) ) } } } + .distinctUntilChanged() val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered @@ -345,10 +346,6 @@ constructor( repository.setQuickSettingsVisible(isVisible) } - fun setClockPosition(x: Int, y: Int) { - repository.setClockPosition(x, y) - } - fun setAlpha(alpha: Float) { repository.setKeyguardAlpha(alpha) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index dc1f33d53853..fc95ec927a4c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -73,7 +73,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -148,6 +147,7 @@ object KeyguardRootViewBinder { viewModel.alpha(viewState).collect { alpha -> view.alpha = alpha childViews[statusViewId]?.alpha = alpha + childViews[burnInLayerId]?.alpha = alpha } } } @@ -195,66 +195,68 @@ object KeyguardRootViewBinder { // large clock isn't added to burnInLayer due to its scale transition // so we also need to add translation to it here // same as translationX - burnInParams - .flatMapLatest { params -> viewModel.translationY(params) } - .collect { y -> - childViews[burnInLayerId]?.translationY = y - childViews[largeClockId]?.translationY = y - childViews[aodNotificationIconContainerId]?.translationY = y - } + viewModel.translationY.collect { y -> + childViews[burnInLayerId]?.translationY = y + childViews[largeClockId]?.translationY = y + childViews[aodNotificationIconContainerId]?.translationY = y + } } launch { - burnInParams - .flatMapLatest { params -> viewModel.translationX(params) } - .collect { state -> - val px = state.value ?: return@collect - when { - state.isToOrFrom(KeyguardState.AOD) -> { - childViews[largeClockId]?.translationX = px - childViews[burnInLayerId]?.translationX = px - childViews[aodNotificationIconContainerId] - ?.translationX = px - } - state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> { - for ((key, childView) in childViews.entries) { - when (key) { - indicationArea, - startButton, - endButton, - lockIcon -> { - // Do not move these views - } - else -> childView.translationX = px + viewModel.translationX.collect { state -> + val px = state.value ?: return@collect + when { + state.isToOrFrom(KeyguardState.AOD) -> { + childViews[largeClockId]?.translationX = px + childViews[burnInLayerId]?.translationX = px + childViews[aodNotificationIconContainerId]?.translationX = + px + } + state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> { + for ((key, childView) in childViews.entries) { + when (key) { + indicationArea, + startButton, + endButton, + lockIcon -> { + // Do not move these views } + else -> childView.translationX = px } } } } + } } launch { - burnInParams - .flatMapLatest { params -> viewModel.scale(params) } - .collect { scaleViewModel -> - if (scaleViewModel.scaleClockOnly) { - // For clocks except weather clock, we have scale transition - // besides translate - childViews[largeClockId]?.let { - it.scaleX = scaleViewModel.scale - it.scaleY = scaleViewModel.scale - } - } else { - // For weather clock, large clock should have only scale - // transition with other parts in burnInLayer - childViews[burnInLayerId]?.scaleX = scaleViewModel.scale - childViews[burnInLayerId]?.scaleY = scaleViewModel.scale - childViews[aodNotificationIconContainerId]?.scaleX = - scaleViewModel.scale - childViews[aodNotificationIconContainerId]?.scaleY = - scaleViewModel.scale + viewModel.scale.collect { scaleViewModel -> + if (scaleViewModel.scaleClockOnly) { + // For clocks except weather clock, we have scale transition + // besides translate + childViews[largeClockId]?.let { + it.scaleX = scaleViewModel.scale + it.scaleY = scaleViewModel.scale + } + // Make sure to reset these views, or they will be invisible + if (childViews[burnInLayerId]?.scaleX != 1f) { + childViews[burnInLayerId]?.scaleX = 1f + childViews[burnInLayerId]?.scaleY = 1f + childViews[aodNotificationIconContainerId]?.scaleX = 1f + childViews[aodNotificationIconContainerId]?.scaleY = 1f + view.requestLayout() } + } else { + // For weather clock, large clock should have only scale + // transition with other parts in burnInLayer + childViews[burnInLayerId]?.scaleX = scaleViewModel.scale + childViews[burnInLayerId]?.scaleY = scaleViewModel.scale + childViews[aodNotificationIconContainerId]?.scaleX = + scaleViewModel.scale + childViews[aodNotificationIconContainerId]?.scaleY = + scaleViewModel.scale } + } } if (NotificationIconContainerRefactor.isEnabled) { @@ -311,6 +313,8 @@ object KeyguardRootViewBinder { } } + launch { burnInParams.collect { viewModel.updateBurnInParams(it) } } + if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { launch { deviceEntryHapticsInteractor.playSuccessHaptic.collect { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt index 98bebd091f1a..88ce9dc88a7b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt @@ -21,6 +21,8 @@ import android.content.Context import android.view.View import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.KeyguardRootView @@ -37,24 +39,24 @@ constructor( private val clockViewModel: KeyguardClockViewModel, ) : KeyguardSection() { private lateinit var burnInLayer: AodBurnInLayer + // The burn-in layer requires at least 1 view at all times + private val emptyView: View by lazy { + View(context, null).apply { + id = R.id.burn_in_layer_empty_view + visibility = View.GONE + } + } override fun addViews(constraintLayout: ConstraintLayout) { if (!migrateClocksToBlueprint()) { return } - // The burn-in layer requires at least 1 view at all times - val emptyView = View(context, null).apply { id = View.generateViewId() } constraintLayout.addView(emptyView) burnInLayer = AodBurnInLayer(context).apply { id = R.id.burn_in_layer registerListener(rootView) addView(emptyView) - if (!migrateClocksToBlueprint()) { - val statusView = - constraintLayout.requireViewById<View>(R.id.keyguard_status_view) - addView(statusView) - } } constraintLayout.addView(burnInLayer) } @@ -70,6 +72,13 @@ constructor( if (!migrateClocksToBlueprint()) { return } + + constraintSet.apply { + // The empty view should not occupy any space + constrainHeight(R.id.burn_in_layer_empty_view, 1) + constrainWidth(R.id.burn_in_layer_empty_view, 0) + connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM) + } } override fun removeViews(constraintLayout: ConstraintLayout) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt index 7be390a4526f..f961e083e64f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.util.Log import android.util.MathUtils import com.android.app.animation.Interpolators import com.android.keyguard.KeyguardClockSwitch @@ -62,23 +63,26 @@ constructor( private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel, private val keyguardClockViewModel: KeyguardClockViewModel, ) { - /** Horizontal translation for elements that need to apply anti-burn-in tactics. */ - fun translationX( - params: BurnInParameters, - ): Flow<Float> { - return burnIn(params).map { it.translationX.toFloat() } - } + private val TAG = "AodBurnInViewModel" - /** Vertical translation for elements that need to apply anti-burn-in tactics. */ - fun translationY( - params: BurnInParameters, - ): Flow<Float> { + /** All burn-in movement: x,y,scale, to shift items and prevent burn-in */ + fun movement( + burnInParams: BurnInParameters, + ): Flow<BurnInModel> { + val params = + if (burnInParams.minViewY < burnInParams.topInset) { + // minViewY should never be below the inset. Correct it if needed + Log.w(TAG, "minViewY is below topInset: $burnInParams") + burnInParams.copy(minViewY = burnInParams.topInset) + } else { + burnInParams + } return configurationInteractor .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y) .flatMapLatest { enterFromTopAmount -> combine( keyguardInteractor.keyguardTranslationY.onStart { emit(0f) }, - burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) }, + burnIn(params).onStart { emit(BurnInModel()) }, goneToAodTransitionViewModel .enterFromTopTranslationY(enterFromTopAmount) .onStart { emit(StateToValue()) }, @@ -88,32 +92,26 @@ constructor( aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart { emit(StateToValue()) }, - ) { keyguardTranslationY, burnInY, goneToAod, occludedToLockscreen, aodToLockscreen - -> - if (isInTransition(aodToLockscreen.transitionState)) { - aodToLockscreen.value ?: 0f - } else if (isInTransition(goneToAod.transitionState)) { - (goneToAod.value ?: 0f) + burnInY - } else { - burnInY + occludedToLockscreen + keyguardTranslationY - } + ) { + keyguardTranslationY, + burnInModel, + goneToAod, + occludedToLockscreen, + aodToLockscreen -> + val translationY = + if (isInTransition(aodToLockscreen.transitionState)) { + aodToLockscreen.value ?: 0f + } else if (isInTransition(goneToAod.transitionState)) { + (goneToAod.value ?: 0f) + burnInModel.translationY + } else { + burnInModel.translationY + occludedToLockscreen + keyguardTranslationY + } + burnInModel.copy(translationY = translationY.toInt()) } } .distinctUntilChanged() } - /** Scale for elements that need to apply anti-burn-in tactics. */ - fun scale( - params: BurnInParameters, - ): Flow<BurnInScaleViewModel> { - return burnIn(params).map { - BurnInScaleViewModel( - scale = it.scale, - scaleClockOnly = it.scaleClockOnly, - ) - } - } - private fun isInTransition(state: TransitionState): Boolean { return state == STARTED || state == RUNNING } @@ -125,7 +123,10 @@ constructor( keyguardTransitionInteractor.dozeAmountTransition.map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) }, - burnInInteractor.keyguardBurnIn, + burnInInteractor.burnIn( + xDimenResourceId = R.dimen.burn_in_prevention_offset_x, + yDimenResourceId = R.dimen.burn_in_prevention_offset_y + ), ) { interpolated, burnIn -> val useScaleOnly = (clockController(params.clockControllerProvider) @@ -149,7 +150,6 @@ constructor( } else { max(params.topInset, params.minViewY + burnInY) - params.minViewY } - BurnInModel( translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(), translationY = translationY, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt index 6458edaecd9e..e35e06533f8c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt @@ -17,10 +17,14 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.Flags.keyguardBottomAreaRefactor +import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.res.R import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -32,9 +36,10 @@ class KeyguardIndicationAreaViewModel @Inject constructor( private val keyguardInteractor: KeyguardInteractor, - bottomAreaInteractor: KeyguardBottomAreaInteractor, + private val bottomAreaInteractor: KeyguardBottomAreaInteractor, keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel, private val burnInHelperWrapper: BurnInHelperWrapper, + private val burnInInteractor: BurnInInteractor, private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, configurationInteractor: ConfigurationInteractor, ) { @@ -63,24 +68,37 @@ constructor( } .distinctUntilChanged() } + + private val burnIn: Flow<BurnInModel> = + burnInInteractor + .burnIn( + xDimenResourceId = R.dimen.burn_in_prevention_offset_x, + yDimenResourceId = R.dimen.default_burn_in_prevention_offset, + ) + .distinctUntilChanged() + /** An observable for the x-offset by which the indication area should be translated. */ val indicationAreaTranslationX: Flow<Float> = - if (keyguardBottomAreaRefactor()) { - keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() + if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) { + burnIn.map { it.translationX.toFloat() } } else { bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged() } /** Returns an observable for the y-offset by which the indication area should be translated. */ fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { - return keyguardInteractor.dozeAmount - .map { dozeAmount -> - dozeAmount * - (burnInHelperWrapper.burnInOffset( - /* amplitude = */ defaultBurnInOffset * 2, - /* xAxis= */ false, - ) - defaultBurnInOffset) - } - .distinctUntilChanged() + return if (migrateClocksToBlueprint()) { + burnIn.map { it.translationY.toFloat() } + } else { + keyguardInteractor.dozeAmount + .map { dozeAmount -> + dozeAmount * + (burnInHelperWrapper.burnInOffset( + /* amplitude = */ defaultBurnInOffset * 2, + /* xAxis= */ false, + ) - defaultBurnInOffset) + } + .distinctUntilChanged() + } } } 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 f848717c2170..5ca9215ce199 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 @@ -24,9 +24,11 @@ import com.android.systemui.Flags.newAodTransition import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.communal.domain.interactor.CommunalInteractor 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.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.BurnInModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -47,8 +49,11 @@ import com.android.systemui.util.ui.toAnimatedValueFlow import com.android.systemui.util.ui.zip import javax.inject.Inject import kotlin.math.max +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -57,12 +62,14 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardRootViewModel @Inject constructor( + @Application private val scope: CoroutineScope, private val deviceEntryInteractor: DeviceEntryInteractor, private val dozeParameters: DozeParameters, private val keyguardInteractor: KeyguardInteractor, @@ -102,6 +109,8 @@ constructor( private val aodAlphaViewModel: AodAlphaViewModel, private val shadeInteractor: ShadeInteractor, ) { + private var burnInJob: Job? = null + private val burnInModel = MutableStateFlow(BurnInModel()) val burnInLayerVisibility: Flow<Int> = keyguardTransitionInteractor.startedKeyguardState @@ -213,22 +222,34 @@ constructor( /** For elements that appear and move during the animation -> AOD */ val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha - fun translationY(params: BurnInParameters): Flow<Float> { - return aodBurnInViewModel.translationY(params) - } + val translationY: Flow<Float> = burnInModel.map { it.translationY.toFloat() } - fun translationX(params: BurnInParameters): Flow<StateToValue> { - return merge( - aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) }, + val translationX: Flow<StateToValue> = + merge( + burnInModel.map { StateToValue(to = AOD, value = it.translationX.toFloat()) }, lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX, glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX, ) - } - fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> { - return aodBurnInViewModel.scale(params) + fun updateBurnInParams(params: BurnInParameters) { + burnInJob?.cancel() + + burnInJob = + scope.launch { + aodBurnInViewModel.movement(params).collect { + burnInModel.value = it + } + } } + val scale: Flow<BurnInScaleViewModel> = + burnInModel.map { + BurnInScaleViewModel( + scale = it.scale, + scaleClockOnly = it.scaleClockOnly, + ) + } + /** Is the notification icon container visible? */ val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> = combine( 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/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 298fc329eb59..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, @@ -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/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/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index 2294fc0be520..d8c38503dbaf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -22,12 +22,14 @@ import android.content.Context import android.graphics.Bitmap import android.graphics.Rect import android.graphics.drawable.Drawable +import android.util.Log import android.view.Display import android.view.LayoutInflater import android.view.ScrollCaptureResponse import android.view.View import android.view.ViewTreeObserver import android.view.WindowInsets +import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags @@ -40,7 +42,6 @@ import com.android.systemui.res.R class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { override val view: ScreenshotView = LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView - override val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener override val screenshotPreview: View override var defaultDisplay: Int = Display.DEFAULT_DISPLAY @@ -51,6 +52,9 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { set(value) { view.setDefaultTimeoutMillis(value) } + override var onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback { + Log.wtf(TAG, "OnBackInvoked called before being set!") + } override var onKeyListener: View.OnKeyListener? = null set(value) { view.setOnKeyListener(value) @@ -84,7 +88,35 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { get() = view.isPendingSharedTransition init { - internalInsetsListener = view + + view.addOnAttachStateChangeListener( + object : View.OnAttachStateChangeListener { + override fun onViewAttachedToWindow(view: View) { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "Registering Predictive Back callback") + } + view + .findOnBackInvokedDispatcher() + ?.registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, + onBackInvokedCallback + ) + } + + override fun onViewDetachedFromWindow(view: View) { + if (LogConfig.DEBUG_INPUT) { + Log.d(TAG, "Unregistering Predictive Back callback") + } + view + .findOnBackInvokedDispatcher() + ?.unregisterOnBackInvokedCallback(onBackInvokedCallback) + } + } + ) + if (LogConfig.DEBUG_WINDOW) { + Log.d(TAG, "adding OnComputeInternalInsetsListener") + } + view.viewTreeObserver.addOnComputeInternalInsetsListener(view) screenshotPreview = view.screenshotPreview } @@ -139,12 +171,6 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { override fun announceForAccessibility(string: String) = view.announceForAccessibility(string) - override fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) = - view.addOnAttachStateChangeListener(listener) - - override fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? = - view.findOnBackInvokedDispatcher() - override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver override fun post(runnable: Runnable) { @@ -156,4 +182,8 @@ class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy { return LegacyScreenshotViewProxy(context) } } + + companion object { + private const val TAG = "LegacyScreenshotViewProxy" + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 13448d258a2c..1ca9b985b090 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -76,8 +76,6 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; -import android.window.OnBackInvokedCallback; -import android.window.OnBackInvokedDispatcher; import android.window.WindowContext; import com.android.internal.app.ChooserActivity; @@ -265,13 +263,6 @@ public class ScreenshotController { private final UserManager mUserManager; private final AssistContentRequester mAssistContentRequester; - private final OnBackInvokedCallback mOnBackInvokedCallback = () -> { - if (DEBUG_INPUT) { - Log.d(TAG, "Predictive Back callback dispatched"); - } - respondToKeyDismissal(); - }; - private final MessageContainerController mMessageContainerController; private Bitmap mScreenBitmap; private SaveImageInBackgroundTask mSaveInBgTask; @@ -594,27 +585,13 @@ public class ScreenshotController { } mMessageContainerController.setView(mViewProxy.getView()); - mViewProxy.addOnAttachStateChangeListener( - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(@NonNull View v) { - if (DEBUG_INPUT) { - Log.d(TAG, "Registering Predictive Back callback"); - } - mViewProxy.findOnBackInvokedDispatcher().registerOnBackInvokedCallback( - OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback); - } - - @Override - public void onViewDetachedFromWindow(@NonNull View v) { - if (DEBUG_INPUT) { - Log.d(TAG, "Unregistering Predictive Back callback"); - } - mViewProxy.findOnBackInvokedDispatcher() - .unregisterOnBackInvokedCallback(mOnBackInvokedCallback); - } - }); mViewProxy.setLogger(mUiEventLogger); + mViewProxy.setOnBackInvokedCallback(() -> { + if (DEBUG_INPUT) { + Log.d(TAG, "Predictive Back callback dispatched"); + } + respondToKeyDismissal(); + }); mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() { @Override public void onUserInteraction() { @@ -657,11 +634,6 @@ public class ScreenshotController { }); if (DEBUG_WINDOW) { - Log.d(TAG, "adding OnComputeInternalInsetsListener"); - } - mViewProxy.getViewTreeObserver().addOnComputeInternalInsetsListener( - mViewProxy.getInternalInsetsListener()); - if (DEBUG_WINDOW) { Log.d(TAG, "setContentView: " + mViewProxy.getView()); } setContentView(mViewProxy.getView()); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index 0064521bd3a4..381404a85587 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -28,18 +28,18 @@ import android.view.View.OnKeyListener import android.view.ViewGroup import android.view.ViewTreeObserver import android.view.WindowInsets -import android.window.OnBackInvokedDispatcher +import android.window.OnBackInvokedCallback import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags /** Abstraction of the surface between ScreenshotController and ScreenshotView */ interface ScreenshotViewProxy { val view: ViewGroup - val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener val screenshotPreview: View var defaultDisplay: Int var defaultTimeoutMillis: Long + var onBackInvokedCallback: OnBackInvokedCallback var onKeyListener: OnKeyListener? var flags: FeatureFlags? var packageName: String @@ -78,8 +78,6 @@ interface ScreenshotViewProxy { fun stopInputListening() fun requestFocus() fun announceForAccessibility(string: String) - fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) - fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? fun getViewTreeObserver(): ViewTreeObserver fun post(runnable: Runnable) diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 2fd438be9610..a1644b219063 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1288,10 +1288,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mView.getContext().getDisplay()); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); - } - mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); - mKeyguardStatusViewController.getView().addOnLayoutChangeListener( + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); + mKeyguardStatusViewController.getView().addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { int oldHeight = oldBottom - oldTop; if (v.getHeight() != oldHeight) { @@ -1299,7 +1298,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } }); - updateClockAppearance(); + updateClockAppearance(); + } } @Override @@ -1326,7 +1326,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onSplitShadeEnabledChanged() { mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); - mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); + if (!migrateClocksToBlueprint()) { + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); + } // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); @@ -1441,11 +1443,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(), mStatusBarStateController.getInterpolatedDozeAmount()); - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - mBarState, - false, - false, - mBarState); + if (!migrateClocksToBlueprint()) { + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + mBarState, + false, + false, + mBarState); + } if (mKeyguardQsUserSwitchController != null) { mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility( mBarState, @@ -1665,13 +1669,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewController.setLockscreenClockY( mClockPositionAlgorithm.getExpandedPreferredClockY()); } - if (keyguardBottomAreaRefactor()) { - mKeyguardInteractor.setClockPosition( - mClockPositionResult.clockX, mClockPositionResult.clockY); - } else { + if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) { mKeyguardBottomAreaInteractor.setClockPosition( mClockPositionResult.clockX, mClockPositionResult.clockY); } + boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; @@ -1749,13 +1751,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void updateKeyguardStatusViewAlignment(boolean animate) { - boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); - ConstraintLayout layout; if (migrateClocksToBlueprint()) { - layout = mKeyguardViewConfigurator.getKeyguardRootView(); - } else { - layout = mNotificationContainerParent; + return; } + boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered(); + ConstraintLayout layout = mNotificationContainerParent; mKeyguardStatusViewController.updateAlignment( layout, mSplitShadeEnabled, shouldBeCentered, animate); mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered)); @@ -3316,6 +3316,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Updates the views to the initial state for the fold to AOD animation. */ @Override public void prepareFoldToAodAnimation() { + if (migrateClocksToBlueprint()) { + return; + } // Force show AOD UI even if we are not locked showAodUi(); @@ -3337,6 +3340,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public void startFoldToAodAnimation(Runnable startAction, Runnable endAction, Runnable cancelAction) { + if (migrateClocksToBlueprint()) { + return; + } final ViewPropertyAnimator viewAnimator = mView.animate(); viewAnimator.cancel(); viewAnimator @@ -3372,6 +3378,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Cancels fold to AOD transition and resets view state. */ @Override public void cancelFoldToAodAnimation() { + if (migrateClocksToBlueprint()) { + return; + } cancelAnimation(); resetAlpha(); resetTranslation(); @@ -4446,11 +4455,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } - mKeyguardStatusViewController.setKeyguardStatusViewVisibility( - statusBarState, - keyguardFadingAway, - goingToFullShade, - mBarState); + if (!migrateClocksToBlueprint()) { + mKeyguardStatusViewController.setKeyguardStatusViewVisibility( + statusBarState, + keyguardFadingAway, + goingToFullShade, + mBarState); + } if (!keyguardBottomAreaRefactor()) { setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java index 1a06eec1cb4d..0091bc5cc5c7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java @@ -420,7 +420,7 @@ public class NotificationLockscreenUserManagerImpl implements filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - if (allowPrivateProfile()){ + if (privateSpaceFlagsEnabled()) { filter.addAction(Intent.ACTION_PROFILE_AVAILABLE); filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); } @@ -813,13 +813,17 @@ public class NotificationLockscreenUserManagerImpl implements } private boolean profileAvailabilityActions(String action){ - return allowPrivateProfile()? + return privateSpaceFlagsEnabled()? Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)|| Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE): Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)|| Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); } + private static boolean privateSpaceFlagsEnabled() { + return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures(); + } + @Override public void dump(PrintWriter pw, String[] args) { pw.println("NotificationLockscreenUserManager state:"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index 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/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index bfda6d5ca2c9..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; @@ -3306,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; @@ -3314,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; @@ -3478,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); @@ -3515,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())) { @@ -3624,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"); @@ -3765,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; 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 78fc1471053d..3a9cdd2d852e 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 @@ -474,7 +474,10 @@ constructor( */ fun translationY(params: BurnInParameters): Flow<Float> { return combine( - aodBurnInViewModel.translationY(params).onStart { emit(0f) }, + aodBurnInViewModel + .movement(params) + .map { it.translationY.toFloat() } + .onStart { emit(0f) }, isOnLockscreenWithoutShade, merge( keyguardInteractor.keyguardTranslationY, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt index 71e25e9556eb..541ac48a2feb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt @@ -23,6 +23,9 @@ import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.Gravity import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.WindowInsets +import android.view.WindowInsets.Type.InsetsType +import android.view.WindowInsetsAnimation import android.view.WindowManager.LayoutParams.MATCH_PARENT import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS @@ -70,16 +73,43 @@ open class SystemUIBottomSheetDialog( override fun onStart() { super.onStart() configurationController?.addCallback(onConfigChanged) + window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback) } override fun onStop() { super.onStop() configurationController?.removeCallback(onConfigChanged) + window?.decorView?.setWindowInsetsAnimationCallback(null) } + /** Called after any insets change. */ + open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {} + /** Can be overridden by subclasses to receive config changed events. */ open fun onConfigurationChanged() {} + private val insetsAnimationCallback = + object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) { + + private var lastInsets: WindowInsets? = null + + override fun onEnd(animation: WindowInsetsAnimation) { + lastInsets?.let { onInsetsChanged(animation.typeMask, it) } + } + + override fun onProgress( + insets: WindowInsets, + animations: MutableList<WindowInsetsAnimation>, + ): WindowInsets { + lastInsets = insets + onInsetsChanged(changedTypes = allAnimationMasks(animations), insets) + return insets + } + + private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int = + animations.fold(0) { acc: Int, it -> acc or it.typeMask } + } + private val onConfigChanged = object : ConfigurationListener { override fun onConfigChanged(newConfig: Configuration?) { diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 44c684c34587..b5efc44cff39 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -379,7 +379,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { + " was received. Deferring... Managed profile? " + isManagedProfile); return; } - if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) { + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures() + && isPrivateProfile(newUserHandle)) { mDeferredThemeEvaluation = true; Log.i(TAG, "Deferring theme for private profile till user setup is complete"); return; diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt index db300ebe6cae..2157fafa78ff 100644 --- a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt +++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt @@ -18,6 +18,7 @@ package com.android.systemui.util.wakelock import android.os.PowerManager import android.util.Log +import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger @@ -36,7 +37,11 @@ class ClientTrackingWakeLock( override fun acquire(why: String) { val count = activeClients.computeIfAbsent(why) { _ -> AtomicInteger(0) }.incrementAndGet() logger?.logAcquire(pmWakeLock, why, count) - pmWakeLock.acquire(maxTimeout) + if (maxTimeout == NO_TIMEOUT) { + pmWakeLock.acquire() + } else { + pmWakeLock.acquire(maxTimeout) + } } override fun release(why: String) { diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java index 707751a58d84..f763ee46666a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java +++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java @@ -130,7 +130,11 @@ public interface WakeLock { if (logger != null) { logger.logAcquire(inner, why, count); } - inner.acquire(maxTimeout); + if (maxTimeout == Builder.NO_TIMEOUT) { + inner.acquire(); + } else { + inner.acquire(maxTimeout); + } } /** @see PowerManager.WakeLock#release() */ @@ -169,6 +173,7 @@ public interface WakeLock { * An injectable Builder that wraps {@link #createPartial(Context, String, long)}. */ class Builder { + public static final long NO_TIMEOUT = -1; private final Context mContext; private final WakeLockLogger mLogger; private String mTag; diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 23e296d70913..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()); }); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt index 593b90aa3c68..4ba7cbb1588c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt @@ -21,12 +21,10 @@ import android.media.Spatializer import com.android.settingslib.media.data.repository.SpatializerRepository import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl import com.android.settingslib.media.domain.interactor.SpatializerInteractor -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import dagger.Module import dagger.Provides import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.CoroutineScope /** Spatializer module. */ @Module @@ -42,9 +40,8 @@ interface SpatializerModule { @Provides fun provdieSpatializerRepository( spatializer: Spatializer, - @Application scope: CoroutineScope, @Background backgroundContext: CoroutineContext, - ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, scope, backgroundContext) + ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, backgroundContext) @Provides fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor = 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/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt index 8ab563a94299..6c47aec563df 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt @@ -23,3 +23,6 @@ data class ToggleButtonViewModel( val icon: Icon, val label: CharSequence, ) + +fun ToggleButtonViewModel.toButtonViewModel(): ButtonViewModel = + ButtonViewModel(icon = icon, label = label) 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/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt index 9d801fc9bfa1..9ef07fa3f11f 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt @@ -24,5 +24,6 @@ object VolumePanelComponents { const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar" const val VOLUME_SLIDERS: VolumePanelComponentKey = "volume_sliders" const val CAPTIONING: VolumePanelComponentKey = "captioning" + const val SPATIAL_AUDIO: VolumePanelComponentKey = "spatial_audio" const val ANC: VolumePanelComponentKey = "anc" } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt index 4358611694b2..6032bfe3b50a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt @@ -71,10 +71,10 @@ constructor( combine( currentAudioDeviceAttributes, changes.onStart { emit(Unit) }, - spatializerInteractor.isHeadTrackingAvailable, - ) { attributes, _, isHeadTrackingAvailable -> + ) { attributes, _, + -> attributes ?: return@combine SpatialAudioAvailabilityModel.Unavailable - if (isHeadTrackingAvailable) { + if (spatializerInteractor.isHeadTrackingAvailable(attributes)) { return@combine SpatialAudioAvailabilityModel.HeadTracking } if (spatializerInteractor.isSpatialAudioAvailable(attributes)) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt index 4e65f60aa0e1..9735e5cbd9c9 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt @@ -19,6 +19,16 @@ package com.android.systemui.volume.panel.component.spatial.domain.model /** Models spatial audio and head tracking enabled/disabled state. */ interface SpatialAudioEnabledModel { + companion object { + /** All possible SpatialAudioEnabledModel implementations. */ + val values = + listOf( + Disabled, + SpatialAudioEnabled, + HeadTrackingEnabled, + ) + } + /** Spatial audio is disabled. */ data object Disabled : SpatialAudioEnabledModel diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt new file mode 100644 index 000000000000..9f9275baf4f9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt @@ -0,0 +1,28 @@ +/* + * 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.volume.panel.component.spatial.ui.viewmodel + +import com.android.systemui.common.shared.model.Color +import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel +import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel + +data class SpatialAudioButtonViewModel( + val model: SpatialAudioEnabledModel, + val button: ToggleButtonViewModel, + val iconColor: Color, + val labelColor: Color, +) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt new file mode 100644 index 000000000000..30715d167c25 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt @@ -0,0 +1,126 @@ +/* + * 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.volume.panel.component.spatial.ui.viewmodel + +import android.content.Context +import com.android.systemui.common.shared.model.Color +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R +import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel +import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel +import com.android.systemui.volume.panel.component.button.ui.viewmodel.toButtonViewModel +import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria +import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor +import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel +import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel +import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +@VolumePanelScope +class SpatialAudioViewModel +@Inject +constructor( + @Application private val context: Context, + @VolumePanelScope private val scope: CoroutineScope, + availabilityCriteria: SpatialAudioAvailabilityCriteria, + private val interactor: SpatialAudioComponentInteractor, +) { + + val spatialAudioButton: StateFlow<ButtonViewModel?> = + interactor.isEnabled + .map { it.toViewModel(true).toButtonViewModel() } + .stateIn(scope, SharingStarted.Eagerly, null) + + val isAvailable: StateFlow<Boolean> = + availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true) + + val spatialAudioButtonByEnabled: StateFlow<List<SpatialAudioButtonViewModel>> = + combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable -> + SpatialAudioEnabledModel.values + .filter { + if (it is SpatialAudioEnabledModel.HeadTrackingEnabled) { + // Spatial audio control can be visible when there is spatial audio + // setting available but not the head tracking. + isAvailable is SpatialAudioAvailabilityModel.HeadTracking + } else { + true + } + } + .map { isEnabled -> + val isChecked = isEnabled == currentIsEnabled + val buttonViewModel: ToggleButtonViewModel = + isEnabled.toViewModel(isChecked) + SpatialAudioButtonViewModel( + button = buttonViewModel, + model = isEnabled, + iconColor = + Color.Attribute( + if (isChecked) + com.android.internal.R.attr.materialColorOnPrimaryContainer + else com.android.internal.R.attr.materialColorOnSurfaceVariant + ), + labelColor = + Color.Attribute( + if (isChecked) + com.android.internal.R.attr.materialColorOnSurface + else com.android.internal.R.attr.materialColorOutline + ), + ) + } + } + .stateIn(scope, SharingStarted.Eagerly, emptyList()) + + fun setEnabled(model: SpatialAudioEnabledModel) { + scope.launch { interactor.setEnabled(model) } + } + + private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ToggleButtonViewModel { + if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) { + return ToggleButtonViewModel( + isChecked = isChecked, + icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null), + label = context.getString(R.string.volume_panel_spatial_audio_tracking) + ) + } + + if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) { + return ToggleButtonViewModel( + isChecked = isChecked, + icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null), + label = context.getString(R.string.volume_panel_spatial_audio_fixed) + ) + } + + if (this is SpatialAudioEnabledModel.Disabled) { + return ToggleButtonViewModel( + isChecked = isChecked, + icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null), + label = context.getString(R.string.volume_panel_spatial_audio_off) + ) + } + + error("Unsupported model: $this") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt index f31ee865eaac..d868c33d0887 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt @@ -20,6 +20,7 @@ import com.android.systemui.volume.panel.component.anc.AncModule import com.android.systemui.volume.panel.component.bottombar.BottomBarModule import com.android.systemui.volume.panel.component.captioning.CaptioningModule import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule +import com.android.systemui.volume.panel.component.spatialaudio.SpatialAudioModule import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope @@ -49,6 +50,7 @@ import kotlinx.coroutines.CoroutineScope // Components modules BottomBarModule::class, AncModule::class, + SpatialAudioModule::class, VolumeSlidersModule::class, CaptioningModule::class, MediaOutputModule::class, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt index 57ea9972012f..999f4c161633 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt @@ -51,6 +51,7 @@ interface DomainModule { fun provideEnabledComponents(): Collection<VolumePanelComponentKey> { return setOf( VolumePanelComponents.ANC, + VolumePanelComponents.SPATIAL_AUDIO, VolumePanelComponents.CAPTIONING, VolumePanelComponents.VOLUME_SLIDERS, VolumePanelComponents.MEDIA_OUTPUT, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt index ec4da0692841..8ba06e10fcf8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt @@ -49,6 +49,7 @@ interface UiModule { fun provideFooterComponents(): Collection<VolumePanelComponentKey> { return listOf( VolumePanelComponents.ANC, + VolumePanelComponents.SPATIAL_AUDIO, VolumePanelComponents.CAPTIONING, ) } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java index 13fb42ce8c3e..90587d7386ce 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java @@ -16,8 +16,6 @@ package com.android.keyguard; -import static kotlinx.coroutines.flow.FlowKt.emptyFlow; - import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,7 +30,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractorFactory; import com.android.systemui.res.R; @@ -62,7 +59,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardStatusViewController mControllerMock; @Mock protected InteractionJankMonitor mInteractionJankMonitor; @Mock protected ViewTreeObserver mViewTreeObserver; - @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor; @Mock protected DumpManager mDumpManager; protected FakeKeyguardRepository mFakeKeyguardRepository; protected FakePowerRepository mFakePowerRepository; @@ -93,7 +89,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { mKeyguardLogger, mInteractionJankMonitor, deps.getKeyguardInteractor(), - mKeyguardTransitionInteractor, mDumpManager, PowerInteractorFactory.create( mFakePowerRepository @@ -110,7 +105,6 @@ public class KeyguardStatusViewControllerBaseTest extends SysuiTestCase { when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver); when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch); - when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow()); when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area)) .thenReturn(mKeyguardStatusAreaView); } 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/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt index b25fb6e2757a..30519b0569d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt @@ -16,14 +16,17 @@ package com.android.systemui.display.ui.view +import android.graphics.Insets import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import android.view.WindowInsets import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock +import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before import org.junit.Test @@ -41,6 +44,7 @@ class MirroringConfirmationDialogTest : SysuiTestCase() { private val onStartMirroringCallback = mock<View.OnClickListener>() private val onCancelCallback = mock<View.OnClickListener>() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -96,10 +100,40 @@ class MirroringConfirmationDialogTest : SysuiTestCase() { verify(onStartMirroringCallback).onClick(any()) } + @Test + fun onInsetsChanged_navBarInsets_updatesBottomPadding() { + dialog.show() + + val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS) + dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets) + + assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom) + .isEqualTo(TEST_BOTTOM_INSETS) + } + + @Test + fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() { + dialog.show() + + val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS) + dialog.onInsetsChanged(WindowInsets.Type.ime(), insets) + + assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom) + .isNotEqualTo(TEST_BOTTOM_INSETS) + } + + private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets { + return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build() + } + @After fun teardown() { if (::dialog.isInitialized) { dialog.dismiss() } } + + private companion object { + const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt index df52265384fa..0bd541c7a704 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt @@ -20,16 +20,19 @@ 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.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -43,41 +46,35 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class BurnInInteractorTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + val configurationRepository = kosmos.fakeConfigurationRepository + val fakeKeyguardRepository = kosmos.fakeKeyguardRepository + private val burnInOffset = 7 private var burnInProgress = 0f @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper - private lateinit var configurationRepository: FakeConfigurationRepository - private lateinit var keyguardInteractor: KeyguardInteractor - private lateinit var fakeKeyguardRepository: FakeKeyguardRepository - private lateinit var testScope: TestScope private lateinit var underTest: BurnInInteractor @Before fun setUp() { MockitoAnnotations.initMocks(this) - configurationRepository = FakeConfigurationRepository() context .getOrCreateTestableResources() .addOverride(R.dimen.burn_in_prevention_offset_y, burnInOffset) - - KeyguardInteractorFactory.create().let { - keyguardInteractor = it.keyguardInteractor - fakeKeyguardRepository = it.repository - } whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset) setBurnInProgress(.65f) - testScope = TestScope() underTest = BurnInInteractor( context, burnInHelperWrapper, - testScope.backgroundScope, - configurationRepository, - keyguardInteractor, + kosmos.applicationCoroutineScope, + kosmos.configurationInteractor, + kosmos.keyguardInteractor, ) } @@ -122,7 +119,13 @@ class BurnInInteractorTest : SysuiTestCase() { testScope.runTest { whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f) - val burnInModel by collectLastValue(underTest.keyguardBurnIn) + val burnInModel by + collectLastValue( + underTest.burnIn( + xDimenResourceId = R.dimen.burn_in_prevention_offset_x, + yDimenResourceId = R.dimen.burn_in_prevention_offset_y + ) + ) // After time tick, returns the configured values fakeKeyguardRepository.dozeTimeTick(10) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt index 87eee1a8d817..0a29821c0660 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -22,23 +22,25 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.keyguard.data.repository.FakeCommandQueue -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +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.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.phone.SystemUIDialogManager +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -53,18 +55,19 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) class UdfpsKeyguardInteractorTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + val configRepository = kosmos.fakeConfigurationRepository + val keyguardRepository = kosmos.fakeKeyguardRepository + private val burnInProgress = 1f private val burnInYOffset = 20 private val burnInXOffset = 10 - private lateinit var testScope: TestScope - private lateinit var configRepository: FakeConfigurationRepository private lateinit var bouncerRepository: KeyguardBouncerRepository - private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var fakeCommandQueue: FakeCommandQueue private lateinit var burnInInteractor: BurnInInteractor private lateinit var shadeRepository: FakeShadeRepository - private lateinit var keyguardInteractor: KeyguardInteractor private lateinit var powerInteractor: PowerInteractor @Mock private lateinit var burnInHelper: BurnInHelperWrapper @@ -75,12 +78,6 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() - configRepository = FakeConfigurationRepository() - KeyguardInteractorFactory.create().let { - keyguardInteractor = it.keyguardInteractor - keyguardRepository = it.repository - } bouncerRepository = FakeKeyguardBouncerRepository() shadeRepository = FakeShadeRepository() fakeCommandQueue = FakeCommandQueue() @@ -89,8 +86,8 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { context, burnInHelper, testScope.backgroundScope, - configRepository, - keyguardInteractor + kosmos.configurationInteractor, + kosmos.keyguardInteractor ) powerInteractor = PowerInteractorFactory.create().powerInteractor @@ -98,7 +95,7 @@ class UdfpsKeyguardInteractorTest : SysuiTestCase() { UdfpsKeyguardInteractor( configRepository, burnInInteractor, - keyguardInteractor, + kosmos.keyguardInteractor, shadeRepository, dialogManager, ) 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/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index ca403e0addec..695d3b2c6c78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -19,7 +19,6 @@ package com.android.systemui.media.dialog; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -48,7 +47,6 @@ import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; -import com.android.settingslib.media.LocalMediaManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.broadcast.BroadcastSender; @@ -129,12 +127,6 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager, mKeyguardManager, mFlags, mUserTracker); - // Using a fake package will cause routing operations to fail, so we intercept - // scanning-related operations. - mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class); - doNothing().when(mMediaOutputController.mLocalMediaManager).startScan(); - doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan(); - mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender, mMediaOutputController); mMediaOutputBaseDialogImpl.onCreate(new Bundle()); 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/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index acbf9976873a..cdff4d1c6561 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -461,7 +461,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mKeyguardLogger, mInteractionJankMonitor, mKeyguardInteractor, - mKeyguardTransitionInteractor, mDumpManager, mPowerInteractor)); 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/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/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 28dfbbb64777..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; @@ -730,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); @@ -746,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); @@ -765,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); @@ -869,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); @@ -941,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); @@ -955,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(), @@ -973,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); + + assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(syntheticDownEvent); + assertTrue(mStackScroller.getIsBeingDragged()); + clearInvocations(mStackScrollLayoutController); + + MotionEvent moveEvent2 = MotionEvent.obtain( + /* downTime= */ downTime, + /* eventTime= */ SystemClock.uptimeMillis(), + MotionEvent.ACTION_MOVE, + 102, + 202, + 0 + ); + + mStackScroller.dispatchTouchEvent(moveEvent2); - verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat( - new MotionEventMatcher(syntheticDownEvent))); + 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(moveEvent); + mStackScroller.dispatchTouchEvent(upEvent); - verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent); + 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( @@ -1011,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. @@ -1073,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/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index c02583ae6f0b..ab28a2fc830f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -718,7 +718,8 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { @Test public void onPrivateProfileAdded_ignoresUntilStartComplete() { - mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); reset(mDeviceProvisionedController); when(mUserManager.isManagedProfile(anyInt())).thenReturn(false); mBroadcastReceiver.getValue().onReceive(null, diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 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/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 793e2d7efcda..1e305d67d40d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point -import com.android.systemui.common.shared.model.Position import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource @@ -58,9 +57,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _bottomAreaAlpha = MutableStateFlow(1f) override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha - private val _clockPosition = MutableStateFlow(Position(0, 0)) - override val clockPosition: StateFlow<Position> = _clockPosition - private val _isKeyguardShowing = MutableStateFlow(false) override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing @@ -149,10 +145,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _bottomAreaAlpha.value = alpha } - override fun setClockPosition(x: Int, y: Int) { - _clockPosition.value = Position(x, y) - } - fun setKeyguardShowing(isShowing: Boolean) { _isKeyguardShowing.value = isShowing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt index a9d89a37c542..40131c772de7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.applicationContext -import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.doze.util.burnInHelperWrapper import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -31,7 +31,7 @@ var Kosmos.burnInInteractor by Fixture { context = applicationContext, burnInHelperWrapper = burnInHelperWrapper, scope = applicationCoroutineScope, - configurationRepository = configurationRepository, + configurationInteractor = configurationInteractor, keyguardInteractor = keyguardInteractor, ) } 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 75e3ac24e381..a863edfc5198 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 @@ -23,6 +23,7 @@ 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.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor import com.android.systemui.statusbar.phone.dozeParameters @@ -31,6 +32,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.keyguardRootViewModel by Fixture { KeyguardRootViewModel( + scope = applicationCoroutineScope, deviceEntryInteractor = deviceEntryInteractor, dozeParameters = dozeParameters, keyguardInteractor = keyguardInteractor, 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/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt index 0183b97090dc..63e2d4b5ed24 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt @@ -18,23 +18,27 @@ package com.android.systemui.media.data.repository import android.media.AudioDeviceAttributes import com.android.settingslib.media.data.repository.SpatializerRepository -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow class FakeSpatializerRepository : SpatializerRepository { var defaultSpatialAudioAvailable: Boolean = false + var defaultHeadTrackingAvailable: Boolean = false private val spatialAudioAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> = mutableMapOf() + private val headTrackingAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> = + mutableMapOf() private val spatialAudioCompatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf() - private val mutableHeadTrackingAvailable = MutableStateFlow(false) private val headTrackingEnabledByDevice = mutableMapOf<AudioDeviceAttributes, Boolean>() - override val isHeadTrackingAvailable: StateFlow<Boolean> = - mutableHeadTrackingAvailable.asStateFlow() + override suspend fun isHeadTrackingAvailableForDevice( + audioDeviceAttributes: AudioDeviceAttributes + ): Boolean = + headTrackingAvailabilityByDevice.getOrDefault( + audioDeviceAttributes, + defaultHeadTrackingAvailable + ) override suspend fun isSpatialAudioAvailableForDevice( audioDeviceAttributes: AudioDeviceAttributes @@ -77,7 +81,10 @@ class FakeSpatializerRepository : SpatializerRepository { spatialAudioAvailabilityByDevice[audioDeviceAttributes] = isAvailable } - fun setIsHeadTrackingAvailable(isAvailable: Boolean) { - mutableHeadTrackingAvailable.value = isAvailable + fun setIsHeadTrackingAvailable( + audioDeviceAttributes: AudioDeviceAttributes, + isAvailable: Boolean, + ) { + headTrackingAvailabilityByDevice[audioDeviceAttributes] = isAvailable } } 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/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..1749ee333b8e 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); @@ -2426,7 +2450,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing); if (android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) { + && android.multiuser.Flags.disablePrivateSpaceItemsOnHome() + && android.multiuser.Flags.enablePrivateSpaceFeatures()) { // Do not add widget providers for profiles with items restricted on home screen. if (info != null && mUserManager .getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) { @@ -2480,6 +2505,7 @@ 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 +4030,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 +4052,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 +4098,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 +4591,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 +4838,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/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 cfe1e181871d..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", @@ -4959,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) { @@ -9101,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) { @@ -11509,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( @@ -11661,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; @@ -11721,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; @@ -13571,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); @@ -14687,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), @@ -15686,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, @@ -15779,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, @@ -16067,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, @@ -16090,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); @@ -16682,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; } @@ -16809,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(); } @@ -18022,9 +17951,7 @@ public class ActivityManagerService extends IActivityManager.Stub } void onProcessFreezableChangedLocked(ProcessRecord app) { - if (mEnableModernQueue) { - mBroadcastQueues[0].onProcessFreezableChangedLocked(app); - } + mBroadcastQueue.onProcessFreezableChangedLocked(app); } @VisibleForTesting @@ -19715,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(); } @@ -19733,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); } @@ -19811,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) { @@ -19858,9 +19779,7 @@ public class ActivityManagerService extends IActivityManager.Stub return; } - for (BroadcastQueue queue : mBroadcastQueues) { - queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs); - } + mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs); } @Override @@ -20414,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); @@ -20430,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 */ @@ -20771,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/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 3abfe082db27..13a1807b3563 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1419,7 +1419,8 @@ class UserController implements Handler.Callback { private boolean allowBiometricUnlockForPrivateProfile() { return android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace(); + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && android.multiuser.Flags.enablePrivateSpaceFeatures(); } /** diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java index 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/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index b2a738fd352b..3d95fee84f2a 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -403,15 +403,14 @@ public class AutomaticBrightnessController { brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness); brightnessEvent.setFlags(brightnessEvent.getFlags() | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0) - | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE - ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); + | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); brightnessEvent.setAutoBrightnessMode(getMode()); } if (!mAmbientLuxValid) { return PowerManager.BRIGHTNESS_INVALID_FLOAT; } - if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) { + if (shouldApplyDozeScaleFactor()) { return mScreenAutoBrightness * mDozeScaleFactor; } return mScreenAutoBrightness; @@ -434,7 +433,7 @@ public class AutomaticBrightnessController { float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux, mForegroundAppPackageName, mForegroundAppCategory); - if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) { + if (shouldApplyDozeScaleFactor()) { brightness *= mDozeScaleFactor; } @@ -443,8 +442,7 @@ public class AutomaticBrightnessController { brightnessEvent.setRecommendedBrightness(brightness); brightnessEvent.setFlags(brightnessEvent.getFlags() | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0) - | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE - ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); + | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); brightnessEvent.setAutoBrightnessMode(getMode()); } return brightness; @@ -1263,6 +1261,12 @@ public class AutomaticBrightnessController { } } + private boolean shouldApplyDozeScaleFactor() { + // Don't apply the doze scale factor if we have a designated brightness curve for doze + return mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE + && getMode() != AUTO_BRIGHTNESS_MODE_DOZE; + } + private class ShortTermModel { // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the // user's adjustment) immediately, but wait for a drastic enough change in the ambient diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java index 91e560e19f0d..b1defe94436a 100644 --- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java +++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java @@ -113,4 +113,14 @@ public class DisplayOffloadSessionImpl implements DisplayManagerInternal.Display Trace.traceEnd(Trace.TRACE_TAG_POWER); } } + + @Override + public float getBrightness() { + return mDisplayPowerController.getScreenBrightnessSetting(); + } + + @Override + public float getDozeBrightness() { + return mDisplayPowerController.getDozeBrightnessForOffload(); + } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 7f014f6dae28..77a43d08506b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -487,6 +487,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private DisplayOffloadSession mDisplayOffloadSession; + // Used to scale the brightness in doze mode + private float mDozeScaleFactor; + /** * Creates the display power controller. */ @@ -547,6 +550,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call loadBrightnessRampRates(); mSkipScreenOnBrightnessRamp = resources.getBoolean( R.bool.config_skipScreenOnBrightnessRamp); + mDozeScaleFactor = context.getResources().getFraction( + R.fraction.config_screenAutoBrightnessDozeScaleFactor, + 1, 1); Runnable modeChangeCallback = () -> { sendUpdatePowerState(); @@ -1042,10 +1048,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } if (defaultModeBrightnessMapper != null) { - final float dozeScaleFactor = context.getResources().getFraction( - R.fraction.config_screenAutoBrightnessDozeScaleFactor, - 1, 1); - // Ambient Lux - Active Mode Brightness Thresholds float[] ambientBrighteningThresholds = mDisplayDeviceConfig.getAmbientBrighteningPercentages(); @@ -1156,7 +1158,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController( this, handler.getLooper(), mSensorManager, mLightSensor, brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN, - PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate, + PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, brighteningLightDebounceIdle, darkeningLightDebounceIdle, autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds, @@ -1473,17 +1475,22 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false); } - // If there's an offload session and auto-brightness is on, we need to set the initial doze - // brightness using the doze auto-brightness curve before the offload session starts - // controlling the brightness. - if (Float.isNaN(brightnessState) && mFlags.areAutoBrightnessModesEnabled() - && mFlags.isDisplayOffloadEnabled() - && mPowerRequest.policy == POLICY_DOZE - && mDisplayOffloadSession != null - && mAutomaticBrightnessController != null - && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { - rawBrightnessState = mAutomaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastObservedLux(mTempBrightnessEvent); + // If there's an offload session, we need to set the initial doze brightness before + // the offload session starts controlling the brightness. + if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled() + && mPowerRequest.policy == POLICY_DOZE && mDisplayOffloadSession != null) { + if (mAutomaticBrightnessController != null + && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { + // Use the auto-brightness curve and the last observed lux + rawBrightnessState = mAutomaticBrightnessController + .getAutomaticScreenBrightnessBasedOnLastObservedLux( + mTempBrightnessEvent); + } else { + rawBrightnessState = getDozeBrightnessForOffload(); + mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() + | BrightnessEvent.FLAG_DOZE_SCALE); + } + if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) { brightnessState = clampScreenBrightness(rawBrightnessState); mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL); @@ -2444,6 +2451,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } @Override + public float getDozeBrightnessForOffload() { + return mDisplayBrightnessController.getCurrentBrightness() * mDozeScaleFactor; + } + + @Override public void setBrightness(float brightness) { mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness)); } @@ -2596,6 +2608,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig); pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig); + pw.println(" mDozeScaleFactor=" + mDozeScaleFactor); mHandler.runWithScissors(() -> dumpLocal(pw), 1000); } diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java index ecf1635a0cd2..408d610dea91 100644 --- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java +++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java @@ -161,6 +161,11 @@ public interface DisplayPowerControllerInterface { float getScreenBrightnessSetting(); /** + * Gets the brightness value used when the device is in doze + */ + float getDozeBrightnessForOffload(); + + /** * Sets up the temporary brightness for the associated display */ void setTemporaryBrightness(float brightness); 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 71cc8725f85b..03fb1478022e 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -71,8 +71,14 @@ public class DisplayBrightnessStrategySelector { @Nullable private final OffloadBrightnessStrategy mOffloadBrightnessStrategy; + // A collective representation of all the strategies that the selector is aware of. This is + // non null, but the strategies this is tracking can be null + @NonNull private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies; + @NonNull + private final DisplayManagerFlags mDisplayManagerFlags; + // We take note of the old brightness strategy so that we can know when the strategy changes. private String mOldBrightnessStrategyName; @@ -86,6 +92,7 @@ public class DisplayBrightnessStrategySelector { if (injector == null) { injector = new Injector(); } + mDisplayManagerFlags = flags; mDisplayId = displayId; mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy(); mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy(); @@ -139,6 +146,10 @@ public class DisplayBrightnessStrategySelector { displayBrightnessStrategy = mOffloadBrightnessStrategy; } + if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) { + postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy)); + } + if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) { Slog.i(TAG, "Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName @@ -186,13 +197,27 @@ public class DisplayBrightnessStrategySelector { " mAllowAutoBrightnessWhileDozingConfig= " + mAllowAutoBrightnessWhileDozingConfig); IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " "); - for (DisplayBrightnessStrategy displayBrightnessStrategy: mDisplayBrightnessStrategies) { + for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) { if (displayBrightnessStrategy != null) { displayBrightnessStrategy.dump(ipw); } } } + private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest( + DisplayBrightnessStrategy selectedDisplayBrightnessStrategy) { + return new StrategySelectionNotifyRequest(selectedDisplayBrightnessStrategy); + } + + private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) { + if (displayBrightnessStrategy != null) { + displayBrightnessStrategy.strategySelectionPostProcessor( + strategySelectionNotifyRequest); + } + } + } + /** * Validates if the conditions are met to qualify for the DozeBrightnessStrategy. */ diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java new file mode 100644 index 000000000000..d8bd2e459730 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java @@ -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.server.display.brightness; + +import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; + +import java.util.Objects; + +/** + * A wrapper class to encapsulate the request to notify the strategies about the selection of a + * DisplayBrightnessStrategy + */ +public final class StrategySelectionNotifyRequest { + // The strategy that was selected with the current request + private final DisplayBrightnessStrategy mSelectedDisplayBrightnessStrategy; + + public StrategySelectionNotifyRequest(DisplayBrightnessStrategy displayBrightnessStrategy) { + mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy; + } + + public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() { + return mSelectedDisplayBrightnessStrategy; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof StrategySelectionNotifyRequest)) { + return false; + } + StrategySelectionNotifyRequest other = (StrategySelectionNotifyRequest) obj; + return other.getSelectedDisplayBrightnessStrategy() + == getSelectedDisplayBrightnessStrategy(); + } + + @Override + public int hashCode() { + return Objects.hash(mSelectedDisplayBrightnessStrategy); + } +} diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java index 9ee1d73726bd..11edde9caaf7 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java @@ -22,6 +22,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -53,4 +54,10 @@ public class BoostBrightnessStrategy implements DisplayBrightnessStrategy { @Override public void dump(PrintWriter writer) {} + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java index 1f28eb4fa113..7b49957a7e31 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.hardware.display.DisplayManagerInternal; import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -48,4 +49,10 @@ public interface DisplayBrightnessStrategy { * @param writer */ void dump(PrintWriter writer); + + /** + * Notifies this strategy about the selection of a DisplayBrightnessStrategy + */ + void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest); } diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java index 2be74438f87a..5afdc42b9eee 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java @@ -21,6 +21,7 @@ import android.hardware.display.DisplayManagerInternal; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -46,4 +47,10 @@ public class DozeBrightnessStrategy implements DisplayBrightnessStrategy { @Override public void dump(PrintWriter writer) {} + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java index 54f9afcbdd94..0650c1c9dc62 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java @@ -22,6 +22,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -82,4 +83,10 @@ public class FollowerBrightnessStrategy implements DisplayBrightnessStrategy { writer.println(" mBrightnessToFollow:" + mBrightnessToFollow); writer.println(" mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange); } + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java index 49c3e03c8742..bf37ee0a9666 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java @@ -22,6 +22,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -44,4 +45,10 @@ public class InvalidBrightnessStrategy implements DisplayBrightnessStrategy { @Override public void dump(PrintWriter writer) {} + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java index 4ffb16be5789..d2bb1e284256 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java @@ -21,6 +21,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -72,4 +73,10 @@ public class OffloadBrightnessStrategy implements DisplayBrightnessStrategy { writer.println("OffloadBrightnessStrategy:"); writer.println(" mOffloadScreenBrightness:" + mOffloadScreenBrightness); } + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java index 7b651d8ced8e..653170c886ea 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java @@ -21,6 +21,7 @@ import android.hardware.display.DisplayManagerInternal; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -45,4 +46,10 @@ public class OverrideBrightnessStrategy implements DisplayBrightnessStrategy { @Override public void dump(PrintWriter writer) {} + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java index 201ef4118854..f0cce23c6f45 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java @@ -22,6 +22,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -46,4 +47,10 @@ public class ScreenOffBrightnessStrategy implements DisplayBrightnessStrategy { @Override public void dump(PrintWriter writer) {} + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java index bbd0c009debb..91e1d091ade3 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java @@ -22,6 +22,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; import java.io.PrintWriter; @@ -73,4 +74,10 @@ public class TemporaryBrightnessStrategy implements DisplayBrightnessStrategy { writer.println("TemporaryBrightnessStrategy:"); writer.println(" mTemporaryScreenBrightness:" + mTemporaryScreenBrightness); } + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + // DO NOTHING + } } diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 516d4b1d4125..3c98ee453913 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -126,6 +126,13 @@ public class DisplayManagerFlags { Flags::sensorBasedBrightnessThrottling ); + + private final FlagState mRefactorDisplayPowerController = new FlagState( + Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER, + Flags::refactorDisplayPowerController + ); + + /** * @return {@code true} if 'port' is allowed in display layout configuration file. */ @@ -256,6 +263,10 @@ public class DisplayManagerFlags { return mSensorBasedBrightnessThrottling.isEnabled(); } + public boolean isRefactorDisplayPowerControllerEnabled() { + return mRefactorDisplayPowerController.isEnabled(); + } + /** * dumps all flagstates * @param pw printWriter @@ -280,6 +291,7 @@ public class DisplayManagerFlags { pw.println(" " + mFastHdrTransitions); pw.println(" " + mRefreshRateVotingTelemetry); pw.println(" " + mSensorBasedBrightnessThrottling); + pw.println(" " + mRefactorDisplayPowerController); } private static class FlagState { diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index 63ab3a95822f..34045273c93a 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -192,3 +192,11 @@ flag { bug: "294900859" is_fixed_read_only: true } + +flag { + name: "refactor_display_power_controller" + namespace: "display_manager" + description: "Feature flag for refactoring the DisplayPowerController and associated components" + bug: "294444204" + is_fixed_read_only: true +} 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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 29ea0713e0a8..c80f988bd61b 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -814,7 +814,8 @@ public class LockSettingsService extends ILockSettings.Stub { // storage is locked, instead of when the user is stopped. This would ensure the flags get // reset if CE storage is locked later for a user that allows delayed locking. if (android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && android.multiuser.Flags.enablePrivateSpaceFeatures()) { UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId)); if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) { return; diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING index 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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ba5882cc7e98..b98424cfade4 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1204,6 +1204,10 @@ public class NotificationManagerService extends SystemService { } } + private static boolean privateSpaceFlagsEnabled() { + return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures(); + } + private final class SavePolicyFileRunnable implements Runnable { @Override public void run() { @@ -2142,7 +2146,7 @@ public class NotificationManagerService extends SystemService { } private boolean isProfileUnavailable(String action) { - return allowPrivateProfile() ? + return privateSpaceFlagsEnabled() ? action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) : action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); } @@ -2744,7 +2748,7 @@ public class NotificationManagerService extends SystemService { filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); - if (allowPrivateProfile()){ + if (privateSpaceFlagsEnabled()){ filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); } getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 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/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 23d48e871d62..9af2b3f6e2ae 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -389,7 +389,8 @@ public final class BroadcastHelper { */ boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) { if (android.os.Flags.allowPrivateProfile() - && Flags.enablePermissionToAccessHiddenProfiles()) { + && Flags.enablePermissionToAccessHiddenProfiles() + && Flags.enablePrivateSpaceFeatures()) { if (mUmInternal.getUserProperties(userId).getProfileApiVisibility() != UserProperties.PROFILE_API_VISIBILITY_HIDDEN) { return true; diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 3abf3a5ab4bf..ecfc768a874e 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -50,13 +50,17 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApexStagedEvent; import android.content.pm.Flags; +import android.content.pm.IPackageManagerNative; +import android.content.pm.IStagedApexObserver; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.dex.ArtManager; import android.os.Binder; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -1054,6 +1058,10 @@ public final class DexOptHelper { artManager.scheduleBackgroundDexoptJob(); } }, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED)); + + if (Flags.useArtServiceV2()) { + StagedApexObserver.registerForStagedApexUpdates(artManager); + } } /** @@ -1168,4 +1176,32 @@ public final class DexOptHelper { && dexoptOptions.isCompilationEnabled() && !isApex; } + + private static class StagedApexObserver extends IStagedApexObserver.Stub { + private final @NonNull ArtManagerLocal mArtManager; + + static void registerForStagedApexUpdates(@NonNull ArtManagerLocal artManager) { + IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface( + ServiceManager.getService("package_native")); + if (packageNative == null) { + Log.e(TAG, "No IPackageManagerNative"); + return; + } + + try { + packageNative.registerStagedApexObserver(new StagedApexObserver(artManager)); + } catch (RemoteException e) { + Log.e(TAG, "Failed to register staged apex observer", e); + } + } + + private StagedApexObserver(@NonNull ArtManagerLocal artManager) { + mArtManager = artManager; + } + + @Override + public void onApexStaged(@NonNull ApexStagedEvent event) { + mArtManager.onApexStaged(event.stagedApexModuleNames); + } + } } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 6b56b85938c2..c7ebb3c2667b 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -584,7 +584,8 @@ public class LauncherAppsService extends SystemService { return android.os.Flags.allowPrivateProfile() && Flags.enableHidingProfiles() && Flags.enableLauncherAppsHiddenProfileChecks() - && Flags.enablePermissionToAccessHiddenProfiles(); + && Flags.enablePermissionToAccessHiddenProfiles() + && Flags.enablePrivateSpaceFeatures(); } @VisibleForTesting // We override it in unit tests diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 211b7546bd8a..4c653f6ce95f 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -2833,7 +2833,8 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) { - if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) { + if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome() + || !android.multiuser.Flags.enablePrivateSpaceFeatures()) { return true; } final long start = getStatStartTime(); diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 7349755402b1..88e75966b12e 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -21,10 +21,15 @@ import static android.content.Intent.ACTION_SCREEN_ON; import static android.content.Intent.EXTRA_USER_ID; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE; +import static android.content.pm.PackageManager.FEATURE_EMBEDDED; +import static android.content.pm.PackageManager.FEATURE_LEANBACK; +import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY; import static android.os.UserManager.DISALLOW_USER_SWITCH; import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY; import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN; +import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID; import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE; @@ -1006,9 +1011,17 @@ public class UserManagerService extends IUserManager.Stub { emulateSystemUserModeIfNeeded(); } + private boolean doesDeviceHardwareSupportPrivateSpace() { + return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0) + && !mPm.hasSystemFeature(FEATURE_WATCH, 0) + && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0) + && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0); + } + private static boolean isAutoLockForPrivateSpaceEnabled() { return android.os.Flags.allowPrivateProfile() - && Flags.supportAutolockForPrivateSpace(); + && Flags.supportAutolockForPrivateSpace() + && android.multiuser.Flags.enablePrivateSpaceFeatures(); } void systemReady() { @@ -1052,7 +1065,8 @@ public class UserManagerService extends IUserManager.Stub { private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() { return android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts(); + && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts() + && android.multiuser.Flags.enablePrivateSpaceFeatures(); } /** @@ -1493,7 +1507,8 @@ public class UserManagerService extends IUserManager.Stub { private boolean isProfileHidden(int userId) { UserProperties userProperties = getUserPropertiesCopy(userId); if (android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enableHidingProfiles()) { + && android.multiuser.Flags.enableHidingProfiles() + && android.multiuser.Flags.enablePrivateSpaceFeatures()) { return userProperties.getProfileApiVisibility() == UserProperties.PROFILE_API_VISIBILITY_HIDDEN; } @@ -1693,7 +1708,8 @@ public class UserManagerService extends IUserManager.Stub { setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage); return true; } - if (android.os.Flags.allowPrivateProfile()) { + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures()) { final UserProperties userProperties = getUserPropertiesInternal(userId); if (userProperties != null && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) { @@ -1839,7 +1855,8 @@ public class UserManagerService extends IUserManager.Stub { logQuietModeEnabled(userId, enableQuietMode, callingPackage); // Broadcast generic intents for all profiles - if (android.os.Flags.allowPrivateProfile()) { + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enablePrivateSpaceFeatures()) { broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(), enableQuietMode, false); } @@ -1852,7 +1869,8 @@ public class UserManagerService extends IUserManager.Stub { private void stopUserForQuietMode(int userId) throws RemoteException { if (android.os.Flags.allowPrivateProfile() - && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() + && android.multiuser.Flags.enablePrivateSpaceFeatures()) { // Allow delayed locking since some profile types want to be able to unlock again via // biometrics. ActivityManager.getService() @@ -2751,6 +2769,18 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean canAddPrivateProfile(@UserIdInt int userId) { + checkCreateUsersPermission("canHaveRestrictedProfile"); + UserInfo parentUserInfo = getUserInfo(userId); + return isUserTypeEnabled(USER_TYPE_PROFILE_PRIVATE) + && canAddMoreProfilesToUser(USER_TYPE_PROFILE_PRIVATE, + userId, /* allowedToRemoveOne */ false) + && (parentUserInfo != null && parentUserInfo.isMain()) + && doesDeviceHardwareSupportPrivateSpace() + && !hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, userId); + } + + @Override public boolean hasRestrictedProfiles(@UserIdInt int userId) { checkManageUsersPermission("hasRestrictedProfiles"); synchronized (mUsersLock) { @@ -5308,7 +5338,7 @@ public class UserManagerService extends IUserManager.Stub { if (!isUserTypeEnabled(userTypeDetails)) { throwCheckedUserOperationException( "Cannot add a user of disabled type " + userType + ".", - UserManager.USER_OPERATION_ERROR_MAX_USERS); + UserManager.USER_OPERATION_ERROR_DISABLED_USER); } synchronized (mUsersLock) { @@ -5341,6 +5371,7 @@ public class UserManagerService extends IUserManager.Stub { final boolean isDemo = UserManager.isUserTypeDemo(userType); final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType); final boolean isCommunalProfile = UserManager.isUserTypeCommunalProfile(userType); + final boolean isPrivateProfile = UserManager.isUserTypePrivateProfile(userType); final long ident = Binder.clearCallingIdentity(); UserInfo userInfo; @@ -5387,6 +5418,12 @@ public class UserManagerService extends IUserManager.Stub { + " for user " + parentId, UserManager.USER_OPERATION_ERROR_MAX_USERS); } + if (android.multiuser.Flags.blockPrivateSpaceCreation() + && isPrivateProfile && !canAddPrivateProfile(parentId)) { + throwCheckedUserOperationException( + "Cannot add profile of type " + userType + " for user " + parentId, + UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE); + } if (isRestricted && (parentId != UserHandle.USER_SYSTEM) && !isCreationOverrideEnabled()) { throwCheckedUserOperationException( diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 114daaac3c18..7f9c1cfafe68 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -292,6 +292,7 @@ public final class UserTypeFactory { .setName(USER_TYPE_PROFILE_PRIVATE) .setBaseType(FLAG_PROFILE) .setMaxAllowedPerParent(1) + .setEnabled(UserManager.isPrivateProfileEnabled() ? 1 : 0) .setLabels(R.string.profile_label_private) .setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge) .setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge) diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 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/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/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/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/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 0b29f9688acd..a6db310f4e63 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -49,6 +49,7 @@ import android.view.ViewDebug; import com.android.internal.os.ByteTransferPipe; import com.android.internal.protolog.LegacyProtoLogImpl; +import com.android.internal.protolog.PerfettoProtoLogImpl; import com.android.internal.protolog.common.IProtoLog; import com.android.internal.protolog.common.ProtoLog; import com.android.server.IoThread; @@ -111,8 +112,13 @@ public class WindowManagerShellCommand extends ShellCommand { case "logging": IProtoLog instance = ProtoLog.getSingleInstance(); int result = 0; - if (instance instanceof LegacyProtoLogImpl) { - result = ((LegacyProtoLogImpl) instance).onShellCommand(this); + if (instance instanceof LegacyProtoLogImpl + || instance instanceof PerfettoProtoLogImpl) { + if (instance instanceof LegacyProtoLogImpl) { + result = ((LegacyProtoLogImpl) instance).onShellCommand(this); + } else { + result = ((PerfettoProtoLogImpl) instance).onShellCommand(this); + } if (result != 0) { pw.println("Not handled, please use " + "`adb shell dumpsys activity service SystemUIService " @@ -120,8 +126,7 @@ public class WindowManagerShellCommand extends ShellCommand { } } else { result = -1; - pw.println("Command not supported. " - + "Only supported when using legacy ProtoLog."); + pw.println("ProtoLog impl doesn't support handling commands"); } return result; case "user-rotation": diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 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/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 9c9aeeaadb71..705dac54fdd6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -19,6 +19,7 @@ package com.android.server.display; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +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.assertArrayEquals; @@ -72,7 +73,7 @@ public class AutomaticBrightnessControllerTest { private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000; private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000; private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000; - private static final float DOZE_SCALE_FACTOR = 0.0f; + private static final float DOZE_SCALE_FACTOR = 0.54f; private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false; private static final int LIGHT_SENSOR_WARMUP_TIME = 0; private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000; @@ -87,6 +88,7 @@ public class AutomaticBrightnessControllerTest { @Mock SensorManager mSensorManager; @Mock BrightnessMappingStrategy mBrightnessMappingStrategy; @Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy; + @Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy; @Mock HysteresisLevels mAmbientBrightnessThresholds; @Mock HysteresisLevels mScreenBrightnessThresholds; @Mock HysteresisLevels mAmbientBrightnessThresholdsIdle; @@ -124,12 +126,15 @@ public class AutomaticBrightnessControllerTest { when(mBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DEFAULT); when(mIdleBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE); + when(mDozeBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DOZE); SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap = new SparseArray<>(); brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DEFAULT, mBrightnessMappingStrategy); brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_IDLE, mIdleBrightnessMappingStrategy); + brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DOZE, + mDozeBrightnessMappingStrategy); mController = new AutomaticBrightnessController( new AutomaticBrightnessController.Injector() { @Override @@ -1032,10 +1037,9 @@ public class AutomaticBrightnessControllerTest { float normalizedBrightness = 0.3f; when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); - when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt())) - .thenReturn(normalizedBrightness); + when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), + /* category= */ anyInt())).thenReturn(normalizedBrightness); when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); - when(mBrightnessThrottler.isThrottled()).thenReturn(true); // Send a new sensor value, disable the sensor and verify listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); @@ -1047,4 +1051,79 @@ public class AutomaticBrightnessControllerTest { mController.getAutomaticScreenBrightnessBasedOnLastObservedLux( /* brightnessEvent= */ null), EPSILON); } + + @Test + public void testAutoBrightnessInDoze_ShouldScaleIfNotUsingDozeCurve() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Set up system to return 0.3f as a brightness value + float lux = 100.0f; + // Brightness as float (from 0.0f to 1.0f) + float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); + when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), + /* category= */ anyInt())).thenReturn(normalizedBrightness); + when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); + + // Set policy to DOZE + mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, + /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, + /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, + /* shouldResetShortTermModel= */ true); + + // Send a new sensor value + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); + + // The brightness should be scaled by the doze factor + assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, + mController.getAutomaticScreenBrightness( + /* brightnessEvent= */ null), EPSILON); + assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, + mController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + /* brightnessEvent= */ null), EPSILON); + } + + @Test + public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception { + ArgumentCaptor<SensorEventListener> listenerCaptor = + ArgumentCaptor.forClass(SensorEventListener.class); + verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), + eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class)); + SensorEventListener listener = listenerCaptor.getValue(); + + // Set up system to return 0.3f as a brightness value + float lux = 100.0f; + // Brightness as float (from 0.0f to 1.0f) + float normalizedBrightness = 0.3f; + when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux); + when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux); + when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null), + /* category= */ anyInt())).thenReturn(normalizedBrightness); + when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT); + + // Switch mode to DOZE + mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE); + + // Set policy to DOZE + mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null, + /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0, + /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE, + /* shouldResetShortTermModel= */ true); + + // Send a new sensor value + listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux)); + + // The brightness should not be scaled by the doze factor + assertEquals(normalizedBrightness, + mController.getAutomaticScreenBrightness( + /* brightnessEvent= */ null), EPSILON); + assertEquals(normalizedBrightness, + mController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + /* brightnessEvent= */ null), EPSILON); + } } 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 14d8a9c5f0f2..76b77808b880 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -114,6 +114,8 @@ public final class DisplayPowerControllerTest { private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1; private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789"; private static final float PROX_SENSOR_MAX_RANGE = 5; + private static final float DOZE_SCALE_FACTOR = 0.34f; + private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f; private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f; private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f; @@ -193,6 +195,9 @@ public final class DisplayPowerControllerTest { mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.bool.config_displayColorFadeDisabled, false); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor, + DOZE_SCALE_FACTOR); doAnswer((Answer<Void>) invocationOnMock -> null).when(() -> SystemProperties.set(anyString(), any())); @@ -1705,7 +1710,7 @@ public final class DisplayPowerControllerTest { } @Test - public void testInitialDozeBrightness() { + public void testInitialDozeBrightness_AutoBrightnessEnabled() { when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true); when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); @@ -1734,6 +1739,39 @@ public final class DisplayPowerControllerTest { } @Test + public void testInitialDozeBrightness_AutoBrightnessDisabled() { + when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + float brightness = 0.277f; + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness); + when(mHolder.hbmController.getCurrentBrightnessMax()) + .thenReturn(PowerManager.BRIGHTNESS_MAX); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_DOZE; + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState, initialize + + ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor = + ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class); + verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture()); + BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue(); + listener.onBrightnessChanged(brightness); + advanceTime(1); // Send messages, run updatePowerState + + verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR), + /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(), + /* ignoreAnimationLimits= */ anyBoolean()); + assertEquals(brightness * DOZE_SCALE_FACTOR, mHolder.dpc.getDozeBrightnessForOffload(), + /* delta= */ 0); + } + + @Test public void testInitialDozeBrightness_AbcIsNull() { when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true); when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true); 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 0e89d8383a8f..a3728c868a4e 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 @@ -18,8 +18,10 @@ package com.android.server.display.brightness; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ContentResolver; @@ -268,4 +270,33 @@ public final class DisplayBrightnessStrategySelectorTest { mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON)); } + + @Test + public void selectStrategyCallsPostProcessorForAllStrategies() { + when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f); + + mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON); + + StrategySelectionNotifyRequest strategySelectionNotifyRequest = + new StrategySelectionNotifyRequest(mFollowerBrightnessStrategy); + verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + verify(mDozeBrightnessModeStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + verify(mFollowerBrightnessStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + verify(mBoostBrightnessStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + verify(mOverrideBrightnessStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + } } 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/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 7bbcd50cdf1f..bc7c9a59ff29 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -15,6 +15,10 @@ */ package com.android.server.pm; +import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE; +import static android.content.pm.PackageManager.FEATURE_EMBEDDED; +import static android.content.pm.PackageManager.FEATURE_LEANBACK; +import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.os.UserManager.DISALLOW_OUTGOING_CALLS; import static android.os.UserManager.DISALLOW_SMS; import static android.os.UserManager.DISALLOW_USER_SWITCH; @@ -41,6 +45,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; import android.annotation.UserIdInt; +import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.KeyguardManager; import android.content.Context; @@ -48,6 +53,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.multiuser.Flags; import android.os.PowerManager; +import android.os.ServiceSpecificException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -76,6 +82,7 @@ import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -120,11 +127,14 @@ public final class UserManagerServiceTest { private static final String TAG_RESTRICTIONS = "restrictions"; + private static final String PRIVATE_PROFILE_NAME = "TestPrivateProfile"; + @Rule public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) .spyStatic(UserManager.class) .spyStatic(LocalServices.class) .spyStatic(SystemProperties.class) + .spyStatic(ActivityManager.class) .mockStatic(Settings.Global.class) .mockStatic(Settings.Secure.class) .build(); @@ -163,6 +173,7 @@ public final class UserManagerServiceTest { @Before @UiThreadTest // Needed to initialize main handler public void setFixtures() { + MockitoAnnotations.initMocks(this); mSpiedContext = spy(mRealContext); // Called when WatchedUserStates is constructed @@ -172,11 +183,12 @@ public final class UserManagerServiceTest { when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false); mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal); when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager); - when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager); + doReturn(mKeyguardManager).when(mSpiedContext).getSystemService(KeyguardManager.class); when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager); mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal); mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal); doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any()); + mockIsLowRamDevice(false); // Must construct UserManagerService in the UiThread mTestDir = new File(mRealContext.getDataDir(), "umstest"); @@ -570,9 +582,10 @@ public final class UserManagerServiceTest { @Test public void testAutoLockPrivateProfile() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = - mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, 0, null); Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync( eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(), @@ -587,10 +600,11 @@ public final class UserManagerServiceTest { @Test public void testAutoLockOnDeviceLockForPrivateProfile() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = - mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, 0, null); mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK); Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync( @@ -606,10 +620,11 @@ public final class UserManagerServiceTest { @Test public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = - mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, 0, null); mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK); @@ -623,10 +638,11 @@ public final class UserManagerServiceTest { @Test public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); UserInfo privateProfileUser = - mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, 0, null); mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true); @@ -641,13 +657,14 @@ public final class UserManagerServiceTest { @Test public void testAutoLockAfterInactityForPrivateProfile() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); UserManagerService mSpiedUms = spy(mUms); mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY); when(mPowerManager.isInteractive()).thenReturn(false); UserInfo privateProfileUser = - mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, 0, null); Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace( eq(privateProfileUser.getUserHandle().getIdentifier()), any(), @@ -662,6 +679,7 @@ public final class UserManagerServiceTest { @Test public void testSetOrUpdateAutoLockPreference_noPrivateProfile() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); mUms.setOrUpdateAutoLockPreferenceForPrivateProfile( @@ -675,8 +693,9 @@ public final class UserManagerServiceTest { @Test public void testSetOrUpdateAutoLockPreference() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE); - mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile", + mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, 0, null); // Set the preference to auto lock on device lock @@ -733,6 +752,77 @@ public final class UserManagerServiceTest { } } + @Test + public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); + UserManagerService mSpiedUms = spy(mUms); + int mainUser = mSpiedUms.getMainUserId(); + doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode(); + assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue(); + assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow( + PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull(); + } + + @Test + public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); + UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0); + assertThat(mUms.canAddPrivateProfile(user.id)).isFalse(); + assertThrows(ServiceSpecificException.class, + () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME, + USER_TYPE_PROFILE_PRIVATE, 0, user.id, null)); + } + + @Test + public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); + doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt()); + int mainUser = mUms.getMainUserId(); + assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThrows(ServiceSpecificException.class, + () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME, + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); + } + + @Test + public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); + doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt()); + int mainUser = mUms.getMainUserId(); + assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThrows(ServiceSpecificException.class, + () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); + } + + @Test + public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); + doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt()); + int mainUser = mUms.getMainUserId(); + assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThrows(ServiceSpecificException.class, + () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); + } + + @Test + public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() { + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION); + doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt()); + int mainUser = mUms.getMainUserId(); + assertThat(mUms.canAddPrivateProfile(0)).isFalse(); + assertThrows(ServiceSpecificException.class, + () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, + USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)); + } + /** * Returns true if the user's XML file has Default restrictions * @param userId Id of the user. @@ -800,6 +890,10 @@ public final class UserManagerServiceTest { any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt())); } + private void mockIsLowRamDevice(boolean isLowRamDevice) { + doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic); + } + private void mockDeviceDemoMode(boolean enabled) { doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt( any(), eq(android.provider.Settings.Global.DEVICE_DEMO_MODE), anyInt())); diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING index 6ac56bfc254a..4ac4484956fc 100644 --- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING @@ -1,5 +1,5 @@ { - "postsubmit": [ + "presubmit": [ { "name": "RollbackPackageHealthObserverTests", "options": [ 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/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index cea10ea9ade4..ea1a68abe37b 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -825,7 +825,8 @@ public class UserControllerTest { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true); verifyUserUnassignedFromDisplay(TEST_USER_ID1); @@ -842,7 +843,8 @@ public class UserControllerTest { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ false); @@ -852,19 +854,28 @@ public class UserControllerTest { public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); - mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.disableFlags( android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ true); - mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); mSetFlagsRule.enableFlags( android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE); assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ true); + + mSetFlagsRule.disableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID3, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); } /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */ @@ -874,7 +885,8 @@ public class UserControllerTest { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED); assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, @@ -890,7 +902,8 @@ public class UserControllerTest { mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, - android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true, /* keyEvictedCallback */ null, /* expectLocking= */ true); diff --git a/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt new file mode 100644 index 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/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 507b3fe62e29..1591a963a406 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -316,6 +316,10 @@ public final class UserManagerTest { .that(userTypeDetails).isNotNull(); final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference(); + // Only run the test if private profile creation is enabled on the device + assumeTrue("Private profile not enabled on the device", + mUserManager.canAddPrivateProfile()); + // Test that only one private profile can be created final int mainUserId = mainUser.getIdentifier(); UserInfo userInfo = createProfileForUser("Private profile1", @@ -1231,6 +1235,20 @@ public final class UserManagerTest { @MediumTest @Test + public void testPrivateProfileCreationRestrictions() { + assumeTrue(mUserManager.canAddPrivateProfile()); + final int mainUserId = ActivityManager.getCurrentUser(); + try { + UserInfo privateProfileInfo = createProfileForUser("Private", + UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId); + assertThat(privateProfileInfo).isNotNull(); + } catch (Exception e) { + fail("Creation of private profile failed due to " + e.getMessage()); + } + } + + @MediumTest + @Test public void testAddRestrictedProfile() throws Exception { if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return; assertWithMessage("There should be no associated restricted profiles before the test") diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index e3ea55a67e71..03f27493c3c7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -14092,7 +14092,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testProfileUnavailableIntent() throws RemoteException { - mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE); verify(mWorkerHandler).post(any(Runnable.class)); verify(mSnoozeHelper).clearData(anyInt()); @@ -14101,7 +14102,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Test public void testManagedProfileUnavailableIntent() throws RemoteException { - mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES); simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); verify(mWorkerHandler).post(any(Runnable.class)); verify(mSnoozeHelper).clearData(anyInt()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/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/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/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. |