diff options
415 files changed, 19951 insertions, 2828 deletions
diff --git a/Android.bp b/Android.bp index cc754f21902a..83878aaf6cbc 100644 --- a/Android.bp +++ b/Android.bp @@ -538,7 +538,9 @@ filegroup { visibility: ["//visibility:private"], } -// These defaults are used for both the jar stubs and the doc stubs. +// Defaults for all stubs that include the non-updatable framework. These defaults do not include +// module symbols, so will not compile correctly on their own. Users must add module APIs to the +// classpath (or sources) somehow. stubs_defaults { name: "android-non-updatable-stubs-defaults", srcs: [":android-non-updatable-stub-sources"], @@ -546,17 +548,14 @@ stubs_defaults { system_modules: "none", java_version: "1.8", arg_files: ["core/res/AndroidManifest.xml"], - // TODO(b/147699819): remove below aidl includes. aidl: { local_include_dirs: [ - "apex/media/aidl/stable", "media/aidl", "telephony/java", ], include_dirs: [ "frameworks/av/aidl", "frameworks/native/libs/permission/aidl", - "packages/modules/Connectivity/framework/aidl-export", ], }, // These are libs from framework-internal-utils that are required (i.e. being referenced) @@ -576,6 +575,30 @@ stubs_defaults { "android.hardware.usb.gadget-V1.0-java", "android.hardware.vibrator-V1.3-java", "framework-protos", + ], + filter_packages: packages_to_document, + high_mem: true, // Lots of sources => high memory use, see b/170701554 + installable: false, + annotations_enabled: true, + previous_api: ":android.api.public.latest", + merge_annotations_dirs: ["metalava-manual"], + defaults_visibility: ["//visibility:private"], + visibility: ["//frameworks/base/api"], +} + +// Defaults with module APIs in the classpath (mostly from prebuilts). +// Suitable for compiling android-non-updatable. +stubs_defaults { + name: "module-classpath-stubs-defaults", + aidl: { + local_include_dirs: [ + "apex/media/aidl/stable", + ], + include_dirs: [ + "packages/modules/Connectivity/framework/aidl-export", + ], + }, + libs: [ "art.module.public.api", "sdk_module-lib_current_framework-tethering", // There are a few classes from modules used by the core that @@ -586,14 +609,7 @@ stubs_defaults { // NOTE: The below can be removed once the prebuilt stub contains IKE. "sdk_system_current_android.net.ipsec.ike", ], - filter_packages: packages_to_document, - high_mem: true, // Lots of sources => high memory use, see b/170701554 - installable: false, - annotations_enabled: true, - previous_api: ":android.api.public.latest", - merge_annotations_dirs: ["metalava-manual"], defaults_visibility: ["//visibility:private"], - visibility: ["//frameworks/base/api"], } build = [ diff --git a/ApiDocs.bp b/ApiDocs.bp index 4aecc8fe4504..98d071eff490 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -57,7 +57,10 @@ framework_docs_only_libs = [ stubs_defaults { name: "android-non-updatable-doc-stubs-defaults", - defaults: ["android-non-updatable-stubs-defaults"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], srcs: [ // No longer part of the stubs, but are included in the docs. ":android-test-base-sources", diff --git a/StubLibraries.bp b/StubLibraries.bp index 77b26f80ab97..8a15758f87ee 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -32,23 +32,15 @@ soong_config_module_type_import { } ///////////////////////////////////////////////////////////////////// -// Common metalava configs -///////////////////////////////////////////////////////////////////// - -stubs_defaults { - name: "metalava-non-updatable-api-stubs-default", - defaults: ["android-non-updatable-stubs-defaults"], - api_levels_annotations_enabled: false, - defaults_visibility: ["//visibility:private"], -} - -///////////////////////////////////////////////////////////////////// // These modules provide source files for the stub libraries ///////////////////////////////////////////////////////////////////// droidstubs { name: "api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args, check_api: { current: { @@ -97,7 +89,10 @@ module_libs = " --show-annotation android.annotation.SystemApi\\(" + droidstubs { name: "system-api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args + priv_apps, check_api: { current: { @@ -133,7 +128,10 @@ droidstubs { droidstubs { name: "test-api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args + test + priv_apps_in_stubs, check_api: { current: { @@ -175,7 +173,10 @@ droidstubs { droidstubs { name: "module-lib-api-stubs-docs-non-updatable", - defaults: ["metalava-non-updatable-api-stubs-default"], + defaults: [ + "android-non-updatable-stubs-defaults", + "module-classpath-stubs-defaults", + ], args: metalava_framework_docs_args + priv_apps_in_stubs + module_libs, check_api: { current: { diff --git a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java index cf94e9e0d384..4cd974141d26 100644 --- a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java +++ b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java @@ -25,7 +25,6 @@ import static android.view.MotionEvent.TOOL_TYPE_STYLUS; import android.app.Instrumentation; import android.content.Context; -import android.graphics.Rect; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.view.inputmethod.EditorInfo; @@ -190,7 +189,7 @@ public class HandwritingInitiatorPerfTest { final View view = new View(mContext); final EditorInfo editorInfo = new EditorInfo(); while (state.keepRunning()) { - mHandwritingInitiator.onInputConnectionCreated(view, editorInfo); + mHandwritingInitiator.onInputConnectionCreated(view); state.pauseTiming(); mHandwritingInitiator.onInputConnectionClosed(view); state.resumeTiming(); @@ -201,24 +200,14 @@ public class HandwritingInitiatorPerfTest { public void onInputConnectionClosed() { final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); final View view = new View(mContext); - final EditorInfo editorInfo = new EditorInfo(); while (state.keepRunning()) { state.pauseTiming(); - mHandwritingInitiator.onInputConnectionCreated(view, editorInfo); + mHandwritingInitiator.onInputConnectionCreated(view); state.resumeTiming(); mHandwritingInitiator.onInputConnectionClosed(view); } } - @Test - public void updateEditorBoundary() { - final BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - final Rect rect = new Rect(0, 0, 100, 100); - while (state.keepRunning()) { - mHandwritingInitiator.updateEditorBound(rect); - } - } - private MotionEvent createMotionEvent(int action, int toolType, int x, int y, long eventTime) { MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); properties[0].toolType = toolType; diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index ee0995281c29..0ad70e40de7f 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -183,7 +183,7 @@ public class AppStandbyController COMPRESS_TIME ? 1 * ONE_MINUTE : 12 * ONE_HOUR, COMPRESS_TIME ? 4 * ONE_MINUTE : 24 * ONE_HOUR, COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR, - COMPRESS_TIME ? 32 * ONE_MINUTE : 45 * ONE_DAY + COMPRESS_TIME ? 32 * ONE_MINUTE : 3 * ONE_DAY }; /** The minimum allowed values for each index in {@link #DEFAULT_ELAPSED_TIME_THRESHOLDS}. */ @@ -2540,7 +2540,7 @@ public class AppStandbyController switch (name) { case KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS: mInjector.mAutoRestrictedBucketDelayMs = Math.max( - COMPRESS_TIME ? ONE_MINUTE : 2 * ONE_HOUR, + COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR, properties.getLong(KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS, DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS)); break; diff --git a/core/api/current.txt b/core/api/current.txt index 4aee690c31a7..207411546978 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -826,6 +826,7 @@ package android { field public static final int indicatorRight = 16843022; // 0x101010e field public static final int indicatorStart = 16843729; // 0x10103d1 field public static final int inflatedId = 16842995; // 0x10100f3 + field public static final int inheritKeyStoreKeys; field public static final int inheritShowWhenLocked = 16844188; // 0x101059c field public static final int initOrder = 16842778; // 0x101001a field public static final int initialKeyguardLayout = 16843714; // 0x10103c2 @@ -952,6 +953,7 @@ package android { field public static final int letterSpacing = 16843958; // 0x10104b6 field public static final int level = 16844032; // 0x1010500 field public static final int lineBreakStyle = 16844365; // 0x101064d + field public static final int lineBreakWordStyle = 16844366; // 0x101064e field public static final int lineHeight = 16844159; // 0x101057f field public static final int lineSpacingExtra = 16843287; // 0x1010217 field public static final int lineSpacingMultiplier = 16843288; // 0x1010218 @@ -1134,6 +1136,7 @@ package android { field public static final int popupWindowStyle = 16842870; // 0x1010076 field public static final int port = 16842793; // 0x1010029 field public static final int positiveButtonText = 16843253; // 0x10101f5 + field public static final int preferKeepClear; field public static final int preferMinimalPostProcessing = 16844300; // 0x101060c field public static final int preferenceCategoryStyle = 16842892; // 0x101008c field public static final int preferenceFragmentStyle = 16844038; // 0x1010506 @@ -7313,6 +7316,7 @@ package android.app.admin { method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName); method public long getMaximumTimeToLock(@Nullable android.content.ComponentName); method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName); + method public int getMinimumRequiredWifiSecurityLevel(); method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy(); method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy(); method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName); @@ -7353,6 +7357,7 @@ package android.app.admin { method @NonNull public java.util.List<java.lang.String> getUserControlDisabledPackages(@NonNull android.content.ComponentName); method @NonNull public android.os.Bundle getUserRestrictions(@NonNull android.content.ComponentName); method @Nullable public String getWifiMacAddress(@NonNull android.content.ComponentName); + method @Nullable public android.app.admin.WifiSsidPolicy getWifiSsidPolicy(); method public boolean grantKeyPairToApp(@Nullable android.content.ComponentName, @NonNull String, @NonNull String); method public boolean grantKeyPairToWifiAuth(@NonNull String); method public boolean hasCaCertInstalled(@Nullable android.content.ComponentName, byte[]); @@ -7457,6 +7462,7 @@ package android.app.admin { method public void setMaximumFailedPasswordsForWipe(@NonNull android.content.ComponentName, int); method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long); method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); + method public void setMinimumRequiredWifiSecurityLevel(int); method public void setNearbyAppStreamingPolicy(int); method public void setNearbyNotificationStreamingPolicy(int); method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean); @@ -7505,6 +7511,7 @@ package android.app.admin { method public void setUsbDataSignalingEnabled(boolean); method public void setUserControlDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>); method public void setUserIcon(@NonNull android.content.ComponentName, android.graphics.Bitmap); + method public void setWifiSsidPolicy(@Nullable android.app.admin.WifiSsidPolicy); method public int startUserInBackground(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); method public int stopUser(@NonNull android.content.ComponentName, @NonNull android.os.UserHandle); method public boolean switchUser(@NonNull android.content.ComponentName, @Nullable android.os.UserHandle); @@ -7675,6 +7682,10 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 + field public static final int WIFI_SECURITY_ENTERPRISE_192 = 3; // 0x3 + field public static final int WIFI_SECURITY_ENTERPRISE_EAP = 2; // 0x2 + field public static final int WIFI_SECURITY_OPEN = 0; // 0x0 + field public static final int WIFI_SECURITY_PERSONAL = 1; // 0x1 field public static final int WIPE_EUICC = 4; // 0x4 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 @@ -7862,6 +7873,18 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.UnsafeStateException> CREATOR; } + public final class WifiSsidPolicy implements android.os.Parcelable { + method @NonNull public static android.app.admin.WifiSsidPolicy createAllowlistPolicy(@NonNull java.util.Set<java.lang.String>); + method @NonNull public static android.app.admin.WifiSsidPolicy createDenylistPolicy(@NonNull java.util.Set<java.lang.String>); + method public int describeContents(); + method public int getPolicyType(); + method @NonNull public java.util.Set<java.lang.String> getSsids(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.WifiSsidPolicy> CREATOR; + field public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0; // 0x0 + field public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1; // 0x1 + } + } package android.app.assist { @@ -9723,6 +9746,7 @@ package android.bluetooth { method public void close(); method protected void finalize(); method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothDevice getConnectedGroupLeadDevice(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getGroupId(@NonNull android.bluetooth.BluetoothDevice); @@ -13188,6 +13212,7 @@ package android.content.pm { field public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct"; field public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint"; field public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt"; + field public static final String FEATURE_WINDOW_MAGNIFICATION = "android.software.window_magnification"; field public static final int FLAG_PERMISSION_WHITELIST_INSTALLER = 2; // 0x2 field public static final int FLAG_PERMISSION_WHITELIST_SYSTEM = 1; // 0x1 field public static final int FLAG_PERMISSION_WHITELIST_UPGRADE = 4; // 0x4 @@ -17594,12 +17619,16 @@ package android.graphics.text { public final class LineBreakConfig { ctor public LineBreakConfig(); method public int getLineBreakStyle(); - method public void set(@Nullable android.graphics.text.LineBreakConfig); + method public int getLineBreakWordStyle(); + method public void set(@NonNull android.graphics.text.LineBreakConfig); method public void setLineBreakStyle(int); + method public void setLineBreakWordStyle(int); field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1 field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2 field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3 + field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0 + field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1 } public class LineBreaker { @@ -25811,12 +25840,12 @@ package android.media.midi { } public final class MidiManager { - method public android.media.midi.MidiDeviceInfo[] getDevices(); - method @NonNull public java.util.Collection<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int); + method @Deprecated public android.media.midi.MidiDeviceInfo[] getDevices(); + method @NonNull public java.util.Set<android.media.midi.MidiDeviceInfo> getDevicesForTransport(int); method public void openBluetoothDevice(android.bluetooth.BluetoothDevice, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler); method public void openDevice(android.media.midi.MidiDeviceInfo, android.media.midi.MidiManager.OnDeviceOpenedListener, android.os.Handler); - method public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler); - method public void registerDeviceCallbackForTransport(@NonNull android.media.midi.MidiManager.DeviceCallback, @Nullable android.os.Handler, int); + method @Deprecated public void registerDeviceCallback(android.media.midi.MidiManager.DeviceCallback, android.os.Handler); + method public void registerDeviceCallback(int, @NonNull java.util.concurrent.Executor, @NonNull android.media.midi.MidiManager.DeviceCallback); method public void unregisterDeviceCallback(android.media.midi.MidiManager.DeviceCallback); field public static final int TRANSPORT_MIDI_BYTE_STREAM = 1; // 0x1 field public static final int TRANSPORT_UNIVERSAL_MIDI_PACKETS = 2; // 0x2 @@ -41814,11 +41843,11 @@ package android.telephony { field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int"; - field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; + field @Deprecated public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; - field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; - field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; + field @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; + field @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool"; field public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; field @Deprecated public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = "carrier_vvm_package_name_string"; @@ -42064,6 +42093,13 @@ package android.telephony { field public static final int IPSEC_ENCRYPTION_ALGORITHM_AES_CBC = 2; // 0x2 field public static final int IPSEC_ENCRYPTION_ALGORITHM_DES_EDE3_CBC = 1; // 0x1 field public static final int IPSEC_ENCRYPTION_ALGORITHM_NULL = 0; // 0x0 + field public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = "ims.key_capability_type_call_composer_int_array"; + field public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = "ims.key_capability_type_options_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = "ims.key_capability_type_presence_uce_int_array"; + field public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = "ims.key_capability_type_sms_int_array"; + field public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = "ims.key_capability_type_ut_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = "ims.key_capability_type_video_int_array"; + field public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = "ims.key_capability_type_voice_int_array"; field public static final String KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL = "ims.enable_presence_capability_exchange_bool"; field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool"; field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool"; @@ -42078,11 +42114,13 @@ package android.telephony { field public static final String KEY_IPV4_SIP_MTU_SIZE_CELLULAR_INT = "ims.ipv4_sip_mtu_size_cellular_int"; field public static final String KEY_IPV6_SIP_MTU_SIZE_CELLULAR_INT = "ims.ipv6_sip_mtu_size_cellular_int"; field public static final String KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL = "ims.keep_pdn_up_in_no_vops_bool"; + field public static final String KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE = "ims.mmtel_requires_provisioning_bundle"; field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int"; field public static final String KEY_PHONE_CONTEXT_DOMAIN_NAME_STRING = "ims.phone_context_domain_name_string"; field public static final String KEY_PREFIX = "ims."; field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool"; field public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = "ims.rcs_feature_tag_allowed_string_array"; + field public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE = "ims.rcs_requires_provisioning_bundle"; field public static final String KEY_REGISTRATION_EVENT_PACKAGE_SUPPORTED_BOOL = "ims.registration_event_package_supported_bool"; field public static final String KEY_REGISTRATION_EXPIRY_TIMER_SEC_INT = "ims.registration_expiry_timer_sec_int"; field public static final String KEY_REGISTRATION_RETRY_BASE_TIMER_MILLIS_INT = "ims.registration_retry_base_timer_millis_int"; @@ -42345,6 +42383,7 @@ package android.telephony { field public static final String KEY_SUPPORTED_IKE_SESSION_ENCRYPTION_ALGORITHMS_INT_ARRAY = "iwlan.supported_ike_session_encryption_algorithms_int_array"; field public static final String KEY_SUPPORTED_INTEGRITY_ALGORITHMS_INT_ARRAY = "iwlan.supported_integrity_algorithms_int_array"; field public static final String KEY_SUPPORTED_PRF_ALGORITHMS_INT_ARRAY = "iwlan.supported_prf_algorithms_int_array"; + field public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL = "iwlan.supports_eap_aka_fast_reauth_bool"; } public abstract class CellIdentity implements android.os.Parcelable { @@ -44730,6 +44769,7 @@ package android.telephony.ims { public class ImsManager { method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int); method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int); + method @NonNull public android.telephony.ims.ProvisioningManager getProvisioningManager(int); field public static final String ACTION_WFC_IMS_REGISTRATION_ERROR = "android.telephony.ims.action.WFC_IMS_REGISTRATION_ERROR"; field public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE"; field public static final String EXTRA_WFC_REGISTRATION_FAILURE_TITLE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_TITLE"; @@ -44980,6 +45020,23 @@ package android.telephony.ims { field public static final int REASON_UNKNOWN_TEMPORARY_ERROR = 1; // 0x1 } + public class ProvisioningManager { + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean isProvisioningRequiredForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean isRcsProvisioningRequiredForCapability(int, int); + method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void registerFeatureProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback) throws android.telephony.ims.ImsException; + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(int, int, boolean); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, int, boolean); + method public void unregisterFeatureProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.FeatureProvisioningCallback); + } + + public static class ProvisioningManager.FeatureProvisioningCallback { + ctor public ProvisioningManager.FeatureProvisioningCallback(); + method public void onFeatureProvisioningChanged(int, int, boolean); + method public void onRcsFeatureProvisioningChanged(int, int, boolean); + } + public class RcsUceAdapter { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException; } @@ -45020,6 +45077,27 @@ package android.telephony.ims.feature { field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1 } + public class RcsFeature { + } + + public static class RcsFeature.RcsImsCapabilities { + field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0 + field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1 + field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 + } + +} + +package android.telephony.ims.stub { + + public class ImsRegistrationImplBase { + field public static final int REGISTRATION_TECH_CROSS_SIM = 2; // 0x2 + field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1 + field public static final int REGISTRATION_TECH_LTE = 0; // 0x0 + field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff + field public static final int REGISTRATION_TECH_NR = 3; // 0x3 + } + } package android.telephony.mbms { @@ -48611,6 +48689,7 @@ package android.view { method public static int[] getDeviceIds(); method public int getId(); method public android.view.KeyCharacterMap getKeyCharacterMap(); + method public int getKeyCodeForKeyLocation(int); method public int getKeyboardType(); method @NonNull public android.hardware.lights.LightsManager getLightsManager(); method public android.view.InputDevice.MotionRange getMotionRange(int); @@ -50218,6 +50297,7 @@ package android.view { method public float getPivotX(); method public float getPivotY(); method public android.view.PointerIcon getPointerIcon(); + method @NonNull public final java.util.List<android.graphics.Rect> getPreferKeepClearRects(); method @Nullable public String[] getReceiveContentMimeTypes(); method public android.content.res.Resources getResources(); method public final boolean getRevealOnFocusHint(); @@ -50335,6 +50415,7 @@ package android.view { method protected boolean isPaddingOffsetRequired(); method public boolean isPaddingRelative(); method public boolean isPivotSet(); + method public final boolean isPreferKeepClear(); method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); @@ -50577,6 +50658,8 @@ package android.view { method public void setPivotX(float); method public void setPivotY(float); method public void setPointerIcon(android.view.PointerIcon); + method public final void setPreferKeepClear(boolean); + method public final void setPreferKeepClearRects(@NonNull java.util.List<android.graphics.Rect>); method public void setPressed(boolean); method public void setRenderEffect(@Nullable android.graphics.RenderEffect); method public final void setRevealOnFocusHint(boolean); diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index b082629ce99c..2a4e7a666beb 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -147,10 +147,12 @@ package android.hardware.usb { field public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2; // 0x2 field public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1; // 0xffffffff field public static final int USB_HAL_NOT_SUPPORTED = -1; // 0xffffffff + field public static final int USB_HAL_RETRY = -2; // 0xfffffffe field public static final int USB_HAL_V1_0 = 10; // 0xa field public static final int USB_HAL_V1_1 = 11; // 0xb field public static final int USB_HAL_V1_2 = 12; // 0xc field public static final int USB_HAL_V1_3 = 13; // 0xd + field public static final int USB_HAL_V2_0 = 20; // 0x14 } } @@ -264,6 +266,32 @@ package android.net { method public int getResourceId(); } + public class NetworkIdentity { + method public int getOemManaged(); + method public int getRatType(); + method @Nullable public String getSubscriberId(); + method public int getType(); + method @Nullable public String getWifiNetworkKey(); + method public boolean isDefaultNetwork(); + method public boolean isMetered(); + method public boolean isRoaming(); + } + + public static final class NetworkIdentity.Builder { + ctor public NetworkIdentity.Builder(); + method @NonNull public android.net.NetworkIdentity build(); + method @NonNull public android.net.NetworkIdentity.Builder clearRatType(); + method @NonNull public android.net.NetworkIdentity.Builder setDefaultNetwork(boolean); + method @NonNull public android.net.NetworkIdentity.Builder setMetered(boolean); + method @NonNull public android.net.NetworkIdentity.Builder setNetworkStateSnapshot(@NonNull android.net.NetworkStateSnapshot); + method @NonNull public android.net.NetworkIdentity.Builder setOemManaged(int); + method @NonNull public android.net.NetworkIdentity.Builder setRatType(int); + method @NonNull public android.net.NetworkIdentity.Builder setRoaming(boolean); + method @NonNull public android.net.NetworkIdentity.Builder setSubscriberId(@Nullable String); + method @NonNull public android.net.NetworkIdentity.Builder setType(int); + method @NonNull public android.net.NetworkIdentity.Builder setWifiNetworkKey(@Nullable String); + } + public class NetworkPolicyManager { method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getMultipathPreference(@NonNull android.net.Network); method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getRestrictBackgroundStatus(int); @@ -291,6 +319,30 @@ package android.net { field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStateSnapshot> CREATOR; } + public final class NetworkStatsHistory implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.List<android.net.NetworkStatsHistory.Entry> getEntries(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStatsHistory> CREATOR; + } + + public static final class NetworkStatsHistory.Builder { + ctor public NetworkStatsHistory.Builder(long, int); + method @NonNull public android.net.NetworkStatsHistory.Builder addEntry(@NonNull android.net.NetworkStatsHistory.Entry); + method @NonNull public android.net.NetworkStatsHistory build(); + } + + public static final class NetworkStatsHistory.Entry { + ctor public NetworkStatsHistory.Entry(long, long, long, long, long, long, long); + method public long getActiveTime(); + method public long getBucketStart(); + method public long getOperations(); + method public long getRxBytes(); + method public long getRxPackets(); + method public long getTxBytes(); + method public long getTxPackets(); + } + public final class NetworkTemplate implements android.os.Parcelable { method public int describeContents(); method public int getDefaultNetworkStatus(); @@ -346,6 +398,10 @@ package android.net { method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo); } + public class TrafficStats { + method public static void init(@NonNull android.content.Context); + } + public final class UnderlyingNetworkInfo implements android.os.Parcelable { ctor public UnderlyingNetworkInfo(int, @NonNull String, @NonNull java.util.List<java.lang.String>); method public int describeContents(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cf20b4c4a12a..7c2bf80c58f9 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2,12 +2,14 @@ package android { public static final class Manifest.permission { + field public static final String ACCESS_AMBIENT_CONTEXT_EVENT = "android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"; field public static final String ACCESS_AMBIENT_LIGHT_STATS = "android.permission.ACCESS_AMBIENT_LIGHT_STATS"; field public static final String ACCESS_BROADCAST_RADIO = "android.permission.ACCESS_BROADCAST_RADIO"; field public static final String ACCESS_CACHE_FILESYSTEM = "android.permission.ACCESS_CACHE_FILESYSTEM"; field public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB"; field public static final String ACCESS_DRM_CERTIFICATES = "android.permission.ACCESS_DRM_CERTIFICATES"; field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO"; + field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER"; field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS"; field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS"; field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION"; @@ -37,6 +39,7 @@ package android { field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BACKUP = "android.permission.BACKUP"; field public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION"; + field public static final String BIND_AMBIENT_CONTEXT_DETECTION_SERVICE = "android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"; field public static final String BIND_ATTENTION_SERVICE = "android.permission.BIND_ATTENTION_SERVICE"; field public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission.BIND_AUGMENTED_AUTOFILL_SERVICE"; field public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"; @@ -159,6 +162,7 @@ package android { field public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING"; field public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS"; field public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission.MANAGE_FACTORY_RESET_PROTECTION"; + field public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE"; field public static final String MANAGE_HOTWORD_DETECTION = "android.permission.MANAGE_HOTWORD_DETECTION"; field public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS"; field public static final String MANAGE_MUSIC_RECOGNITION = "android.permission.MANAGE_MUSIC_RECOGNITION"; @@ -755,7 +759,17 @@ package android.app { } public final class GameManager { - method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public android.app.GameModeInfo getGameModeInfo(@NonNull String); + method @RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(@NonNull String, int); + } + + public final class GameModeInfo implements android.os.Parcelable { + ctor public GameModeInfo(int, @NonNull int[]); + method public int describeContents(); + method public int getActiveGameMode(); + method @NonNull public int[] getAvailableGameModes(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeInfo> CREATOR; } public abstract class InstantAppResolverService extends android.app.Service { @@ -1129,7 +1143,36 @@ package android.app.admin { } public static final class DevicePolicyResources.Strings { - field public static final String INVALID_ID = "INVALID_ID"; + field public static final String UNDEFINED = "UNDEFINED"; + } + + public static final class DevicePolicyResources.Strings.DocumentsUi { + field public static final String CANT_SAVE_TO_PERSONAL_MESSAGE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE"; + field public static final String CANT_SAVE_TO_PERSONAL_TITLE = "DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE"; + field public static final String CANT_SAVE_TO_WORK_MESSAGE = "DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE"; + field public static final String CANT_SAVE_TO_WORK_TITLE = "DocumentsUi.CANT_SAVE_TO_WORK_TITLE"; + field public static final String CANT_SELECT_PERSONAL_FILES_MESSAGE = "DocumentsUi.CANT_SELECT_PERSONAL_FILES_MESSAGE"; + field public static final String CANT_SELECT_PERSONAL_FILES_TITLE = "DocumentsUi.CANT_SELECT_PERSONAL_FILES_TITLE"; + field public static final String CANT_SELECT_WORK_FILES_MESSAGE = "DocumentsUi.CANT_SELECT_WORK_FILES_MESSAGE"; + field public static final String CANT_SELECT_WORK_FILES_TITLE = "DocumentsUi.CANT_SELECT_WORK_FILES_TITLE"; + field public static final String CROSS_PROFILE_NOT_ALLOWED_MESSAGE = "DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_MESSAGE"; + field public static final String CROSS_PROFILE_NOT_ALLOWED_TITLE = "DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_TITLE"; + field public static final String PERSONAL_TAB = "DocumentsUi.PERSONAL_TAB"; + field public static final String PREVIEW_WORK_FILE_ACCESSIBILITY = "DocumentsUi.PREVIEW_WORK_FILE_ACCESSIBILITY"; + field public static final String WORK_ACCESSIBILITY = "DocumentsUi.WORK_ACCESSIBILITY"; + field public static final String WORK_PROFILE_OFF_ENABLE_BUTTON = "DocumentsUi.WORK_PROFILE_OFF_ENABLE_BUTTON"; + field public static final String WORK_PROFILE_OFF_ERROR_TITLE = "DocumentsUi.WORK_PROFILE_OFF_ERROR_TITLE"; + field public static final String WORK_TAB = "DocumentsUi.WORK_TAB"; + } + + public static final class DevicePolicyResources.Strings.MediaProvider { + field public static final String BLOCKED_BY_ADMIN_TITLE = "MediaProvider.BLOCKED_BY_ADMIN_TITLE"; + field public static final String BLOCKED_FROM_PERSONAL_MESSAGE = "MediaProvider.BLOCKED_FROM_PERSONAL_MESSAGE"; + field public static final String BLOCKED_FROM_WORK_MESSAGE = "MediaProvider.BLOCKED_FROM_WORK_MESSAGE"; + field public static final String SWITCH_TO_PERSONAL_MESSAGE = "MediaProvider.SWITCH_TO_PERSONAL_MESSAGE"; + field public static final String SWITCH_TO_WORK_MESSAGE = "MediaProvider.SWITCH_TO_WORK_MESSAGE"; + field public static final String WORK_PROFILE_PAUSED_MESSAGE = "MediaProvider.WORK_PROFILE_PAUSED_MESSAGE"; + field public static final String WORK_PROFILE_PAUSED_TITLE = "MediaProvider.WORK_PROFILE_PAUSED_TITLE"; } public final class DevicePolicyStringResource implements android.os.Parcelable { @@ -1212,6 +1255,87 @@ package android.app.admin { } +package android.app.ambientcontext { + + public final class AmbientContextEvent implements android.os.Parcelable { + method public int describeContents(); + method public int getConfidenceLevel(); + method public int getDensityLevel(); + method @NonNull public java.time.Instant getEndTime(); + method public int getEventType(); + method @NonNull public java.time.Instant getStartTime(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR; + field public static final int EVENT_COUGH = 1; // 0x1 + field public static final int EVENT_SNORE = 2; // 0x2 + field public static final int EVENT_UNKNOWN = 0; // 0x0 + field public static final int LEVEL_HIGH = 5; // 0x5 + field public static final int LEVEL_LOW = 1; // 0x1 + field public static final int LEVEL_MEDIUM = 3; // 0x3 + field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4 + field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2 + field public static final int LEVEL_UNKNOWN = 0; // 0x0 + } + + public static final class AmbientContextEvent.Builder { + ctor public AmbientContextEvent.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEvent build(); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setConfidenceLevel(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setDensityLevel(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int); + method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant); + } + + public final class AmbientContextEventRequest implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public java.util.Set<java.lang.Integer> getEventTypes(); + method @NonNull public android.os.PersistableBundle getOptions(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventRequest> CREATOR; + } + + public static final class AmbientContextEventRequest.Builder { + ctor public AmbientContextEventRequest.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder addEventType(int); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest build(); + method @NonNull public android.app.ambientcontext.AmbientContextEventRequest.Builder setOptions(@NonNull android.os.PersistableBundle); + } + + public final class AmbientContextEventResponse implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public android.app.PendingIntent getActionPendingIntent(); + method @NonNull public java.util.List<android.app.ambientcontext.AmbientContextEvent> getEvents(); + method @NonNull public String getPackageName(); + method public int getStatusCode(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEventResponse> CREATOR; + field public static final int STATUS_ACCESS_DENIED = 5; // 0x5 + field public static final int STATUS_MICROPHONE_DISABLED = 4; // 0x4 + field public static final int STATUS_NOT_SUPPORTED = 2; // 0x2 + field public static final int STATUS_SERVICE_UNAVAILABLE = 3; // 0x3 + field public static final int STATUS_SUCCESS = 1; // 0x1 + field public static final int STATUS_UNKNOWN = 0; // 0x0 + } + + public static final class AmbientContextEventResponse.Builder { + ctor public AmbientContextEventResponse.Builder(); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder addEvent(@NonNull android.app.ambientcontext.AmbientContextEvent); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse build(); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setActionPendingIntent(@NonNull android.app.PendingIntent); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setPackageName(@NonNull String); + method @NonNull public android.app.ambientcontext.AmbientContextEventResponse.Builder setStatusCode(int); + } + + public final class AmbientContextManager { + method @Nullable public static android.app.ambientcontext.AmbientContextEventResponse getResponseFromIntent(@NonNull android.content.Intent); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void registerObserver(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull android.app.PendingIntent); + method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) public void unregisterObserver(); + field public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + } + +} + package android.app.assist { public class ActivityId { @@ -2310,6 +2434,10 @@ package android.bluetooth { field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; } + public final class BluetoothLeAudio implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getAudioLocation(@NonNull android.bluetooth.BluetoothDevice); + } + public final class BluetoothMap implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { method public void close(); method protected void finalize(); @@ -2348,6 +2476,15 @@ package android.bluetooth { field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; } + public final class BluetoothPbapClient implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice); + method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]); + method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED"; + } + public interface BluetoothProfile { field public static final int A2DP_SINK = 11; // 0xb field public static final int AVRCP_CONTROLLER = 12; // 0xc @@ -2689,6 +2826,7 @@ package android.content { method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle); method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle); + field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context"; field public static final String APP_HIBERNATION_SERVICE = "app_hibernation"; field public static final String APP_INTEGRITY_SERVICE = "app_integrity"; field public static final String APP_PREDICTION_SERVICE = "app_prediction"; @@ -4121,6 +4259,7 @@ package android.hardware.input { public class VirtualMouse implements java.io.Closeable { method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close(); + method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.graphics.PointF getCursorPosition(); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull android.hardware.input.VirtualMouseButtonEvent); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull android.hardware.input.VirtualMouseRelativeEvent); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull android.hardware.input.VirtualMouseScrollEvent); @@ -5080,8 +5219,20 @@ package android.hardware.usb { } public final class UsbPort { + method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableLimitPowerTransfer(boolean); + method @CheckResult @RequiresPermission(android.Manifest.permission.MANAGE_USB) public int enableUsbData(boolean); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USB) public android.hardware.usb.UsbPortStatus getStatus(); method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setRoles(int, int); + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; // 0x1 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; // 0x2 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; // 0x4 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH = 3; // 0x3 + field public static final int ENABLE_LIMIT_POWER_TRANSFER_SUCCESS = 0; // 0x0 + field public static final int ENABLE_USB_DATA_ERROR_INTERNAL = 1; // 0x1 + field public static final int ENABLE_USB_DATA_ERROR_NOT_SUPPORTED = 2; // 0x2 + field public static final int ENABLE_USB_DATA_ERROR_OTHER = 4; // 0x4 + field public static final int ENABLE_USB_DATA_ERROR_PORT_MISMATCH = 3; // 0x3 + field public static final int ENABLE_USB_DATA_SUCCESS = 0; // 0x0 } public final class UsbPortStatus implements android.os.Parcelable { @@ -5091,6 +5242,7 @@ package android.hardware.usb { method public int getCurrentPowerRole(); method public int getSupportedRoleCombinations(); method public boolean isConnected(); + method public boolean isPowerTransferLimited(); method public boolean isRoleCombinationSupported(int, int); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.usb.UsbPortStatus> CREATOR; @@ -7061,7 +7213,7 @@ package android.media.tv.tuner.filter { } public abstract class SectionSettings extends android.media.tv.tuner.filter.Settings { - method public int getBitWidthOfLengthField(); + method public int getLengthFieldBitWidth(); method public boolean isCrcEnabled(); method public boolean isRaw(); method public boolean isRepeat(); @@ -8696,6 +8848,7 @@ package android.net.wifi.nl80211 { method @Nullable public android.net.wifi.nl80211.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String); method @NonNull public java.util.List<android.net.wifi.nl80211.NativeScanResult> getScanResults(@NonNull String, int); method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); + method public boolean notifyCountryCodeChanged(); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener); @@ -9693,12 +9846,17 @@ package android.permission { public final class PermissionControllerManager { method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); + method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void getHibernationEligibility(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>); method public void getUnusedAppCount(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback); method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle); field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1 field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2 + field public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0; // 0x0 + field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1; // 0x1 + field public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2; // 0x2 + field public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1; // 0xffffffff field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2 field public static final int REASON_MALWARE = 1; // 0x1 } @@ -9716,6 +9874,7 @@ package android.permission { method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>); method @BinderThread public void onGetGroupOfPlatformPermission(@NonNull String, @NonNull java.util.function.Consumer<java.lang.String>); + method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetHibernationEligibility(@NonNull String, @NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>); method @BinderThread public void onGetPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>); method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable); @@ -9934,6 +10093,7 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean); field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager"; field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot"; + field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service"; field public static final String NAMESPACE_APPSEARCH = "appsearch"; field public static final String NAMESPACE_APP_COMPAT = "app_compat"; field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; @@ -9976,6 +10136,7 @@ package android.provider { field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot"; field @Deprecated public static final String NAMESPACE_STORAGE = "storage"; field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot"; + field public static final String NAMESPACE_SUPPLEMENTAL_API = "supplemental_api"; field public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT = "surface_flinger_native_boot"; field public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native"; field public static final String NAMESPACE_SYSTEMUI = "systemui"; @@ -10204,6 +10365,7 @@ package android.provider { field public static final String AUTO_REVOKE_DISABLED = "auto_revoke_disabled"; field public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category."; field public static final String DOZE_ALWAYS_ON = "doze_always_on"; + field public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; field public static final String HUSH_GESTURE_USED = "hush_gesture_used"; field public static final String INSTANT_APPS_ENABLED = "instant_apps_enabled"; field public static final String LAST_SETUP_SHOWN = "last_setup_shown"; @@ -10504,6 +10666,18 @@ package android.security.keystore.recovery { } +package android.service.ambientcontext { + + public abstract class AmbientContextDetectionService extends android.app.Service { + ctor public AmbientContextDetectionService(); + method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.app.ambientcontext.AmbientContextEventResponse>); + method public abstract void onStopDetection(@NonNull String); + field public static final String SERVICE_INTERFACE = "android.service.ambientcontext.AmbientContextDetectionService"; + } + +} + package android.service.appprediction { public abstract class AppPredictionService extends android.app.Service { @@ -10955,7 +11129,15 @@ package android.service.games { ctor public GameSession(); method public void onCreate(); method public void onDestroy(); + method public void onGameTaskFocusChanged(boolean); method public void setTaskOverlayView(@NonNull android.view.View, @NonNull android.view.ViewGroup.LayoutParams); + method public void takeScreenshot(@NonNull java.util.concurrent.Executor, @NonNull android.service.games.GameSession.ScreenshotCallback); + } + + public static interface GameSession.ScreenshotCallback { + method public void onFailure(int); + method public void onSuccess(@NonNull android.graphics.Bitmap); + field public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; // 0x0 } public abstract class GameSessionService extends android.app.Service { @@ -14380,18 +14562,16 @@ package android.telephony.ims { public class ProvisioningManager { method @NonNull public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(int, int); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int); - method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); + method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public boolean isRcsVolteSingleRegistrationCapable() throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException; method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void registerRcsProvisioningCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback) throws android.telephony.ims.ImsException; method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(int, int, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void setRcsClientConfiguration(@NonNull android.telephony.ims.RcsClientConfiguration) throws android.telephony.ims.ImsException; - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean); method @RequiresPermission(android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void triggerRcsReconfiguration(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback); method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void unregisterRcsProvisioningCallback(@NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback); @@ -14824,9 +15004,6 @@ package android.telephony.ims.feature { method public void addCapabilities(int); method public boolean isCapable(int); method public void removeCapabilities(int); - field public static final int CAPABILITY_TYPE_NONE = 0; // 0x0 - field public static final int CAPABILITY_TYPE_OPTIONS_UCE = 1; // 0x1 - field public static final int CAPABILITY_TYPE_PRESENCE_UCE = 2; // 0x2 } } @@ -14976,11 +15153,6 @@ package android.telephony.ims.stub { method public void triggerFullNetworkRegistration(@IntRange(from=100, to=699) int, @Nullable String); method public void triggerSipDelegateDeregistration(); method public void updateSipDelegateRegistration(); - field public static final int REGISTRATION_TECH_CROSS_SIM = 2; // 0x2 - field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1 - field public static final int REGISTRATION_TECH_LTE = 0; // 0x0 - field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff - field public static final int REGISTRATION_TECH_NR = 3; // 0x3 } public class ImsSmsImplBase { @@ -15258,6 +15430,8 @@ package android.view { public interface WindowManager extends android.view.ViewManager { method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public android.graphics.Region getCurrentImeTouchRegion(); + method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull android.window.TaskFpsCallback); + method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback); } public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { @@ -15845,3 +16019,15 @@ package android.webkit { } +package android.window { + + public final class TaskFpsCallback { + ctor public TaskFpsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback.OnFpsCallbackListener); + } + + public static interface TaskFpsCallback.OnFpsCallbackListener { + method public void onFpsReported(float); + } + +} + diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index 9a0642356303..3d756bafa292 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -91,6 +91,10 @@ MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId( +NoSettingsProvider: android.provider.Settings.Secure#FAST_PAIR_SCAN_ENABLED: + New setting keys are not allowed (Field: FAST_PAIR_SCAN_ENABLED); use getters/setters in relevant manager class + + OnNameExpected: android.service.smartspace.SmartspaceService#notifySmartspaceEvent(android.app.smartspace.SmartspaceSessionId, android.app.smartspace.SmartspaceTargetEvent): Methods implemented by developers should follow the on<Something> style, was `notifySmartspaceEvent` diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e97ef6c912f5..ff26ae8d09fc 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -38,6 +38,7 @@ package android { field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; field public static final String REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL = "android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL"; field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS"; + field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT"; field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC"; @@ -1180,9 +1181,23 @@ package android.hardware.hdmi { package android.hardware.input { + public final class InputDeviceIdentifier implements android.os.Parcelable { + ctor public InputDeviceIdentifier(@NonNull String, int, int); + method public int describeContents(); + method @NonNull public String getDescriptor(); + method public int getProductId(); + method public int getVendorId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.input.InputDeviceIdentifier> CREATOR; + } + public final class InputManager { method public int getBlockUntrustedTouchesMode(@NonNull android.content.Context); + method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier); + method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice); + method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setBlockUntrustedTouchesMode(@NonNull android.content.Context, int); + method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setMaximumObscuringOpacityForTouch(@FloatRange(from=0, to=1) float); field public static final long BLOCK_UNTRUSTED_TOUCHES = 158002302L; // 0x96aec7eL } @@ -2740,6 +2755,7 @@ package android.view { public final class InputDevice implements android.os.Parcelable { method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void disable(); method @RequiresPermission("android.permission.DISABLE_INPUT_DEVICE") public void enable(); + method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier(); } public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable { diff --git a/core/java/android/accounts/CantAddAccountActivity.java b/core/java/android/accounts/CantAddAccountActivity.java index f7f232e5c422..107efc3cce95 100644 --- a/core/java/android/accounts/CantAddAccountActivity.java +++ b/core/java/android/accounts/CantAddAccountActivity.java @@ -16,9 +16,13 @@ package android.accounts; +import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE; + import android.app.Activity; +import android.app.admin.DevicePolicyManager; import android.os.Bundle; import android.view.View; +import android.widget.TextView; import com.android.internal.R; @@ -33,6 +37,12 @@ public class CantAddAccountActivity extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_not_authorized); + + TextView view = findViewById(R.id.description); + String text = getSystemService(DevicePolicyManager.class).getString( + CANT_ADD_ACCOUNT_MESSAGE, + () -> getString(R.string.error_message_change_not_allowed)); + view.setText(text); } public void onCancelButtonClicked(View view) { diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java index 2e9f73ca388e..0d82ac942148 100644 --- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java +++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java @@ -15,7 +15,10 @@ */ package android.accounts; +import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE; + import android.app.Activity; +import android.app.admin.DevicePolicyManager; import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; @@ -199,7 +202,14 @@ public class ChooseTypeAndAccountActivity extends Activity if (mPossiblyVisibleAccounts.isEmpty() && mDisallowAddAccounts) { requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.app_not_authorized); + TextView view = findViewById(R.id.description); + String text = getSystemService(DevicePolicyManager.class).getString( + CANT_ADD_ACCOUNT_MESSAGE, + () -> getString(R.string.error_message_change_not_allowed)); + view.setText(text); + mDontShowPicker = true; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 686ca3bad087..3ddbe9e85240 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -105,6 +105,7 @@ import android.media.MediaFrameworkPlatformInitializer; import android.media.MediaServiceManager; import android.net.ConnectivityManager; import android.net.Proxy; +import android.net.TrafficStats; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -6725,6 +6726,13 @@ public final class ActivityThread extends ClientTransactionHandler NetworkSecurityConfigProvider.install(appContext); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + // For backward compatibility, TrafficStats needs static access to the application context. + // But for isolated apps which cannot access network related services, service discovery + // is restricted. Hence, calling this would result in NPE. + if (!Process.isIsolated()) { + TrafficStats.init(appContext); + } + // Continue loading instrumentation. if (ii != null) { initInstrumentation(ii, data, appContext); diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index 29e1b70097f2..76471d30eaf9 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -119,6 +119,31 @@ public final class GameManager { } /** + * Returns the {@link GameModeInfo} associated with the game associated with + * the given {@code packageName}. If the given package is not a game, {@code null} is + * always returned. + * <p> + * An application can use <code>android:isGame="true"</code> or + * <code>android:appCategory="game"</code> to indicate that the application is a game. + * If the manifest doesn't define a category, the category can also be + * provided by the installer via + * {@link android.content.pm.PackageManager#setApplicationCategoryHint(String, int)}. + * <p> + * + * @hide + */ + @SystemApi + @UserHandleAware + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public @Nullable GameModeInfo getGameModeInfo(@NonNull String packageName) { + try { + return mService.getGameModeInfo(packageName, mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the game mode for the given package. * <p> * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}. diff --git a/core/java/android/app/GameModeInfo.aidl b/core/java/android/app/GameModeInfo.aidl new file mode 100644 index 000000000000..3b13201c5d1b --- /dev/null +++ b/core/java/android/app/GameModeInfo.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 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; + +/** + * @hide + */ +parcelable GameModeInfo;
\ No newline at end of file diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java new file mode 100644 index 000000000000..fe0ac352404d --- /dev/null +++ b/core/java/android/app/GameModeInfo.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 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; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * GameModeInfo returned from {@link GameManager#getGameModeInfo(String)}. + * @hide + */ +@SystemApi +public final class GameModeInfo implements Parcelable { + + public static final @NonNull Creator<GameModeInfo> CREATOR = new Creator<GameModeInfo>() { + @Override + public GameModeInfo createFromParcel(Parcel in) { + return new GameModeInfo(in); + } + + @Override + public GameModeInfo[] newArray(int size) { + return new GameModeInfo[size]; + } + }; + + public GameModeInfo(@GameManager.GameMode int activeGameMode, + @NonNull @GameManager.GameMode int[] availableGameModes) { + mActiveGameMode = activeGameMode; + mAvailableGameModes = availableGameModes; + } + + GameModeInfo(Parcel in) { + mActiveGameMode = in.readInt(); + final int availableGameModesCount = in.readInt(); + mAvailableGameModes = new int[availableGameModesCount]; + in.readIntArray(mAvailableGameModes); + } + + /** + * Returns the {@link GameManager.GameMode} the application is currently using. + * Developers can enable game modes by adding + * <code> + * <meta-data android:name="android.game_mode_intervention" + * android:resource="@xml/GAME_MODE_CONFIG_FILE" /> + * </code> + * to the {@link <application> tag}, where the GAME_MODE_CONFIG_FILE is an XML file that + * specifies the game mode enablement and configuration: + * <code> + * <game-mode-config xmlns:android="http://schemas.android.com/apk/res/android" + * android:gameModePerformance="true" + * android:gameModeBattery="false" + * /> + * </code> + */ + public @GameManager.GameMode int getActiveGameMode() { + return mActiveGameMode; + } + + /** + * The collection of {@link GameManager.GameMode GameModes} that can be applied to the game. + */ + @NonNull + public @GameManager.GameMode int[] getAvailableGameModes() { + return mAvailableGameModes; + } + + // Ideally there should be callback that the caller can register to know when the available + // GameMode and/or the active GameMode is changed, however, there's no concrete use case + // at the moment so there's no callback mechanism introduced . + private final @GameManager.GameMode int[] mAvailableGameModes; + private final @GameManager.GameMode int mActiveGameMode; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mActiveGameMode); + dest.writeInt(mAvailableGameModes.length); + dest.writeIntArray(mAvailableGameModes); + } +} diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl index d9aa586c6bbb..57de8c70e742 100644 --- a/core/java/android/app/IGameManagerService.aidl +++ b/core/java/android/app/IGameManagerService.aidl @@ -16,6 +16,7 @@ package android.app; +import android.app.GameModeInfo; import android.app.GameState; /** @@ -27,4 +28,5 @@ interface IGameManagerService { int[] getAvailableGameModes(String packageName); boolean getAngleEnabled(String packageName, int userId); void setGameState(String packageName, in GameState gameState, int userId); -}
\ No newline at end of file + GameModeInfo getGameModeInfo(String packageName, int userId); +} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 63c1fd8f2a4a..f0208c621886 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -24,6 +24,8 @@ import android.annotation.SystemApi; import android.app.ContextImpl.ServiceInitializationState; import android.app.admin.DevicePolicyManager; import android.app.admin.IDevicePolicyManager; +import android.app.ambientcontext.AmbientContextManager; +import android.app.ambientcontext.IAmbientContextEventObserver; import android.app.appsearch.AppSearchManagerFrameworkInitializer; import android.app.blob.BlobStoreManagerFrameworkInitializer; import android.app.contentsuggestions.ContentSuggestionsManager; @@ -1523,6 +1525,18 @@ public final class SystemServiceRegistry { } }); + registerService(Context.AMBIENT_CONTEXT_SERVICE, AmbientContextManager.class, + new CachedServiceFetcher<AmbientContextManager>() { + @Override + public AmbientContextManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder iBinder = ServiceManager.getServiceOrThrow( + Context.AMBIENT_CONTEXT_SERVICE); + IAmbientContextEventObserver manager = + IAmbientContextEventObserver.Stub.asInterface(iBinder); + return new AmbientContextManager(ctx.getOuterContext(), manager); + }}); + sInitializing = true; try { // Note: the following functions need to be @SystemApis, once they become mainline diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index cefd25ac7ac5..fa0af2d47242 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1578,6 +1578,78 @@ public class DevicePolicyManager { public static final int FLAG_SUPPORTED_MODES_DEVICE_OWNER = 1 << 2; /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: no minimum security level. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_PERSONAL + * @see #WIFI_SECURITY_ENTERPRISE_EAP + * @see #WIFI_SECURITY_ENTERPRISE_192 + */ + public static final int WIFI_SECURITY_OPEN = 0; + + /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: personal network such as WEP, WPA2-PSK. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_OPEN + * @see #WIFI_SECURITY_ENTERPRISE_EAP + * @see #WIFI_SECURITY_ENTERPRISE_192 + */ + public static final int WIFI_SECURITY_PERSONAL = 1; + + /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: enterprise EAP network. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_OPEN + * @see #WIFI_SECURITY_PERSONAL + * @see #WIFI_SECURITY_ENTERPRISE_192 + */ + public static final int WIFI_SECURITY_ENTERPRISE_EAP = 2; + + /** + * Constant for {@link #getMinimumRequiredWifiSecurityLevel()} and + * {@link #setMinimumRequiredWifiSecurityLevel(int)}: enterprise 192 bit network. + * + * <p> When returned from {@link #getMinimumRequiredWifiSecurityLevel()}, the constant + * represents the current minimum security level required. + * When passed to {@link #setMinimumRequiredWifiSecurityLevel(int)}, it sets the + * minimum security level a Wi-Fi network must meet. + * + * @see #WIFI_SECURITY_OPEN + * @see #WIFI_SECURITY_PERSONAL + * @see #WIFI_SECURITY_ENTERPRISE_EAP + */ + public static final int WIFI_SECURITY_ENTERPRISE_192 = 3; + + /** + * Possible Wi-Fi minimum security levels + * + * @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"WIFI_SECURITY_"}, value = { + WIFI_SECURITY_OPEN, + WIFI_SECURITY_PERSONAL, + WIFI_SECURITY_ENTERPRISE_EAP, + WIFI_SECURITY_ENTERPRISE_192}) + public @interface WifiSecurity {} + + /** * This MIME type is used for starting the device owner provisioning. * * <p>During device owner provisioning a device admin app is set as the owner of the device. @@ -14560,6 +14632,105 @@ public class DevicePolicyManager { } /** + * Called by device owner or profile owner of an organization-owned managed profile to + * specify the minimum security level required for Wi-Fi networks. + * The device may not connect to networks that do not meet the minimum security level. + * If the current network does not meet the minimum security level set, it will be disconnected. + * + * + * @param level minimum security level + * @throws SecurityException if the caller is not a device owner or a profile owner on + * an organization-owned managed profile. + */ + public void setMinimumRequiredWifiSecurityLevel(@WifiSecurity int level) { + throwIfParentInstance("setMinimumRequiredWifiSecurityLevel"); + if (mService != null) { + try { + mService.setMinimumRequiredWifiSecurityLevel(level); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the current Wi-Fi minimum security level. + * + * @see #setMinimumRequiredWifiSecurityLevel(int) + */ + public @WifiSecurity int getMinimumRequiredWifiSecurityLevel() { + throwIfParentInstance("getMinimumRequiredWifiSecurityLevel"); + if (mService == null) { + return WIFI_SECURITY_OPEN; + } + try { + return mService.getMinimumRequiredWifiSecurityLevel(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Called by device owner or profile owner of an organization-owned managed profile to + * specify the Wi-Fi SSID policy ({@link WifiSsidPolicy}). + * Wi-Fi SSID policy specifies the SSID restriction the network must satisfy + * in order to be eligible for a connection. Providing a null policy results in the + * deactivation of the SSID restriction + * + * @param policy Wi-Fi SSID policy + * @throws SecurityException if the caller is not a device owner or a profile owner on + * an organization-owned managed profile. + */ + public void setWifiSsidPolicy(@Nullable WifiSsidPolicy policy) { + throwIfParentInstance("setWifiSsidPolicy"); + if (mService != null) { + try { + if (policy == null) { + mService.setSsidAllowlist(new ArrayList<>()); + } else { + int policyType = policy.getPolicyType(); + if (policyType == WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST) { + mService.setSsidAllowlist(new ArrayList<>(policy.getSsids())); + } else { + mService.setSsidDenylist(new ArrayList<>(policy.getSsids())); + } + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Returns the current Wi-Fi SSID policy. + * If the policy has not been set, it will return NULL. + * + * @see #setWifiSsidPolicy(WifiSsidPolicy) + * @throws SecurityException if the caller is not a device owner or a profile owner on + * an organization-owned managed profile or a system app. + */ + @Nullable + public WifiSsidPolicy getWifiSsidPolicy() { + throwIfParentInstance("getWifiSsidPolicy"); + if (mService == null) { + return null; + } + try { + List<String> allowlist = mService.getSsidAllowlist(); + if (!allowlist.isEmpty()) { + return WifiSsidPolicy.createAllowlistPolicy(new ArraySet<>(allowlist)); + } + List<String> denylist = mService.getSsidDenylist(); + if (!denylist.isEmpty()) { + return WifiSsidPolicy.createDenylistPolicy(new ArraySet<>(denylist)); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return null; + } + + /** * For each {@link DevicePolicyDrawableResource} item in {@code drawables}, if * {@link DevicePolicyDrawableResource#getDrawableSource()} is not set or is set to * {@link DevicePolicyResources.Drawable.Source#UNDEFINED}, it updates the drawable resource for @@ -14896,7 +15067,7 @@ public class DevicePolicyManager { Objects.requireNonNull(stringId, "stringId can't be null"); Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); - if (stringId.equals(DevicePolicyResources.Strings.INVALID_ID)) { + if (stringId.equals(DevicePolicyResources.Strings.UNDEFINED)) { return ParcelableResource.loadDefaultString(defaultStringLoader); } if (mService != null) { @@ -14944,7 +15115,7 @@ public class DevicePolicyManager { Objects.requireNonNull(stringId, "stringId can't be null"); Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null"); - if (stringId.equals(DevicePolicyResources.Strings.INVALID_ID)) { + if (stringId.equals(DevicePolicyResources.Strings.UNDEFINED)) { return ParcelableResource.loadDefaultString(defaultStringLoader); } if (mService != null) { diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 21e20cd0d90e..46e2cceefaf7 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -16,6 +16,64 @@ package android.app.admin; +import static android.app.admin.DevicePolicyResources.Strings.Core.CANT_ADD_ACCOUNT_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTALLED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_SOON_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_PERSONAL_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SAVE_TO_WORK_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_PERSONAL_FILES_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_PERSONAL_FILES_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_WORK_FILES_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CANT_SELECT_WORK_FILES_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.CROSS_PROFILE_NOT_ALLOWED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.PREVIEW_WORK_FILE_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_PROFILE_OFF_ENABLE_BUTTON; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_PROFILE_OFF_ERROR_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.DocumentsUi.WORK_TAB; import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB; import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_PERSONAL_TAB_ACCESSIBILITY; import static android.app.admin.DevicePolicyResources.Strings.Launcher.ALL_APPS_WORK_TAB; @@ -28,8 +86,13 @@ import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROF import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_EDU_ACCEPT; import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_ENABLE_BUTTON; import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSED_DESCRIPTION; -import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSED_TITLE; import static android.app.admin.DevicePolicyResources.Strings.Launcher.WORK_PROFILE_PAUSE_BUTTON; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_BY_ADMIN_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_FROM_PERSONAL_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.BLOCKED_FROM_WORK_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_PERSONAL_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.SWITCH_TO_WORK_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.MediaProvider.WORK_PROFILE_PAUSED_MESSAGE; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT; @@ -65,11 +128,11 @@ import static android.app.admin.DevicePolicyResources.Strings.SystemUi.QS_MSG_WO import static android.app.admin.DevicePolicyResources.Strings.SystemUi.STATUS_BAR_WORK_ICON_ACCESSIBILITY; import static android.app.admin.DevicePolicyResources.Strings.SystemUi.WORK_LOCK_ACCESSIBILITY; - import android.annotation.IntDef; import android.annotation.StringDef; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.os.UserHandle; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -138,7 +201,7 @@ public final class DevicePolicyResources { @Retention(RetentionPolicy.SOURCE) @StringDef({ // Launcher Strings - WORK_PROFILE_EDU, WORK_PROFILE_EDU_ACCEPT, WORK_PROFILE_PAUSED_TITLE, + WORK_PROFILE_EDU, WORK_PROFILE_EDU_ACCEPT, Strings.Launcher.WORK_PROFILE_PAUSED_TITLE, WORK_PROFILE_PAUSED_DESCRIPTION, WORK_PROFILE_PAUSE_BUTTON, WORK_PROFILE_ENABLE_BUTTON, ALL_APPS_WORK_TAB, ALL_APPS_PERSONAL_TAB, ALL_APPS_WORK_TAB_ACCESSIBILITY, ALL_APPS_PERSONAL_TAB_ACCESSIBILITY, WORK_FOLDER_NAME, WIDGETS_WORK_TAB, @@ -160,7 +223,41 @@ public final class DevicePolicyResources { BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT, BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT, BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS, STATUS_BAR_WORK_ICON_ACCESSIBILITY, ONGOING_PRIVACY_DIALOG_WORK, KEYGUARD_MANAGEMENT_DISCLOSURE, - KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE, WORK_LOCK_ACCESSIBILITY + KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE, WORK_LOCK_ACCESSIBILITY, + + // Core Strings + WORK_PROFILE_DELETED_TITLE, WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE, + WORK_PROFILE_DELETED_GENERIC_MESSAGE, WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE, + PERSONAL_APP_SUSPENSION_TITLE, PERSONAL_APP_SUSPENSION_MESSAGE, + PERSONAL_APP_SUSPENSION_SOON_MESSAGE, PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE, + PRINTING_DISABLED_NAMED_ADMIN, LOCATION_CHANGED_TITLE, LOCATION_CHANGED_MESSAGE, + NETWORK_LOGGING_TITLE, NETWORK_LOGGING_MESSAGE, + NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, NOTIFICATION_CHANNEL_DEVICE_ADMIN, + SWITCH_TO_WORK_LABEL, SWITCH_TO_PERSONAL_LABEL, FORWARD_INTENT_TO_WORK, + FORWARD_INTENT_TO_PERSONAL, RESOLVER_WORK_PROFILE_NOT_SUPPORTED, RESOLVER_PERSONAL_TAB, + RESOLVER_WORK_TAB, RESOLVER_PERSONAL_TAB_ACCESSIBILITY, RESOLVER_WORK_TAB_ACCESSIBILITY, + RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, RESOLVER_CANT_SHARE_WITH_PERSONAL, + RESOLVER_CANT_SHARE_WITH_WORK, RESOLVER_CANT_ACCESS_PERSONAL, RESOLVER_CANT_ACCESS_WORK, + RESOLVER_WORK_PAUSED_TITLE, RESOLVER_NO_WORK_APPS, RESOLVER_NO_PERSONAL_APPS, + CANT_ADD_ACCOUNT_MESSAGE, PACKAGE_INSTALLED_BY_DO, PACKAGE_UPDATED_BY_DO, + PACKAGE_DELETED_BY_DO, UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, + UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE, PROFILE_ENCRYPTED_TITLE, PROFILE_ENCRYPTED_DETAIL, + PROFILE_ENCRYPTED_MESSAGE, WORK_PROFILE_BADGED_LABEL, + + // DocsUi Strings + WORK_PROFILE_OFF_ERROR_TITLE, WORK_PROFILE_OFF_ENABLE_BUTTON, + CANT_SELECT_WORK_FILES_TITLE, CANT_SELECT_WORK_FILES_MESSAGE, + CANT_SELECT_PERSONAL_FILES_TITLE, CANT_SELECT_PERSONAL_FILES_MESSAGE, + CANT_SAVE_TO_WORK_TITLE, CANT_SAVE_TO_WORK_MESSAGE, CANT_SAVE_TO_PERSONAL_TITLE, + CANT_SAVE_TO_PERSONAL_MESSAGE, CROSS_PROFILE_NOT_ALLOWED_TITLE, + CROSS_PROFILE_NOT_ALLOWED_MESSAGE, PREVIEW_WORK_FILE_ACCESSIBILITY, PERSONAL_TAB, + WORK_TAB, WORK_ACCESSIBILITY, + + // MediaProvider Strings + SWITCH_TO_WORK_MESSAGE, SWITCH_TO_PERSONAL_MESSAGE, BLOCKED_BY_ADMIN_TITLE, + BLOCKED_FROM_PERSONAL_MESSAGE, BLOCKED_FROM_PERSONAL_MESSAGE, + BLOCKED_FROM_WORK_MESSAGE, Strings.MediaProvider.WORK_PROFILE_PAUSED_TITLE, + WORK_PROFILE_PAUSED_MESSAGE }) public @interface UpdatableStringId { } @@ -340,7 +437,7 @@ public final class DevicePolicyResources { /** * An ID for any string that can't be updated. */ - public static final String INVALID_ID = "INVALID_ID"; + public static final String UNDEFINED = "UNDEFINED"; /** * @hide @@ -351,6 +448,9 @@ public final class DevicePolicyResources { Set<String> strings = new HashSet<>(); strings.addAll(Launcher.buildStringsSet()); strings.addAll(SystemUi.buildStringsSet()); + strings.addAll(Core.buildStringsSet()); + strings.addAll(DocumentsUi.buildStringsSet()); + strings.addAll(MediaProvider.buildStringsSet()); return strings; } @@ -364,78 +464,84 @@ public final class DevicePolicyResources { private Launcher(){} + private static final String PREFIX = "Launcher."; + /** * User on-boarding title for work profile apps. */ - public static final String WORK_PROFILE_EDU = "WORK_PROFILE_EDU"; + public static final String WORK_PROFILE_EDU = PREFIX + "WORK_PROFILE_EDU"; /** * Action label to finish work profile edu. */ - public static final String WORK_PROFILE_EDU_ACCEPT = "WORK_PROFILE_EDU_ACCEPT"; + public static final String WORK_PROFILE_EDU_ACCEPT = PREFIX + "WORK_PROFILE_EDU_ACCEPT"; /** * Title shown when user opens work apps tab while work profile is paused. */ - public static final String WORK_PROFILE_PAUSED_TITLE = "WORK_PROFILE_PAUSED_TITLE"; + public static final String WORK_PROFILE_PAUSED_TITLE = + PREFIX + "WORK_PROFILE_PAUSED_TITLE"; /** * Description shown when user opens work apps tab while work profile is paused. */ public static final String WORK_PROFILE_PAUSED_DESCRIPTION = - "WORK_PROFILE_PAUSED_DESCRIPTION"; + PREFIX + "WORK_PROFILE_PAUSED_DESCRIPTION"; /** * Shown on the button to pause work profile. */ - public static final String WORK_PROFILE_PAUSE_BUTTON = "WORK_PROFILE_PAUSE_BUTTON"; + public static final String WORK_PROFILE_PAUSE_BUTTON = + PREFIX + "WORK_PROFILE_PAUSE_BUTTON"; /** * Shown on the button to enable work profile. */ - public static final String WORK_PROFILE_ENABLE_BUTTON = "WORK_PROFILE_ENABLE_BUTTON"; + public static final String WORK_PROFILE_ENABLE_BUTTON = + PREFIX + "WORK_PROFILE_ENABLE_BUTTON"; /** * Label on launcher tab to indicate work apps. */ - public static final String ALL_APPS_WORK_TAB = "ALL_APPS_WORK_TAB"; + public static final String ALL_APPS_WORK_TAB = PREFIX + "ALL_APPS_WORK_TAB"; /** * Label on launcher tab to indicate personal apps. */ - public static final String ALL_APPS_PERSONAL_TAB = "ALL_APPS_PERSONAL_TAB"; + public static final String ALL_APPS_PERSONAL_TAB = PREFIX + "ALL_APPS_PERSONAL_TAB"; /** * Accessibility description for launcher tab to indicate work apps. */ public static final String ALL_APPS_WORK_TAB_ACCESSIBILITY = - "ALL_APPS_WORK_TAB_ACCESSIBILITY"; + PREFIX + "ALL_APPS_WORK_TAB_ACCESSIBILITY"; /** * Accessibility description for launcher tab to indicate personal apps. */ public static final String ALL_APPS_PERSONAL_TAB_ACCESSIBILITY = - "ALL_APPS_PERSONAL_TAB_ACCESSIBILITY"; + PREFIX + "ALL_APPS_PERSONAL_TAB_ACCESSIBILITY"; /** * Work folder name. */ - public static final String WORK_FOLDER_NAME = "WORK_FOLDER_NAME"; + public static final String WORK_FOLDER_NAME = PREFIX + "WORK_FOLDER_NAME"; /** * Label on widget tab to indicate work app widgets. */ - public static final String WIDGETS_WORK_TAB = "WIDGETS_WORK_TAB"; + public static final String WIDGETS_WORK_TAB = PREFIX + "WIDGETS_WORK_TAB"; /** * Label on widget tab to indicate personal app widgets. */ - public static final String WIDGETS_PERSONAL_TAB = "WIDGETS_PERSONAL_TAB"; + public static final String WIDGETS_PERSONAL_TAB = PREFIX + "WIDGETS_PERSONAL_TAB"; /** * Message shown when a feature is disabled by the admin (e.g. changing wallpaper). */ - public static final String DISABLED_BY_ADMIN_MESSAGE = "DISABLED_BY_ADMIN_MESSAGE"; + public static final String DISABLED_BY_ADMIN_MESSAGE = + PREFIX + "DISABLED_BY_ADMIN_MESSAGE"; /** * @hide @@ -470,231 +576,236 @@ public final class DevicePolicyResources { private SystemUi() { } + private static final String PREFIX = "SystemUi."; /** * Label in quick settings for toggling work profile on/off. */ - public static final String QS_WORK_PROFILE_LABEL = "QS_WORK_PROFILE_LABEL"; + public static final String QS_WORK_PROFILE_LABEL = PREFIX + "QS_WORK_PROFILE_LABEL"; /** * Disclosure at the bottom of Quick Settings to indicate device management. */ - public static final String QS_MSG_MANAGEMENT = "QS_MSG_MANAGEMENT"; + public static final String QS_MSG_MANAGEMENT = PREFIX + "QS_MSG_MANAGEMENT"; /** * Similar to {@link #QS_MSG_MANAGEMENT} but accepts the organization name as a * param. */ - public static final String QS_MSG_NAMED_MANAGEMENT = "QS_MSG_NAMED_MANAGEMENT"; + public static final String QS_MSG_NAMED_MANAGEMENT = PREFIX + "QS_MSG_NAMED_MANAGEMENT"; /** * Disclosure at the bottom of Quick Settings to indicate device management monitoring. */ public static final String QS_MSG_MANAGEMENT_MONITORING = - "QS_MSG_MANAGEMENT_MONITORING"; + PREFIX + "QS_MSG_MANAGEMENT_MONITORING"; /** * Similar to {@link #QS_MSG_MANAGEMENT_MONITORING} but accepts the * organization name as a param. */ public static final String QS_MSG_NAMED_MANAGEMENT_MONITORING = - "QS_MSG_NAMED_MANAGEMENT_MONITORING"; + PREFIX + "QS_MSG_NAMED_MANAGEMENT_MONITORING"; /** * Disclosure at the bottom of Quick Settings to indicate device management and the * device is connected to a VPN, accepts VPN name as a param. */ public static final String QS_MSG_MANAGEMENT_NAMED_VPN = - "QS_MSG_MANAGEMENT_NAMED_VPN"; + PREFIX + "QS_MSG_MANAGEMENT_NAMED_VPN"; /** * Similar to {@link #QS_MSG_MANAGEMENT_NAMED_VPN} but also accepts the * organization name as a param. */ public static final String QS_MSG_NAMED_MANAGEMENT_NAMED_VPN = - "QS_MSG_NAMED_MANAGEMENT_NAMED_VPN"; + PREFIX + "QS_MSG_NAMED_MANAGEMENT_NAMED_VPN"; /** * Disclosure at the bottom of Quick Settings to indicate device management and the * device is connected to multiple VPNs. */ public static final String QS_MSG_MANAGEMENT_MULTIPLE_VPNS = - "QS_MSG_MANAGEMENT_MULTIPLE_VPNS"; + PREFIX + "QS_MSG_MANAGEMENT_MULTIPLE_VPNS"; /** * Similar to {@link #QS_MSG_MANAGEMENT_MULTIPLE_VPNS} but also accepts the * organization name as a param. */ public static final String QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS = - "QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS"; + PREFIX + "QS_MSG_NAMED_MANAGEMENT_MULTIPLE_VPNS"; /** * Disclosure at the bottom of Quick Settings to indicate work profile monitoring. */ public static final String QS_MSG_WORK_PROFILE_MONITORING = - "QS_MSG_WORK_PROFILE_MONITORING"; + PREFIX + "QS_MSG_WORK_PROFILE_MONITORING"; /** * Similar to {@link #QS_MSG_WORK_PROFILE_MONITORING} but accepts the * organization name as a param. */ public static final String QS_MSG_NAMED_WORK_PROFILE_MONITORING = - "QS_MSG_NAMED_WORK_PROFILE_MONITORING"; + PREFIX + "QS_MSG_NAMED_WORK_PROFILE_MONITORING"; /** * Disclosure at the bottom of Quick Settings to indicate network activity is visible to * admin. */ - public static final String QS_MSG_WORK_PROFILE_NETWORK = "QS_MSG_WORK_PROFILE_NETWORK"; + public static final String QS_MSG_WORK_PROFILE_NETWORK = + PREFIX + "QS_MSG_WORK_PROFILE_NETWORK"; /** * Disclosure at the bottom of Quick Settings to indicate work profile is connected to a * VPN, accepts VPN name as a param. */ public static final String QS_MSG_WORK_PROFILE_NAMED_VPN = - "QS_MSG_WORK_PROFILE_NAMED_VPN"; + PREFIX + "QS_MSG_WORK_PROFILE_NAMED_VPN"; /** * Disclosure at the bottom of Quick Settings to indicate personal profile is connected * to a VPN, accepts VPN name as a param. */ public static final String QS_MSG_PERSONAL_PROFILE_NAMED_VPN = - "QS_MSG_PERSONAL_PROFILE_NAMED_VPN"; + PREFIX + "QS_MSG_PERSONAL_PROFILE_NAMED_VPN"; /** * Title for dialog to indicate device management. */ - public static final String QS_DIALOG_MANAGEMENT_TITLE = "QS_DIALOG_MANAGEMENT_TITLE"; + public static final String QS_DIALOG_MANAGEMENT_TITLE = + PREFIX + "QS_DIALOG_MANAGEMENT_TITLE"; /** * Label for button in the device management dialog to open a page with more information * on the admin's abilities. */ - public static final String QS_DIALOG_VIEW_POLICIES = "QS_DIALOG_VIEW_POLICIES"; + public static final String QS_DIALOG_VIEW_POLICIES = + PREFIX + "QS_DIALOG_VIEW_POLICIES"; /** * Description for device management dialog to indicate admin abilities. */ - public static final String QS_DIALOG_MANAGEMENT = "QS_DIALOG_MANAGEMENT"; + public static final String QS_DIALOG_MANAGEMENT = PREFIX + "QS_DIALOG_MANAGEMENT"; /** * Similar to {@link #QS_DIALOG_MANAGEMENT} but accepts the organization name as a * param. */ - public static final String QS_DIALOG_NAMED_MANAGEMENT = "QS_DIALOG_NAMED_MANAGEMENT"; + public static final String QS_DIALOG_NAMED_MANAGEMENT = + PREFIX + "QS_DIALOG_NAMED_MANAGEMENT"; /** * Description for the managed device certificate authorities in the device management * dialog. */ public static final String QS_DIALOG_MANAGEMENT_CA_CERT = - "QS_DIALOG_MANAGEMENT_CA_CERT"; + PREFIX + "QS_DIALOG_MANAGEMENT_CA_CERT"; /** * Description for the work profile certificate authorities in the device management * dialog. */ public static final String QS_DIALOG_WORK_PROFILE_CA_CERT = - "QS_DIALOG_WORK_PROFILE_CA_CERT"; + PREFIX + "QS_DIALOG_WORK_PROFILE_CA_CERT"; /** * Description for the managed device network logging in the device management dialog. */ public static final String QS_DIALOG_MANAGEMENT_NETWORK = - "QS_DIALOG_MANAGEMENT_NETWORK"; + PREFIX + "QS_DIALOG_MANAGEMENT_NETWORK"; /** * Description for the work profile network logging in the device management dialog. */ public static final String QS_DIALOG_WORK_PROFILE_NETWORK = - "QS_DIALOG_WORK_PROFILE_NETWORK"; + PREFIX + "QS_DIALOG_WORK_PROFILE_NETWORK"; /** * Description for an active VPN in the device management dialog, accepts VPN name as a * param. */ public static final String QS_DIALOG_MANAGEMENT_NAMED_VPN = - "QS_DIALOG_MANAGEMENT_NAMED_VPN"; + PREFIX + "QS_DIALOG_MANAGEMENT_NAMED_VPN"; /** * Description for two active VPN in the device management dialog, accepts two VPN names * as params. */ public static final String QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN = - "QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN"; + PREFIX + "QS_DIALOG_MANAGEMENT_TWO_NAMED_VPN"; /** * Description for an active work profile VPN in the device management dialog, accepts * VPN name as a param. */ public static final String QS_DIALOG_WORK_PROFILE_NAMED_VPN = - "QS_DIALOG_WORK_PROFILE_NAMED_VPN"; + PREFIX + "QS_DIALOG_WORK_PROFILE_NAMED_VPN"; /** * Description for an active personal profile VPN in the device management dialog, * accepts VPN name as a param. */ public static final String QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN = - "QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN"; + PREFIX + "QS_DIALOG_PERSONAL_PROFILE_NAMED_VPN"; /** * Content of a dialog shown when the user only has one attempt left to provide the * correct pin before the work profile is removed. */ public static final String BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT = - "BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT"; + PREFIX + "BIOMETRIC_DIALOG_WORK_PIN_LAST_ATTEMPT"; /** * Content of a dialog shown when the user only has one attempt left to provide the * correct pattern before the work profile is removed. */ public static final String BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT = - "BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT"; + PREFIX + "BIOMETRIC_DIALOG_WORK_PATTERN_LAST_ATTEMPT"; /** * Content of a dialog shown when the user only has one attempt left to provide the * correct password before the work profile is removed. */ public static final String BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT = - "BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT"; + PREFIX + "BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT"; /** * Content of a dialog shown when the user has failed to provide the work lock too many * times and the work profile is removed. */ public static final String BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS = - "BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS"; + PREFIX + "BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS"; /** * Accessibility label for managed profile icon in the status bar */ public static final String STATUS_BAR_WORK_ICON_ACCESSIBILITY = - "STATUS_BAR_WORK_ICON_ACCESSIBILITY"; + PREFIX + "STATUS_BAR_WORK_ICON_ACCESSIBILITY"; /** * Text appended to privacy dialog, indicating that the application is in the work * profile. */ public static final String ONGOING_PRIVACY_DIALOG_WORK = - "ONGOING_PRIVACY_DIALOG_WORK"; + PREFIX + "ONGOING_PRIVACY_DIALOG_WORK"; /** * Text on keyguard screen indicating device management. */ public static final String KEYGUARD_MANAGEMENT_DISCLOSURE = - "KEYGUARD_MANAGEMENT_DISCLOSURE"; + PREFIX + "KEYGUARD_MANAGEMENT_DISCLOSURE"; /** * Similar to {@link #KEYGUARD_MANAGEMENT_DISCLOSURE} but also accepts organization name * as a param. */ public static final String KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE = - "KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE"; + PREFIX + "KEYGUARD_NAMED_MANAGEMENT_DISCLOSURE"; /** * Content description for the work profile lock screen. */ - public static final String WORK_LOCK_ACCESSIBILITY = "WORK_LOCK_ACCESSIBILITY"; + public static final String WORK_LOCK_ACCESSIBILITY = PREFIX + "WORK_LOCK_ACCESSIBILITY"; /** * @hide @@ -739,5 +850,568 @@ public final class DevicePolicyResources { return strings; } } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the android core package. + * + * @hide + */ + public static final class Core { + + private Core() { + } + + private static final String PREFIX = "Core."; + /** + * Notification title when the system deletes the work profile. + */ + public static final String WORK_PROFILE_DELETED_TITLE = + PREFIX + "WORK_PROFILE_DELETED_TITLE"; + + /** + * Content text for the "Work profile deleted" notification to indicates that a work + * profile has been deleted because the maximum failed password attempts as been + * reached. + */ + public static final String WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE = + PREFIX + "WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE"; + + /** + * Content text for the "Work profile deleted" notification to indicate that a work + * profile has been deleted. + */ + public static final String WORK_PROFILE_DELETED_GENERIC_MESSAGE = + PREFIX + "WORK_PROFILE_DELETED_GENERIC_MESSAGE"; + + /** + * Content text for the "Work profile deleted" notification to indicates that a work + * profile has been deleted because the admin of an organization-owned device has + * relinquishes it. + */ + public static final String WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE = + PREFIX + "WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE"; + + /** + * Notification title for when personal apps are either blocked or will be blocked + * soon due to a work policy from their admin. + */ + public static final String PERSONAL_APP_SUSPENSION_TITLE = + PREFIX + "PERSONAL_APP_SUSPENSION_TITLE"; + + /** + * Content text for the personal app suspension notification to indicate that personal + * apps are blocked due to a work policy from the admin. + */ + public static final String PERSONAL_APP_SUSPENSION_MESSAGE = + PREFIX + "PERSONAL_APP_SUSPENSION_MESSAGE"; + + /** + * Content text for the personal app suspension notification to indicate that personal + * apps will be blocked at a particular time due to a work policy from their admin. + * It also explains for how many days the profile is allowed to be off. + * <ul>Takes in the following as params: + * <li> The date that the personal apps will get suspended at</li> + * <li> The time that the personal apps will get suspended at</li> + * <li> The max allowed days for the work profile stay switched off</li> + * </ul> + */ + public static final String PERSONAL_APP_SUSPENSION_SOON_MESSAGE = + PREFIX + "PERSONAL_APP_SUSPENSION_SOON_MESSAGE"; + + /** + * Title for the button that turns work profile in the personal app suspension + * notification. + */ + public static final String PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE = + PREFIX + "PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE"; + + /** + * A toast message displayed when printing is attempted but disabled by policy, accepts + * admin name as a param. + */ + public static final String PRINTING_DISABLED_NAMED_ADMIN = + PREFIX + "PRINTING_DISABLED_NAMED_ADMIN"; + + /** + * Notification title to indicate that the device owner has changed the location + * settings. + */ + public static final String LOCATION_CHANGED_TITLE = PREFIX + "LOCATION_CHANGED_TITLE"; + + /** + * Content text for the location changed notification to indicate that the device owner + * has changed the location settings. + */ + public static final String LOCATION_CHANGED_MESSAGE = + PREFIX + "LOCATION_CHANGED_MESSAGE"; + + /** + * Notification title to indicate that the device is managed and network logging was + * activated by a device owner. + */ + public static final String NETWORK_LOGGING_TITLE = PREFIX + "NETWORK_LOGGING_TITLE"; + + /** + * Content text for the network logging notification to indicate that the device is + * managed and network logging was activated by a device owner. + */ + public static final String NETWORK_LOGGING_MESSAGE = PREFIX + "NETWORK_LOGGING_MESSAGE"; + + /** + * Content description of the work profile icon in the notifications. + */ + public static final String NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION = + PREFIX + "NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION"; + + /** + * Notification channel name for high-priority alerts from the user's IT admin for key + * updates about the device. + */ + public static final String NOTIFICATION_CHANNEL_DEVICE_ADMIN = + PREFIX + "NOTIFICATION_CHANNEL_DEVICE_ADMIN"; + + /** + * Label returned from + * {@link android.content.pm.CrossProfileApps#getProfileSwitchingLabel(UserHandle)} + * that calling app can show to user for the semantic of switching to work profile. + */ + public static final String SWITCH_TO_WORK_LABEL = PREFIX + "SWITCH_TO_WORK_LABEL"; + + /** + * Label returned from + * {@link android.content.pm.CrossProfileApps#getProfileSwitchingLabel(UserHandle)} + * that calling app can show to user for the semantic of switching to personal profile. + */ + public static final String SWITCH_TO_PERSONAL_LABEL = + PREFIX + "SWITCH_TO_PERSONAL_LABEL"; + + /** + * Message to show when an intent automatically switches users into the work profile. + */ + public static final String FORWARD_INTENT_TO_WORK = PREFIX + "FORWARD_INTENT_TO_WORK"; + + /** + * Message to show when an intent automatically switches users into the personal + * profile. + */ + public static final String FORWARD_INTENT_TO_PERSONAL = + PREFIX + "FORWARD_INTENT_TO_PERSONAL"; + + /** + * Text for the toast that is shown when the user clicks on a launcher that doesn't + * support the work profile, takes in the launcher name as a param. + */ + public static final String RESOLVER_WORK_PROFILE_NOT_SUPPORTED = + PREFIX + "RESOLVER_WORK_PROFILE_NOT_SUPPORTED"; + + /** + * Label for the personal tab in the {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_PERSONAL_TAB = PREFIX + "RESOLVER_PERSONAL_TAB"; + + /** + * Label for the work tab in the {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_WORK_TAB = PREFIX + "RESOLVER_WORK_TAB"; + + /** + * Accessibility Label for the personal tab in the + * {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_PERSONAL_TAB_ACCESSIBILITY = + PREFIX + "RESOLVER_PERSONAL_TAB_ACCESSIBILITY"; + + /** + * Accessibility Label for the work tab in the + * {@link com.android.internal.app.ResolverActivity). + */ + public static final String RESOLVER_WORK_TAB_ACCESSIBILITY = + PREFIX + "RESOLVER_WORK_TAB_ACCESSIBILITY"; + + /** + * Title for resolver screen to let the user know that their IT admin doesn't allow + * them to share this content across profiles. + */ + public static final String RESOLVER_CROSS_PROFILE_BLOCKED_TITLE = + PREFIX + "RESOLVER_CROSS_PROFILE_BLOCKED_TITLE"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to share this content with apps in their personal profile. + */ + public static final String RESOLVER_CANT_SHARE_WITH_PERSONAL = + PREFIX + "RESOLVER_CANT_SHARE_WITH_PERSONAL"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to share this content with apps in their work profile. + */ + public static final String RESOLVER_CANT_SHARE_WITH_WORK = + PREFIX + "RESOLVER_CANT_SHARE_WITH_WORK"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to open this specific content with an app in their personal profile. + */ + public static final String RESOLVER_CANT_ACCESS_PERSONAL = + PREFIX + "RESOLVER_CANT_ACCESS_PERSONAL"; + + /** + * Description for resolver screen to let the user know that their IT admin doesn't + * allow them to open this specific content with an app in their work profile. + */ + public static final String RESOLVER_CANT_ACCESS_WORK = + PREFIX + "RESOLVER_CANT_ACCESS_WORK"; + + /** + * Title for resolver screen to let the user know that they need to turn on work apps + * in order to share or open content + */ + public static final String RESOLVER_WORK_PAUSED_TITLE = + PREFIX + "RESOLVER_WORK_PAUSED_TITLE"; + + /** + * Text on resolver screen to let the user know that their current work apps don't + * support the specific content. + */ + public static final String RESOLVER_NO_WORK_APPS = PREFIX + "RESOLVER_NO_WORK_APPS"; + + /** + * Text on resolver screen to let the user know that their current personal apps don't + * support the specific content. + */ + public static final String RESOLVER_NO_PERSONAL_APPS = + PREFIX + "RESOLVER_NO_PERSONAL_APPS"; + + /** + * Message informing user that the adding the account is disallowed by an administrator. + */ + public static final String CANT_ADD_ACCOUNT_MESSAGE = + PREFIX + "CANT_ADD_ACCOUNT_MESSAGE"; + + /** + * Notification shown when device owner silently installs a package. + */ + public static final String PACKAGE_INSTALLED_BY_DO = PREFIX + "PACKAGE_INSTALLED_BY_DO"; + + /** + * Notification shown when device owner silently updates a package. + */ + public static final String PACKAGE_UPDATED_BY_DO = PREFIX + "PACKAGE_UPDATED_BY_DO"; + + /** + * Notification shown when device owner silently deleted a package. + */ + public static final String PACKAGE_DELETED_BY_DO = PREFIX + "PACKAGE_DELETED_BY_DO"; + + /** + * Title for dialog shown when user tries to open a work app when the work profile is + * turned off, confirming that the user wants to turn on access to their + * work apps. + */ + public static final String UNLAUNCHABLE_APP_WORK_PAUSED_TITLE = + PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_TITLE"; + + /** + * Text for dialog shown when user tries to open a work app when the work profile is + * turned off, confirming that the user wants to turn on access to their + * work apps. + */ + public static final String UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE = + PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE"; + + /** + * Notification title shown when work profile is credential encrypted and requires + * the user to unlock before it's usable. + */ + public static final String PROFILE_ENCRYPTED_TITLE = PREFIX + "PROFILE_ENCRYPTED_TITLE"; + + /** + * Notification detail shown when work profile is credential encrypted and requires + * the user to unlock before it's usable. + */ + public static final String PROFILE_ENCRYPTED_DETAIL = + PREFIX + "PROFILE_ENCRYPTED_DETAIL"; + + /** + * Notification message shown when work profile is credential encrypted and requires + * the user to unlock before it's usable. + */ + public static final String PROFILE_ENCRYPTED_MESSAGE = + PREFIX + "PROFILE_ENCRYPTED_MESSAGE"; + + /** + * Used to badge a string with "Work" for work profile content, e.g. "Work Email". + * Accepts the string to badge as an argument. + * <p>See {@link android.content.pm.PackageManager#getUserBadgedLabel}</p> + */ + public static final String WORK_PROFILE_BADGED_LABEL = + PREFIX + "WORK_PROFILE_BADGED_LABEL"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(WORK_PROFILE_DELETED_TITLE); + strings.add(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE); + strings.add(WORK_PROFILE_DELETED_GENERIC_MESSAGE); + strings.add(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE); + strings.add(PERSONAL_APP_SUSPENSION_TITLE); + strings.add(PERSONAL_APP_SUSPENSION_MESSAGE); + strings.add(PERSONAL_APP_SUSPENSION_SOON_MESSAGE); + strings.add(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE); + strings.add(PRINTING_DISABLED_NAMED_ADMIN); + strings.add(LOCATION_CHANGED_TITLE); + strings.add(LOCATION_CHANGED_MESSAGE); + strings.add(NETWORK_LOGGING_TITLE); + strings.add(NETWORK_LOGGING_MESSAGE); + strings.add(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION); + strings.add(NOTIFICATION_CHANNEL_DEVICE_ADMIN); + strings.add(SWITCH_TO_WORK_LABEL); + strings.add(SWITCH_TO_PERSONAL_LABEL); + strings.add(FORWARD_INTENT_TO_WORK); + strings.add(FORWARD_INTENT_TO_PERSONAL); + strings.add(RESOLVER_WORK_PROFILE_NOT_SUPPORTED); + strings.add(RESOLVER_PERSONAL_TAB); + strings.add(RESOLVER_WORK_TAB); + strings.add(RESOLVER_PERSONAL_TAB_ACCESSIBILITY); + strings.add(RESOLVER_WORK_TAB_ACCESSIBILITY); + strings.add(RESOLVER_CROSS_PROFILE_BLOCKED_TITLE); + strings.add(RESOLVER_CANT_SHARE_WITH_PERSONAL); + strings.add(RESOLVER_CANT_SHARE_WITH_WORK); + strings.add(RESOLVER_CANT_ACCESS_PERSONAL); + strings.add(RESOLVER_CANT_ACCESS_WORK); + strings.add(RESOLVER_WORK_PAUSED_TITLE); + strings.add(RESOLVER_NO_WORK_APPS); + strings.add(RESOLVER_NO_PERSONAL_APPS); + strings.add(CANT_ADD_ACCOUNT_MESSAGE); + strings.add(PACKAGE_INSTALLED_BY_DO); + strings.add(PACKAGE_UPDATED_BY_DO); + strings.add(PACKAGE_DELETED_BY_DO); + strings.add(UNLAUNCHABLE_APP_WORK_PAUSED_TITLE); + strings.add(UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE); + strings.add(PROFILE_ENCRYPTED_TITLE); + strings.add(PROFILE_ENCRYPTED_DETAIL); + strings.add(PROFILE_ENCRYPTED_MESSAGE); + strings.add(WORK_PROFILE_BADGED_LABEL); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the DocumentsUi package. + */ + public static final class DocumentsUi { + + private DocumentsUi() { + } + + private static final String PREFIX = "DocumentsUi."; + + /** + * Title for error message shown when work profile is turned off. + */ + public static final String WORK_PROFILE_OFF_ERROR_TITLE = + PREFIX + "WORK_PROFILE_OFF_ERROR_TITLE"; + + /** + * Button text shown when work profile is turned off. + */ + public static final String WORK_PROFILE_OFF_ENABLE_BUTTON = + PREFIX + "WORK_PROFILE_OFF_ENABLE_BUTTON"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to + * select work files from a personal app. + */ + public static final String CANT_SELECT_WORK_FILES_TITLE = + PREFIX + "CANT_SELECT_WORK_FILES_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to select work files + * from a personal app. + */ + public static final String CANT_SELECT_WORK_FILES_MESSAGE = + PREFIX + "CANT_SELECT_WORK_FILES_MESSAGE"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to + * select personal files from a work app. + */ + public static final String CANT_SELECT_PERSONAL_FILES_TITLE = + PREFIX + "CANT_SELECT_PERSONAL_FILES_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to select personal files + * from a work app. + */ + public static final String CANT_SELECT_PERSONAL_FILES_MESSAGE = + PREFIX + "CANT_SELECT_PERSONAL_FILES_MESSAGE"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to save + * files from their personal profile to their work profile. + */ + public static final String CANT_SAVE_TO_WORK_TITLE = + PREFIX + "CANT_SAVE_TO_WORK_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to save files from their + * personal profile to their work profile. + */ + public static final String CANT_SAVE_TO_WORK_MESSAGE = + PREFIX + "CANT_SAVE_TO_WORK_MESSAGE"; + + /** + * Title for error message shown when a user's IT admin does not allow the user to save + * files from their work profile to their personal profile. + */ + public static final String CANT_SAVE_TO_PERSONAL_TITLE = + PREFIX + "CANT_SAVE_TO_PERSONAL_TITLE"; + + /** + * Message shown when a user's IT admin does not allow the user to save files from their + * work profile to their personal profile. + */ + public static final String CANT_SAVE_TO_PERSONAL_MESSAGE = + PREFIX + "CANT_SAVE_TO_PERSONAL_MESSAGE"; + + /** + * Title for error message shown when a user tries to do something on their work + * device, but that action isn't allowed by their IT admin. + */ + public static final String CROSS_PROFILE_NOT_ALLOWED_TITLE = + PREFIX + "CROSS_PROFILE_NOT_ALLOWED_TITLE"; + + /** + * Message shown when a user tries to do something on their work device, but that action + * isn't allowed by their IT admin. + */ + public static final String CROSS_PROFILE_NOT_ALLOWED_MESSAGE = + PREFIX + "CROSS_PROFILE_NOT_ALLOWED_MESSAGE"; + + /** + * Content description text that's spoken by a screen reader for previewing a work file + * before opening it. Accepts file name as a param. + */ + public static final String PREVIEW_WORK_FILE_ACCESSIBILITY = + PREFIX + "PREVIEW_WORK_FILE_ACCESSIBILITY"; + + /** + * Label for tab and sidebar to indicate personal content. + */ + public static final String PERSONAL_TAB = PREFIX + "PERSONAL_TAB"; + + /** + * Label for tab and sidebar tab to indicate work content + */ + public static final String WORK_TAB = PREFIX + "WORK_TAB"; + + /** + * Accessibility label to indicate the subject(e.g. file/folder) is from work profile. + */ + public static final String WORK_ACCESSIBILITY = PREFIX + "WORK_ACCESSIBILITY"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(WORK_PROFILE_OFF_ERROR_TITLE); + strings.add(WORK_PROFILE_OFF_ENABLE_BUTTON); + strings.add(CANT_SELECT_WORK_FILES_TITLE); + strings.add(CANT_SELECT_WORK_FILES_MESSAGE); + strings.add(CANT_SELECT_PERSONAL_FILES_TITLE); + strings.add(CANT_SELECT_PERSONAL_FILES_MESSAGE); + strings.add(CANT_SAVE_TO_WORK_TITLE); + strings.add(CANT_SAVE_TO_WORK_MESSAGE); + strings.add(CANT_SAVE_TO_PERSONAL_TITLE); + strings.add(CANT_SAVE_TO_PERSONAL_MESSAGE); + strings.add(CROSS_PROFILE_NOT_ALLOWED_TITLE); + strings.add(CROSS_PROFILE_NOT_ALLOWED_MESSAGE); + strings.add(PREVIEW_WORK_FILE_ACCESSIBILITY); + strings.add(PERSONAL_TAB); + strings.add(WORK_TAB); + strings.add(WORK_ACCESSIBILITY); + return strings; + } + } + + /** + * Class containing the identifiers used to update device management-related system strings + * in the MediaProvider module. + */ + public static final class MediaProvider { + + private MediaProvider() { + } + + private static final String PREFIX = "MediaProvider."; + + /** + * The text shown to switch to the work profile in PhotoPicker. + */ + public static final String SWITCH_TO_WORK_MESSAGE = + PREFIX + "SWITCH_TO_WORK_MESSAGE"; + + /** + * The text shown to switch to the personal profile in PhotoPicker. + */ + public static final String SWITCH_TO_PERSONAL_MESSAGE = + PREFIX + "SWITCH_TO_PERSONAL_MESSAGE"; + + /** + * The title for error dialog in PhotoPicker when the admin blocks cross user + * interaction for the intent. + */ + public static final String BLOCKED_BY_ADMIN_TITLE = + PREFIX + "BLOCKED_BY_ADMIN_TITLE"; + + /** + * The message for error dialog in PhotoPicker when the admin blocks cross user + * interaction from the personal profile. + */ + public static final String BLOCKED_FROM_PERSONAL_MESSAGE = + PREFIX + "BLOCKED_FROM_PERSONAL_MESSAGE"; + + /** + * The message for error dialog in PhotoPicker when the admin blocks cross user + * interaction from the work profile. + */ + public static final String BLOCKED_FROM_WORK_MESSAGE = + PREFIX + "BLOCKED_FROM_WORK_MESSAGE"; + + /** + * The title of the error dialog in PhotoPicker when the user tries to switch to work + * content, but work profile is off. + */ + public static final String WORK_PROFILE_PAUSED_TITLE = + PREFIX + "WORK_PROFILE_PAUSED_TITLE"; + + /** + * The message of the error dialog in PhotoPicker when the user tries to switch to work + * content, but work profile is off. + */ + public static final String WORK_PROFILE_PAUSED_MESSAGE = + PREFIX + "WORK_PROFILE_PAUSED_MESSAGE"; + + /** + * @hide + */ + static Set<String> buildStringsSet() { + Set<String> strings = new HashSet<>(); + strings.add(SWITCH_TO_WORK_MESSAGE); + strings.add(SWITCH_TO_PERSONAL_MESSAGE); + strings.add(BLOCKED_BY_ADMIN_TITLE); + strings.add(BLOCKED_FROM_PERSONAL_MESSAGE); + strings.add(BLOCKED_FROM_WORK_MESSAGE); + strings.add(WORK_PROFILE_PAUSED_TITLE); + strings.add(WORK_PROFILE_PAUSED_MESSAGE); + return strings; + } + } } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index fae64d735b17..f663c17c7884 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -533,6 +533,14 @@ interface IDevicePolicyManager { boolean isUsbDataSignalingEnabledForUser(int userId); boolean canUsbDataSignalingBeDisabled(); + void setMinimumRequiredWifiSecurityLevel(int level); + int getMinimumRequiredWifiSecurityLevel(); + + void setSsidAllowlist(in List<String> ssids); + List<String> getSsidAllowlist(); + void setSsidDenylist(in List<String> ssids); + List<String> getSsidDenylist(); + List<UserHandle> listForegroundAffiliatedUsers(); void setDrawables(in List<DevicePolicyDrawableResource> drawables); void resetDrawables(in int[] drawableIds); diff --git a/core/java/android/app/admin/WifiSsidPolicy.java b/core/java/android/app/admin/WifiSsidPolicy.java new file mode 100644 index 000000000000..37150179cc68 --- /dev/null +++ b/core/java/android/app/admin/WifiSsidPolicy.java @@ -0,0 +1,153 @@ +/* + * 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 android.app.admin; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +/** + * Used to indicate the Wi-Fi SSID restriction policy the network must satisfy + * in order to be eligible for a connection. + * + * If the policy type is a denylist, the device may not connect to networks on the denylist. + * If the policy type is an allowlist, the device may only connect to networks on the allowlist. + * Admin configured networks are not exempt from this restriction. + * This policy only prohibits connecting to a restricted network and + * does not affect adding a restricted network. + * If the current network is present in the denylist or not present in the allowlist, + * it will be disconnected. + */ +public final class WifiSsidPolicy implements Parcelable { + /** + * SSID policy type indicator for {@link WifiSsidPolicy}. + * + * <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant + * indicates that the SSID policy type is an allowlist. + * + * @see #WIFI_SSID_POLICY_TYPE_DENYLIST + */ + public static final int WIFI_SSID_POLICY_TYPE_ALLOWLIST = 0; + + /** + * SSID policy type indicator for {@link WifiSsidPolicy}. + * + * <p> When returned from {@link WifiSsidPolicy#getPolicyType()}, the constant + * indicates that the SSID policy type is a denylist. + * + * @see #WIFI_SSID_POLICY_TYPE_ALLOWLIST + */ + public static final int WIFI_SSID_POLICY_TYPE_DENYLIST = 1; + + /** + * Possible SSID policy types + * + * @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"WIFI_SSID_POLICY_TYPE_"}, value = { + WIFI_SSID_POLICY_TYPE_ALLOWLIST, + WIFI_SSID_POLICY_TYPE_DENYLIST}) + public @interface WifiSsidPolicyType {} + + private @WifiSsidPolicyType int mPolicyType; + private ArraySet<String> mSsids; + + private WifiSsidPolicy(@WifiSsidPolicyType int policyType, @NonNull Set<String> ssids) { + mPolicyType = policyType; + mSsids = new ArraySet<>(ssids); + } + + private WifiSsidPolicy(Parcel in) { + mPolicyType = in.readInt(); + mSsids = (ArraySet<String>) in.readArraySet(null); + } + /** + * Create the allowlist Wi-Fi SSID Policy. + * + * @param ssids allowlist of SSIDs in UTF-8 without double quotes format + * @throws IllegalArgumentException if the input ssids list is empty + */ + @NonNull + public static WifiSsidPolicy createAllowlistPolicy(@NonNull Set<String> ssids) { + if (ssids.isEmpty()) { + throw new IllegalArgumentException("SSID list cannot be empty"); + } + return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_ALLOWLIST, ssids); + } + + /** + * Create the denylist Wi-Fi SSID Policy. + * + * @param ssids denylist of SSIDs in UTF-8 without double quotes format + * @throws IllegalArgumentException if the input ssids list is empty + */ + @NonNull + public static WifiSsidPolicy createDenylistPolicy(@NonNull Set<String> ssids) { + if (ssids.isEmpty()) { + throw new IllegalArgumentException("SSID list cannot be empty"); + } + return new WifiSsidPolicy(WIFI_SSID_POLICY_TYPE_DENYLIST, ssids); + } + + /** + * Returns the set of SSIDs in UTF-8 without double quotes format. + */ + @NonNull + public Set<String> getSsids() { + return mSsids; + } + + /** + * Returns the policy type. + */ + public @WifiSsidPolicyType int getPolicyType() { + return mPolicyType; + } + + /** + * @see Parcelable.Creator + */ + @NonNull + public static final Creator<WifiSsidPolicy> CREATOR = new Creator<WifiSsidPolicy>() { + @Override + public WifiSsidPolicy createFromParcel(Parcel source) { + return new WifiSsidPolicy(source); + } + + @Override + public WifiSsidPolicy[] newArray(int size) { + return new WifiSsidPolicy[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mPolicyType); + dest.writeArraySet(mSsids); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.aidl b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl new file mode 100644 index 000000000000..0965b1a3f013 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.ambientcontext; + +parcelable AmbientContextEvent; diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java new file mode 100644 index 000000000000..11e695ad7fad --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java @@ -0,0 +1,492 @@ +/* + * 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 android.app.ambientcontext; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; +import com.android.internal.util.Parcelling; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.Instant; + + +/** + * Represents a detected ambient event. Each event has a type, start time, end time, + * plus some optional data. + * + * @hide + */ +@SystemApi +@DataClass( + genBuilder = true, + genConstructor = false, + genHiddenConstDefs = true, + genParcelable = true, + genToString = true +) +public final class AmbientContextEvent implements Parcelable { + /** + * The integer indicating an unknown event was detected. + */ + public static final int EVENT_UNKNOWN = 0; + + /** + * The integer indicating a cough event was detected. + */ + public static final int EVENT_COUGH = 1; + + /** + * The integer indicating a snore event was detected. + */ + public static final int EVENT_SNORE = 2; + + /** @hide */ + @IntDef(prefix = { "EVENT_" }, value = { + EVENT_UNKNOWN, + EVENT_COUGH, + EVENT_SNORE, + }) public @interface EventCode {} + + /** The integer indicating an unknown level. */ + public static final int LEVEL_UNKNOWN = 0; + + /** The integer indicating a low level. */ + public static final int LEVEL_LOW = 1; + + /** The integer indicating a medium low level. */ + public static final int LEVEL_MEDIUM_LOW = 2; + + /** The integer indicating a medium Level. */ + public static final int LEVEL_MEDIUM = 3; + + /** The integer indicating a medium high level. */ + public static final int LEVEL_MEDIUM_HIGH = 4; + + /** The integer indicating a high level. */ + public static final int LEVEL_HIGH = 5; + + /** @hide */ + @IntDef(prefix = {"LEVEL_"}, value = { + LEVEL_UNKNOWN, + LEVEL_LOW, + LEVEL_MEDIUM_LOW, + LEVEL_MEDIUM, + LEVEL_MEDIUM_HIGH, + LEVEL_HIGH + }) public @interface LevelValue {} + + @EventCode private final int mEventType; + private static int defaultEventType() { + return EVENT_UNKNOWN; + } + + /** Event start time */ + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class) + @NonNull private final Instant mStartTime; + @NonNull private static Instant defaultStartTime() { + return Instant.MIN; + } + + /** Event end time */ + @DataClass.ParcelWith(Parcelling.BuiltIn.ForInstant.class) + @NonNull private final Instant mEndTime; + @NonNull private static Instant defaultEndTime() { + return Instant.MAX; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @LevelValue private final int mConfidenceLevel; + private static int defaultConfidenceLevel() { + return LEVEL_UNKNOWN; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @LevelValue private final int mDensityLevel; + private static int defaultDensityLevel() { + return LEVEL_UNKNOWN; + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** @hide */ + @IntDef(prefix = "EVENT_", value = { + EVENT_UNKNOWN, + EVENT_COUGH, + EVENT_SNORE + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Event {} + + /** @hide */ + @DataClass.Generated.Member + public static String eventToString(@Event int value) { + switch (value) { + case EVENT_UNKNOWN: + return "EVENT_UNKNOWN"; + case EVENT_COUGH: + return "EVENT_COUGH"; + case EVENT_SNORE: + return "EVENT_SNORE"; + default: return Integer.toHexString(value); + } + } + + /** @hide */ + @IntDef(prefix = "LEVEL_", value = { + LEVEL_UNKNOWN, + LEVEL_LOW, + LEVEL_MEDIUM_LOW, + LEVEL_MEDIUM, + LEVEL_MEDIUM_HIGH, + LEVEL_HIGH + }) + @Retention(RetentionPolicy.SOURCE) + @DataClass.Generated.Member + public @interface Level {} + + /** @hide */ + @DataClass.Generated.Member + public static String levelToString(@Level int value) { + switch (value) { + case LEVEL_UNKNOWN: + return "LEVEL_UNKNOWN"; + case LEVEL_LOW: + return "LEVEL_LOW"; + case LEVEL_MEDIUM_LOW: + return "LEVEL_MEDIUM_LOW"; + case LEVEL_MEDIUM: + return "LEVEL_MEDIUM"; + case LEVEL_MEDIUM_HIGH: + return "LEVEL_MEDIUM_HIGH"; + case LEVEL_HIGH: + return "LEVEL_HIGH"; + default: return Integer.toHexString(value); + } + } + + @DataClass.Generated.Member + /* package-private */ AmbientContextEvent( + @EventCode int eventType, + @NonNull Instant startTime, + @NonNull Instant endTime, + @LevelValue int confidenceLevel, + @LevelValue int densityLevel) { + this.mEventType = eventType; + com.android.internal.util.AnnotationValidations.validate( + EventCode.class, null, mEventType); + this.mStartTime = startTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStartTime); + this.mEndTime = endTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEndTime); + this.mConfidenceLevel = confidenceLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mConfidenceLevel); + this.mDensityLevel = densityLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mDensityLevel); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @EventCode int getEventType() { + return mEventType; + } + + /** + * Event start time + */ + @DataClass.Generated.Member + public @NonNull Instant getStartTime() { + return mStartTime; + } + + /** + * Event end time + */ + @DataClass.Generated.Member + public @NonNull Instant getEndTime() { + return mEndTime; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @LevelValue int getConfidenceLevel() { + return mConfidenceLevel; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @LevelValue int getDensityLevel() { + return mDensityLevel; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "AmbientContextEvent { " + + "eventType = " + mEventType + ", " + + "startTime = " + mStartTime + ", " + + "endTime = " + mEndTime + ", " + + "confidenceLevel = " + mConfidenceLevel + ", " + + "densityLevel = " + mDensityLevel + + " }"; + } + + @DataClass.Generated.Member + static Parcelling<Instant> sParcellingForStartTime = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForInstant.class); + static { + if (sParcellingForStartTime == null) { + sParcellingForStartTime = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForInstant()); + } + } + + @DataClass.Generated.Member + static Parcelling<Instant> sParcellingForEndTime = + Parcelling.Cache.get( + Parcelling.BuiltIn.ForInstant.class); + static { + if (sParcellingForEndTime == null) { + sParcellingForEndTime = Parcelling.Cache.put( + new Parcelling.BuiltIn.ForInstant()); + } + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(mEventType); + sParcellingForStartTime.parcel(mStartTime, dest, flags); + sParcellingForEndTime.parcel(mEndTime, dest, flags); + dest.writeInt(mConfidenceLevel); + dest.writeInt(mDensityLevel); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ AmbientContextEvent(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int eventType = in.readInt(); + Instant startTime = sParcellingForStartTime.unparcel(in); + Instant endTime = sParcellingForEndTime.unparcel(in); + int confidenceLevel = in.readInt(); + int densityLevel = in.readInt(); + + this.mEventType = eventType; + com.android.internal.util.AnnotationValidations.validate( + EventCode.class, null, mEventType); + this.mStartTime = startTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStartTime); + this.mEndTime = endTime; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEndTime); + this.mConfidenceLevel = confidenceLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mConfidenceLevel); + this.mDensityLevel = densityLevel; + com.android.internal.util.AnnotationValidations.validate( + LevelValue.class, null, mDensityLevel); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<AmbientContextEvent> CREATOR + = new Parcelable.Creator<AmbientContextEvent>() { + @Override + public AmbientContextEvent[] newArray(int size) { + return new AmbientContextEvent[size]; + } + + @Override + public AmbientContextEvent createFromParcel(@NonNull android.os.Parcel in) { + return new AmbientContextEvent(in); + } + }; + + /** + * A builder for {@link AmbientContextEvent} + */ + @SuppressWarnings("WeakerAccess") + @DataClass.Generated.Member + public static final class Builder { + + private @EventCode int mEventType; + private @NonNull Instant mStartTime; + private @NonNull Instant mEndTime; + private @LevelValue int mConfidenceLevel; + private @LevelValue int mDensityLevel; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + @DataClass.Generated.Member + public @NonNull Builder setEventType(@EventCode int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mEventType = value; + return this; + } + + /** + * Event start time + */ + @DataClass.Generated.Member + public @NonNull Builder setStartTime(@NonNull Instant value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mStartTime = value; + return this; + } + + /** + * Event end time + */ + @DataClass.Generated.Member + public @NonNull Builder setEndTime(@NonNull Instant value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mEndTime = value; + return this; + } + + /** + * Confidence level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @NonNull Builder setConfidenceLevel(@LevelValue int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mConfidenceLevel = value; + return this; + } + + /** + * Density level from LEVEL_LOW to LEVEL_HIGH, or LEVEL_NONE if not available. + * Apps can add post-processing filter using this value if needed. + */ + @DataClass.Generated.Member + public @NonNull Builder setDensityLevel(@LevelValue int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; + mDensityLevel = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEvent build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x20; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEventType = defaultEventType(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mStartTime = defaultStartTime(); + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mEndTime = defaultEndTime(); + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mConfidenceLevel = defaultConfidenceLevel(); + } + if ((mBuilderFieldsSet & 0x10) == 0) { + mDensityLevel = defaultDensityLevel(); + } + AmbientContextEvent o = new AmbientContextEvent( + mEventType, + mStartTime, + mEndTime, + mConfidenceLevel, + mDensityLevel); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x20) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } + + @DataClass.Generated( + time = 1642040319323L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java", + inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl b/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl new file mode 100644 index 000000000000..e24c6ad1bcea --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.aidl @@ -0,0 +1,19 @@ +/* + * 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 android.app.ambientcontext; + +parcelable AmbientContextEventRequest; diff --git a/core/java/android/app/ambientcontext/AmbientContextEventRequest.java b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java new file mode 100644 index 000000000000..82b16a2db0ce --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventRequest.java @@ -0,0 +1,169 @@ +/* + * 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 android.app.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.util.ArraySet; + +import java.util.HashSet; +import java.util.Set; + +/** + * Represents the request for ambient event detection. + * + * @hide + */ +@SystemApi +public final class AmbientContextEventRequest implements Parcelable { + @NonNull private final Set<Integer> mEventTypes; + @NonNull private final PersistableBundle mOptions; + + AmbientContextEventRequest( + @NonNull Set<Integer> eventTypes, + @NonNull PersistableBundle options) { + this.mEventTypes = eventTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEventTypes); + this.mOptions = options; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mOptions); + } + + /** + * The event types to detect. + */ + public @NonNull Set<Integer> getEventTypes() { + return mEventTypes; + } + + /** + * Optional detection options. + */ + public @NonNull PersistableBundle getOptions() { + return mOptions; + } + + @Override + public String toString() { + return "AmbientContextEventRequest { " + "eventTypes = " + mEventTypes + ", " + + "options = " + mOptions + " }"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeArraySet(new ArraySet<>(mEventTypes)); + dest.writeTypedObject(mOptions, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextEventRequest(@NonNull Parcel in) { + Set<Integer> eventTypes = (Set<Integer>) in.readArraySet(Integer.class.getClassLoader()); + PersistableBundle options = (PersistableBundle) in.readTypedObject( + PersistableBundle.CREATOR); + + this.mEventTypes = eventTypes; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mEventTypes); + this.mOptions = options; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mOptions); + } + + public static final @NonNull Parcelable.Creator<AmbientContextEventRequest> CREATOR = + new Parcelable.Creator<AmbientContextEventRequest>() { + @Override + public AmbientContextEventRequest[] newArray(int size) { + return new AmbientContextEventRequest[size]; + } + + @Override + public AmbientContextEventRequest createFromParcel(@NonNull Parcel in) { + return new AmbientContextEventRequest(in); + } + }; + + /** + * A builder for {@link AmbientContextEventRequest} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @NonNull Set<Integer> mEventTypes; + private @NonNull PersistableBundle mOptions; + + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * Add an event type to detect. + */ + public @NonNull Builder addEventType(@AmbientContextEvent.EventCode int value) { + checkNotUsed(); + if (mEventTypes == null) { + mBuilderFieldsSet |= 0x1; + mEventTypes = new HashSet<>(); + } + mEventTypes.add(value); + return this; + } + + /** + * Optional detection options. + */ + public @NonNull Builder setOptions(@NonNull PersistableBundle value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x2; + mOptions = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEventRequest build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mEventTypes = new HashSet<>(); + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mOptions = new PersistableBundle(); + } + AmbientContextEventRequest o = new AmbientContextEventRequest( + mEventTypes, + mOptions); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x4) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl new file mode 100644 index 000000000000..4dc6466c7365 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.ambientcontext; + +parcelable AmbientContextEventResponse;
\ No newline at end of file diff --git a/core/java/android/app/ambientcontext/AmbientContextEventResponse.java b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java new file mode 100644 index 000000000000..472a78b177c9 --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextEventResponse.java @@ -0,0 +1,293 @@ +/* + * 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 android.app.ambientcontext; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.PendingIntent; +import android.os.Parcelable; + +import com.android.internal.util.AnnotationValidations; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a response from the {@code AmbientContextEvent} service. + * + * @hide + */ +@SystemApi +public final class AmbientContextEventResponse implements Parcelable { + /** + * An unknown status. + */ + public static final int STATUS_UNKNOWN = 0; + /** + * The value of the status code that indicates success. + */ + public static final int STATUS_SUCCESS = 1; + /** + * The value of the status code that indicates one or more of the + * requested events are not supported. + */ + public static final int STATUS_NOT_SUPPORTED = 2; + /** + * The value of the status code that indicates service not available. + */ + public static final int STATUS_SERVICE_UNAVAILABLE = 3; + /** + * The value of the status code that microphone is disabled. + */ + public static final int STATUS_MICROPHONE_DISABLED = 4; + /** + * The value of the status code that the app is not granted access. + */ + public static final int STATUS_ACCESS_DENIED = 5; + + /** @hide */ + @IntDef(prefix = { "STATUS_" }, value = { + STATUS_UNKNOWN, + STATUS_SUCCESS, + STATUS_NOT_SUPPORTED, + STATUS_SERVICE_UNAVAILABLE, + STATUS_MICROPHONE_DISABLED, + STATUS_ACCESS_DENIED + }) public @interface StatusCode {} + + @StatusCode private final int mStatusCode; + @NonNull private final List<AmbientContextEvent> mEvents; + @NonNull private final String mPackageName; + @Nullable private final PendingIntent mActionPendingIntent; + + /** @hide */ + public static String statusToString(@StatusCode int value) { + switch (value) { + case STATUS_UNKNOWN: + return "STATUS_UNKNOWN"; + case STATUS_SUCCESS: + return "STATUS_SUCCESS"; + case STATUS_NOT_SUPPORTED: + return "STATUS_NOT_SUPPORTED"; + case STATUS_SERVICE_UNAVAILABLE: + return "STATUS_SERVICE_UNAVAILABLE"; + case STATUS_MICROPHONE_DISABLED: + return "STATUS_MICROPHONE_DISABLED"; + case STATUS_ACCESS_DENIED: + return "STATUS_ACCESS_DENIED"; + default: return Integer.toHexString(value); + } + } + + AmbientContextEventResponse( + @StatusCode int statusCode, + @NonNull List<AmbientContextEvent> events, + @NonNull String packageName, + @Nullable PendingIntent actionPendingIntent) { + this.mStatusCode = statusCode; + AnnotationValidations.validate(StatusCode.class, null, mStatusCode); + this.mEvents = events; + AnnotationValidations.validate(NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate(NonNull.class, null, mPackageName); + this.mActionPendingIntent = actionPendingIntent; + } + + /** + * The status of the response. + */ + public @StatusCode int getStatusCode() { + return mStatusCode; + } + + /** + * The detected event. + */ + public @NonNull List<AmbientContextEvent> getEvents() { + return mEvents; + } + + /** + * The package to deliver the response to. + */ + public @NonNull String getPackageName() { + return mPackageName; + } + + /** + * A {@link PendingIntent} that the client should call to allow further actions by user. + * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to the + * grant access activity. + */ + public @Nullable PendingIntent getActionPendingIntent() { + return mActionPendingIntent; + } + + @Override + public String toString() { + return "AmbientContextEventResponse { " + "statusCode = " + mStatusCode + ", " + + "events = " + mEvents + ", " + "packageName = " + mPackageName + ", " + + "callbackPendingIntent = " + mActionPendingIntent + " }"; + } + + @Override + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + byte flg = 0; + if (mActionPendingIntent != null) flg |= 0x8; + dest.writeByte(flg); + dest.writeInt(mStatusCode); + dest.writeParcelableList(mEvents, flags); + dest.writeString(mPackageName); + if (mActionPendingIntent != null) dest.writeTypedObject(mActionPendingIntent, flags); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + AmbientContextEventResponse(@NonNull android.os.Parcel in) { + byte flg = in.readByte(); + int statusCode = in.readInt(); + List<AmbientContextEvent> events = new ArrayList<>(); + in.readParcelableList(events, AmbientContextEvent.class.getClassLoader(), + AmbientContextEvent.class); + String packageName = in.readString(); + PendingIntent callbackPendingIntent = (flg & 0x8) == 0 ? null + : (PendingIntent) in.readTypedObject(PendingIntent.CREATOR); + + this.mStatusCode = statusCode; + AnnotationValidations.validate( + StatusCode.class, null, mStatusCode); + this.mEvents = events; + AnnotationValidations.validate( + NonNull.class, null, mEvents); + this.mPackageName = packageName; + AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mActionPendingIntent = callbackPendingIntent; + } + + public static final @NonNull Parcelable.Creator<AmbientContextEventResponse> CREATOR = + new Parcelable.Creator<AmbientContextEventResponse>() { + @Override + public AmbientContextEventResponse[] newArray(int size) { + return new AmbientContextEventResponse[size]; + } + + @Override + public AmbientContextEventResponse createFromParcel(@NonNull android.os.Parcel in) { + return new AmbientContextEventResponse(in); + } + }; + + /** + * A builder for {@link AmbientContextEventResponse} + */ + @SuppressWarnings("WeakerAccess") + public static final class Builder { + private @StatusCode int mStatusCode; + private @NonNull List<AmbientContextEvent> mEvents; + private @NonNull String mPackageName; + private @Nullable PendingIntent mCallbackPendingIntent; + private long mBuilderFieldsSet = 0L; + + public Builder() { + } + + /** + * The status of the response. + */ + public @NonNull Builder setStatusCode(@StatusCode int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x1; + mStatusCode = value; + return this; + } + + /** + * Adds an event to the builder. + */ + public @NonNull Builder addEvent(@NonNull AmbientContextEvent value) { + checkNotUsed(); + if (mEvents == null) { + mBuilderFieldsSet |= 0x2; + mEvents = new ArrayList<>(); + } + mEvents.add(value); + return this; + } + + /** + * The package to deliver the response to. + */ + public @NonNull Builder setPackageName(@NonNull String value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x4; + mPackageName = value; + return this; + } + + /** + * A {@link PendingIntent} that the client should call to allow further actions by user. + * For example, with {@link STATUS_ACCESS_DENIED}, the PendingIntent can redirect users to + * the grant access activity. + */ + public @NonNull Builder setActionPendingIntent(@NonNull PendingIntent value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x8; + mCallbackPendingIntent = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + public @NonNull AmbientContextEventResponse build() { + checkNotUsed(); + mBuilderFieldsSet |= 0x10; // Mark builder used + + if ((mBuilderFieldsSet & 0x1) == 0) { + mStatusCode = STATUS_UNKNOWN; + } + if ((mBuilderFieldsSet & 0x2) == 0) { + mEvents = new ArrayList<>(); + } + if ((mBuilderFieldsSet & 0x4) == 0) { + mPackageName = ""; + } + if ((mBuilderFieldsSet & 0x8) == 0) { + mCallbackPendingIntent = null; + } + AmbientContextEventResponse o = new AmbientContextEventResponse( + mStatusCode, + mEvents, + mPackageName, + mCallbackPendingIntent); + return o; + } + + private void checkNotUsed() { + if ((mBuilderFieldsSet & 0x10) != 0) { + throw new IllegalStateException( + "This Builder should not be reused. Use a new Builder instance instead"); + } + } + } +} diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java new file mode 100644 index 000000000000..6841d1bbfc1f --- /dev/null +++ b/core/java/android/app/ambientcontext/AmbientContextManager.java @@ -0,0 +1,147 @@ +/* + * 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 android.app.ambientcontext; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; + +import com.android.internal.util.Preconditions; + +/** + * Allows granted apps to register for particular pre-defined {@link AmbientContextEvent}s. + * After successful registration, the app receives a callback on the provided {@link PendingIntent} + * when the requested event is detected. + * <p /> + * + * Example: + * + * <pre><code> + * // Create request + * AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() + * .addEventType(AmbientContextEvent.EVENT_COUGH) + * .addEventTYpe(AmbientContextEvent.EVENT_SNORE) + * .build(); + * // Create PendingIntent + * Intent intent = new Intent(actionString, null, context, MyBroadcastReceiver.class) + * .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + * PendingIntent pendingIntent = PendingIntents.getBroadcastMutable(context, 0, intent, 0); + * // Register for events + * AmbientContextManager ambientContextManager = + * context.getSystemService(AmbientContextManager.class); + * ambientContextManager.registerObserver(request, pendingIntent); + * + * // Handle the callback intent in your receiver + * {@literal @}Override + * protected void onReceive(Context context, Intent intent) { + * AmbientContextEventResponse response = + * AmbientContextManager.getResponseFromIntent(intent); + * if (response != null) { + * if (response.getStatusCode() == AmbientContextEventResponse.STATUS_SUCCESS) { + * // Do something useful with response.getEvent() + * } else if (response.getStatusCode() == AmbientContextEventResponse.STATUS_ACCESS_DENIED) { + * // Redirect users to grant access + * PendingIntent callbackPendingIntent = response.getCallbackPendingIntent(); + * if (callbackPendingIntent != null) { + * callbackPendingIntent.send(); + * } + * } else ... + * } + * } + * </code></pre> + * + * @hide + */ +@SystemApi +@SystemService(Context.AMBIENT_CONTEXT_SERVICE) +public final class AmbientContextManager { + + /** + * The key of an Intent extra indicating the response. + */ + public static final String EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE = + "android.app.ambientcontext.extra.AMBIENT_CONTEXT_EVENT_RESPONSE"; + + /** + * Allows clients to retrieve the response from the intent. + * @param intent received from the PendingIntent callback + * + * @return the AmbientContextEventResponse, or null if not present + */ + @Nullable + public static AmbientContextEventResponse getResponseFromIntent( + @NonNull Intent intent) { + if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE)) { + return intent.getParcelableExtra(EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE); + } else { + return null; + } + } + + private final Context mContext; + private final IAmbientContextEventObserver mService; + + /** + * {@hide} + */ + public AmbientContextManager(Context context, IAmbientContextEventObserver service) { + mContext = context; + mService = service; + } + + /** + * Allows app to register as a {@link AmbientContextEvent} observer. The + * observer receives a callback on the provided {@link PendingIntent} when the requested + * event is detected. Registering another observer from the same package that has already been + * registered will override the previous observer. + * + * @param request The request with events to observe. + * @param pendingIntent A mutable {@link PendingIntent} that will be dispatched when any + * requested event is detected. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void registerObserver( + @NonNull AmbientContextEventRequest request, + @NonNull PendingIntent pendingIntent) { + Preconditions.checkArgument(!pendingIntent.isImmutable()); + try { + mService.registerObserver(request, pendingIntent); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Unregisters the requesting app as an {@code AmbientContextEvent} observer. Unregistering an + * observer that was already unregistered or never registered will have no effect. + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT) + public void unregisterObserver() { + try { + mService.unregisterObserver(mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } +} diff --git a/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl new file mode 100644 index 000000000000..9032fe1ee045 --- /dev/null +++ b/core/java/android/app/ambientcontext/IAmbientContextEventObserver.aidl @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 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.ambientcontext; + +import android.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEventRequest; + +/** + * Interface for an AmbientContextEventManager that provides access to AmbientContextEvents. + * + * @hide + */ +oneway interface IAmbientContextEventObserver { + void registerObserver(in AmbientContextEventRequest request, in PendingIntent pendingIntent); + void unregisterObserver(in String callingPackage); +}
\ No newline at end of file diff --git a/core/java/android/app/ambientcontext/OWNERS b/core/java/android/app/ambientcontext/OWNERS new file mode 100644 index 000000000000..a863297b84e8 --- /dev/null +++ b/core/java/android/app/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/core/java/android/app/trust/ITrustListener.aidl b/core/java/android/app/trust/ITrustListener.aidl index 65b024999a5b..6b9d2c73450e 100644 --- a/core/java/android/app/trust/ITrustListener.aidl +++ b/core/java/android/app/trust/ITrustListener.aidl @@ -16,13 +16,16 @@ */ package android.app.trust; +import java.util.List; + /** * Private API to be notified about trust changes. * * {@hide} */ oneway interface ITrustListener { - void onTrustChanged(boolean enabled, int userId, int flags); + void onTrustChanged(boolean enabled, int userId, int flags, + in List<String> trustGrantedMessages); void onTrustManagedChanged(boolean managed, int userId); void onTrustError(in CharSequence message); }
\ No newline at end of file diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 937fcf0c0709..70b7de0767e4 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -29,6 +29,9 @@ import android.os.Message; import android.os.RemoteException; import android.util.ArrayMap; +import java.util.ArrayList; +import java.util.List; + /** * See {@link com.android.server.trust.TrustManagerService} * @hide @@ -43,6 +46,7 @@ public class TrustManager { private static final String TAG = "TrustManager"; private static final String DATA_FLAGS = "initiatedByUser"; private static final String DATA_MESSAGE = "message"; + private static final String DATA_GRANTED_MESSAGES = "grantedMessages"; private final ITrustManager mService; private final ArrayMap<TrustListener, ITrustListener> mTrustListeners; @@ -152,12 +156,15 @@ public class TrustManager { try { ITrustListener.Stub iTrustListener = new ITrustListener.Stub() { @Override - public void onTrustChanged(boolean enabled, int userId, int flags) { + public void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages) { Message m = mHandler.obtainMessage(MSG_TRUST_CHANGED, (enabled ? 1 : 0), userId, trustListener); if (flags != 0) { m.getData().putInt(DATA_FLAGS, flags); } + m.getData().putCharSequenceArrayList( + DATA_GRANTED_MESSAGES, (ArrayList) trustGrantedMessages); m.sendToTarget(); } @@ -244,14 +251,15 @@ public class TrustManager { switch(msg.what) { case MSG_TRUST_CHANGED: int flags = msg.peekData() != null ? msg.peekData().getInt(DATA_FLAGS) : 0; - ((TrustListener)msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags); + ((TrustListener) msg.obj).onTrustChanged(msg.arg1 != 0, msg.arg2, flags, + msg.getData().getStringArrayList(DATA_GRANTED_MESSAGES)); break; case MSG_TRUST_MANAGED_CHANGED: ((TrustListener)msg.obj).onTrustManagedChanged(msg.arg1 != 0, msg.arg2); break; case MSG_TRUST_ERROR: final CharSequence message = msg.peekData().getCharSequence(DATA_MESSAGE); - ((TrustListener)msg.obj).onTrustError(message); + ((TrustListener) msg.obj).onTrustError(message); } } }; @@ -265,8 +273,11 @@ public class TrustManager { * @param flags Flags specified by the trust agent when granting trust. See * {@link android.service.trust.TrustAgentService#grantTrust(CharSequence, long, int) * TrustAgentService.grantTrust(CharSequence, long, int)}. + * @param trustGrantedMessages Messages to display to the user when trust has been granted + * by one or more trust agents. */ - void onTrustChanged(boolean enabled, int userId, int flags); + void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages); /** * Reports that whether trust is managed has changed diff --git a/core/java/android/app/wallpapereffectsgeneration/OWNERS b/core/java/android/app/wallpapereffectsgeneration/OWNERS new file mode 100644 index 000000000000..2bc01541a939 --- /dev/null +++ b/core/java/android/app/wallpapereffectsgeneration/OWNERS @@ -0,0 +1,5 @@ +susharon@google.com +shanh@google.com +huiwu@google.com +srazdan@google.com + diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 85855bedfbeb..339e9a2ff1bc 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -18,6 +18,7 @@ package android.companion.virtual; import android.app.PendingIntent; import android.graphics.Point; +import android.graphics.PointF; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -75,4 +76,5 @@ interface IVirtualDevice { */ void launchPendingIntent( int displayId, in PendingIntent pendingIntent, in ResultReceiver resultReceiver); + PointF getCursorPosition(IBinder token); } diff --git a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl new file mode 100644 index 000000000000..53af4c5da761 --- /dev/null +++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl @@ -0,0 +1,43 @@ +/* + * 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 android.companion.virtual; + +import android.content.ComponentName; + +/** + * Interface to listen for activity changes in a virtual device. + * + * @hide + */ +interface IVirtualDeviceActivityListener { + + /** + * Called when the top activity is changed. + * + * @param displayId The display ID on which the activity change happened. + * @param topActivity The component name of the top activity. + */ + void onTopActivityChanged(int displayId, in ComponentName topActivity); + + /** + * Called when the display becomes empty (e.g. if the user hits back on the last + * activity of the root task). + * + * @param displayId The display ID that became empty. + */ + void onDisplayEmpty(int displayId); +} diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index d80bee668f18..b7f826a940a8 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -17,6 +17,7 @@ package android.companion.virtual; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceParams; /** @@ -39,5 +40,5 @@ interface IVirtualDeviceManager { */ IVirtualDevice createVirtualDevice( in IBinder token, String packageName, int associationId, - in VirtualDeviceParams params); + in VirtualDeviceParams params, in IVirtualDeviceActivityListener activityListener); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index a97b7b3ae81c..64f16ac3e1b3 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -25,6 +25,7 @@ import android.annotation.SystemService; import android.app.Activity; import android.app.PendingIntent; import android.companion.AssociationInfo; +import android.content.ComponentName; import android.content.Context; import android.graphics.Point; import android.hardware.display.DisplayManager; @@ -40,6 +41,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; +import android.util.ArrayMap; import android.view.Surface; import java.util.concurrent.Executor; @@ -87,9 +89,7 @@ public final class VirtualDeviceManager { int associationId, @NonNull VirtualDeviceParams params) { try { - IVirtualDevice virtualDevice = mService.createVirtualDevice( - new Binder(), mContext.getPackageName(), associationId, params); - return new VirtualDevice(mContext, virtualDevice); + return new VirtualDevice(mService, mContext, associationId, params); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -106,10 +106,49 @@ public final class VirtualDeviceManager { private final Context mContext; private final IVirtualDevice mVirtualDevice; + private final ArrayMap<ActivityListener, ActivityListenerDelegate> mActivityListeners = + new ArrayMap<>(); + private final IVirtualDeviceActivityListener mActivityListenerBinder = + new IVirtualDeviceActivityListener.Stub() { - private VirtualDevice(Context context, IVirtualDevice virtualDevice) { + @Override + public void onTopActivityChanged(int displayId, ComponentName topActivity) { + final long token = Binder.clearCallingIdentity(); + try { + for (int i = 0; i < mActivityListeners.size(); i++) { + mActivityListeners.valueAt(i) + .onTopActivityChanged(displayId, topActivity); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onDisplayEmpty(int displayId) { + final long token = Binder.clearCallingIdentity(); + try { + for (int i = 0; i < mActivityListeners.size(); i++) { + mActivityListeners.valueAt(i).onDisplayEmpty(displayId); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + private VirtualDevice( + IVirtualDeviceManager service, + Context context, + int associationId, + VirtualDeviceParams params) throws RemoteException { mContext = context.getApplicationContext(); - mVirtualDevice = virtualDevice; + mVirtualDevice = service.createVirtualDevice( + new Binder(), + mContext.getPackageName(), + associationId, + params, + mActivityListenerBinder); } /** @@ -137,7 +176,7 @@ public final class VirtualDeviceManager { mVirtualDevice.launchPendingIntent( displayId, pendingIntent, - new ResultReceiver(new Handler(Looper.myLooper())) { + new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { super.onReceiveResult(resultCode, resultData); @@ -157,7 +196,9 @@ public final class VirtualDeviceManager { /** * Creates a virtual display for this virtual device. All displays created on the same - * device belongs to the same display group. + * device belongs to the same display group. Requires the ADD_TRUSTED_DISPLAY permission + * to create a virtual display which is not in the default DisplayGroup, and to create + * trusted displays. * * @param width The width of the virtual display in pixels, must be greater than 0. * @param height The height of the virtual display in pixels, must be greater than 0. @@ -321,6 +362,47 @@ public final class VirtualDeviceManager { throw e.rethrowFromSystemServer(); } } + + /** + * Adds an activity listener to listen for events such as top activity change or virtual + * display task stack became empty. + * + * @param listener The listener to add. + * @see #removeActivityListener(ActivityListener) + * @hide + */ + // TODO(b/194949534): Unhide this API + public void addActivityListener(@NonNull ActivityListener listener) { + addActivityListener(listener, mContext.getMainExecutor()); + } + + /** + * Adds an activity listener to listen for events such as top activity change or virtual + * display task stack became empty. + * + * @param listener The listener to add. + * @param executor The executor where the callback is executed on. + * @see #removeActivityListener(ActivityListener) + * @hide + */ + // TODO(b/194949534): Unhide this API + public void addActivityListener( + @NonNull ActivityListener listener, @NonNull Executor executor) { + mActivityListeners.put(listener, new ActivityListenerDelegate(listener, executor)); + } + + /** + * Removes an activity listener previously added with + * {@link #addActivityListener}. + * + * @param listener The listener to remove. + * @see #addActivityListener(ActivityListener, Executor) + * @hide + */ + // TODO(b/194949534): Unhide this API + public void removeActivityListener(@NonNull ActivityListener listener) { + mActivityListeners.remove(listener); + } } /** @@ -340,4 +422,50 @@ public final class VirtualDeviceManager { */ void onLaunchFailed(); } + + /** + * Listener for activity changes in this virtual device. + * + * @hide + */ + // TODO(b/194949534): Unhide this API + public interface ActivityListener { + + /** + * Called when the top activity is changed. + * + * @param displayId The display ID on which the activity change happened. + * @param topActivity The component name of the top activity. + */ + void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity); + + /** + * Called when the display becomes empty (e.g. if the user hits back on the last + * activity of the root task). + * + * @param displayId The display ID that became empty. + */ + void onDisplayEmpty(int displayId); + } + + /** + * A wrapper for {@link ActivityListener} that executes callbacks on the given executor. + */ + private static class ActivityListenerDelegate { + @NonNull private final ActivityListener mActivityListener; + @NonNull private final Executor mExecutor; + + ActivityListenerDelegate(@NonNull ActivityListener listener, @NonNull Executor executor) { + mActivityListener = listener; + mExecutor = executor; + } + + public void onTopActivityChanged(int displayId, ComponentName topActivity) { + mExecutor.execute(() -> mActivityListener.onTopActivityChanged(displayId, topActivity)); + } + + public void onDisplayEmpty(int displayId) { + mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId)); + } + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 845d23c199a3..ce2efcf4ac7f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -41,6 +41,7 @@ import android.app.GameManager; import android.app.IApplicationThread; import android.app.IServiceConnection; import android.app.VrManager; +import android.app.ambientcontext.AmbientContextManager; import android.app.people.PeopleManager; import android.app.time.TimeManager; import android.compat.annotation.UnsupportedAppUsage; @@ -5931,6 +5932,17 @@ public abstract class Context { public static final String NEARBY_SERVICE = "nearby"; /** + * Use with {@link #getSystemService(String)} to retrieve a + * {@link android.app.ambientcontext.AmbientContextManager}. + * + * @see #getSystemService(String) + * @see AmbientContextManager + * @hide + */ + @SystemApi + public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 58a7d8796ffb..7f00bcb1dccb 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -8190,6 +8190,37 @@ public class Intent implements Parcelable, Cloneable { hasIntentInfo = true; } break; + case "--ed": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + intent.putExtra(key, Double.valueOf(value)); + hasIntentInfo = true; + } + break; + case "--eda": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + double[] list = new double[strings.length]; + for (int i = 0; i < strings.length; i++) { + list[i] = Double.valueOf(strings[i]); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; + case "--edal": { + String key = cmd.getNextArgRequired(); + String value = cmd.getNextArgRequired(); + String[] strings = value.split(","); + ArrayList<Double> list = new ArrayList<>(strings.length); + for (int i = 0; i < strings.length; i++) { + list.add(Double.valueOf(strings[i])); + } + intent.putExtra(key, list); + hasIntentInfo = true; + } + break; case "--esa": { String key = cmd.getNextArgRequired(); String value = cmd.getNextArgRequired(); @@ -8435,25 +8466,30 @@ public class Intent implements Parcelable, Cloneable { " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]", " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]", " [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]", + " [--ed <EXTRA_KEY> <EXTRA_DOUBLE_VALUE> ...]", " [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]", " [--ecn <EXTRA_KEY> <EXTRA_COMPONENT_NAME_VALUE>]", " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]", - " (mutiple extras passed as Integer[])", + " (multiple extras passed as Integer[])", " [--eial <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]", - " (mutiple extras passed as List<Integer>)", + " (multiple extras passed as List<Integer>)", " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]", - " (mutiple extras passed as Long[])", + " (multiple extras passed as Long[])", " [--elal <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]", - " (mutiple extras passed as List<Long>)", + " (multiple extras passed as List<Long>)", " [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]", - " (mutiple extras passed as Float[])", + " (multiple extras passed as Float[])", " [--efal <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]", - " (mutiple extras passed as List<Float>)", + " (multiple extras passed as List<Float>)", + " [--eda <EXTRA_KEY> <EXTRA_DOUBLE_VALUE>[,<EXTRA_DOUBLE_VALUE...]]", + " (multiple extras passed as Double[])", + " [--edal <EXTRA_KEY> <EXTRA_DOUBLE_VALUE>[,<EXTRA_DOUBLE_VALUE...]]", + " (multiple extras passed as List<Double>)", " [--esa <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]", - " (mutiple extras passed as String[]; to embed a comma into a string,", + " (multiple extras passed as String[]; to embed a comma into a string,", " escape it using \"\\,\")", " [--esal <EXTRA_KEY> <EXTRA_STRING_VALUE>[,<EXTRA_STRING_VALUE...]]", - " (mutiple extras passed as List<String>; to embed a comma into a string,", + " (multiple extras passed as List<String>; to embed a comma into a string,", " escape it using \"\\,\")", " [-f <FLAG>]", " [--grant-read-uri-permission] [--grant-write-uri-permission]", diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 11b2ea1f6523..1e887582e5c3 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -15,6 +15,9 @@ */ package android.content.pm; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -22,6 +25,7 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.app.Activity; import android.app.AppOpsManager.Mode; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -241,12 +245,24 @@ public class CrossProfileApps { public @NonNull CharSequence getProfileSwitchingLabel(@NonNull UserHandle userHandle) { verifyCanAccessUser(userHandle); - final int stringRes = mUserManager.isManagedProfile(userHandle.getIdentifier()) - ? R.string.managed_profile_label - : R.string.user_owner_label; + final boolean isManagedProfile = mUserManager.isManagedProfile(userHandle.getIdentifier()); + final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString( + getUpdatableProfileSwitchingLabelId(isManagedProfile), + () -> getDefaultProfileSwitchingLabel(isManagedProfile)); + } + + private String getUpdatableProfileSwitchingLabelId(boolean isManagedProfile) { + return isManagedProfile ? SWITCH_TO_WORK_LABEL : SWITCH_TO_PERSONAL_LABEL; + } + + private String getDefaultProfileSwitchingLabel(boolean isManagedProfile) { + final int stringRes = isManagedProfile + ? R.string.managed_profile_label : R.string.user_owner_label; return mResources.getString(stringRes); } + /** * Return a drawable that calling app can show to user for the semantic of profile switching -- * launching its own activity in specified user profile. For example, it may return a briefcase diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c8f88f2edc62..1021d3e8e3fb 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4077,6 +4077,14 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_DREAM_OVERLAY = "android.software.dream_overlay"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports window magnification. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_WINDOW_MAGNIFICATION = + "android.software.window_magnification"; + /** @hide */ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 2ac194b67192..0304815ef8fe 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -50,6 +50,10 @@ interface IInputManager { // Reports whether the hardware supports the given keys; returns true if successful boolean hasKeys(int deviceId, int sourceMask, in int[] keyCodes, out boolean[] keyExists); + // Returns the keyCode produced when pressing the key at the specified location, given the + // active keyboard layout. + int getKeyCodeForKeyLocation(int deviceId, in int locationKeyCode); + // Temporarily changes the pointer speed. void tryPointerSpeed(int speed); diff --git a/core/java/android/hardware/input/InputDeviceIdentifier.java b/core/java/android/hardware/input/InputDeviceIdentifier.java index c673e7ab7c53..a5b9a2a4da76 100644 --- a/core/java/android/hardware/input/InputDeviceIdentifier.java +++ b/core/java/android/hardware/input/InputDeviceIdentifier.java @@ -16,7 +16,9 @@ package android.hardware.input; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -28,12 +30,13 @@ import java.util.Objects; * * @hide */ +@TestApi public final class InputDeviceIdentifier implements Parcelable { private final String mDescriptor; private final int mVendorId; private final int mProductId; - public InputDeviceIdentifier(String descriptor, int vendorId, int productId) { + public InputDeviceIdentifier(@NonNull String descriptor, int vendorId, int productId) { this.mDescriptor = descriptor; this.mVendorId = vendorId; this.mProductId = productId; @@ -51,12 +54,13 @@ public final class InputDeviceIdentifier implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mDescriptor); dest.writeInt(mVendorId); dest.writeInt(mProductId); } + @NonNull public String getDescriptor() { return mDescriptor; } diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index ef349a96ee17..cbc837393b6b 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -58,6 +58,7 @@ import android.util.SparseArray; import android.view.InputDevice; import android.view.InputEvent; import android.view.InputMonitor; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.PointerIcon; import android.view.VerifiedInputEvent; @@ -637,6 +638,30 @@ public final class InputManager { } /** + * Returns the descriptors of all supported keyboard layouts appropriate for the specified + * input device. + * <p> + * The input manager consults the built-in keyboard layouts as well as all keyboard layouts + * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. + * </p> + * + * @param device The input device to query. + * @return The ids of all keyboard layouts which are supported by the specified input device. + * + * @hide + */ + @TestApi + @NonNull + public List<String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull InputDevice device) { + KeyboardLayout[] layouts = getKeyboardLayoutsForInputDevice(device.getIdentifier()); + List<String> res = new ArrayList<>(); + for (KeyboardLayout kl : layouts) { + res.add(kl.getDescriptor()); + } + return res; + } + + /** * Gets information about all supported keyboard layouts appropriate * for a specific input device. * <p> @@ -650,7 +675,9 @@ public final class InputManager { * * @hide */ - public KeyboardLayout[] getKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { + @NonNull + public KeyboardLayout[] getKeyboardLayoutsForInputDevice( + @NonNull InputDeviceIdentifier identifier) { try { return mIm.getKeyboardLayoutsForInputDevice(identifier); } catch (RemoteException ex) { @@ -680,15 +707,17 @@ public final class InputManager { } /** - * Gets the current keyboard layout descriptor for the specified input - * device. + * Gets the current keyboard layout descriptor for the specified input device. * * @param identifier Identifier for the input device - * @return The keyboard layout descriptor, or null if no keyboard layout has - * been set. + * @return The keyboard layout descriptor, or null if no keyboard layout has been set. + * * @hide */ - public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) { + @TestApi + @Nullable + public String getCurrentKeyboardLayoutForInputDevice( + @NonNull InputDeviceIdentifier identifier) { try { return mIm.getCurrentKeyboardLayoutForInputDevice(identifier); } catch (RemoteException ex) { @@ -697,20 +726,21 @@ public final class InputManager { } /** - * Sets the current keyboard layout descriptor for the specified input - * device. + * Sets the current keyboard layout descriptor for the specified input device. * <p> - * This method may have the side-effect of causing the input device in - * question to be reconfigured. + * This method may have the side-effect of causing the input device in question to be + * reconfigured. * </p> * * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, - * must not be null. + * @param keyboardLayoutDescriptor The keyboard layout descriptor to use, must not be null. + * * @hide */ - public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { + @TestApi + @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) + public void setCurrentKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, + @NonNull String keyboardLayoutDescriptor) { if (identifier == null) { throw new IllegalArgumentException("identifier must not be null"); } @@ -727,11 +757,11 @@ public final class InputManager { } /** - * Gets all keyboard layout descriptors that are enabled for the specified - * input device. + * Gets all keyboard layout descriptors that are enabled for the specified input device. * * @param identifier The identifier for the input device. * @return The keyboard layout descriptors. + * * @hide */ public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { @@ -749,15 +779,16 @@ public final class InputManager { /** * Adds the keyboard layout descriptor for the specified input device. * <p> - * This method may have the side-effect of causing the input device in - * question to be reconfigured. + * This method may have the side-effect of causing the input device in question to be + * reconfigured. * </p> * * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to - * add. + * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to add. + * * @hide */ + @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (identifier == null) { @@ -777,17 +808,19 @@ public final class InputManager { /** * Removes the keyboard layout descriptor for the specified input device. * <p> - * This method may have the side-effect of causing the input device in - * question to be reconfigured. + * This method may have the side-effect of causing the input device in question to be + * reconfigured. * </p> * * @param identifier The identifier for the input device. - * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to - * remove. + * @param keyboardLayoutDescriptor The descriptor of the keyboard layout to remove. + * * @hide */ - public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, - String keyboardLayoutDescriptor) { + @TestApi + @RequiresPermission(Manifest.permission.SET_KEYBOARD_LAYOUT) + public void removeKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier, + @NonNull String keyboardLayoutDescriptor) { if (identifier == null) { throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); } @@ -1044,6 +1077,27 @@ public final class InputManager { return ret; } + /** + * Gets the key code produced by the specified location on a US keyboard layout. + * Key code as defined in {@link android.view.KeyEvent}. + * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available + * which can alter their key mapping using country specific keyboard layouts. + * + * @param deviceId The input device id. + * @param locationKeyCode The location of a key on a US keyboard layout. + * @return The key code produced when pressing the key at the specified location, given the + * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested + * mapping could not be determined, or if an error occurred. + * @hide + */ + public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) { + try { + return mIm.getKeyCodeForKeyLocation(deviceId, locationKeyCode); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Injects an input event into the event system on behalf of an application. diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index 1173c311bd26..f866a2e1c691 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -17,6 +17,7 @@ package android.hardware.input; import android.annotation.NonNull; +import android.graphics.PointF; import android.hardware.display.DisplayViewport; import android.os.IBinder; import android.view.InputEvent; @@ -79,6 +80,22 @@ public abstract class InputManagerInternal { public abstract boolean transferTouchFocus(@NonNull IBinder fromChannelToken, @NonNull IBinder toChannelToken); + /** + * Sets the display id that the MouseCursorController will be forced to target. Pass + * {@link android.view.Display#INVALID_DISPLAY} to clear the override. + */ + public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId); + + /** Gets the current position of the mouse cursor. */ + public abstract PointF getCursorPosition(); + + /** + * Sets the eligibility of windows on a given display for pointer capture. If a display is + * marked ineligible, requests to enable pointer capture for windows on that display will be + * ignored. + */ + public abstract void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible); + /** Registers the {@link LidSwitchCallback} to begin receiving notifications. */ public abstract void registerLidSwitchCallback(@NonNull LidSwitchCallback callbacks); diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java index 6599dd2e28eb..6e2b56a2b5bc 100644 --- a/core/java/android/hardware/input/VirtualMouse.java +++ b/core/java/android/hardware/input/VirtualMouse.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.companion.virtual.IVirtualDevice; +import android.graphics.PointF; import android.os.IBinder; import android.os.RemoteException; import android.view.MotionEvent; @@ -61,6 +62,8 @@ public class VirtualMouse implements Closeable { * Send a mouse button event to the system. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendButtonEvent(@NonNull VirtualMouseButtonEvent event) { @@ -76,6 +79,8 @@ public class VirtualMouse implements Closeable { * {@link MotionEvent#AXIS_SCROLL}. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendScrollEvent(@NonNull VirtualMouseScrollEvent event) { @@ -90,6 +95,8 @@ public class VirtualMouse implements Closeable { * Sends a relative movement event to the system. * * @param event the event + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendRelativeEvent(@NonNull VirtualMouseRelativeEvent event) { @@ -99,4 +106,20 @@ public class VirtualMouse implements Closeable { throw e.rethrowFromSystemServer(); } } + + /** + * Gets the current cursor position. + * + * @return the position, expressed as x and y coordinates + * @throws IllegalStateException if the display this mouse is associated with is not currently + * targeted + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public @NonNull PointF getCursorPosition() { + try { + return mVirtualDevice.getCursorPosition(mToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 7f07af79ab69..1d0837e3f118 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -18,6 +18,7 @@ package android.hardware.usb; import android.app.PendingIntent; import android.content.ComponentName; +import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; import android.hardware.usb.ParcelableUsbPort; @@ -136,7 +137,7 @@ interface IUsbManager void resetUsbGadget(); /* Set USB data on or off */ - boolean enableUsbDataSignal(boolean enable); + boolean enableUsbData(in String portId, boolean enable, int operationId, in IUsbOperationInternal callback); /* Gets the USB Hal Version. */ int getUsbHalVersion(); @@ -156,9 +157,14 @@ interface IUsbManager /* Sets the port's current role. */ void setPortRoles(in String portId, int powerRole, int dataRole); + /* Limit power transfer in & out of the port within the allowed limit by the USB + * specification. + */ + void enableLimitPowerTransfer(in String portId, boolean limit, int operationId, in IUsbOperationInternal callback); + /* Enable/disable contaminant detection */ void enableContaminantDetection(in String portId, boolean enable); - /* Sets USB device connection handler. */ - void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler); + /* Sets USB device connection handler. */ + void setUsbDeviceConnectionHandler(in ComponentName usbDeviceConnectionHandler); } diff --git a/core/java/android/hardware/usb/IUsbOperationInternal.aidl b/core/java/android/hardware/usb/IUsbOperationInternal.aidl new file mode 100644 index 000000000000..3f3bbf63ed8b --- /dev/null +++ b/core/java/android/hardware/usb/IUsbOperationInternal.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 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.hardware.usb; + +/** + * @hide + */ +oneway interface IUsbOperationInternal { +void onOperationComplete(in int status); +} diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index c29a948c2a26..eb3e84d27c7c 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -36,6 +36,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.usb.gadget.V1_0.GadgetFunction; import android.hardware.usb.gadget.V1_2.UsbSpeed; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbPort; import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -48,6 +50,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.StringJoiner; /** @@ -517,6 +520,14 @@ public class UsbManager { public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024; /** + * Returned when the client has to retry querying the version. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int USB_HAL_RETRY = -2; + + /** * The Value for USB hal is not presented. * * {@hide} @@ -557,6 +568,14 @@ public class UsbManager { public static final int USB_HAL_V1_3 = 13; /** + * Value for USB Hal Version v2.0. + * + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public static final int USB_HAL_V2_0 = 20; + + /** * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)} * {@hide} */ @@ -665,6 +684,7 @@ public class UsbManager { USB_HAL_V1_1, USB_HAL_V1_2, USB_HAL_V1_3, + USB_HAL_V2_0, }) public @interface UsbHalVersion {} @@ -1169,8 +1189,9 @@ public class UsbManager { /** * Enable/Disable the USB data signaling. * <p> - * Enables/Disables USB data path in all the USB ports. + * Enables/Disables USB data path of the first port.. * It will force to stop or restore USB data signaling. + * Call UsbPort API if the device has more than one UsbPort. * </p> * * @param enable enable or disable USB data signaling @@ -1181,11 +1202,11 @@ public class UsbManager { */ @RequiresPermission(Manifest.permission.MANAGE_USB) public boolean enableUsbDataSignal(boolean enable) { - try { - return mService.enableUsbDataSignal(enable); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + List<UsbPort> usbPorts = getPorts(); + if (usbPorts.size() == 1) { + return usbPorts.get(0).enableUsbData(enable) == UsbPort.ENABLE_USB_DATA_SUCCESS; } + return false; } /** @@ -1271,6 +1292,73 @@ public class UsbManager { } /** + * Should only be called by {@link UsbPort#enableLimitPowerTransfer}. + * <p> + * limits or restores power transfer in and out of USB port. + * + * @param port USB port for which power transfer has to be limited or restored. + * @param limit limit power transfer when true. + * relax power transfer restrictions when false. + * @param operationId operationId for the request. + * @param callback callback object to be invoked when the operation is complete. + * + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + void enableLimitPowerTransfer(@NonNull UsbPort port, boolean limit, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(port, "enableLimitPowerTransfer:port must not be null. opId:" + + operationId); + try { + mService.enableLimitPowerTransfer(port.getId(), limit, operationId, callback); + } catch (RemoteException e) { + Log.e(TAG, "enableLimitPowerTransfer failed. opId:" + operationId, e); + try { + callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + Log.e(TAG, "enableLimitPowerTransfer failed to call onOperationComplete. opId:" + + operationId, r); + } + throw e.rethrowFromSystemServer(); + } + } + + /** + * Should only be called by {@link UsbPort#enableUsbData}. + * <p> + * Enables or disables USB data on the specific port. + * + * @param port USB port for which USB data needs to be enabled or disabled. + * @param enable Enable USB data when true. + * Disable USB data when false. + * @param operationId operationId for the request. + * @param callback callback object to be invoked when the operation is complete. + * @return True when the operation is asynchronous. The caller must therefore call + * {@link UsbOperationInternal#waitForOperationComplete} for processing + * the result. + * False when the operation is synchronous. Caller can proceed reading the result + * through {@link UsbOperationInternal#getStatus} + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_USB) + boolean enableUsbData(@NonNull UsbPort port, boolean enable, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(port, "enableUsbData: port must not be null. opId:" + operationId); + try { + return mService.enableUsbData(port.getId(), enable, operationId, callback); + } catch (RemoteException e) { + Log.e(TAG, "enableUsbData: failed. opId:" + operationId, e); + try { + callback.onOperationComplete(UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + Log.e(TAG, "enableUsbData: failed to call onOperationComplete. opId:" + + operationId, r); + } + throw e.rethrowFromSystemServer(); + } + } + + /** * Sets the component that will handle USB device connection. * <p> * Setting component allows to specify external USB host manager to handle use cases, where diff --git a/core/java/android/hardware/usb/UsbOperationInternal.java b/core/java/android/hardware/usb/UsbOperationInternal.java new file mode 100644 index 000000000000..9bc2b3892a1e --- /dev/null +++ b/core/java/android/hardware/usb/UsbOperationInternal.java @@ -0,0 +1,131 @@ +/* + * 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 android.hardware.usb; + +import android.annotation.IntDef; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbPort; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.concurrent.TimeUnit; +/** + * UsbOperationInternal allows UsbPort to support both synchronous and + * asynchronous function irrespective of whether the underlying hal + * method is synchronous or asynchronous. + * + * @hide + */ +public final class UsbOperationInternal extends IUsbOperationInternal.Stub { + private static final String TAG = "UsbPortStatus"; + private final int mOperationID; + // Cached portId. + private final String mId; + // True implies operation did not timeout. + private boolean mOperationComplete; + private @UsbOperationStatus int mStatus; + final ReentrantLock mLock = new ReentrantLock(); + final Condition mOperationWait = mLock.newCondition(); + // Maximum time the caller has to wait for onOperationComplete to be called. + private static final int USB_OPERATION_TIMEOUT_MSECS = 5000; + + /** + * The requested operation was successfully completed. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_SUCCESS = 0; + + /** + * The requested operation failed due to internal error. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_ERROR_INTERNAL = 1; + + /** + * The requested operation failed as it's not supported. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_ERROR_NOT_SUPPORTED = 2; + + /** + * The requested operation failed as it's not supported. + * Returned in {@link onOperationComplete} and {@link getStatus}. + */ + public static final int USB_OPERATION_ERROR_PORT_MISMATCH = 3; + + @IntDef(prefix = { "USB_OPERATION_" }, value = { + USB_OPERATION_SUCCESS, + USB_OPERATION_ERROR_INTERNAL, + USB_OPERATION_ERROR_NOT_SUPPORTED, + USB_OPERATION_ERROR_PORT_MISMATCH + }) + @Retention(RetentionPolicy.SOURCE) + @interface UsbOperationStatus{} + + UsbOperationInternal(int operationID, String id) { + this.mOperationID = operationID; + this.mId = id; + } + + /** + * Hal glue layer would directly call this function when the requested + * operation is complete. + */ + @Override + public void onOperationComplete(@UsbOperationStatus int status) { + mLock.lock(); + try { + mOperationComplete = true; + mStatus = status; + Log.i(TAG, "Port:" + mId + " opID:" + mOperationID + " status:" + mStatus); + mOperationWait.signal(); + } finally { + mLock.unlock(); + } + } + + /** + * Caller invokes this function to wait for the operation to be complete. + */ + public void waitForOperationComplete() { + mLock.lock(); + try { + long now = System.currentTimeMillis(); + long deadline = now + USB_OPERATION_TIMEOUT_MSECS; + // Wait in loop to overcome spurious wakeups. + do { + mOperationWait.await(deadline - System.currentTimeMillis(), + TimeUnit.MILLISECONDS); + } while (!mOperationComplete && System.currentTimeMillis() < deadline); + if (!mOperationComplete) { + Log.e(TAG, "Port:" + mId + " opID:" + mOperationID + + " operationComplete not received in " + USB_OPERATION_TIMEOUT_MSECS + + "msecs"); + } + } catch (InterruptedException e) { + Log.e(TAG, "Port:" + mId + " opID:" + mOperationID + " operationComplete interrupted"); + } finally { + mLock.unlock(); + } + } + + public @UsbOperationStatus int getStatus() { + return mOperationComplete ? mStatus : USB_OPERATION_ERROR_INTERNAL; + } +} diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java index 274e23fff292..e908c245fc8e 100644 --- a/core/java/android/hardware/usb/UsbPort.java +++ b/core/java/android/hardware/usb/UsbPort.java @@ -16,6 +16,10 @@ package android.hardware.usb; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_NOT_SUPPORTED; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_PORT_MISMATCH; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DETECTED; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_DISABLED; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED; @@ -34,15 +38,23 @@ import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; import android.Manifest; +import android.annotation.CheckResult; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.hardware.usb.UsbOperationInternal; import android.hardware.usb.V1_0.Constants; +import android.os.Binder; +import android.util.Log; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; /** * Represents a physical USB port and describes its characteristics. @@ -51,6 +63,7 @@ import java.util.Objects; */ @SystemApi public final class UsbPort { + private static final String TAG = "UsbPort"; private final String mId; private final int mSupportedModes; private final UsbManager mUsbManager; @@ -64,6 +77,83 @@ public final class UsbPort { */ private static final int POWER_ROLE_OFFSET = Constants.PortPowerRole.NONE; + /** + * Counter for tracking UsbOperation operations. + */ + private static final AtomicInteger sUsbOperationCount = new AtomicInteger(); + + /** + * The {@link #enableUsbData} request was successfully completed. + */ + public static final int ENABLE_USB_DATA_SUCCESS = 0; + + /** + * The {@link #enableUsbData} request failed due to internal error. + */ + public static final int ENABLE_USB_DATA_ERROR_INTERNAL = 1; + + /** + * The {@link #enableUsbData} request failed as it's not supported. + */ + public static final int ENABLE_USB_DATA_ERROR_NOT_SUPPORTED = 2; + + /** + * The {@link #enableUsbData} request failed as port id mismatched. + */ + public static final int ENABLE_USB_DATA_ERROR_PORT_MISMATCH = 3; + + /** + * The {@link #enableUsbData} request failed due to other reasons. + */ + public static final int ENABLE_USB_DATA_ERROR_OTHER = 4; + + /** @hide */ + @IntDef(prefix = { "ENABLE_USB_DATA_" }, value = { + ENABLE_USB_DATA_SUCCESS, + ENABLE_USB_DATA_ERROR_INTERNAL, + ENABLE_USB_DATA_ERROR_NOT_SUPPORTED, + ENABLE_USB_DATA_ERROR_PORT_MISMATCH, + ENABLE_USB_DATA_ERROR_OTHER + }) + @Retention(RetentionPolicy.SOURCE) + @interface EnableUsbDataStatus{} + + /** + * The {@link #enableLimitPowerTransfer} request was successfully completed. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_SUCCESS = 0; + + /** + * The {@link #enableLimitPowerTransfer} request failed due to internal error. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL = 1; + + /** + * The {@link #enableLimitPowerTransfer} request failed as it's not supported. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED = 2; + + /** + * The {@link #enableLimitPowerTransfer} request failed as port id mismatched. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH = 3; + + /** + * The {@link #enableLimitPowerTransfer} request failed due to other reasons. + */ + public static final int ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER = 4; + + /** @hide */ + @IntDef(prefix = { "ENABLE_LIMIT_POWER_TRANSFER_" }, value = { + ENABLE_LIMIT_POWER_TRANSFER_SUCCESS, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH, + ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER + }) + @Retention(RetentionPolicy.SOURCE) + @interface EnableLimitPowerTransferStatus{} + /** @hide */ public UsbPort(@NonNull UsbManager usbManager, @NonNull String id, int supportedModes, int supportedContaminantProtectionModes, @@ -157,7 +247,7 @@ public final class UsbPort { * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}. * </p><p> * Note: This function is asynchronous and may fail silently without applying - * the requested changes. If this function does cause a status change to occur then + * the operationed changes. If this function does cause a status change to occur then * a {@link UsbManager#ACTION_USB_PORT_CHANGED} broadcast will be sent. * </p> * @@ -177,6 +267,88 @@ public final class UsbPort { } /** + * Enables/Disables Usb data on the port. + * + * @param enable When true enables USB data if disabled. + * When false disables USB data if enabled. + * @return {@link #ENABLE_USB_DATA_SUCCESS} when request completes successfully or + * {@link #ENABLE_USB_DATA_ERROR_INTERNAL} when request fails due to internal + * error or + * {@link ENABLE_USB_DATA_ERROR_NOT_SUPPORTED} when not supported or + * {@link ENABLE_USB_DATA_ERROR_PORT_MISMATCH} when request fails due to port id + * mismatch or + * {@link ENABLE_USB_DATA_ERROR_OTHER} when fails due to other reasons. + */ + @CheckResult + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @EnableUsbDataStatus int enableUsbData(boolean enable) { + // UID is added To minimize operationID overlap between two different packages. + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); + Log.i(TAG, "enableUsbData opId:" + operationId + + " callingUid:" + Binder.getCallingUid()); + UsbOperationInternal opCallback = + new UsbOperationInternal(operationId, mId); + if (mUsbManager.enableUsbData(this, enable, operationId, opCallback) == true) { + opCallback.waitForOperationComplete(); + } + + int result = opCallback.getStatus(); + switch (result) { + case USB_OPERATION_SUCCESS: + return ENABLE_USB_DATA_SUCCESS; + case USB_OPERATION_ERROR_INTERNAL: + return ENABLE_USB_DATA_ERROR_INTERNAL; + case USB_OPERATION_ERROR_NOT_SUPPORTED: + return ENABLE_USB_DATA_ERROR_NOT_SUPPORTED; + case USB_OPERATION_ERROR_PORT_MISMATCH: + return ENABLE_USB_DATA_ERROR_PORT_MISMATCH; + default: + return ENABLE_USB_DATA_ERROR_OTHER; + } + } + + /** + * Limits power transfer In and out of the port. + * <p> + * Disables charging and limits sourcing power(when permitted by the USB spec) until + * port disconnect event. + * </p> + * @param enable limits power transfer when true. + * @return {@link #ENABLE_LIMIT_POWER_TRANSFER_SUCCESS} when request completes successfully or + * {@link #ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL} when request fails due to + * internal error or + * {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED} when not supported or + * {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH} when request fails due to + * port id mismatch or + * {@link ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER} when fails due to other reasons. + */ + @CheckResult + @RequiresPermission(Manifest.permission.MANAGE_USB) + public @EnableLimitPowerTransferStatus int enableLimitPowerTransfer(boolean enable) { + // UID is added To minimize operationID overlap between two different packages. + int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid(); + Log.i(TAG, "enableLimitPowerTransfer opId:" + operationId + + " callingUid:" + Binder.getCallingUid()); + UsbOperationInternal opCallback = + new UsbOperationInternal(operationId, mId); + mUsbManager.enableLimitPowerTransfer(this, enable, operationId, opCallback); + opCallback.waitForOperationComplete(); + int result = opCallback.getStatus(); + switch (result) { + case USB_OPERATION_SUCCESS: + return ENABLE_LIMIT_POWER_TRANSFER_SUCCESS; + case USB_OPERATION_ERROR_INTERNAL: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_INTERNAL; + case USB_OPERATION_ERROR_NOT_SUPPORTED: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_NOT_SUPPORTED; + case USB_OPERATION_ERROR_PORT_MISMATCH: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_PORT_MISMATCH; + default: + return ENABLE_LIMIT_POWER_TRANSFER_ERROR_OTHER; + } + } + + /** * @hide **/ public void enableContaminantDetection(boolean enable) { diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java index bb7aff651b3d..934c5067c11f 100644 --- a/core/java/android/hardware/usb/UsbPortStatus.java +++ b/core/java/android/hardware/usb/UsbPortStatus.java @@ -19,7 +19,6 @@ package android.hardware.usb; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; -import android.hardware.usb.V1_0.Constants; import android.os.Parcel; import android.os.Parcelable; @@ -36,27 +35,30 @@ import java.lang.annotation.RetentionPolicy; @Immutable @SystemApi public final class UsbPortStatus implements Parcelable { + private static final String TAG = "UsbPortStatus"; private final int mCurrentMode; private final @UsbPowerRole int mCurrentPowerRole; private final @UsbDataRole int mCurrentDataRole; private final int mSupportedRoleCombinations; private final @ContaminantProtectionStatus int mContaminantProtectionStatus; private final @ContaminantDetectionStatus int mContaminantDetectionStatus; + private final boolean mUsbDataEnabled; + private final boolean mPowerTransferLimited; /** * Power role: This USB port does not have a power role. */ - public static final int POWER_ROLE_NONE = Constants.PortPowerRole.NONE; + public static final int POWER_ROLE_NONE = 0; /** * Power role: This USB port can act as a source (provide power). */ - public static final int POWER_ROLE_SOURCE = Constants.PortPowerRole.SOURCE; + public static final int POWER_ROLE_SOURCE = 1; /** * Power role: This USB port can act as a sink (receive power). */ - public static final int POWER_ROLE_SINK = Constants.PortPowerRole.SINK; + public static final int POWER_ROLE_SINK = 2; @IntDef(prefix = { "POWER_ROLE_" }, value = { POWER_ROLE_NONE, @@ -69,17 +71,17 @@ public final class UsbPortStatus implements Parcelable { /** * Power role: This USB port does not have a data role. */ - public static final int DATA_ROLE_NONE = Constants.PortDataRole.NONE; + public static final int DATA_ROLE_NONE = 0; /** * Data role: This USB port can act as a host (access data services). */ - public static final int DATA_ROLE_HOST = Constants.PortDataRole.HOST; + public static final int DATA_ROLE_HOST = 1; /** * Data role: This USB port can act as a device (offer data services). */ - public static final int DATA_ROLE_DEVICE = Constants.PortDataRole.DEVICE; + public static final int DATA_ROLE_DEVICE = 2; @IntDef(prefix = { "DATA_ROLE_" }, value = { DATA_ROLE_NONE, @@ -92,23 +94,23 @@ public final class UsbPortStatus implements Parcelable { /** * There is currently nothing connected to this USB port. */ - public static final int MODE_NONE = Constants.PortMode.NONE; + public static final int MODE_NONE = 0; /** - * This USB port can act as a downstream facing port (host). + * This USB port can act as an upstream facing port (device). * - * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and - * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well). + * <p> Implies that the port supports the {@link #POWER_ROLE_SINK} and + * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). */ - public static final int MODE_DFP = Constants.PortMode.DFP; + public static final int MODE_UFP = 1 << 0; /** - * This USB port can act as an upstream facing port (device). + * This USB port can act as a downstream facing port (host). * - * <p> Implies that the port supports the {@link #POWER_ROLE_SINK} and - * {@link #DATA_ROLE_DEVICE} combination of roles (and possibly others as well). + * <p> Implies that the port supports the {@link #POWER_ROLE_SOURCE} and + * {@link #DATA_ROLE_HOST} combination of roles (and possibly others as well). */ - public static final int MODE_UFP = Constants.PortMode.UFP; + public static final int MODE_DFP = 1 << 1; /** * This USB port can act either as an downstream facing port (host) or as @@ -120,87 +122,76 @@ public final class UsbPortStatus implements Parcelable { * * @hide */ - public static final int MODE_DUAL = Constants.PortMode.DRP; + public static final int MODE_DUAL = MODE_UFP | MODE_DFP; /** * This USB port can support USB Type-C Audio accessory. */ - public static final int MODE_AUDIO_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.AUDIO_ACCESSORY; + public static final int MODE_AUDIO_ACCESSORY = 1 << 2; /** * This USB port can support USB Type-C debug accessory. */ - public static final int MODE_DEBUG_ACCESSORY = - android.hardware.usb.V1_1.Constants.PortMode_1_1.DEBUG_ACCESSORY; + public static final int MODE_DEBUG_ACCESSORY = 1 << 3; /** * Contaminant presence detection not supported by the device. * @hide */ - public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_SUPPORTED; + public static final int CONTAMINANT_DETECTION_NOT_SUPPORTED = 0; /** * Contaminant presence detection supported but disabled. * @hide */ - public static final int CONTAMINANT_DETECTION_DISABLED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DISABLED; + public static final int CONTAMINANT_DETECTION_DISABLED = 1; /** * Contaminant presence enabled but not detected. * @hide */ - public static final int CONTAMINANT_DETECTION_NOT_DETECTED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.NOT_DETECTED; + public static final int CONTAMINANT_DETECTION_NOT_DETECTED = 2; /** * Contaminant presence enabled and detected. * @hide */ - public static final int CONTAMINANT_DETECTION_DETECTED = - android.hardware.usb.V1_2.Constants.ContaminantDetectionStatus.DETECTED; + public static final int CONTAMINANT_DETECTION_DETECTED = 3; /** * Contaminant protection - No action performed upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_NONE = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.NONE; + public static final int CONTAMINANT_PROTECTION_NONE = 0; /** * Contaminant protection - Port is forced to sink upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_SINK = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SINK; + public static final int CONTAMINANT_PROTECTION_SINK = 1 << 0; /** * Contaminant protection - Port is forced to source upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_SOURCE = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_SOURCE; + public static final int CONTAMINANT_PROTECTION_SOURCE = 1 << 1; /** * Contaminant protection - Port is disabled upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.FORCE_DISABLE; + public static final int CONTAMINANT_PROTECTION_FORCE_DISABLE = 1 << 2; /** * Contaminant protection - Port is disabled upon detection of * contaminant presence. * @hide */ - public static final int CONTAMINANT_PROTECTION_DISABLED = - android.hardware.usb.V1_2.Constants.ContaminantProtectionStatus.DISABLED; + public static final int CONTAMINANT_PROTECTION_DISABLED = 1 << 3; @IntDef(prefix = { "CONTAMINANT_DETECTION_" }, value = { CONTAMINANT_DETECTION_NOT_SUPPORTED, @@ -234,6 +225,21 @@ public final class UsbPortStatus implements Parcelable { /** @hide */ public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, + int contaminantDetectionStatus, boolean usbDataEnabled, + boolean powerTransferLimited) { + mCurrentMode = currentMode; + mCurrentPowerRole = currentPowerRole; + mCurrentDataRole = currentDataRole; + mSupportedRoleCombinations = supportedRoleCombinations; + mContaminantProtectionStatus = contaminantProtectionStatus; + mContaminantDetectionStatus = contaminantDetectionStatus; + mUsbDataEnabled = usbDataEnabled; + mPowerTransferLimited = powerTransferLimited; + } + + /** @hide */ + public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole, + int supportedRoleCombinations, int contaminantProtectionStatus, int contaminantDetectionStatus) { mCurrentMode = currentMode; mCurrentPowerRole = currentPowerRole; @@ -241,6 +247,8 @@ public final class UsbPortStatus implements Parcelable { mSupportedRoleCombinations = supportedRoleCombinations; mContaminantProtectionStatus = contaminantProtectionStatus; mContaminantDetectionStatus = contaminantDetectionStatus; + mUsbDataEnabled = true; + mPowerTransferLimited = false; } /** @@ -323,6 +331,25 @@ public final class UsbPortStatus implements Parcelable { return mContaminantProtectionStatus; } + /** + * Returns UsbData status. + * + * @hide + */ + public boolean getUsbDataStatus() { + return mUsbDataEnabled; + } + + /** + * Returns whether power transfer is limited. + * + * @return true when power transfer is limited. + * false otherwise. + */ + public boolean isPowerTransferLimited() { + return mPowerTransferLimited; + } + @NonNull @Override public String toString() { @@ -336,6 +363,10 @@ public final class UsbPortStatus implements Parcelable { + getContaminantDetectionStatus() + ", contaminantProtectionStatus=" + getContaminantProtectionStatus() + + ", usbDataEnabled=" + + getUsbDataStatus() + + ", isPowerTransferLimited=" + + isPowerTransferLimited() + "}"; } @@ -352,6 +383,8 @@ public final class UsbPortStatus implements Parcelable { dest.writeInt(mSupportedRoleCombinations); dest.writeInt(mContaminantProtectionStatus); dest.writeInt(mContaminantDetectionStatus); + dest.writeBoolean(mUsbDataEnabled); + dest.writeBoolean(mPowerTransferLimited); } public static final @NonNull Parcelable.Creator<UsbPortStatus> CREATOR = @@ -364,9 +397,11 @@ public final class UsbPortStatus implements Parcelable { int supportedRoleCombinations = in.readInt(); int contaminantProtectionStatus = in.readInt(); int contaminantDetectionStatus = in.readInt(); + boolean usbDataEnabled = in.readBoolean(); + boolean powerTransferLimited = in.readBoolean(); return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataEnabled, powerTransferLimited); } @Override diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 6f15588c0724..cc325cde1f41 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -72,7 +72,6 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_START_INPUT = 32; private static final int DO_CREATE_SESSION = 40; private static final int DO_SET_SESSION_ENABLED = 45; - private static final int DO_REVOKE_SESSION = 50; private static final int DO_SHOW_SOFT_INPUT = 60; private static final int DO_HIDE_SOFT_INPUT = 70; private static final int DO_CHANGE_INPUTMETHOD_SUBTYPE = 80; @@ -215,9 +214,6 @@ class IInputMethodWrapper extends IInputMethod.Stub inputMethod.setSessionEnabled((InputMethodSession)msg.obj, msg.arg1 != 0); return; - case DO_REVOKE_SESSION: - inputMethod.revokeSession((InputMethodSession)msg.obj); - return; case DO_SHOW_SOFT_INPUT: { final SomeArgs args = (SomeArgs)msg.obj; inputMethod.showSoftInputWithToken( @@ -368,22 +364,6 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void revokeSession(IInputMethodSession session) { - try { - InputMethodSession ls = ((IInputMethodSessionWrapper) - session).getInternalInputMethodSession(); - if (ls == null) { - Log.w(TAG, "Session is already finished: " + session); - return; - } - mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REVOKE_SESSION, ls)); - } catch (ClassCastException e) { - Log.w(TAG, "Incoming session not of correct type: " + session, e); - } - } - - @BinderThread - @Override public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT, flags, showInputToken, resultReceiver)); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 09d50850788b..5d2d8eafb3a8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -469,6 +469,10 @@ public class InputMethodService extends AbstractInputMethodService { InputMethodManager mImm; private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations(); + @NonNull + private final NavigationBarController mNavigationBarController = + new NavigationBarController(this); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) int mTheme = 0; @@ -611,6 +615,7 @@ public class InputMethodService extends AbstractInputMethodService { info.touchableRegion.set(mTmpInsets.touchableRegion); info.setTouchableInsets(mTmpInsets.touchableInsets); } + mNavigationBarController.updateTouchableInsets(mTmpInsets, info); if (mInputFrame != null) { setImeExclusionRect(mTmpInsets.visibleTopInsets); @@ -1534,6 +1539,7 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesVisibility = getCandidatesHiddenVisibility(); mCandidatesFrame.setVisibility(mCandidatesVisibility); mInputFrame.setVisibility(View.GONE); + mNavigationBarController.onViewInitialized(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -1543,6 +1549,7 @@ public class InputMethodService extends AbstractInputMethodService { mRootView.getViewTreeObserver().removeOnComputeInternalInsetsListener( mInsetsComputer); doFinishInput(); + mNavigationBarController.onDestroy(); mWindow.dismissForDestroyIfNecessary(); if (mSettingsObserver != null) { mSettingsObserver.unregister(); @@ -2451,6 +2458,7 @@ public class InputMethodService extends AbstractInputMethodService { setImeWindowStatus(nextImeWindowStatus, mBackDisposition); } + mNavigationBarController.onWindowShown(); // compute visibility onWindowShown(); mWindowVisible = true; @@ -3656,6 +3664,7 @@ public class InputMethodService extends AbstractInputMethodService { + " touchableInsets=" + mTmpInsets.touchableInsets + " touchableRegion=" + mTmpInsets.touchableRegion); p.println(" mSettingsObserver=" + mSettingsObserver); + p.println(" mNavigationBarController=" + mNavigationBarController.toDebugString()); } private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() { diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java new file mode 100644 index 000000000000..7295b72c276b --- /dev/null +++ b/core/java/android/inputmethodservice/NavigationBarController.java @@ -0,0 +1,328 @@ +/* + * 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 android.inputmethodservice; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.StatusBarManager; +import android.content.res.Resources; +import android.graphics.Insets; +import android.graphics.Rect; +import android.graphics.Region; +import android.inputmethodservice.navigationbar.NavigationBarFrame; +import android.inputmethodservice.navigationbar.NavigationBarView; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManagerPolicyConstants; +import android.widget.FrameLayout; + +import java.util.Objects; + +/** + * This class hides details behind {@link InputMethodService#canImeRenderGesturalNavButtons()} from + * {@link InputMethodService}. + * + * <p>All the package-private methods are no-op when + * {@link InputMethodService#canImeRenderGesturalNavButtons()} returns {@code false}.</p> + */ +final class NavigationBarController { + + private interface Callback { + default void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets, + @NonNull ViewTreeObserver.InternalInsetsInfo dest) { + } + + default void onViewInitialized() { + } + + default void onWindowShown() { + } + + default void onDestroy() { + } + + default String toDebugString() { + return "No-op implementation"; + } + + Callback NOOP = new Callback() { + }; + } + + private final Callback mImpl; + + NavigationBarController(@NonNull InputMethodService inputMethodService) { + mImpl = InputMethodService.canImeRenderGesturalNavButtons() + ? new Impl(inputMethodService) : Callback.NOOP; + } + + void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets, + @NonNull ViewTreeObserver.InternalInsetsInfo dest) { + mImpl.updateTouchableInsets(originalInsets, dest); + } + + void onViewInitialized() { + mImpl.onViewInitialized(); + } + + void onWindowShown() { + mImpl.onWindowShown(); + } + + void onDestroy() { + mImpl.onDestroy(); + } + + String toDebugString() { + return mImpl.toDebugString(); + } + + private static final class Impl implements Callback { + @NonNull + private final InputMethodService mService; + + private boolean mDestroyed = false; + + private boolean mRenderGesturalNavButtons; + + @Nullable + private NavigationBarFrame mNavigationBarFrame; + @Nullable + Insets mLastInsets; + + Impl(@NonNull InputMethodService inputMethodService) { + mService = inputMethodService; + } + + @Nullable + private Insets getSystemInsets() { + if (mService.mWindow == null) { + return null; + } + final View decorView = mService.mWindow.getWindow().getDecorView(); + if (decorView == null) { + return null; + } + final WindowInsets windowInsets = decorView.getRootWindowInsets(); + if (windowInsets == null) { + return null; + } + final Insets stableBarInsets = + windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()); + return Insets.min(windowInsets.getInsets(WindowInsets.Type.systemBars() + | WindowInsets.Type.displayCutout()), stableBarInsets); + } + + private void installNavigationBarFrameIfNecessary() { + if (!mRenderGesturalNavButtons) { + return; + } + final View rawDecorView = mService.mWindow.getWindow().getDecorView(); + if (!(rawDecorView instanceof ViewGroup)) { + return; + } + final ViewGroup decorView = (ViewGroup) rawDecorView; + mNavigationBarFrame = decorView.findViewByPredicate( + NavigationBarFrame.class::isInstance); + final Insets systemInsets = getSystemInsets(); + if (mNavigationBarFrame == null) { + mNavigationBarFrame = new NavigationBarFrame(mService); + LayoutInflater.from(mService).inflate( + com.android.internal.R.layout.input_method_navigation_bar, + mNavigationBarFrame); + if (systemInsets != null) { + decorView.addView(mNavigationBarFrame, new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + systemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = systemInsets; + } else { + decorView.addView(mNavigationBarFrame); + } + final NavigationBarView navigationBarView = mNavigationBarFrame.findViewByPredicate( + NavigationBarView.class::isInstance); + if (navigationBarView != null) { + // TODO(b/213337792): Support InputMethodService#setBackDisposition(). + // TODO(b/213337792): Set NAVIGATION_HINT_IME_SHOWN only when necessary. + final int hints = StatusBarManager.NAVIGATION_HINT_BACK_ALT + | StatusBarManager.NAVIGATION_HINT_IME_SHOWN; + navigationBarView.setNavigationIconHints(hints); + } + } else { + mNavigationBarFrame.setLayoutParams(new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, systemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = systemInsets; + } + + mNavigationBarFrame.setBackground(null); + } + + @Override + public void updateTouchableInsets(@NonNull InputMethodService.Insets originalInsets, + @NonNull ViewTreeObserver.InternalInsetsInfo dest) { + if (!mRenderGesturalNavButtons || mNavigationBarFrame == null + || mService.isExtractViewShown()) { + return; + } + + final Insets systemInsets = getSystemInsets(); + if (systemInsets != null) { + final Window window = mService.mWindow.getWindow(); + final View decor = window.getDecorView(); + Region touchableRegion = null; + final View inputFrame = mService.mInputFrame; + switch (originalInsets.touchableInsets) { + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME: + if (inputFrame.getVisibility() == View.VISIBLE) { + touchableRegion = new Region(inputFrame.getLeft(), + inputFrame.getTop(), inputFrame.getRight(), + inputFrame.getBottom()); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT: + if (inputFrame.getVisibility() == View.VISIBLE) { + touchableRegion = new Region(inputFrame.getLeft(), + originalInsets.contentTopInsets, inputFrame.getRight(), + inputFrame.getBottom()); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE: + if (inputFrame.getVisibility() == View.VISIBLE) { + touchableRegion = new Region(inputFrame.getLeft(), + originalInsets.visibleTopInsets, inputFrame.getRight(), + inputFrame.getBottom()); + } + break; + case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: + touchableRegion = new Region(); + touchableRegion.set(originalInsets.touchableRegion); + break; + } + final Rect navBarRect = new Rect(decor.getLeft(), + decor.getBottom() - systemInsets.bottom, + decor.getRight(), decor.getBottom()); + if (touchableRegion == null) { + touchableRegion = new Region(navBarRect); + } else { + touchableRegion.union(navBarRect); + } + + dest.touchableRegion.set(touchableRegion); + dest.setTouchableInsets( + ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + + // TODO(b/205803355): See if we can use View#OnLayoutChangeListener(). + // TODO(b/205803355): See if we can replace DecorView#mNavigationColorViewState.view + boolean zOrderChanged = false; + if (decor instanceof ViewGroup) { + ViewGroup decorGroup = (ViewGroup) decor; + final View navbarBackgroundView = window.getNavigationBarBackgroundView(); + zOrderChanged = navbarBackgroundView != null + && decorGroup.indexOfChild(navbarBackgroundView) + > decorGroup.indexOfChild(mNavigationBarFrame); + } + final boolean insetChanged = !Objects.equals(systemInsets, mLastInsets); + if (zOrderChanged || insetChanged) { + final NavigationBarFrame that = mNavigationBarFrame; + that.post(() -> { + if (!that.isAttachedToWindow()) { + return; + } + final Insets currentSystemInsets = getSystemInsets(); + if (!Objects.equals(currentSystemInsets, mLastInsets)) { + that.setLayoutParams( + new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + currentSystemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = currentSystemInsets; + } + if (decor instanceof ViewGroup) { + ViewGroup decorGroup = (ViewGroup) decor; + final View navbarBackgroundView = + window.getNavigationBarBackgroundView(); + if (navbarBackgroundView != null + && decorGroup.indexOfChild(navbarBackgroundView) + > decorGroup.indexOfChild(that)) { + decorGroup.bringChildToFront(that); + } + } + }); + } + } + } + + private boolean isGesturalNavigationEnabled() { + final Resources resources = mService.getResources(); + if (resources == null) { + return false; + } + return resources.getInteger(com.android.internal.R.integer.config_navBarInteractionMode) + == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + } + + @Override + public void onViewInitialized() { + if (mDestroyed) { + return; + } + mRenderGesturalNavButtons = isGesturalNavigationEnabled(); + installNavigationBarFrameIfNecessary(); + } + + @Override + public void onDestroy() { + mDestroyed = true; + } + + @Override + public void onWindowShown() { + if (mDestroyed || !mRenderGesturalNavButtons || mNavigationBarFrame == null) { + return; + } + final Insets systemInsets = getSystemInsets(); + if (systemInsets != null) { + if (!Objects.equals(systemInsets, mLastInsets)) { + mNavigationBarFrame.setLayoutParams(new NavigationBarFrame.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + systemInsets.bottom, Gravity.BOTTOM)); + mLastInsets = systemInsets; + } + final Window window = mService.mWindow.getWindow(); + View rawDecorView = window.getDecorView(); + if (rawDecorView instanceof ViewGroup) { + final ViewGroup decor = (ViewGroup) rawDecorView; + final View navbarBackgroundView = window.getNavigationBarBackgroundView(); + if (navbarBackgroundView != null + && decor.indexOfChild(navbarBackgroundView) + > decor.indexOfChild(mNavigationBarFrame)) { + decor.bringChildToFront(mNavigationBarFrame); + } + } + mNavigationBarFrame.setVisibility(View.VISIBLE); + } + } + + @Override + public String toDebugString() { + return "{mRenderGesturalNavButtons=" + mRenderGesturalNavButtons + "}"; + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java new file mode 100644 index 000000000000..3f26fa461097 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/ButtonDispatcher.java @@ -0,0 +1,302 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.view.View; +import android.view.View.AccessibilityDelegate; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +import java.util.ArrayList; + +/** + * Dispatches common view calls to multiple views. This is used to handle + * multiples of the same nav bar icon appearing. + */ +final class ButtonDispatcher { + private static final int FADE_DURATION_IN = 150; + private static final int FADE_DURATION_OUT = 250; + public static final Interpolator LINEAR = new LinearInterpolator(); + + private final ArrayList<View> mViews = new ArrayList<>(); + + private final int mId; + + private View.OnClickListener mClickListener; + private View.OnTouchListener mTouchListener; + private View.OnLongClickListener mLongClickListener; + private View.OnHoverListener mOnHoverListener; + private Boolean mLongClickable; + private float mAlpha = 1.0f; + private Float mDarkIntensity; + private int mVisibility = View.VISIBLE; + private Boolean mDelayTouchFeedback; + private KeyButtonDrawable mImageDrawable; + private View mCurrentView; + private ValueAnimator mFadeAnimator; + private AccessibilityDelegate mAccessibilityDelegate; + + private final ValueAnimator.AnimatorUpdateListener mAlphaListener = animation -> + setAlpha( + (float) animation.getAnimatedValue(), + false /* animate */, + false /* cancelAnimator */); + + private final AnimatorListenerAdapter mFadeListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mFadeAnimator = null; + setVisibility(getAlpha() == 1 ? View.VISIBLE : View.INVISIBLE); + } + }; + + public ButtonDispatcher(int id) { + mId = id; + } + + public void clear() { + mViews.clear(); + } + + public void addView(View view) { + mViews.add(view); + view.setOnClickListener(mClickListener); + view.setOnTouchListener(mTouchListener); + view.setOnLongClickListener(mLongClickListener); + view.setOnHoverListener(mOnHoverListener); + if (mLongClickable != null) { + view.setLongClickable(mLongClickable); + } + view.setAlpha(mAlpha); + view.setVisibility(mVisibility); + if (mAccessibilityDelegate != null) { + view.setAccessibilityDelegate(mAccessibilityDelegate); + } + if (view instanceof ButtonInterface) { + final ButtonInterface button = (ButtonInterface) view; + if (mDarkIntensity != null) { + button.setDarkIntensity(mDarkIntensity); + } + if (mImageDrawable != null) { + button.setImageDrawable(mImageDrawable); + } + if (mDelayTouchFeedback != null) { + button.setDelayTouchFeedback(mDelayTouchFeedback); + } + } + } + + public int getId() { + return mId; + } + + public int getVisibility() { + return mVisibility; + } + + public boolean isVisible() { + return getVisibility() == View.VISIBLE; + } + + public float getAlpha() { + return mAlpha; + } + + public KeyButtonDrawable getImageDrawable() { + return mImageDrawable; + } + + public void setImageDrawable(KeyButtonDrawable drawable) { + mImageDrawable = drawable; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + if (mViews.get(i) instanceof ButtonInterface) { + ((ButtonInterface) mViews.get(i)).setImageDrawable(mImageDrawable); + } + } + if (mImageDrawable != null) { + mImageDrawable.setCallback(mCurrentView); + } + } + + public void setVisibility(int visibility) { + if (mVisibility == visibility) return; + if (mFadeAnimator != null) { + mFadeAnimator.cancel(); + } + + mVisibility = visibility; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setVisibility(mVisibility); + } + } + + public void setAlpha(float alpha) { + setAlpha(alpha, false /* animate */); + } + + public void setAlpha(float alpha, boolean animate) { + setAlpha(alpha, animate, true /* cancelAnimator */); + } + + public void setAlpha(float alpha, boolean animate, long duration) { + setAlpha(alpha, animate, duration, true /* cancelAnimator */); + } + + public void setAlpha(float alpha, boolean animate, boolean cancelAnimator) { + setAlpha( + alpha, + animate, + (getAlpha() < alpha) ? FADE_DURATION_IN : FADE_DURATION_OUT, + cancelAnimator); + } + + public void setAlpha(float alpha, boolean animate, long duration, boolean cancelAnimator) { + if (mFadeAnimator != null && (cancelAnimator || animate)) { + mFadeAnimator.cancel(); + } + if (animate) { + setVisibility(View.VISIBLE); + mFadeAnimator = ValueAnimator.ofFloat(getAlpha(), alpha); + mFadeAnimator.setDuration(duration); + mFadeAnimator.setInterpolator(LINEAR); + mFadeAnimator.addListener(mFadeListener); + mFadeAnimator.addUpdateListener(mAlphaListener); + mFadeAnimator.start(); + } else { + // Discretize the alpha updates to prevent too frequent updates when there is a long + // alpha animation + int prevAlpha = (int) (getAlpha() * 255); + int nextAlpha = (int) (alpha * 255); + if (prevAlpha != nextAlpha) { + mAlpha = nextAlpha / 255f; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setAlpha(mAlpha); + } + } + } + } + + public void setDarkIntensity(float darkIntensity) { + mDarkIntensity = darkIntensity; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + if (mViews.get(i) instanceof ButtonInterface) { + ((ButtonInterface) mViews.get(i)).setDarkIntensity(darkIntensity); + } + } + } + + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + if (mViews.get(i) instanceof ButtonInterface) { + ((ButtonInterface) mViews.get(i)).setDelayTouchFeedback(delay); + } + } + } + + public void setOnClickListener(View.OnClickListener clickListener) { + mClickListener = clickListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnClickListener(mClickListener); + } + } + + public void setOnTouchListener(View.OnTouchListener touchListener) { + mTouchListener = touchListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnTouchListener(mTouchListener); + } + } + + public void setLongClickable(boolean isLongClickable) { + mLongClickable = isLongClickable; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setLongClickable(mLongClickable); + } + } + + public void setOnLongClickListener(View.OnLongClickListener longClickListener) { + mLongClickListener = longClickListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnLongClickListener(mLongClickListener); + } + } + + public void setOnHoverListener(View.OnHoverListener hoverListener) { + mOnHoverListener = hoverListener; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setOnHoverListener(mOnHoverListener); + } + } + + public void setAccessibilityDelegate(AccessibilityDelegate delegate) { + mAccessibilityDelegate = delegate; + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + mViews.get(i).setAccessibilityDelegate(delegate); + } + } + + public void setTranslation(int x, int y, int z) { + final int N = mViews.size(); + for (int i = 0; i < N; i++) { + final View view = mViews.get(i); + view.setTranslationX(x); + view.setTranslationY(y); + view.setTranslationZ(z); + } + } + + public ArrayList<View> getViews() { + return mViews; + } + + public View getCurrentView() { + return mCurrentView; + } + + public void setCurrentView(View currentView) { + mCurrentView = currentView.findViewById(mId); + if (mImageDrawable != null) { + mImageDrawable.setCallback(mCurrentView); + } + if (mCurrentView != null) { + mCurrentView.setTranslationX(0); + mCurrentView.setTranslationY(0); + mCurrentView.setTranslationZ(0); + } + } + + /** + * Executes when button is detached from window. + */ + public void onDestroy() { + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java new file mode 100644 index 000000000000..1c9c86d2a7e5 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/ButtonInterface.java @@ -0,0 +1,29 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import android.annotation.Nullable; +import android.graphics.drawable.Drawable; + +interface ButtonInterface { + + void setImageDrawable(@Nullable Drawable drawable); + + void setDarkIntensity(float intensity); + + void setDelayTouchFeedback(boolean shouldDelay); +} diff --git a/core/java/android/inputmethodservice/navigationbar/DeadZone.java b/core/java/android/inputmethodservice/navigationbar/DeadZone.java new file mode 100644 index 000000000000..cd857369bc5a --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/DeadZone.java @@ -0,0 +1,203 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_DECAY; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_HOLD; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVIGATION_BAR_DEADZONE_SIZE_MAX; +import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx; + +import android.animation.ObjectAnimator; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.os.SystemClock; +import android.util.Log; +import android.view.MotionEvent; +import android.view.Surface; + +/** + * The "dead zone" consumes unintentional taps along the top edge of the navigation bar. + * When users are typing quickly on an IME they may attempt to hit the space bar, overshoot, and + * accidentally hit the home button. The DeadZone expands temporarily after each tap in the UI + * outside the navigation bar (since this is when accidental taps are more likely), then contracts + * back over time (since a later tap might be intended for the top of the bar). + */ +final class DeadZone { + public static final String TAG = "DeadZone"; + + public static final boolean DEBUG = false; + public static final int HORIZONTAL = 0; // Consume taps along the top edge. + public static final int VERTICAL = 1; // Consume taps along the left edge. + + private static final boolean CHATTY = true; // print to logcat when we eat a click + private final NavigationBarView mNavigationBarView; + + private boolean mShouldFlash; + private float mFlashFrac = 0f; + + private int mSizeMax; + private int mSizeMin; + // Upon activity elsewhere in the UI, the dead zone will hold steady for + // mHold ms, then move back over the course of mDecay ms + private int mHold, mDecay; + private boolean mVertical; + private long mLastPokeTime; + private int mDisplayRotation; + + private final Runnable mDebugFlash = new Runnable() { + @Override + public void run() { + ObjectAnimator.ofFloat(DeadZone.this, "flash", 1f, 0f).setDuration(150).start(); + } + }; + + public DeadZone(NavigationBarView view) { + mNavigationBarView = view; + onConfigurationChanged(Surface.ROTATION_0); + } + + static float lerp(float a, float b, float f) { + return (b - a) * f + a; + } + + private float getSize(long now) { + if (mSizeMax == 0) + return 0; + long dt = (now - mLastPokeTime); + if (dt > mHold + mDecay) + return mSizeMin; + if (dt < mHold) + return mSizeMax; + return (int) lerp(mSizeMax, mSizeMin, (float) (dt - mHold) / mDecay); + } + + public void setFlashOnTouchCapture(boolean dbg) { + mShouldFlash = dbg; + mFlashFrac = 0f; + mNavigationBarView.postInvalidate(); + } + + public void onConfigurationChanged(int rotation) { + mDisplayRotation = rotation; + + final Resources res = mNavigationBarView.getResources(); + mHold = NAVIGATION_BAR_DEADZONE_HOLD; + mDecay = NAVIGATION_BAR_DEADZONE_DECAY; + + mSizeMin = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE, res); + mSizeMax = dpToPx(NAVIGATION_BAR_DEADZONE_SIZE_MAX, res); + mVertical = (res.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); + + if (DEBUG) { + Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + + (mVertical ? " vertical" : " horizontal")); + } + setFlashOnTouchCapture(false); // hard-coded from "bool/config_dead_zone_flash" + } + + // I made you a touch event... + public boolean onTouchEvent(MotionEvent event) { + if (DEBUG) { + Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); + } + + // Don't consume events for high precision pointing devices. For this purpose a stylus is + // considered low precision (like a finger), so its events may be consumed. + if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) { + return false; + } + + final int action = event.getAction(); + if (action == MotionEvent.ACTION_OUTSIDE) { + poke(event); + return true; + } else if (action == MotionEvent.ACTION_DOWN) { + if (DEBUG) { + Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); + } + //TODO(b/205803355): call mNavBarController.touchAutoDim(mDisplayId); here + int size = (int) getSize(event.getEventTime()); + // In the vertical orientation consume taps along the left edge. + // In horizontal orientation consume taps along the top edge. + final boolean consumeEvent; + if (mVertical) { + if (mDisplayRotation == Surface.ROTATION_270) { + consumeEvent = event.getX() > mNavigationBarView.getWidth() - size; + } else { + consumeEvent = event.getX() < size; + } + } else { + consumeEvent = event.getY() < size; + } + if (consumeEvent) { + if (CHATTY) { + Log.v(TAG, "consuming errant click: (" + event.getX() + "," + + event.getY() + ")"); + } + if (mShouldFlash) { + mNavigationBarView.post(mDebugFlash); + mNavigationBarView.postInvalidate(); + } + return true; // ...but I eated it + } + } + return false; + } + + private void poke(MotionEvent event) { + mLastPokeTime = event.getEventTime(); + if (DEBUG) + Log.v(TAG, "poked! size=" + getSize(mLastPokeTime)); + if (mShouldFlash) mNavigationBarView.postInvalidate(); + } + + public void setFlash(float f) { + mFlashFrac = f; + mNavigationBarView.postInvalidate(); + } + + public float getFlash() { + return mFlashFrac; + } + + public void onDraw(Canvas can) { + if (!mShouldFlash || mFlashFrac <= 0f) { + return; + } + + final int size = (int) getSize(SystemClock.uptimeMillis()); + if (mVertical) { + if (mDisplayRotation == Surface.ROTATION_270) { + can.clipRect(can.getWidth() - size, 0, can.getWidth(), can.getHeight()); + } else { + can.clipRect(0, 0, size, can.getHeight()); + } + } else { + can.clipRect(0, 0, can.getWidth(), size); + } + + final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac; + can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA); + + if (DEBUG && size > mSizeMin) { + // Very aggressive redrawing here, for debugging only + mNavigationBarView.postInvalidateDelayed(100); + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java new file mode 100644 index 000000000000..25a443de916b --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonDrawable.java @@ -0,0 +1,483 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_COLOR; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_X; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_OFFSET_Y; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAV_KEY_BUTTON_SHADOW_RADIUS; +import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx; + +import android.animation.ArgbEvaluator; +import android.annotation.ColorInt; +import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; +import android.graphics.BlurMaskFilter.Blur; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; +import android.util.FloatProperty; +import android.view.View; + + +/** + * Drawable for {@link KeyButtonView}s that supports tinting between two colors, rotation and shows + * a shadow. AnimatedVectorDrawable will only support tinting from intensities but has no support + * for shadows nor rotations. + */ +final class KeyButtonDrawable extends Drawable { + + public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_ROTATE = + new FloatProperty<KeyButtonDrawable>("KeyButtonRotation") { + @Override + public void setValue(KeyButtonDrawable drawable, float degree) { + drawable.setRotation(degree); + } + + @Override + public Float get(KeyButtonDrawable drawable) { + return drawable.getRotation(); + } + }; + + public static final FloatProperty<KeyButtonDrawable> KEY_DRAWABLE_TRANSLATE_Y = + new FloatProperty<KeyButtonDrawable>("KeyButtonTranslateY") { + @Override + public void setValue(KeyButtonDrawable drawable, float y) { + drawable.setTranslationY(y); + } + + @Override + public Float get(KeyButtonDrawable drawable) { + return drawable.getTranslationY(); + } + }; + + private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final ShadowDrawableState mState; + private AnimatedVectorDrawable mAnimatedDrawable; + private final Callback mAnimatedDrawableCallback = new Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } + }; + + public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor, + boolean horizontalFlip, Color ovalBackgroundColor) { + this(d, new ShadowDrawableState(lightColor, darkColor, + d instanceof AnimatedVectorDrawable, horizontalFlip, ovalBackgroundColor)); + } + + private KeyButtonDrawable(Drawable d, ShadowDrawableState state) { + mState = state; + if (d != null) { + mState.mBaseHeight = d.getIntrinsicHeight(); + mState.mBaseWidth = d.getIntrinsicWidth(); + mState.mChangingConfigurations = d.getChangingConfigurations(); + mState.mChildState = d.getConstantState(); + } + if (canAnimate()) { + mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate(); + mAnimatedDrawable.setCallback(mAnimatedDrawableCallback); + setDrawableBounds(mAnimatedDrawable); + } + } + + public void setDarkIntensity(float intensity) { + mState.mDarkIntensity = intensity; + final int color = (int) ArgbEvaluator.getInstance() + .evaluate(intensity, mState.mLightColor, mState.mDarkColor); + updateShadowAlpha(); + setColorFilter(new PorterDuffColorFilter(color, Mode.SRC_ATOP)); + } + + public void setRotation(float degrees) { + if (canAnimate()) { + // AnimatedVectorDrawables will not support rotation + return; + } + if (mState.mRotateDegrees != degrees) { + mState.mRotateDegrees = degrees; + invalidateSelf(); + } + } + + public void setTranslationX(float x) { + setTranslation(x, mState.mTranslationY); + } + + public void setTranslationY(float y) { + setTranslation(mState.mTranslationX, y); + } + + public void setTranslation(float x, float y) { + if (mState.mTranslationX != x || mState.mTranslationY != y) { + mState.mTranslationX = x; + mState.mTranslationY = y; + invalidateSelf(); + } + } + + public void setShadowProperties(int x, int y, int size, int color) { + if (canAnimate()) { + // AnimatedVectorDrawables will not support shadows + return; + } + if (mState.mShadowOffsetX != x || mState.mShadowOffsetY != y + || mState.mShadowSize != size || mState.mShadowColor != color) { + mState.mShadowOffsetX = x; + mState.mShadowOffsetY = y; + mState.mShadowSize = size; + mState.mShadowColor = color; + mShadowPaint.setColorFilter( + new PorterDuffColorFilter(mState.mShadowColor, Mode.SRC_ATOP)); + updateShadowAlpha(); + invalidateSelf(); + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + if (changed) { + // End any existing animations when the visibility changes + jumpToCurrentState(); + } + return changed; + } + + @Override + public void jumpToCurrentState() { + super.jumpToCurrentState(); + if (mAnimatedDrawable != null) { + mAnimatedDrawable.jumpToCurrentState(); + } + } + + @Override + public void setAlpha(int alpha) { + mState.mAlpha = alpha; + mIconPaint.setAlpha(alpha); + updateShadowAlpha(); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + mIconPaint.setColorFilter(colorFilter); + if (mAnimatedDrawable != null) { + if (hasOvalBg()) { + mAnimatedDrawable.setColorFilter( + new PorterDuffColorFilter(mState.mLightColor, PorterDuff.Mode.SRC_IN)); + } else { + mAnimatedDrawable.setColorFilter(colorFilter); + } + } + invalidateSelf(); + } + + public float getDarkIntensity() { + return mState.mDarkIntensity; + } + + public float getRotation() { + return mState.mRotateDegrees; + } + + public float getTranslationX() { + return mState.mTranslationX; + } + + public float getTranslationY() { + return mState.mTranslationY; + } + + @Override + public ConstantState getConstantState() { + return mState; + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getIntrinsicHeight() { + return mState.mBaseHeight + (mState.mShadowSize + Math.abs(mState.mShadowOffsetY)) * 2; + } + + @Override + public int getIntrinsicWidth() { + return mState.mBaseWidth + (mState.mShadowSize + Math.abs(mState.mShadowOffsetX)) * 2; + } + + public boolean canAnimate() { + return mState.mSupportsAnimation; + } + + public void startAnimation() { + if (mAnimatedDrawable != null) { + mAnimatedDrawable.start(); + } + } + + public void resetAnimation() { + if (mAnimatedDrawable != null) { + mAnimatedDrawable.reset(); + } + } + + public void clearAnimationCallbacks() { + if (mAnimatedDrawable != null) { + mAnimatedDrawable.clearAnimationCallbacks(); + } + } + + @Override + public void draw(Canvas canvas) { + Rect bounds = getBounds(); + if (bounds.isEmpty()) { + return; + } + + if (mAnimatedDrawable != null) { + mAnimatedDrawable.draw(canvas); + } else { + // If no cache or previous cached bitmap is hardware/software acceleration does not + // match the current canvas on draw then regenerate + boolean hwBitmapChanged = mState.mIsHardwareBitmap != canvas.isHardwareAccelerated(); + if (hwBitmapChanged) { + mState.mIsHardwareBitmap = canvas.isHardwareAccelerated(); + } + if (mState.mLastDrawnIcon == null || hwBitmapChanged) { + regenerateBitmapIconCache(); + } + canvas.save(); + canvas.translate(mState.mTranslationX, mState.mTranslationY); + canvas.rotate(mState.mRotateDegrees, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2); + + if (mState.mShadowSize > 0) { + if (mState.mLastDrawnShadow == null || hwBitmapChanged) { + regenerateBitmapShadowCache(); + } + + // Translate (with rotation offset) before drawing the shadow + final float radians = (float) (mState.mRotateDegrees * Math.PI / 180); + final float shadowOffsetX = (float) (Math.sin(radians) * mState.mShadowOffsetY + + Math.cos(radians) * mState.mShadowOffsetX) - mState.mTranslationX; + final float shadowOffsetY = (float) (Math.cos(radians) * mState.mShadowOffsetY + - Math.sin(radians) * mState.mShadowOffsetX) - mState.mTranslationY; + canvas.drawBitmap(mState.mLastDrawnShadow, shadowOffsetX, shadowOffsetY, + mShadowPaint); + } + canvas.drawBitmap(mState.mLastDrawnIcon, null, bounds, mIconPaint); + canvas.restore(); + } + } + + @Override + public boolean canApplyTheme() { + return mState.canApplyTheme(); + } + + @ColorInt int getDrawableBackgroundColor() { + return mState.mOvalBackgroundColor.toArgb(); + } + + boolean hasOvalBg() { + return mState.mOvalBackgroundColor != null; + } + + private void regenerateBitmapIconCache() { + final int width = getIntrinsicWidth(); + final int height = getIntrinsicHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + + // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared. + final Drawable d = mState.mChildState.newDrawable().mutate(); + setDrawableBounds(d); + canvas.save(); + if (mState.mHorizontalFlip) { + canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f); + } + d.draw(canvas); + canvas.restore(); + + if (mState.mIsHardwareBitmap) { + bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false); + } + mState.mLastDrawnIcon = bitmap; + } + + private void regenerateBitmapShadowCache() { + if (mState.mShadowSize == 0) { + // No shadow + mState.mLastDrawnIcon = null; + return; + } + + final int width = getIntrinsicWidth(); + final int height = getIntrinsicHeight(); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + // Call mutate, so that the pixel allocation by the underlying vector drawable is cleared. + final Drawable d = mState.mChildState.newDrawable().mutate(); + setDrawableBounds(d); + canvas.save(); + if (mState.mHorizontalFlip) { + canvas.scale(-1f, 1f, width * 0.5f, height * 0.5f); + } + d.draw(canvas); + canvas.restore(); + + // Draws the shadow from original drawable + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, Blur.NORMAL)); + int[] offset = new int[2]; + final Bitmap shadow = bitmap.extractAlpha(paint, offset); + paint.setMaskFilter(null); + bitmap.eraseColor(Color.TRANSPARENT); + canvas.drawBitmap(shadow, offset[0], offset[1], paint); + + if (mState.mIsHardwareBitmap) { + bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false); + } + mState.mLastDrawnShadow = bitmap; + } + + /** + * Set the alpha of the shadow. As dark intensity increases, drop the alpha of the shadow since + * dark color and shadow should not be visible at the same time. + */ + private void updateShadowAlpha() { + // Update the color from the original color's alpha as the max + int alpha = Color.alpha(mState.mShadowColor); + mShadowPaint.setAlpha( + Math.round(alpha * (mState.mAlpha / 255f) * (1 - mState.mDarkIntensity))); + } + + /** + * Prevent shadow clipping by offsetting the drawable bounds by the shadow and its offset + * @param d the drawable to set the bounds + */ + private void setDrawableBounds(Drawable d) { + final int offsetX = mState.mShadowSize + Math.abs(mState.mShadowOffsetX); + final int offsetY = mState.mShadowSize + Math.abs(mState.mShadowOffsetY); + d.setBounds(offsetX, offsetY, getIntrinsicWidth() - offsetX, + getIntrinsicHeight() - offsetY); + } + + private static class ShadowDrawableState extends ConstantState { + int mChangingConfigurations; + int mBaseWidth; + int mBaseHeight; + float mRotateDegrees; + float mTranslationX; + float mTranslationY; + int mShadowOffsetX; + int mShadowOffsetY; + int mShadowSize; + int mShadowColor; + float mDarkIntensity; + int mAlpha; + boolean mHorizontalFlip; + + boolean mIsHardwareBitmap; + Bitmap mLastDrawnIcon; + Bitmap mLastDrawnShadow; + ConstantState mChildState; + + final int mLightColor; + final int mDarkColor; + final boolean mSupportsAnimation; + final Color mOvalBackgroundColor; + + public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor, + boolean animated, boolean horizontalFlip, Color ovalBackgroundColor) { + mLightColor = lightColor; + mDarkColor = darkColor; + mSupportsAnimation = animated; + mAlpha = 255; + mHorizontalFlip = horizontalFlip; + mOvalBackgroundColor = ovalBackgroundColor; + } + + @Override + public Drawable newDrawable() { + return new KeyButtonDrawable(null, this); + } + + @Override + public int getChangingConfigurations() { + return mChangingConfigurations; + } + + @Override + public boolean canApplyTheme() { + return true; + } + } + + /** + * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see + * {@link #create(Context, int, boolean, boolean)}. + */ + public static KeyButtonDrawable create(Context context, @ColorInt int lightColor, + @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow, + Color ovalBackgroundColor) { + final Resources res = context.getResources(); + boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + Drawable d = context.getDrawable(iconResId); + final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor, + isRtl && d.isAutoMirrored(), ovalBackgroundColor); + if (hasShadow) { + int offsetX = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_X, res); + int offsetY = dpToPx(NAV_KEY_BUTTON_SHADOW_OFFSET_Y, res); + int radius = dpToPx(NAV_KEY_BUTTON_SHADOW_RADIUS, res); + int color = NAV_KEY_BUTTON_SHADOW_COLOR; + drawable.setShadowProperties(offsetX, offsetY, radius, color); + } + return drawable; + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java new file mode 100644 index 000000000000..38a63b661ac0 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonRipple.java @@ -0,0 +1,525 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.DimenRes; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.CanvasProperty; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.RecordingCanvas; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Trace; +import android.view.RenderNodeAnimator; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; + +import java.util.ArrayList; +import java.util.HashSet; + +final class KeyButtonRipple extends Drawable { + + private static final float GLOW_MAX_SCALE_FACTOR = 1.35f; + private static final float GLOW_MAX_ALPHA = 0.2f; + private static final float GLOW_MAX_ALPHA_DARK = 0.1f; + private static final int ANIMATION_DURATION_SCALE = 350; + private static final int ANIMATION_DURATION_FADE = 450; + private static final Interpolator ALPHA_OUT_INTERPOLATOR = + new PathInterpolator(0f, 0f, 0.8f, 1f); + + @DimenRes + private final int mMaxWidthResource; + + private Paint mRipplePaint; + private CanvasProperty<Float> mLeftProp; + private CanvasProperty<Float> mTopProp; + private CanvasProperty<Float> mRightProp; + private CanvasProperty<Float> mBottomProp; + private CanvasProperty<Float> mRxProp; + private CanvasProperty<Float> mRyProp; + private CanvasProperty<Paint> mPaintProp; + private float mGlowAlpha = 0f; + private float mGlowScale = 1f; + private boolean mPressed; + private boolean mVisible; + private boolean mDrawingHardwareGlow; + private int mMaxWidth; + private boolean mLastDark; + private boolean mDark; + private boolean mDelayTouchFeedback; + + private final Interpolator mInterpolator = new LogInterpolator(); + private boolean mSupportHardware; + private final View mTargetView; + private final Handler mHandler = new Handler(); + + private final HashSet<Animator> mRunningAnimations = new HashSet<>(); + private final ArrayList<Animator> mTmpArray = new ArrayList<>(); + + private final TraceAnimatorListener mExitHwTraceAnimator = + new TraceAnimatorListener("exitHardware"); + private final TraceAnimatorListener mEnterHwTraceAnimator = + new TraceAnimatorListener("enterHardware"); + + public enum Type { + OVAL, + ROUNDED_RECT + } + + private Type mType = Type.ROUNDED_RECT; + + public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) { + mMaxWidthResource = maxWidthResource; + mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource); + mTargetView = targetView; + } + + public void updateResources() { + mMaxWidth = mTargetView.getContext().getResources() + .getDimensionPixelSize(mMaxWidthResource); + invalidateSelf(); + } + + public void setDarkIntensity(float darkIntensity) { + mDark = darkIntensity >= 0.5f; + } + + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + } + + public void setType(Type type) { + mType = type; + } + + private Paint getRipplePaint() { + if (mRipplePaint == null) { + mRipplePaint = new Paint(); + mRipplePaint.setAntiAlias(true); + mRipplePaint.setColor(mLastDark ? 0xff000000 : 0xffffffff); + } + return mRipplePaint; + } + + private void drawSoftware(Canvas canvas) { + if (mGlowAlpha > 0f) { + final Paint p = getRipplePaint(); + p.setAlpha((int)(mGlowAlpha * 255f)); + + final float w = getBounds().width(); + final float h = getBounds().height(); + final boolean horizontal = w > h; + final float diameter = getRippleSize() * mGlowScale; + final float radius = diameter * .5f; + final float cx = w * .5f; + final float cy = h * .5f; + final float rx = horizontal ? radius : cx; + final float ry = horizontal ? cy : radius; + final float corner = horizontal ? cy : cx; + + if (mType == Type.ROUNDED_RECT) { + canvas.drawRoundRect(cx - rx, cy - ry, cx + rx, cy + ry, corner, corner, p); + } else { + canvas.save(); + canvas.translate(cx, cy); + float r = Math.min(rx, ry); + canvas.drawOval(-r, -r, r, r, p); + canvas.restore(); + } + } + } + + @Override + public void draw(Canvas canvas) { + mSupportHardware = canvas.isHardwareAccelerated(); + if (mSupportHardware) { + drawHardware((RecordingCanvas) canvas); + } else { + drawSoftware(canvas); + } + } + + @Override + public void setAlpha(int alpha) { + // Not supported. + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + // Not supported. + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + private boolean isHorizontal() { + return getBounds().width() > getBounds().height(); + } + + private void drawHardware(RecordingCanvas c) { + if (mDrawingHardwareGlow) { + if (mType == Type.ROUNDED_RECT) { + c.drawRoundRect(mLeftProp, mTopProp, mRightProp, mBottomProp, mRxProp, mRyProp, + mPaintProp); + } else { + CanvasProperty<Float> cx = CanvasProperty.createFloat(getBounds().width() / 2); + CanvasProperty<Float> cy = CanvasProperty.createFloat(getBounds().height() / 2); + int d = Math.min(getBounds().width(), getBounds().height()); + CanvasProperty<Float> r = CanvasProperty.createFloat(1.0f * d / 2); + c.drawCircle(cx, cy, r, mPaintProp); + } + } + } + + /** Gets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */ + public float getGlowAlpha() { + return mGlowAlpha; + } + + /** Sets the glow alpha, used by {@link android.animation.ObjectAnimator} via reflection. */ + public void setGlowAlpha(float x) { + mGlowAlpha = x; + invalidateSelf(); + } + + /** Gets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */ + public float getGlowScale() { + return mGlowScale; + } + + /** Sets the glow scale, used by {@link android.animation.ObjectAnimator} via reflection. */ + public void setGlowScale(float x) { + mGlowScale = x; + invalidateSelf(); + } + + private float getMaxGlowAlpha() { + return mLastDark ? GLOW_MAX_ALPHA_DARK : GLOW_MAX_ALPHA; + } + + @Override + protected boolean onStateChange(int[] state) { + boolean pressed = false; + for (int i = 0; i < state.length; i++) { + if (state[i] == android.R.attr.state_pressed) { + pressed = true; + break; + } + } + if (pressed != mPressed) { + setPressed(pressed); + mPressed = pressed; + return true; + } else { + return false; + } + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + boolean changed = super.setVisible(visible, restart); + if (changed) { + // End any existing animations when the visibility changes + jumpToCurrentState(); + } + return changed; + } + + @Override + public void jumpToCurrentState() { + endAnimations("jumpToCurrentState", false /* cancel */); + } + + @Override + public boolean isStateful() { + return true; + } + + @Override + public boolean hasFocusStateSpecified() { + return true; + } + + public void setPressed(boolean pressed) { + if (mDark != mLastDark && pressed) { + mRipplePaint = null; + mLastDark = mDark; + } + if (mSupportHardware) { + setPressedHardware(pressed); + } else { + setPressedSoftware(pressed); + } + } + + /** + * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch + * is enabled. + */ + public void abortDelayedRipple() { + mHandler.removeCallbacksAndMessages(null); + } + + private void endAnimations(String reason, boolean cancel) { + Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel); + Trace.endSection(); + mVisible = false; + mTmpArray.addAll(mRunningAnimations); + int size = mTmpArray.size(); + for (int i = 0; i < size; i++) { + Animator a = mTmpArray.get(i); + if (cancel) { + a.cancel(); + } else { + a.end(); + } + } + mTmpArray.clear(); + mRunningAnimations.clear(); + mHandler.removeCallbacksAndMessages(null); + } + + private void setPressedSoftware(boolean pressed) { + if (pressed) { + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterSoftware(); + } + } else { + enterSoftware(); + } + } else { + exitSoftware(); + } + } + + private void enterSoftware() { + endAnimations("enterSoftware", true /* cancel */); + mVisible = true; + mGlowAlpha = getMaxGlowAlpha(); + ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", + 0f, GLOW_MAX_SCALE_FACTOR); + scaleAnimator.setInterpolator(mInterpolator); + scaleAnimator.setDuration(ANIMATION_DURATION_SCALE); + scaleAnimator.addListener(mAnimatorListener); + scaleAnimator.start(); + mRunningAnimations.add(scaleAnimator); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitSoftware(); + } + } + + private void exitSoftware() { + ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(this, "glowAlpha", mGlowAlpha, 0f); + alphaAnimator.setInterpolator(ALPHA_OUT_INTERPOLATOR); + alphaAnimator.setDuration(ANIMATION_DURATION_FADE); + alphaAnimator.addListener(mAnimatorListener); + alphaAnimator.start(); + mRunningAnimations.add(alphaAnimator); + } + + private void setPressedHardware(boolean pressed) { + if (pressed) { + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterHardware(); + } + } else { + enterHardware(); + } + } else { + exitHardware(); + } + } + + /** + * Sets the left/top property for the round rect to {@code prop} depending on whether we are + * horizontal or vertical mode. + */ + private void setExtendStart(CanvasProperty<Float> prop) { + if (isHorizontal()) { + mLeftProp = prop; + } else { + mTopProp = prop; + } + } + + private CanvasProperty<Float> getExtendStart() { + return isHorizontal() ? mLeftProp : mTopProp; + } + + /** + * Sets the right/bottom property for the round rect to {@code prop} depending on whether we are + * horizontal or vertical mode. + */ + private void setExtendEnd(CanvasProperty<Float> prop) { + if (isHorizontal()) { + mRightProp = prop; + } else { + mBottomProp = prop; + } + } + + private CanvasProperty<Float> getExtendEnd() { + return isHorizontal() ? mRightProp : mBottomProp; + } + + private int getExtendSize() { + return isHorizontal() ? getBounds().width() : getBounds().height(); + } + + private int getRippleSize() { + int size = isHorizontal() ? getBounds().width() : getBounds().height(); + return Math.min(size, mMaxWidth); + } + + private void enterHardware() { + endAnimations("enterHardware", true /* cancel */); + mVisible = true; + mDrawingHardwareGlow = true; + setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); + final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), + getExtendSize()/2 - GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); + startAnim.setDuration(ANIMATION_DURATION_SCALE); + startAnim.setInterpolator(mInterpolator); + startAnim.addListener(mAnimatorListener); + startAnim.setTarget(mTargetView); + + setExtendEnd(CanvasProperty.createFloat(getExtendSize() / 2)); + final RenderNodeAnimator endAnim = new RenderNodeAnimator(getExtendEnd(), + getExtendSize()/2 + GLOW_MAX_SCALE_FACTOR * getRippleSize()/2); + endAnim.setDuration(ANIMATION_DURATION_SCALE); + endAnim.setInterpolator(mInterpolator); + endAnim.addListener(mAnimatorListener); + endAnim.addListener(mEnterHwTraceAnimator); + endAnim.setTarget(mTargetView); + + if (isHorizontal()) { + mTopProp = CanvasProperty.createFloat(0f); + mBottomProp = CanvasProperty.createFloat(getBounds().height()); + mRxProp = CanvasProperty.createFloat(getBounds().height()/2); + mRyProp = CanvasProperty.createFloat(getBounds().height()/2); + } else { + mLeftProp = CanvasProperty.createFloat(0f); + mRightProp = CanvasProperty.createFloat(getBounds().width()); + mRxProp = CanvasProperty.createFloat(getBounds().width()/2); + mRyProp = CanvasProperty.createFloat(getBounds().width()/2); + } + + mGlowScale = GLOW_MAX_SCALE_FACTOR; + mGlowAlpha = getMaxGlowAlpha(); + mRipplePaint = getRipplePaint(); + mRipplePaint.setAlpha((int) (mGlowAlpha * 255)); + mPaintProp = CanvasProperty.createPaint(mRipplePaint); + + startAnim.start(); + endAnim.start(); + mRunningAnimations.add(startAnim); + mRunningAnimations.add(endAnim); + + invalidateSelf(); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitHardware(); + } + } + + private void exitHardware() { + mPaintProp = CanvasProperty.createPaint(getRipplePaint()); + final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPaintProp, + RenderNodeAnimator.PAINT_ALPHA, 0); + opacityAnim.setDuration(ANIMATION_DURATION_FADE); + opacityAnim.setInterpolator(ALPHA_OUT_INTERPOLATOR); + opacityAnim.addListener(mAnimatorListener); + opacityAnim.addListener(mExitHwTraceAnimator); + opacityAnim.setTarget(mTargetView); + + opacityAnim.start(); + mRunningAnimations.add(opacityAnim); + + invalidateSelf(); + } + + private final AnimatorListenerAdapter mAnimatorListener = + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRunningAnimations.remove(animation); + if (mRunningAnimations.isEmpty() && !mPressed) { + mVisible = false; + mDrawingHardwareGlow = false; + invalidateSelf(); + } + } + }; + + private static final class TraceAnimatorListener extends AnimatorListenerAdapter { + private final String mName; + TraceAnimatorListener(String name) { + mName = name; + } + + @Override + public void onAnimationStart(Animator animation) { + Trace.beginSection("KeyButtonRipple.start." + mName); + Trace.endSection(); + } + + @Override + public void onAnimationCancel(Animator animation) { + Trace.beginSection("KeyButtonRipple.cancel." + mName); + Trace.endSection(); + } + + @Override + public void onAnimationEnd(Animator animation) { + Trace.beginSection("KeyButtonRipple.end." + mName); + Trace.endSection(); + } + } + + /** + * Interpolator with a smooth log deceleration + */ + private static final class LogInterpolator implements Interpolator { + @Override + public float getInterpolation(float input) { + return 1 - (float) Math.pow(400, -input * 1.4); + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java new file mode 100644 index 000000000000..74d30f8f8806 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/KeyButtonView.java @@ -0,0 +1,370 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import static android.view.Display.INVALID_DISPLAY; +import static android.view.KeyEvent.KEYCODE_BACK; +import static android.view.KeyEvent.KEYCODE_UNKNOWN; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.InputMethodService; +import android.media.AudioManager; +import android.os.Bundle; +import android.os.SystemClock; +import android.util.AttributeSet; +import android.view.HapticFeedbackConstants; +import android.view.InputDevice; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.inputmethod.InputConnection; +import android.widget.ImageView; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * @hide + */ +public class KeyButtonView extends ImageView implements ButtonInterface { + private static final String TAG = KeyButtonView.class.getSimpleName(); + + private final boolean mPlaySounds; + private long mDownTime; + private boolean mTracking; + private int mCode; + private int mTouchDownX; + private int mTouchDownY; + private AudioManager mAudioManager; + private boolean mGestureAborted; + @VisibleForTesting boolean mLongClicked; + private OnClickListener mOnClickListener; + private final KeyButtonRipple mRipple; + private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private float mDarkIntensity; + private boolean mHasOvalBg = false; + + private final Runnable mCheckLongPress = new Runnable() { + public void run() { + if (isPressed()) { + // Log.d("KeyButtonView", "longpressed: " + this); + if (isLongClickable()) { + // Just an old-fashioned ImageView + performLongClick(); + mLongClicked = true; + } else { + if (mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + } + mLongClicked = true; + } + } + } + }; + + public KeyButtonView(Context context, AttributeSet attrs) { + super(context, attrs); + + // TODO(b/205803355): Figure out better place to set this. + switch (getId()) { + case com.android.internal.R.id.input_method_nav_back: + mCode = KEYCODE_BACK; + break; + default: + mCode = KEYCODE_UNKNOWN; + break; + } + + mPlaySounds = true; + + setClickable(true); + mAudioManager = context.getSystemService(AudioManager.class); + + mRipple = new KeyButtonRipple(context, this, + com.android.internal.R.dimen.input_method_nav_key_button_ripple_max_width); + setBackground(mRipple); + setWillNotDraw(false); + forceHasOverlappingRendering(false); + } + + @Override + public boolean isClickable() { + return mCode != KEYCODE_UNKNOWN || super.isClickable(); + } + + public void setCode(int code) { + mCode = code; + } + + @Override + public void setOnClickListener(OnClickListener onClickListener) { + super.setOnClickListener(onClickListener); + mOnClickListener = onClickListener; + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + if (mCode != KEYCODE_UNKNOWN) { + info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK, null)); + if (isLongClickable()) { + info.addAction( + new AccessibilityNodeInfo.AccessibilityAction(ACTION_LONG_CLICK, null)); + } + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + if (visibility != View.VISIBLE) { + jumpDrawablesToCurrentState(); + } + } + + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (action == ACTION_CLICK && mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, 0, SystemClock.uptimeMillis()); + sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0); + mTracking = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + playSoundEffect(SoundEffectConstants.CLICK); + return true; + } else if (action == ACTION_LONG_CLICK && mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS); + sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0); + mTracking = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + return true; + } + return super.performAccessibilityActionInternal(action, arguments); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + final boolean showSwipeUI = false; // mOverviewProxyService.shouldShowSwipeUpUI(); + final int action = ev.getAction(); + int x, y; + if (action == MotionEvent.ACTION_DOWN) { + mGestureAborted = false; + } + if (mGestureAborted) { + setPressed(false); + return false; + } + + switch (action) { + case MotionEvent.ACTION_DOWN: + mDownTime = SystemClock.uptimeMillis(); + mLongClicked = false; + setPressed(true); + + // Use raw X and Y to detect gestures in case a parent changes the x and y values + mTouchDownX = (int) ev.getRawX(); + mTouchDownY = (int) ev.getRawY(); + if (mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime); + } else { + // Provide the same haptic feedback that the system offers for virtual keys. + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + } + if (!showSwipeUI) { + playSoundEffect(SoundEffectConstants.CLICK); + } + removeCallbacks(mCheckLongPress); + postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); + break; + case MotionEvent.ACTION_MOVE: + x = (int)ev.getRawX(); + y = (int)ev.getRawY(); + + float slop = getQuickStepTouchSlopPx(getContext()); + if (Math.abs(x - mTouchDownX) > slop || Math.abs(y - mTouchDownY) > slop) { + // When quick step is enabled, prevent animating the ripple triggered by + // setPressed and decide to run it on touch up + setPressed(false); + removeCallbacks(mCheckLongPress); + } + break; + case MotionEvent.ACTION_CANCEL: + setPressed(false); + if (mCode != KEYCODE_UNKNOWN) { + sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); + } + removeCallbacks(mCheckLongPress); + break; + case MotionEvent.ACTION_UP: + final boolean doIt = isPressed() && !mLongClicked; + setPressed(false); + final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150; + if (showSwipeUI) { + if (doIt) { + // Apply haptic feedback on touch up since there is none on touch down + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + playSoundEffect(SoundEffectConstants.CLICK); + } + } else if (doHapticFeedback && !mLongClicked) { + // Always send a release ourselves because it doesn't seem to be sent elsewhere + // and it feels weird to sometimes get a release haptic and other times not. + performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE); + } + if (mCode != KEYCODE_UNKNOWN) { + if (doIt) { + sendEvent(KeyEvent.ACTION_UP, mTracking ? KeyEvent.FLAG_TRACKING : 0); + mTracking = false; + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + } else { + sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED); + } + } else { + // no key code, just a regular ImageView + if (doIt && mOnClickListener != null) { + mOnClickListener.onClick(this); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + } + } + removeCallbacks(mCheckLongPress); + break; + } + + return true; + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + + if (drawable == null) { + return; + } + KeyButtonDrawable keyButtonDrawable = (KeyButtonDrawable) drawable; + keyButtonDrawable.setDarkIntensity(mDarkIntensity); + mHasOvalBg = keyButtonDrawable.hasOvalBg(); + if (mHasOvalBg) { + mOvalBgPaint.setColor(keyButtonDrawable.getDrawableBackgroundColor()); + } + mRipple.setType(keyButtonDrawable.hasOvalBg() ? KeyButtonRipple.Type.OVAL + : KeyButtonRipple.Type.ROUNDED_RECT); + } + + public void playSoundEffect(int soundConstant) { + if (!mPlaySounds) return; + mAudioManager.playSoundEffect(soundConstant); + } + + public void sendEvent(int action, int flags) { + sendEvent(action, flags, SystemClock.uptimeMillis()); + } + + private void sendEvent(int action, int flags, long when) { + if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) { + if (action == MotionEvent.ACTION_UP) { + // TODO(b/205803355): Implement notifyBackAction(); + } + } + + // TODO(b/205803355): Consolidate this logic to somewhere else. + if (mContext instanceof InputMethodService) { + final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0; + final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount, + 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, + flags | KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_VIRTUAL_HARD_KEY, + InputDevice.SOURCE_KEYBOARD); + int displayId = INVALID_DISPLAY; + + // Make KeyEvent work on multi-display environment + if (getDisplay() != null) { + displayId = getDisplay().getDisplayId(); + } + if (displayId != INVALID_DISPLAY) { + ev.setDisplayId(displayId); + } + final InputMethodService ims = (InputMethodService) mContext; + final boolean handled; + switch (action) { + case KeyEvent.ACTION_DOWN: + handled = ims.onKeyDown(ev.getKeyCode(), ev); + mTracking = handled && ev.getRepeatCount() == 0 && + (ev.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0; + break; + case KeyEvent.ACTION_UP: + handled = ims.onKeyUp(ev.getKeyCode(), ev); + break; + default: + handled = false; + break; + } + if (!handled) { + final InputConnection ic = ims.getCurrentInputConnection(); + if (ic != null) { + ic.sendKeyEvent(ev); + } + } + } + } + + @Override + public void setDarkIntensity(float darkIntensity) { + mDarkIntensity = darkIntensity; + + Drawable drawable = getDrawable(); + if (drawable != null) { + ((KeyButtonDrawable) drawable).setDarkIntensity(darkIntensity); + // Since we reuse the same drawable for multiple views, we need to invalidate the view + // manually. + invalidate(); + } + mRipple.setDarkIntensity(darkIntensity); + } + + @Override + public void setDelayTouchFeedback(boolean shouldDelay) { + mRipple.setDelayTouchFeedback(shouldDelay); + } + + @Override + public void draw(Canvas canvas) { + if (mHasOvalBg) { + int d = Math.min(getWidth(), getHeight()); + canvas.drawOval(0, 0, d, d, mOvalBgPaint); + } + super.draw(canvas); + } + + /** + * Ratio of quickstep touch slop (when system takes over the touch) to view touch slop + */ + public static final float QUICKSTEP_TOUCH_SLOP_RATIO = 3; + + /** + * Touch slop for quickstep gesture + */ + private static float getQuickStepTouchSlopPx(Context context) { + return QUICKSTEP_TOUCH_SLOP_RATIO * ViewConfiguration.get(context).getScaledTouchSlop(); + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java new file mode 100644 index 000000000000..93c54395f972 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarConstants.java @@ -0,0 +1,62 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import android.annotation.ColorInt; + +final class NavigationBarConstants { + private NavigationBarConstants() { + // Not intended to be instantiated. + } + + // Copied from "navbar_back_button_ime_offset" + // TODO(b/215443343): Handle this in the drawable then remove this constant. + static final float NAVBAR_BACK_BUTTON_IME_OFFSET = 2.0f; + + // Copied from "light_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml + @ColorInt + static final int LIGHT_MODE_ICON_COLOR_SINGLE_TONE = 0xffffffff; + + // Copied from "dark_mode_icon_color_single_tone" at packages/SettingsLib/res/values/colors.xml + @ColorInt + static final int DARK_MODE_ICON_COLOR_SINGLE_TONE = 0x99000000; + + // Copied from "navigation_bar_deadzone_hold" + static final int NAVIGATION_BAR_DEADZONE_HOLD = 333; + + // Copied from "navigation_bar_deadzone_hold" + static final int NAVIGATION_BAR_DEADZONE_DECAY = 333; + + // Copied from "navigation_bar_deadzone_size" + static final float NAVIGATION_BAR_DEADZONE_SIZE = 12.0f; + + // Copied from "navigation_bar_deadzone_size_max" + static final float NAVIGATION_BAR_DEADZONE_SIZE_MAX = 32.0f; + + // Copied from "nav_key_button_shadow_offset_x" + static final float NAV_KEY_BUTTON_SHADOW_OFFSET_X = 0.0f; + + // Copied from "nav_key_button_shadow_offset_y" + static final float NAV_KEY_BUTTON_SHADOW_OFFSET_Y = 1.0f; + + // Copied from "nav_key_button_shadow_radius" + static final float NAV_KEY_BUTTON_SHADOW_RADIUS = 0.5f; + + // Copied from "nav_key_button_shadow_color" + @ColorInt + static final int NAV_KEY_BUTTON_SHADOW_COLOR = 0x30000000; +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java new file mode 100644 index 000000000000..f01173e0fdae --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarFrame.java @@ -0,0 +1,62 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import static android.view.MotionEvent.ACTION_OUTSIDE; + +import android.annotation.AttrRes; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +/** + * @hide + */ +public final class NavigationBarFrame extends FrameLayout { + + private DeadZone mDeadZone = null; + + public NavigationBarFrame(@NonNull Context context) { + super(context); + } + + public NavigationBarFrame(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NavigationBarFrame(@NonNull Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setDeadZone(@NonNull DeadZone deadZone) { + mDeadZone = deadZone; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (event.getAction() == ACTION_OUTSIDE) { + if (mDeadZone != null) { + return mDeadZone.onTouchEvent(event); + } + } + return super.dispatchTouchEvent(event); + } +}
\ No newline at end of file diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java new file mode 100644 index 000000000000..d488890b27d4 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarInflaterView.java @@ -0,0 +1,429 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.inputmethodservice.navigationbar.ReverseLinearLayout.ReverseRelativeLayout; +import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.Space; + +/** + * @hide + */ +public final class NavigationBarInflaterView extends FrameLayout { + + private static final String TAG = "NavBarInflater"; + + public static final String NAV_BAR_VIEWS = "sysui_nav_bar"; + public static final String NAV_BAR_LEFT = "sysui_nav_bar_left"; + public static final String NAV_BAR_RIGHT = "sysui_nav_bar_right"; + + public static final String MENU_IME_ROTATE = "menu_ime"; + public static final String BACK = "back"; + public static final String HOME = "home"; + public static final String RECENT = "recent"; + public static final String NAVSPACE = "space"; + public static final String CLIPBOARD = "clipboard"; + public static final String HOME_HANDLE = "home_handle"; + public static final String KEY = "key"; + public static final String LEFT = "left"; + public static final String RIGHT = "right"; + public static final String CONTEXTUAL = "contextual"; + public static final String IME_SWITCHER = "ime_switcher"; + + public static final String GRAVITY_SEPARATOR = ";"; + public static final String BUTTON_SEPARATOR = ","; + + public static final String SIZE_MOD_START = "["; + public static final String SIZE_MOD_END = "]"; + + public static final String KEY_CODE_START = "("; + public static final String KEY_IMAGE_DELIM = ":"; + public static final String KEY_CODE_END = ")"; + private static final String WEIGHT_SUFFIX = "W"; + private static final String WEIGHT_CENTERED_SUFFIX = "WC"; + private static final String ABSOLUTE_SUFFIX = "A"; + private static final String ABSOLUTE_VERTICAL_CENTERED_SUFFIX = "C"; + + // Copied from "config_navBarLayoutHandle: + private static final String CONFIG_NAV_BAR_LAYOUT_HANDLE = + "back[70AC];home_handle;ime_switcher[70AC]"; + + protected LayoutInflater mLayoutInflater; + protected LayoutInflater mLandscapeInflater; + + protected FrameLayout mHorizontal; + + SparseArray<ButtonDispatcher> mButtonDispatchers; + + private View mLastPortrait; + private View mLastLandscape; + + private boolean mAlternativeOrder; + + public NavigationBarInflaterView(Context context, AttributeSet attrs) { + super(context, attrs); + createInflaters(); + } + + void createInflaters() { + mLayoutInflater = LayoutInflater.from(mContext); + Configuration landscape = new Configuration(); + landscape.setTo(mContext.getResources().getConfiguration()); + landscape.orientation = Configuration.ORIENTATION_LANDSCAPE; + mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + inflateChildren(); + clearViews(); + inflateLayout(getDefaultLayout()); + } + + private void inflateChildren() { + removeAllViews(); + mHorizontal = (FrameLayout) mLayoutInflater.inflate( + com.android.internal.R.layout.input_method_navigation_layout, + this /* root */, false /* attachToRoot */); + addView(mHorizontal); + updateAlternativeOrder(); + } + + String getDefaultLayout() { + return CONFIG_NAV_BAR_LAYOUT_HANDLE; + } + + public void setButtonDispatchers(SparseArray<ButtonDispatcher> buttonDispatchers) { + mButtonDispatchers = buttonDispatchers; + for (int i = 0; i < buttonDispatchers.size(); i++) { + initiallyFill(buttonDispatchers.valueAt(i)); + } + } + + void updateButtonDispatchersCurrentView() { + if (mButtonDispatchers != null) { + View view = mHorizontal; + for (int i = 0; i < mButtonDispatchers.size(); i++) { + final ButtonDispatcher dispatcher = mButtonDispatchers.valueAt(i); + dispatcher.setCurrentView(view); + } + } + } + + void setAlternativeOrder(boolean alternativeOrder) { + if (alternativeOrder != mAlternativeOrder) { + mAlternativeOrder = alternativeOrder; + updateAlternativeOrder(); + } + } + + private void updateAlternativeOrder() { + updateAlternativeOrder(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group)); + updateAlternativeOrder(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_center_group)); + } + + private void updateAlternativeOrder(View v) { + if (v instanceof ReverseLinearLayout) { + ((ReverseLinearLayout) v).setAlternativeOrder(mAlternativeOrder); + } + } + + private void initiallyFill( + ButtonDispatcher buttonDispatcher) { + addAll(buttonDispatcher, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group)); + addAll(buttonDispatcher, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_center_group)); + } + + private void addAll(ButtonDispatcher buttonDispatcher, ViewGroup parent) { + for (int i = 0; i < parent.getChildCount(); i++) { + // Need to manually search for each id, just in case each group has more than one + // of a single id. It probably mostly a waste of time, but shouldn't take long + // and will only happen once. + if (parent.getChildAt(i).getId() == buttonDispatcher.getId()) { + buttonDispatcher.addView(parent.getChildAt(i)); + } + if (parent.getChildAt(i) instanceof ViewGroup) { + addAll(buttonDispatcher, (ViewGroup) parent.getChildAt(i)); + } + } + } + + protected void inflateLayout(String newLayout) { + if (newLayout == null) { + newLayout = getDefaultLayout(); + } + String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3); + if (sets.length != 3) { + Log.d(TAG, "Invalid layout."); + newLayout = getDefaultLayout(); + sets = newLayout.split(GRAVITY_SEPARATOR, 3); + } + String[] start = sets[0].split(BUTTON_SEPARATOR); + String[] center = sets[1].split(BUTTON_SEPARATOR); + String[] end = sets[2].split(BUTTON_SEPARATOR); + // Inflate these in start to end order or accessibility traversal will be messed up. + inflateButtons(start, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group), + false /* landscape */, true /* start */); + + inflateButtons(center, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_center_group), + false /* landscape */, false /* start */); + + addGravitySpacer(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group)); + + inflateButtons(end, mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_ends_group), + false /* landscape */, false /* start */); + + updateButtonDispatchersCurrentView(); + } + + private void addGravitySpacer(LinearLayout layout) { + layout.addView(new Space(mContext), new LinearLayout.LayoutParams(0, 0, 1)); + } + + private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape, + boolean start) { + for (int i = 0; i < buttons.length; i++) { + inflateButton(buttons[i], parent, landscape, start); + } + } + + private ViewGroup.LayoutParams copy(ViewGroup.LayoutParams layoutParams) { + if (layoutParams instanceof LinearLayout.LayoutParams) { + return new LinearLayout.LayoutParams(layoutParams.width, layoutParams.height, + ((LinearLayout.LayoutParams) layoutParams).weight); + } + return new LayoutParams(layoutParams.width, layoutParams.height); + } + + @Nullable + protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape, + boolean start) { + LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater; + View v = createView(buttonSpec, parent, inflater); + if (v == null) return null; + + v = applySize(v, buttonSpec, landscape, start); + parent.addView(v); + addToDispatchers(v); + View lastView = landscape ? mLastLandscape : mLastPortrait; + View accessibilityView = v; + if (v instanceof ReverseRelativeLayout) { + accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0); + } + if (lastView != null) { + accessibilityView.setAccessibilityTraversalAfter(lastView.getId()); + } + if (landscape) { + mLastLandscape = accessibilityView; + } else { + mLastPortrait = accessibilityView; + } + return v; + } + + private View applySize(View v, String buttonSpec, boolean landscape, boolean start) { + String sizeStr = extractSize(buttonSpec); + if (sizeStr == null) return v; + + if (sizeStr.contains(WEIGHT_SUFFIX) || sizeStr.contains(ABSOLUTE_SUFFIX)) { + // To support gravity, wrap in RelativeLayout and apply gravity to it. + // Children wanting to use gravity must be smaller than the frame. + ReverseRelativeLayout frame = new ReverseRelativeLayout(mContext); + LayoutParams childParams = new LayoutParams(v.getLayoutParams()); + + // Compute gravity to apply + int gravity = (landscape) ? (start ? Gravity.TOP : Gravity.BOTTOM) + : (start ? Gravity.START : Gravity.END); + if (sizeStr.endsWith(WEIGHT_CENTERED_SUFFIX)) { + gravity = Gravity.CENTER; + } else if (sizeStr.endsWith(ABSOLUTE_VERTICAL_CENTERED_SUFFIX)) { + gravity = Gravity.CENTER_VERTICAL; + } + + // Set default gravity, flipped if needed in reversed layouts (270 RTL and 90 LTR) + frame.setDefaultGravity(gravity); + frame.setGravity(gravity); // Apply gravity to root + + frame.addView(v, childParams); + + if (sizeStr.contains(WEIGHT_SUFFIX)) { + // Use weighting to set the width of the frame + float weight = Float.parseFloat( + sizeStr.substring(0, sizeStr.indexOf(WEIGHT_SUFFIX))); + frame.setLayoutParams(new LinearLayout.LayoutParams(0, MATCH_PARENT, weight)); + } else { + int width = (int) convertDpToPx(mContext, + Float.parseFloat(sizeStr.substring(0, sizeStr.indexOf(ABSOLUTE_SUFFIX)))); + frame.setLayoutParams(new LinearLayout.LayoutParams(width, MATCH_PARENT)); + } + + // Ensure ripples can be drawn outside bounds + frame.setClipChildren(false); + frame.setClipToPadding(false); + + return frame; + } + + float size = Float.parseFloat(sizeStr); + ViewGroup.LayoutParams params = v.getLayoutParams(); + params.width = (int) (params.width * size); + return v; + } + + View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) { + View v = null; + String button = extractButton(buttonSpec); + if (LEFT.equals(button)) { + button = extractButton(NAVSPACE); + } else if (RIGHT.equals(button)) { + button = extractButton(MENU_IME_ROTATE); + } + if (HOME.equals(button)) { + //v = inflater.inflate(R.layout.home, parent, false); + } else if (BACK.equals(button)) { + v = inflater.inflate(com.android.internal.R.layout.input_method_nav_back, parent, + false); + } else if (RECENT.equals(button)) { + //v = inflater.inflate(R.layout.recent_apps, parent, false); + } else if (MENU_IME_ROTATE.equals(button)) { + //v = inflater.inflate(R.layout.menu_ime, parent, false); + } else if (NAVSPACE.equals(button)) { + //v = inflater.inflate(R.layout.nav_key_space, parent, false); + } else if (CLIPBOARD.equals(button)) { + //v = inflater.inflate(R.layout.clipboard, parent, false); + } else if (CONTEXTUAL.equals(button)) { + //v = inflater.inflate(R.layout.contextual, parent, false); + } else if (HOME_HANDLE.equals(button)) { + v = inflater.inflate(com.android.internal.R.layout.input_method_nav_home_handle, + parent, false); + } else if (IME_SWITCHER.equals(button)) { + v = inflater.inflate(com.android.internal.R.layout.input_method_nav_ime_switcher, + parent, false); + } else if (button.startsWith(KEY)) { + /* + String uri = extractImage(button); + int code = extractKeycode(button); + v = inflater.inflate(R.layout.custom_key, parent, false); + ((KeyButtonView) v).setCode(code); + if (uri != null) { + if (uri.contains(":")) { + ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri)); + } else if (uri.contains("/")) { + int index = uri.indexOf('/'); + String pkg = uri.substring(0, index); + int id = Integer.parseInt(uri.substring(index + 1)); + ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id)); + } + } + */ + } + return v; + } + + /* + public static String extractImage(String buttonSpec) { + if (!buttonSpec.contains(KEY_IMAGE_DELIM)) { + return null; + } + final int start = buttonSpec.indexOf(KEY_IMAGE_DELIM); + String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_CODE_END)); + return subStr; + } + + public static int extractKeycode(String buttonSpec) { + if (!buttonSpec.contains(KEY_CODE_START)) { + return 1; + } + final int start = buttonSpec.indexOf(KEY_CODE_START); + String subStr = buttonSpec.substring(start + 1, buttonSpec.indexOf(KEY_IMAGE_DELIM)); + return Integer.parseInt(subStr); + } + */ + + public static String extractSize(String buttonSpec) { + if (!buttonSpec.contains(SIZE_MOD_START)) { + return null; + } + final int sizeStart = buttonSpec.indexOf(SIZE_MOD_START); + return buttonSpec.substring(sizeStart + 1, buttonSpec.indexOf(SIZE_MOD_END)); + } + + public static String extractButton(String buttonSpec) { + if (!buttonSpec.contains(SIZE_MOD_START)) { + return buttonSpec; + } + return buttonSpec.substring(0, buttonSpec.indexOf(SIZE_MOD_START)); + } + + private void addToDispatchers(View v) { + if (mButtonDispatchers != null) { + final int indexOfKey = mButtonDispatchers.indexOfKey(v.getId()); + if (indexOfKey >= 0) { + mButtonDispatchers.valueAt(indexOfKey).addView(v); + } + if (v instanceof ViewGroup) { + final ViewGroup viewGroup = (ViewGroup)v; + final int N = viewGroup.getChildCount(); + for (int i = 0; i < N; i++) { + addToDispatchers(viewGroup.getChildAt(i)); + } + } + } + } + + private void clearViews() { + if (mButtonDispatchers != null) { + for (int i = 0; i < mButtonDispatchers.size(); i++) { + mButtonDispatchers.valueAt(i).clear(); + } + } + clearAllChildren(mHorizontal.findViewById( + com.android.internal.R.id.input_method_nav_buttons)); + } + + private void clearAllChildren(ViewGroup group) { + for (int i = 0; i < group.getChildCount(); i++) { + ((ViewGroup) group.getChildAt(i)).removeAllViews(); + } + } + + private static float convertDpToPx(Context context, float dp) { + return dp * context.getResources().getDisplayMetrics().density; + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java new file mode 100644 index 000000000000..c6096d7ba0a1 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarUtils.java @@ -0,0 +1,42 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import static android.util.TypedValue.COMPLEX_UNIT_DIP; + +import android.content.res.Resources; +import android.util.TypedValue; + +final class NavigationBarUtils { + private NavigationBarUtils() { + // Not intended to be instantiated. + } + + /** + * A utility method to convert "dp" to "pixel". + * + * <p>TODO(b/215443343): Remove this method by migrating DP values from + * {@link NavigationBarConstants} to resource files.</p> + * + * @param dpValue "dp" value to be converted to "pixel" + * @param res {@link Resources} to be used when dealing with "dp". + * @return the pixels for a given dp value. + */ + static int dpToPx(float dpValue, Resources res) { + return (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, dpValue, res.getDisplayMetrics()); + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java new file mode 100644 index 000000000000..42847784dd2b --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationBarView.java @@ -0,0 +1,380 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import static android.inputmethodservice.navigationbar.NavigationBarConstants.DARK_MODE_ICON_COLOR_SINGLE_TONE; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.LIGHT_MODE_ICON_COLOR_SINGLE_TONE; +import static android.inputmethodservice.navigationbar.NavigationBarConstants.NAVBAR_BACK_BUTTON_IME_OFFSET; +import static android.inputmethodservice.navigationbar.NavigationBarUtils.dpToPx; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; + +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.annotation.DrawableRes; +import android.annotation.FloatRange; +import android.app.StatusBarManager; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.Log; +import android.util.SparseArray; +import android.view.Display; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.View; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; +import android.view.inputmethod.InputMethodManager; +import android.widget.FrameLayout; + +import java.util.function.Consumer; + +/** + * @hide + */ +public final class NavigationBarView extends FrameLayout { + final static boolean DEBUG = false; + final static String TAG = "NavBarView"; + + // Copied from com.android.systemui.animation.Interpolators#FAST_OUT_SLOW_IN + private static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); + + // The current view is always mHorizontal. + View mCurrentView = null; + private View mHorizontal; + + private int mCurrentRotation = -1; + + int mDisabledFlags = 0; + int mNavigationIconHints = StatusBarManager.NAVIGATION_HINT_BACK_ALT; + private final int mNavBarMode = NAV_BAR_MODE_GESTURAL; + + private KeyButtonDrawable mBackIcon; + private KeyButtonDrawable mImeSwitcherIcon; + private Context mLightContext; + private final int mLightIconColor; + private final int mDarkIconColor; + + private final android.inputmethodservice.navigationbar.DeadZone mDeadZone; + private boolean mDeadZoneConsuming = false; + + private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>(); + private Configuration mConfiguration; + private Configuration mTmpLastConfiguration; + + private NavigationBarInflaterView mNavigationInflaterView; + + public NavigationBarView(Context context, AttributeSet attrs) { + super(context, attrs); + + mLightContext = context; + mLightIconColor = LIGHT_MODE_ICON_COLOR_SINGLE_TONE; + mDarkIconColor = DARK_MODE_ICON_COLOR_SINGLE_TONE; + + mConfiguration = new Configuration(); + mTmpLastConfiguration = new Configuration(); + mConfiguration.updateFrom(context.getResources().getConfiguration()); + + mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_back, + new ButtonDispatcher(com.android.internal.R.id.input_method_nav_back)); + mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_ime_switcher, + new ButtonDispatcher(com.android.internal.R.id.input_method_nav_ime_switcher)); + mButtonDispatchers.put(com.android.internal.R.id.input_method_nav_home_handle, + new ButtonDispatcher(com.android.internal.R.id.input_method_nav_home_handle)); + + mDeadZone = new android.inputmethodservice.navigationbar.DeadZone(this); + + getImeSwitchButton().setOnClickListener(view -> view.getContext() + .getSystemService(InputMethodManager.class).showInputMethodPicker()); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + shouldDeadZoneConsumeTouchEvents(event); + return super.onTouchEvent(event); + } + + private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) { + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_DOWN) { + mDeadZoneConsuming = false; + } + if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) { + switch (action) { + case MotionEvent.ACTION_DOWN: + mDeadZoneConsuming = true; + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mDeadZoneConsuming = false; + break; + } + return true; + } + return false; + } + + public View getCurrentView() { + return mCurrentView; + } + + /** + * Applies {@param consumer} to each of the nav bar views. + */ + public void forEachView(Consumer<View> consumer) { + if (mHorizontal != null) { + consumer.accept(mHorizontal); + } + } + + public ButtonDispatcher getBackButton() { + return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_back); + } + + public ButtonDispatcher getImeSwitchButton() { + return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_ime_switcher); + } + + public ButtonDispatcher getHomeHandle() { + return mButtonDispatchers.get(com.android.internal.R.id.input_method_nav_home_handle); + } + + public SparseArray<ButtonDispatcher> getButtonDispatchers() { + return mButtonDispatchers; + } + + private void reloadNavIcons() { + updateIcons(Configuration.EMPTY); + } + + private void updateIcons(Configuration oldConfig) { + final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation; + final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi; + final boolean dirChange = + oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection(); + + if (densityChange || dirChange) { + mImeSwitcherIcon = getDrawable(com.android.internal.R.drawable.ic_ime_switcher); + } + if (orientationChange || densityChange || dirChange) { + mBackIcon = getBackDrawable(); + } + } + + public KeyButtonDrawable getBackDrawable() { + KeyButtonDrawable drawable = getDrawable(com.android.internal.R.drawable.ic_ime_nav_back); + orientBackButton(drawable); + return drawable; + } + + /** + * @return whether this nav bar mode is edge to edge + */ + public static boolean isGesturalMode(int mode) { + return mode == NAV_BAR_MODE_GESTURAL; + } + + private void orientBackButton(KeyButtonDrawable drawable) { + final boolean useAltBack = + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + float degrees = useAltBack ? (isRtl ? 90 : -90) : 0; + if (drawable.getRotation() == degrees) { + return; + } + + if (isGesturalMode(mNavBarMode)) { + drawable.setRotation(degrees); + return; + } + + // Animate the back button's rotation to the new degrees and only in portrait move up the + // back button to line up with the other buttons + float targetY = useAltBack + ? - dpToPx(NAVBAR_BACK_BUTTON_IME_OFFSET, getResources()) + : 0; + ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable, + PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees), + PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY)); + navBarAnimator.setInterpolator(FAST_OUT_SLOW_IN); + navBarAnimator.setDuration(200); + navBarAnimator.start(); + } + + private KeyButtonDrawable getDrawable(@DrawableRes int icon) { + return KeyButtonDrawable.create(mLightContext, mLightIconColor, mDarkIconColor, icon, + true /* hasShadow */, null /* ovalBackgroundColor */); + } + + @Override + public void setLayoutDirection(int layoutDirection) { + reloadNavIcons(); + + super.setLayoutDirection(layoutDirection); + } + + public void setNavigationIconHints(int hints) { + if (hints == mNavigationIconHints) return; + final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + final boolean oldBackAlt = + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + if (newBackAlt != oldBackAlt) { + //onImeVisibilityChanged(newBackAlt); + } + + if (DEBUG) { + android.widget.Toast.makeText(getContext(), "Navigation icon hints = " + hints, 500) + .show(); + } + mNavigationIconHints = hints; + updateNavButtonIcons(); + } + + public void setDisabledFlags(int disabledFlags) { + if (mDisabledFlags == disabledFlags) return; + + mDisabledFlags = disabledFlags; + + updateNavButtonIcons(); + } + + public void updateNavButtonIcons() { + // We have to replace or restore the back and home button icons when exiting or entering + // carmode, respectively. Recents are not available in CarMode in nav bar so change + // to recent icon is not required. + KeyButtonDrawable backIcon = mBackIcon; + orientBackButton(backIcon); + getBackButton().setImageDrawable(backIcon); + + getImeSwitchButton().setImageDrawable(mImeSwitcherIcon); + + // Update IME button visibility, a11y and rotate button always overrides the appearance + final boolean imeSwitcherVisible = + (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0; + getImeSwitchButton().setVisibility(imeSwitcherVisible ? View.VISIBLE : View.INVISIBLE); + + getBackButton().setVisibility(View.VISIBLE); + getHomeHandle().setVisibility(View.INVISIBLE); + + // We used to be reporting the touch regions via notifyActiveTouchRegions() here. + // TODO(b/215593010): Consider taking care of this in the Launcher side. + } + + private Display getContextDisplay() { + return getContext().getDisplay(); + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + mNavigationInflaterView = findViewById(com.android.internal.R.id.input_method_nav_inflater); + mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); + + updateOrientationViews(); + reloadNavIcons(); + } + + @Override + protected void onDraw(Canvas canvas) { + mDeadZone.onDraw(canvas); + super.onDraw(canvas); + } + + private void updateOrientationViews() { + mHorizontal = findViewById(com.android.internal.R.id.input_method_nav_horizontal); + + updateCurrentView(); + } + + private void updateCurrentView() { + resetViews(); + mCurrentView = mHorizontal; + mCurrentView.setVisibility(View.VISIBLE); + mCurrentRotation = getContextDisplay().getRotation(); + mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90); + mNavigationInflaterView.updateButtonDispatchersCurrentView(); + } + + private void resetViews() { + mHorizontal.setVisibility(View.GONE); + } + + public void reorient() { + updateCurrentView(); + + final android.inputmethodservice.navigationbar.NavigationBarFrame frame = + getRootView().findViewByPredicate(view -> view instanceof NavigationBarFrame); + frame.setDeadZone(mDeadZone); + mDeadZone.onConfigurationChanged(mCurrentRotation); + + if (DEBUG) { + Log.d(TAG, "reorient(): rot=" + mCurrentRotation); + } + + // Resolve layout direction if not resolved since components changing layout direction such + // as changing languages will recreate this view and the direction will be resolved later + if (!isLayoutDirectionResolved()) { + resolveLayoutDirection(); + } + updateNavButtonIcons(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mTmpLastConfiguration.updateFrom(mConfiguration); + final int changes = mConfiguration.updateFrom(newConfig); + + updateIcons(mTmpLastConfiguration); + if (mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi + || mTmpLastConfiguration.getLayoutDirection() + != mConfiguration.getLayoutDirection()) { + // If car mode or density changes, we need to reset the icons. + updateNavButtonIcons(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // This needs to happen first as it can changed the enabled state which can affect whether + // the back button is visible + requestApplyInsets(); + reorient(); + updateNavButtonIcons(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + for (int i = 0; i < mButtonDispatchers.size(); ++i) { + mButtonDispatchers.valueAt(i).onDestroy(); + } + } + + public void setDarkIntensity(@FloatRange(from = 0.0f, to = 1.0f) float intensity) { + for (int i = 0; i < mButtonDispatchers.size(); ++i) { + mButtonDispatchers.valueAt(i).setDarkIntensity(intensity); + } + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java new file mode 100644 index 000000000000..273cafb7fca7 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/NavigationHandle.java @@ -0,0 +1,57 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * TODO(b/215443343): Remove this file, as IME actually doesn't use this. + * + * @hide + */ +public class NavigationHandle extends View implements ButtonInterface { + + public NavigationHandle(Context context) { + this(context, null); + } + + public NavigationHandle(Context context, AttributeSet attr) { + super(context, attr); + setFocusable(false); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return false; + } + + @Override + public void setImageDrawable(Drawable drawable) { + } + + @Override + public void setDarkIntensity(float intensity) { + } + + @Override + public void setDelayTouchFeedback(boolean shouldDelay) { + } +} diff --git a/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java new file mode 100644 index 000000000000..68163c35f784 --- /dev/null +++ b/core/java/android/inputmethodservice/navigationbar/ReverseLinearLayout.java @@ -0,0 +1,172 @@ +/* + * 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 android.inputmethodservice.navigationbar; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; + +import java.util.ArrayList; + +/** + * Automatically reverses the order of children as they are added. + * Also reverse the width and height values of layout params + * @hide + */ +public class ReverseLinearLayout extends LinearLayout { + + /** If true, the layout is reversed vs. a regular linear layout */ + private boolean mIsLayoutReverse; + + /** If true, the layout is opposite to it's natural reversity from the layout direction */ + private boolean mIsAlternativeOrder; + + public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + updateOrder(); + } + + @Override + public void addView(View child) { + reverseParams(child.getLayoutParams(), child, mIsLayoutReverse); + if (mIsLayoutReverse) { + super.addView(child, 0); + } else { + super.addView(child); + } + } + + @Override + public void addView(View child, ViewGroup.LayoutParams params) { + reverseParams(params, child, mIsLayoutReverse); + if (mIsLayoutReverse) { + super.addView(child, 0, params); + } else { + super.addView(child, params); + } + } + + @Override + public void onRtlPropertiesChanged(int layoutDirection) { + super.onRtlPropertiesChanged(layoutDirection); + updateOrder(); + } + + public void setAlternativeOrder(boolean alternative) { + mIsAlternativeOrder = alternative; + updateOrder(); + } + + /** + * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we + * have to do it manually + */ + private void updateOrder() { + boolean isLayoutRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; + boolean isLayoutReverse = isLayoutRtl ^ mIsAlternativeOrder; + + if (mIsLayoutReverse != isLayoutReverse) { + // reversity changed, swap the order of all views. + int childCount = getChildCount(); + ArrayList<View> childList = new ArrayList<>(childCount); + for (int i = 0; i < childCount; i++) { + childList.add(getChildAt(i)); + } + removeAllViews(); + for (int i = childCount - 1; i >= 0; i--) { + final View child = childList.get(i); + super.addView(child); + } + mIsLayoutReverse = isLayoutReverse; + } + } + + private static void reverseParams(ViewGroup.LayoutParams params, View child, + boolean isLayoutReverse) { + if (child instanceof Reversible) { + ((Reversible) child).reverse(isLayoutReverse); + } + if (child.getPaddingLeft() == child.getPaddingRight() + && child.getPaddingTop() == child.getPaddingBottom()) { + child.setPadding(child.getPaddingTop(), child.getPaddingLeft(), + child.getPaddingTop(), child.getPaddingLeft()); + } + if (params == null) { + return; + } + int width = params.width; + params.width = params.height; + params.height = width; + } + + interface Reversible { + void reverse(boolean isLayoutReverse); + } + + public static class ReverseRelativeLayout extends RelativeLayout implements Reversible { + + public ReverseRelativeLayout(Context context) { + super(context); + } + + @Override + public void reverse(boolean isLayoutReverse) { + updateGravity(isLayoutReverse); + reverseGroup(this, isLayoutReverse); + } + + private int mDefaultGravity = Gravity.NO_GRAVITY; + public void setDefaultGravity(int gravity) { + mDefaultGravity = gravity; + } + + public void updateGravity(boolean isLayoutReverse) { + // Flip gravity if top of bottom is used + if (mDefaultGravity != Gravity.TOP && mDefaultGravity != Gravity.BOTTOM) return; + + // Use the default (intended for 270 LTR and 90 RTL) unless layout is otherwise + int gravityToApply = mDefaultGravity; + if (isLayoutReverse) { + gravityToApply = mDefaultGravity == Gravity.TOP ? Gravity.BOTTOM : Gravity.TOP; + } + + if (getGravity() != gravityToApply) setGravity(gravityToApply); + } + } + + private static void reverseGroup(ViewGroup group, boolean isLayoutReverse) { + for (int i = 0; i < group.getChildCount(); i++) { + final View child = group.getChildAt(i); + reverseParams(child.getLayoutParams(), child, isLayoutReverse); + + // Recursively reverse all children + if (child instanceof ViewGroup) { + reverseGroup((ViewGroup) child, isLayoutReverse); + } + } + } +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 4666c5c12189..2d338179186e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -3406,6 +3406,11 @@ public abstract class BatteryStats implements Parcelable { public abstract WakeLockStats getWakeLockStats(); /** + * Returns aggregated Bluetooth stats. + */ + public abstract BluetoothBatteryStats getBluetoothBatteryStats(); + + /** * Returns Timers tracking the total time of each Resource Power Manager state and voter. */ public abstract Map<String, ? extends Timer> getRpmStats(); diff --git a/core/java/android/os/BatteryStatsManager.java b/core/java/android/os/BatteryStatsManager.java index 6339435e539c..2a609b8f6ae4 100644 --- a/core/java/android/os/BatteryStatsManager.java +++ b/core/java/android/os/BatteryStatsManager.java @@ -368,6 +368,21 @@ public final class BatteryStatsManager { } /** + * Retrieves accumulated bluetooth stats. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BATTERY_STATS) + @NonNull + public BluetoothBatteryStats getBluetoothBatteryStats() { + try { + return mBatteryStats.getBluetoothBatteryStats(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Indicates an app acquiring full wifi lock. * * @param ws worksource (to be used for battery blaming). diff --git a/core/java/android/os/BluetoothBatteryStats.aidl b/core/java/android/os/BluetoothBatteryStats.aidl new file mode 100644 index 000000000000..d0514b67e223 --- /dev/null +++ b/core/java/android/os/BluetoothBatteryStats.aidl @@ -0,0 +1,20 @@ +/* + * 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 android.os; + +/** {@hide} */ +parcelable BluetoothBatteryStats; diff --git a/core/java/android/os/BluetoothBatteryStats.java b/core/java/android/os/BluetoothBatteryStats.java new file mode 100644 index 000000000000..3d99a08a59c5 --- /dev/null +++ b/core/java/android/os/BluetoothBatteryStats.java @@ -0,0 +1,127 @@ +/* + * 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 android.os; + +import android.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * Snapshot of Bluetooth battery stats. + * + * @hide + */ +public class BluetoothBatteryStats implements Parcelable { + + /** @hide */ + public static class UidStats { + public final int uid; + public final long scanTimeMs; + public final long unoptimizedScanTimeMs; + public final int scanResultCount; + public final long rxTimeMs; + public final long txTimeMs; + + public UidStats(int uid, long scanTimeMs, long unoptimizedScanTimeMs, int scanResultCount, + long rxTimeMs, long txTimeMs) { + this.uid = uid; + this.scanTimeMs = scanTimeMs; + this.unoptimizedScanTimeMs = unoptimizedScanTimeMs; + this.scanResultCount = scanResultCount; + this.rxTimeMs = rxTimeMs; + this.txTimeMs = txTimeMs; + } + + private UidStats(Parcel in) { + uid = in.readInt(); + scanTimeMs = in.readLong(); + unoptimizedScanTimeMs = in.readLong(); + scanResultCount = in.readInt(); + rxTimeMs = in.readLong(); + txTimeMs = in.readLong(); + } + + private void writeToParcel(Parcel out) { + out.writeInt(uid); + out.writeLong(scanTimeMs); + out.writeLong(unoptimizedScanTimeMs); + out.writeInt(scanResultCount); + out.writeLong(rxTimeMs); + out.writeLong(txTimeMs); + } + + @Override + public String toString() { + return "UidStats{" + + "uid=" + uid + + ", scanTimeMs=" + scanTimeMs + + ", unoptimizedScanTimeMs=" + unoptimizedScanTimeMs + + ", scanResultCount=" + scanResultCount + + ", rxTimeMs=" + rxTimeMs + + ", txTimeMs=" + txTimeMs + + '}'; + } + } + + private final List<UidStats> mUidStats; + + public BluetoothBatteryStats(@NonNull List<UidStats> uidStats) { + mUidStats = uidStats; + } + + @NonNull + public List<UidStats> getUidStats() { + return mUidStats; + } + + protected BluetoothBatteryStats(Parcel in) { + final int size = in.readInt(); + mUidStats = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + mUidStats.add(new UidStats(in)); + } + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + final int size = mUidStats.size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + UidStats stats = mUidStats.get(i); + stats.writeToParcel(out); + } + } + + public static final Creator<BluetoothBatteryStats> CREATOR = + new Creator<BluetoothBatteryStats>() { + @Override + public BluetoothBatteryStats createFromParcel(Parcel in) { + return new BluetoothBatteryStats(in); + } + + @Override + public BluetoothBatteryStats[] newArray(int size) { + return new BluetoothBatteryStats[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index 07f40828eb56..22ddbccbaaae 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -31,15 +31,6 @@ ] }, { - "file_patterns": ["Environment\\.java"], - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.pm.parsing.PackageInfoUserFieldsTest" - } - ] - }, - { "file_patterns": [ "BatteryStats[^/]*\\.java", "BatteryUsageStats[^/]*\\.java", diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 190f5f127f3d..f18c9c92889e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -16,6 +16,9 @@ package android.os; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_BADGED_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.UNDEFINED; + import android.Manifest; import android.accounts.AccountManager; import android.annotation.ColorInt; @@ -820,6 +823,20 @@ public class UserManager { public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile"; /** + * Specifies if a user is disallowed from creating clone profile. + * <p>The default value for an unmanaged user is <code>false</code>. + * For users with a device owner set, the default is <code>true</code>. + * + * <p>Key for user restrictions. + * <p>Type: Boolean + * @see DevicePolicyManager#addUserRestriction(ComponentName, String) + * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) + * @see #getUserRestrictions() + * @hide + */ + public static final String DISALLOW_ADD_CLONE_PROFILE = "no_add_clone_profile"; + + /** * Specifies if a user is disallowed from disabling application verification. The default * value is <code>false</code>. * @@ -1497,6 +1514,7 @@ public class UserManager { DISALLOW_FACTORY_RESET, DISALLOW_ADD_USER, DISALLOW_ADD_MANAGED_PROFILE, + DISALLOW_ADD_CLONE_PROFILE, ENSURE_VERIFY_APPS, DISALLOW_CONFIG_CELL_BROADCASTS, DISALLOW_CONFIG_MOBILE_NETWORKS, @@ -4645,6 +4663,18 @@ public class UserManager { if (!hasBadge(userId)) { return label; } + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString( + getUpdatableUserBadgedLabelId(userId), + () -> getDefaultUserBadgedLabel(label, userId), + /* formatArgs= */ label); + } + + private String getUpdatableUserBadgedLabelId(int userId) { + return isManagedProfile(userId) ? WORK_PROFILE_BADGED_LABEL : UNDEFINED; + } + + private String getDefaultUserBadgedLabel(CharSequence label, int userId) { try { final int resourceId = mService.getUserBadgeLabelResId(userId); return Resources.getSystem().getString(resourceId, label); diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl index 0894e372efc2..c9dd06cfaa43 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -56,6 +56,9 @@ oneway interface IPermissionController { in AndroidFuture<String> callback); void getUnusedAppCount( in AndroidFuture callback); + void getHibernationEligibility( + in String packageName, + in AndroidFuture callback); void revokeOwnPermissionsOnKill(in String packageName, in List<String> permissions, in AndroidFuture callback); } diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java index 8733ac44af98..0cf06aa364ec 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -128,6 +128,51 @@ public final class PermissionControllerManager { /** Count and app even if it is a system app. */ public static final int COUNT_WHEN_SYSTEM = 2; + /** @hide */ + @IntDef(prefix = { "HIBERNATION_ELIGIBILITY_"}, value = { + HIBERNATION_ELIGIBILITY_UNKNOWN, + HIBERNATION_ELIGIBILITY_ELIGIBLE, + HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM, + HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface HibernationEligibilityFlag {} + + /** + * Unknown whether package is eligible for hibernation. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_UNKNOWN = -1; + + /** + * Package is eligible for app hibernation and may be hibernated when the job runs. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_ELIGIBLE = 0; + + /** + * Package is not eligible for app hibernation because it is categorically exempt via the + * system. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_SYSTEM = 1; + + /** + * Package is not eligible for app hibernation because it has been exempt by the user's + * preferences. Note that this should not be set if the package is exempt from hibernation by + * the system as the user preference would have no effect. + * + * @hide + */ + @SystemApi + public static final int HIBERNATION_ELIGIBILITY_EXEMPT_BY_USER = 2; + /** * Callback for delivering the result of {@link #revokeRuntimePermissions}. */ @@ -819,6 +864,39 @@ public final class PermissionControllerManager { } /** + * Get the hibernation eligibility of a package. See {@link HibernationEligibilityFlag}. + * + * @param packageName package name to check eligibility + * @param executor executor to run callback on + * @param callback callback for when result is generated + */ + @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION) + public void getHibernationEligibility(@NonNull String packageName, + @NonNull @CallbackExecutor Executor executor, + @NonNull IntConsumer callback) { + checkNotNull(executor); + checkNotNull(callback); + + mRemoteService.postAsync(service -> { + AndroidFuture<Integer> eligibilityResult = new AndroidFuture<>(); + service.getHibernationEligibility(packageName, eligibilityResult); + return eligibilityResult; + }).whenCompleteAsync((eligibility, err) -> { + if (err != null) { + Log.e(TAG, "Error getting hibernation eligibility", err); + callback.accept(HIBERNATION_ELIGIBILITY_UNKNOWN); + } else { + final long token = Binder.clearCallingIdentity(); + try { + callback.accept(eligibility); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }, executor); + } + + /** * Triggers the revocation of one or more permissions for a package, under the following * conditions: * <ul> @@ -835,17 +913,17 @@ public final class PermissionControllerManager { * * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. + * @param callback Callback called when the revocation request has been completed. * * @see Context#revokeOwnPermissionsOnKill(Collection) * * @hide */ public void revokeOwnPermissionsOnKill(@NonNull String packageName, - @NonNull List<String> permissions) { + @NonNull List<String> permissions, AndroidFuture<Void> callback) { mRemoteService.postAsync(service -> { - AndroidFuture<Void> future = new AndroidFuture<>(); - service.revokeOwnPermissionsOnKill(packageName, permissions, future); - return future; + service.revokeOwnPermissionsOnKill(packageName, permissions, callback); + return callback; }).whenComplete((result, err) -> { if (err != null) { Log.e(TAG, "Failed to self revoke " + String.join(",", permissions) diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index b1e3cfc161de..8d9f82b04b54 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -375,6 +375,22 @@ public abstract class PermissionControllerService extends Service { throw new AbstractMethodError("Must be overridden in implementing class"); } + /** + * Get the hibernation eligibility of the app. See + * {@link android.permission.PermissionControllerManager.HibernationEligibilityFlag}. + * + * @param packageName package to check eligibility + * @param callback callback after eligibility is returned + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MANAGE_APP_HIBERNATION) + public void onGetHibernationEligibility(@NonNull String packageName, + @NonNull IntConsumer callback) { + throw new AbstractMethodError("Must be overridden in implementing class"); + } + @Override public final @NonNull IBinder onBind(Intent intent) { return new IPermissionController.Stub() { @@ -669,6 +685,22 @@ public abstract class PermissionControllerService extends Service { } @Override + public void getHibernationEligibility(@NonNull String packageName, + @NonNull AndroidFuture callback) { + try { + Objects.requireNonNull(callback); + + enforceSomePermissionsGrantedToCaller( + Manifest.permission.MANAGE_APP_HIBERNATION); + + PermissionControllerService.this.onGetHibernationEligibility(packageName, + callback::complete); + } catch (Throwable t) { + callback.completeExceptionally(t); + } + } + + @Override public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, @NonNull AndroidFuture callback) { try { diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 6349cde2b8fc..87f4489e30f8 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -464,6 +464,13 @@ public final class DeviceConfig { public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot"; /** + * Namespace for all Supplemental Api related features. + * @hide + */ + @SystemApi + public static final String NAMESPACE_SUPPLEMENTAL_API = "supplemental_api"; + + /** * Namespace for all SurfaceFlinger features that are used at the native level. * These features are applied on boot or after reboot. * @@ -687,6 +694,15 @@ public final class DeviceConfig { @SystemApi public static final String NAMESPACE_UWB = "uwb"; + /** + * Namespace for AmbientContextEventManagerService related features. + * + * @hide + */ + @SystemApi + public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = + "ambient_context_manager_service"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index be2e3b24a902..3f544089fcf3 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10602,6 +10602,14 @@ public final class Settings { "communal_mode_trusted_networks"; /** + * Setting to allow Fast Pair scans to be enabled. + * @hide + */ + @SystemApi + @Readable + public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. */ diff --git a/core/java/android/service/ambientcontext/AmbientContextDetectionService.java b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java new file mode 100644 index 000000000000..dccfe3693b35 --- /dev/null +++ b/core/java/android/service/ambientcontext/AmbientContextDetectionService.java @@ -0,0 +1,137 @@ +/* + * 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 android.service.ambientcontext; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteCallback; +import android.util.Slog; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Abstract base class for {@link AmbientContextEvent} detection service. + * + * <p> A service that provides requested ambient context events to the system. + * The system's default AmbientContextDetectionService implementation is configured in + * {@code config_defaultAmbientContextDetectionService}. If this config has no value, a stub is + * returned. + * + * See: {@code AmbientContextManagerService}. + * + * <pre> + * {@literal + * <service android:name=".YourAmbientContextDetectionService" + * android:permission="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE"> + * </service>} + * </pre> + * + * @hide + */ +@SystemApi +public abstract class AmbientContextDetectionService extends Service { + private static final String TAG = AmbientContextDetectionService.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_AMBIENT_CONTEXT_DETECTION_SERVICE} + * permission so that other applications can not abuse it. + */ + public static final String SERVICE_INTERFACE = + "android.service.ambientcontext.AmbientContextDetectionService"; + + /** + * The key for the bundle the parameter of {@code RemoteCallback#sendResult}. Implementation + * should set bundle result with this key. + * + * @hide + */ + public static final String RESPONSE_BUNDLE_KEY = + "android.service.ambientcontext.EventResponseKey"; + + @Nullable + @Override + public final IBinder onBind(@NonNull Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return new IAmbientContextDetectionService.Stub() { + /** {@inheritDoc} */ + @Override + public void startDetection( + @NonNull AmbientContextEventRequest request, String packageName, + RemoteCallback callback) { + Objects.requireNonNull(request); + Objects.requireNonNull(callback); + Consumer<AmbientContextEventResponse> consumer = + response -> { + Bundle bundle = new Bundle(); + bundle.putParcelable( + AmbientContextDetectionService.RESPONSE_BUNDLE_KEY, + response); + callback.sendResult(bundle); + }; + AmbientContextDetectionService.this.onStartDetection( + request, packageName, consumer); + Slog.d(TAG, "startDetection " + request); + } + + /** {@inheritDoc} */ + @Override + public void stopDetection(String packageName) { + Objects.requireNonNull(packageName); + AmbientContextDetectionService.this.onStopDetection(packageName); + } + }; + } + return null; + } + + /** + * Starts detection and provides detected events to the consumer. The ongoing detection will + * keep running, until onStopDetection is called. If there were previously requested + * detection from the same package, the previous request will be replaced with the new request. + * The implementation should keep track of whether the user consented each requested + * AmbientContextEvent for the app. If not consented, the response should set status + * STATUS_ACCESS_DENIED and include an action PendingIntent for the app to redirect the user + * to the consent screen. + * + * @param request The request with events to detect, optional detection window and other + * options. + * @param packageName the requesting app's package name + * @param consumer the consumer for the detected event + */ + public abstract void onStartDetection( + @NonNull AmbientContextEventRequest request, + @NonNull String packageName, + @NonNull Consumer<AmbientContextEventResponse> consumer); + + /** + * Stops detection of the events. Events that are not being detected will be ignored. + * + * @param packageName stops detection for the given package. + */ + public abstract void onStopDetection(@NonNull String packageName); +} diff --git a/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl new file mode 100644 index 000000000000..1c6e25efeabe --- /dev/null +++ b/core/java/android/service/ambientcontext/IAmbientContextDetectionService.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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.service.ambientcontext; + +import android.app.ambientcontext.AmbientContextEventRequest; +import android.os.RemoteCallback; + +/** + * Interface for a concrete implementation to provide AmbientContextEvents to the framework. + * + * @hide + */ +oneway interface IAmbientContextDetectionService { + void startDetection(in AmbientContextEventRequest request, in String packageName, + in RemoteCallback callback); + void stopDetection(in String packageName); +}
\ No newline at end of file diff --git a/core/java/android/service/ambientcontext/OWNERS b/core/java/android/service/ambientcontext/OWNERS new file mode 100644 index 000000000000..a863297b84e8 --- /dev/null +++ b/core/java/android/service/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/core/java/android/service/games/GameScreenshotResult.java b/core/java/android/service/games/GameScreenshotResult.java new file mode 100644 index 000000000000..ae76e08c7971 --- /dev/null +++ b/core/java/android/service/games/GameScreenshotResult.java @@ -0,0 +1,181 @@ +/* + * 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 android.service.games; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Result object for calls to {@link IGameSessionController#takeScreenshot}. + * + * It includes a status (see {@link #getStatus}) and, if the status is + * {@link #GAME_SCREENSHOT_SUCCESS} an {@link android.graphics.Bitmap} result (see {@link + * #getBitmap}). + * + * @hide + */ +public final class GameScreenshotResult implements Parcelable { + + /** + * The status of a call to {@link IGameSessionController#takeScreenshot} will be represented by + * one of these values. + * + * @hide + */ + @IntDef(flag = false, prefix = {"GAME_SCREENSHOT_"}, value = { + GAME_SCREENSHOT_SUCCESS, // 0 + GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, // 1 + }) + @Retention(RetentionPolicy.SOURCE) + public @interface GameScreenshotStatus { + } + + /** + * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} was + * successful and an {@link android.graphics.Bitmap} result should be available by calling + * {@link #getBitmap}. + * + * @hide + */ + public static final int GAME_SCREENSHOT_SUCCESS = 0; + + /** + * Indicates that the result of a call to {@link IGameSessionController#takeScreenshot} failed + * due to an internal error. + * + * This error may occur if the device is not in a suitable state for a screenshot to be taken + * (e.g., the screen is off) or if the game task is not in a suitable state for a screenshot + * to be taken (e.g., the task is not visible). To make sure that the device and game are + * in a suitable state, the caller can monitor the lifecycle methods for the {@link + * GameSession} to make sure that the game task is focused. If the conditions are met, then the + * caller may try again immediately. + * + * @hide + */ + public static final int GAME_SCREENSHOT_ERROR_INTERNAL_ERROR = 1; + + @NonNull + public static final Parcelable.Creator<GameScreenshotResult> CREATOR = + new Parcelable.Creator<GameScreenshotResult>() { + @Override + public GameScreenshotResult createFromParcel(Parcel source) { + return new GameScreenshotResult( + source.readInt(), + source.readParcelable(null, Bitmap.class)); + } + + @Override + public GameScreenshotResult[] newArray(int size) { + return new GameScreenshotResult[0]; + } + }; + + @GameScreenshotStatus + private final int mStatus; + + @Nullable + private final Bitmap mBitmap; + + /** + * Creates a successful {@link GameScreenshotResult} with the provided bitmap. + */ + public static GameScreenshotResult createSuccessResult(@NonNull Bitmap bitmap) { + return new GameScreenshotResult(GAME_SCREENSHOT_SUCCESS, bitmap); + } + + /** + * Creates a failed {@link GameScreenshotResult} with an + * {@link #GAME_SCREENSHOT_ERROR_INTERNAL_ERROR} status. + */ + public static GameScreenshotResult createInternalErrorResult() { + return new GameScreenshotResult(GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, null); + } + + private GameScreenshotResult(@GameScreenshotStatus int status, @Nullable Bitmap bitmap) { + this.mStatus = status; + this.mBitmap = bitmap; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mStatus); + dest.writeParcelable(mBitmap, flags); + } + + @GameScreenshotStatus + public int getStatus() { + return mStatus; + } + + /** + * Gets the {@link Bitmap} result from a successful screenshot attempt. + * + * @return The bitmap. + * @throws IllegalStateException if this method is called when {@link #getStatus} does not + * return {@link #GAME_SCREENSHOT_SUCCESS}. + */ + @NonNull + public Bitmap getBitmap() { + if (mBitmap == null) { + throw new IllegalStateException("Bitmap not available for failed screenshot result"); + } + return mBitmap; + } + + @Override + public String toString() { + return "GameScreenshotResult{" + + "mStatus=" + + mStatus + + ", has bitmap='" + + mBitmap != null ? "yes" : "no" + + "\'}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof GameScreenshotResult)) { + return false; + } + + GameScreenshotResult that = (GameScreenshotResult) o; + return mStatus == that.mStatus + && Objects.equals(mBitmap, that.mBitmap); + } + + @Override + public int hashCode() { + return Objects.hash(mStatus, mBitmap); + } +} diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java index 1a5331f10525..cb5c19b72bd0 100644 --- a/core/java/android/service/games/GameSession.java +++ b/core/java/android/service/games/GameSession.java @@ -17,19 +17,31 @@ package android.service.games; import android.annotation.Hide; +import android.annotation.IntDef; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.Rect; import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Slog; import android.view.SurfaceControlViewHost; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.function.pooled.PooledLambda; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + /** * An active game session, providing a facility for the implementation to interact with the game. * @@ -37,28 +49,83 @@ import com.android.internal.util.function.pooled.PooledLambda; * which is then returned when a game session is created via * {@link GameSessionService#onNewSession(CreateGameSessionRequest)}. * + * This class exposes various lifecycle methods which are guaranteed to be called in the following + * fashion: + * + * {@link #onCreate()}: Will always be the first lifecycle method to be called, once the game + * session is created. + * + * {@link #onGameTaskFocusChanged(boolean)}: Will be called after {@link #onCreate()} with + * focused=true when the game task first comes into focus (if it does). If the game task is focused + * when the game session is created, this method will be called immediately after + * {@link #onCreate()} with focused=true. After this method is called with focused=true, it will be + * called again with focused=false when the task goes out of focus. If this method is ever called + * with focused=true, it is guaranteed to be called again with focused=false before + * {@link #onDestroy()} is called. If the game task never comes into focus during the session + * lifetime, this method will never be called. + * + * {@link #onDestroy()}: Will always be called after {@link #onCreate()}. If the game task ever + * comes into focus before the game session is destroyed, then this method will be called after one + * or more pairs of calls to {@link #onGameTaskFocusChanged(boolean)}. + * * @hide */ @SystemApi public abstract class GameSession { + private static final String TAG = "GameSession"; + private static final boolean DEBUG = false; final IGameSession mInterface = new IGameSession.Stub() { @Override - public void destroy() { + public void onDestroyed() { Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( GameSession::doDestroy, GameSession.this)); } + + @Override + public void onTaskFocusChanged(boolean focused) { + Handler.getMain().executeOrSendMessage(PooledLambda.obtainMessage( + GameSession::moveToState, GameSession.this, + focused ? LifecycleState.TASK_FOCUSED : LifecycleState.TASK_UNFOCUSED)); + } }; + /** + * @hide + */ + @VisibleForTesting + public enum LifecycleState { + // Initial state; may transition to CREATED. + INITIALIZED, + // May transition to TASK_FOCUSED or DESTROYED. + CREATED, + // May transition to TASK_UNFOCUSED. + TASK_FOCUSED, + // May transition to TASK_FOCUSED or DESTROYED. + TASK_UNFOCUSED, + // May not transition once reached. + DESTROYED + } + + private LifecycleState mLifecycleState = LifecycleState.INITIALIZED; + private IGameSessionController mGameSessionController; + private int mTaskId; private GameSessionRootView mGameSessionRootView; private SurfaceControlViewHost mSurfaceControlViewHost; - @Hide - void attach( + /** + * @hide + */ + @VisibleForTesting + public void attach( + IGameSessionController gameSessionController, + int taskId, @NonNull Context context, @NonNull SurfaceControlViewHost surfaceControlViewHost, int widthPx, int heightPx) { + mGameSessionController = gameSessionController; + mTaskId = taskId; mSurfaceControlViewHost = surfaceControlViewHost; mGameSessionRootView = new GameSessionRootView(context, mSurfaceControlViewHost); surfaceControlViewHost.setView(mGameSessionRootView, widthPx, heightPx); @@ -66,13 +133,93 @@ public abstract class GameSession { @Hide void doCreate() { - onCreate(); + moveToState(LifecycleState.CREATED); } @Hide void doDestroy() { - onDestroy(); mSurfaceControlViewHost.release(); + moveToState(LifecycleState.DESTROYED); + } + + /** + * @hide + */ + @VisibleForTesting + @MainThread + public void moveToState(LifecycleState newLifecycleState) { + if (DEBUG) { + Slog.d(TAG, "moveToState: " + mLifecycleState + " -> " + newLifecycleState); + } + + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("moveToState should be used only from the main thread"); + } + + if (mLifecycleState == newLifecycleState) { + // Nothing to do. + return; + } + + switch (mLifecycleState) { + case INITIALIZED: + if (newLifecycleState == LifecycleState.CREATED) { + onCreate(); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onCreate(); + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: INITIALIZED -> " + newLifecycleState); + } + return; + } + break; + case CREATED: + if (newLifecycleState == LifecycleState.TASK_FOCUSED) { + onGameTaskFocusChanged(/*focused=*/ true); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: CREATED -> " + newLifecycleState); + } + return; + } + break; + case TASK_FOCUSED: + if (newLifecycleState == LifecycleState.TASK_UNFOCUSED) { + onGameTaskFocusChanged(/*focused=*/ false); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onGameTaskFocusChanged(/*focused=*/ false); + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: TASK_FOCUSED -> " + newLifecycleState); + } + return; + } + break; + case TASK_UNFOCUSED: + if (newLifecycleState == LifecycleState.TASK_FOCUSED) { + onGameTaskFocusChanged(/*focused=*/ true); + } else if (newLifecycleState == LifecycleState.DESTROYED) { + onDestroy(); + } else { + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: TASK_UNFOCUSED -> " + newLifecycleState); + } + return; + } + break; + case DESTROYED: + if (DEBUG) { + Slog.d(TAG, "Ignoring moveToState: DESTROYED -> " + newLifecycleState); + } + return; + } + + mLifecycleState = newLifecycleState; } /** @@ -84,13 +231,27 @@ public abstract class GameSession { } /** - * Finalizer called when the game session is ending. + * Finalizer called when the game session is ending. This method will always be called after a + * call to {@link #onCreate()}. If the game task is ever in focus, this method will be called + * after one or more pairs of calls to {@link #onGameTaskFocusChanged(boolean)}. * * This should be used to perform any cleanup before the game session is destroyed. */ public void onDestroy() { } + /** + * Called when the game task for this session is or unfocused. The initial call to this method + * will always come after a call to {@link #onCreate()} with focused=true (when the game task + * first comes into focus after the session is created, or immediately after the session is + * created if the game task is already focused). + * + * This should be used to perform any setup required when the game task comes into focus or any + * cleanup that is required when the game task goes out of focus. + * + * @param focused True if the game task is focused, false if the game task is unfocused. + */ + public void onGameTaskFocusChanged(boolean focused) {} /** * Sets the task overlay content to an explicit view. This view is placed directly into the game @@ -133,4 +294,106 @@ public abstract class GameSession { mSurfaceControlViewHost.relayout(bounds.width(), bounds.height()); } } + + /** + * Interface for returning screenshot outcome from calls to {@link #takeScreenshot}. + */ + public interface ScreenshotCallback { + + /** + * The status of a failed screenshot attempt provided by {@link #onFailure}. + * + * @hide + */ + @IntDef(flag = false, prefix = {"ERROR_TAKE_SCREENSHOT_"}, value = { + ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, // 0 + }) + @Retention(RetentionPolicy.SOURCE) + @interface ScreenshotFailureStatus { + } + + /** + * An error code indicating that an internal error occurred when attempting to take a + * screenshot of the game task. If this code is returned, the caller should verify that the + * conditions for taking a screenshot are met (device screen is on and the game task is + * visible). To do so, the caller can monitor the lifecycle methods for this session to + * make sure that the game task is focused. If the conditions are met, then the caller may + * try again immediately. + */ + int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 0; + + /** + * Called when taking the screenshot failed. + * @param statusCode Indicates the reason for failure. + */ + void onFailure(@ScreenshotFailureStatus int statusCode); + + /** + * Called when taking the screenshot succeeded. + * @param bitmap The screenshot. + */ + void onSuccess(@NonNull Bitmap bitmap); + } + + /** + * Takes a screenshot of the associated game. For this call to succeed, the device screen + * must be turned on and the game task must be visible. + * + * If the callback is called with {@link ScreenshotCallback#onSuccess}, the provided {@link + * Bitmap} may be used. + * + * If the callback is called with {@link ScreenshotCallback#onFailure}, the provided status + * code should be checked. + * + * If the status code is {@link ScreenshotCallback#ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR}, + * then the caller should verify that the conditions for calling this method are met (device + * screen is on and the game task is visible). To do so, the caller can monitor the lifecycle + * methods for this session to make sure that the game task is focused. If the conditions are + * met, then the caller may try again immediately. + * + * @param executor Executor on which to run the callback. + * @param callback The callback invoked when taking screenshot has succeeded + * or failed. + * @throws IllegalStateException if this method is called prior to {@link #onCreate}. + */ + public void takeScreenshot(@NonNull Executor executor, @NonNull ScreenshotCallback callback) { + if (mGameSessionController == null) { + throw new IllegalStateException("Can not call before onCreate()"); + } + + AndroidFuture<GameScreenshotResult> takeScreenshotResult = + new AndroidFuture<GameScreenshotResult>().whenCompleteAsync((result, error) -> { + handleScreenshotResult(callback, result, error); + }, executor); + + try { + mGameSessionController.takeScreenshot(mTaskId, takeScreenshotResult); + } catch (RemoteException ex) { + takeScreenshotResult.completeExceptionally(ex); + } + } + + private void handleScreenshotResult( + @NonNull ScreenshotCallback callback, + @NonNull GameScreenshotResult result, + @NonNull Throwable error) { + if (error != null) { + Slog.w(TAG, error.getMessage(), error.getCause()); + callback.onFailure( + ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR); + return; + } + + @GameScreenshotResult.GameScreenshotStatus int status = result.getStatus(); + switch (status) { + case GameScreenshotResult.GAME_SCREENSHOT_SUCCESS: + callback.onSuccess(result.getBitmap()); + break; + case GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR: + Slog.w(TAG, "Error taking screenshot"); + callback.onFailure( + ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR); + break; + } + } } diff --git a/core/java/android/service/games/GameSessionService.java b/core/java/android/service/games/GameSessionService.java index 195a0f233307..df5bad5c53b2 100644 --- a/core/java/android/service/games/GameSessionService.java +++ b/core/java/android/service/games/GameSessionService.java @@ -52,8 +52,6 @@ import java.util.Objects; */ @SystemApi public abstract class GameSessionService extends Service { - private static final String TAG = "GameSessionService"; - /** * The {@link Intent} action used when binding to the service. * To be supported, the service must require the @@ -67,11 +65,13 @@ public abstract class GameSessionService extends Service { private final IGameSessionService mInterface = new IGameSessionService.Stub() { @Override public void create( + IGameSessionController gameSessionController, CreateGameSessionRequest createGameSessionRequest, GameSessionViewHostConfiguration gameSessionViewHostConfiguration, AndroidFuture gameSessionFuture) { Handler.getMain().post(PooledLambda.obtainRunnable( GameSessionService::doCreate, GameSessionService.this, + gameSessionController, createGameSessionRequest, gameSessionViewHostConfiguration, gameSessionFuture)); @@ -101,6 +101,7 @@ public abstract class GameSessionService extends Service { } private void doCreate( + IGameSessionController gameSessionController, CreateGameSessionRequest createGameSessionRequest, GameSessionViewHostConfiguration gameSessionViewHostConfiguration, AndroidFuture<CreateGameSessionResult> createGameSessionResultFuture) { @@ -119,7 +120,10 @@ public abstract class GameSessionService extends Service { SurfaceControlViewHost surfaceControlViewHost = new SurfaceControlViewHost(this, display, hostToken); - gameSession.attach(this, + gameSession.attach( + gameSessionController, + createGameSessionRequest.getTaskId(), + this, surfaceControlViewHost, gameSessionViewHostConfiguration.mWidthPx, gameSessionViewHostConfiguration.mHeightPx); @@ -130,7 +134,6 @@ public abstract class GameSessionService extends Service { createGameSessionResultFuture.complete(createGameSessionResult); - gameSession.doCreate(); } diff --git a/core/java/android/service/games/IGameSession.aidl b/core/java/android/service/games/IGameSession.aidl index b2e9f1d21f6e..71da6302b63d 100644 --- a/core/java/android/service/games/IGameSession.aidl +++ b/core/java/android/service/games/IGameSession.aidl @@ -20,5 +20,6 @@ package android.service.games; * @hide */ oneway interface IGameSession { - void destroy(); + void onDestroyed(); + void onTaskFocusChanged(boolean focused); } diff --git a/core/java/android/service/games/IGameSessionController.aidl b/core/java/android/service/games/IGameSessionController.aidl new file mode 100644 index 000000000000..fe1d3629918e --- /dev/null +++ b/core/java/android/service/games/IGameSessionController.aidl @@ -0,0 +1,26 @@ +/* + * 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 android.service.games; + +import com.android.internal.infra.AndroidFuture; + +/** + * @hide + */ +oneway interface IGameSessionController { + void takeScreenshot(int taskId, in AndroidFuture gameScreenshotResultFuture); +}
\ No newline at end of file diff --git a/core/java/android/service/games/IGameSessionService.aidl b/core/java/android/service/games/IGameSessionService.aidl index dcbcbc16a374..37cde561f549 100644 --- a/core/java/android/service/games/IGameSessionService.aidl +++ b/core/java/android/service/games/IGameSessionService.aidl @@ -16,6 +16,7 @@ package android.service.games; +import android.service.games.IGameSessionController; import android.service.games.IGameSession; import android.service.games.CreateGameSessionRequest; import android.service.games.GameSessionViewHostConfiguration; @@ -28,6 +29,7 @@ import com.android.internal.infra.AndroidFuture; */ oneway interface IGameSessionService { void create( + in IGameSessionController gameSessionController, in CreateGameSessionRequest createGameSessionRequest, in GameSessionViewHostConfiguration gameSessionViewHostConfiguration, in AndroidFuture /* T=CreateGameSessionResult */ createGameSessionResultFuture); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index bff75a62b590..dd4355d3b7a1 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -278,6 +278,7 @@ public abstract class WallpaperService extends Service { private Display mDisplay; private Context mDisplayContext; private int mDisplayState; + private @Surface.Rotation int mDisplayInstallOrientation; private float mWallpaperDimAmount = 0.05f; private float mPreviousWallpaperDimAmount = mWallpaperDimAmount; private float mDefaultDimAmount = mWallpaperDimAmount; @@ -1122,6 +1123,11 @@ public abstract class WallpaperService extends Service { mWindow, mLayout, mWidth, mHeight, View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls, mSurfaceSize); + + final int transformHint = SurfaceControl.rotationToBufferTransform( + (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); + mSurfaceControl.setTransformHint(transformHint); + if (mSurfaceControl.isValid()) { if (mBbqSurfaceControl == null) { mBbqSurfaceControl = new SurfaceControl.Builder() @@ -1135,9 +1141,9 @@ public abstract class WallpaperService extends Service { .build(); updateSurfaceDimming(); } - // Propagate transform hint from WM so we can use the right hint for the + // Propagate transform hint from WM, so we can use the right hint for the // first frame. - mBbqSurfaceControl.setTransformHint(mSurfaceControl.getTransformHint()); + mBbqSurfaceControl.setTransformHint(transformHint); Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, mSurfaceSize.y, mFormat); // If blastSurface == null that means it hasn't changed since the last @@ -1377,6 +1383,7 @@ public abstract class WallpaperService extends Service { mWallpaperDimAmount = mDefaultDimAmount; mPreviousWallpaperDimAmount = mWallpaperDimAmount; mDisplayState = mDisplay.getState(); + mDisplayInstallOrientation = mDisplay.getInstallOrientation(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); onCreate(mSurfaceHolder); @@ -1629,6 +1636,7 @@ public abstract class WallpaperService extends Service { return; } Surface surface = mSurfaceHolder.getSurface(); + if (!surface.isValid()) return; boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; int smaller = widthIsLarger ? mSurfaceSize.x : mSurfaceSize.y; diff --git a/core/java/android/service/wallpapereffectsgeneration/OWNERS b/core/java/android/service/wallpapereffectsgeneration/OWNERS new file mode 100644 index 000000000000..d2d3e2c0a7b6 --- /dev/null +++ b/core/java/android/service/wallpapereffectsgeneration/OWNERS @@ -0,0 +1,4 @@ +susharon@google.com +shanh@google.com +huiwu@google.com +srazdan@google.com diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index ce63376a6d63..be66db2a4c05 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -363,6 +363,9 @@ public class PrecomputedText implements Spannable { public String toString() { int lineBreakStyle = (mLineBreakConfig != null) ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; + int lineBreakWordStyle = (mLineBreakConfig != null) + ? mLineBreakConfig.getLineBreakWordStyle() + : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; return "{" + "textSize=" + mPaint.getTextSize() + ", textScaleX=" + mPaint.getTextScaleX() @@ -376,6 +379,7 @@ public class PrecomputedText implements Spannable { + ", breakStrategy=" + mBreakStrategy + ", hyphenationFrequency=" + mHyphenationFrequency + ", lineBreakStyle=" + lineBreakStyle + + ", lineBreakWordStyle=" + lineBreakWordStyle + "}"; } }; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 52f1faef0fc3..96a1fc60e458 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -81,6 +81,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "true"); DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true"); + DEFAULT_FLAGS.put("settings_search_always_expand", "false"); DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 70266c1717a1..d6e074fbe178 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -928,6 +928,18 @@ public final class Display { } /** + * Returns the install orientation of the display. + * @hide + */ + @Surface.Rotation + public int getInstallOrientation() { + synchronized (mLock) { + updateDisplayInfoLocked(); + return mDisplayInfo.installOrientation; + } + } + + /** * @deprecated use {@link #getRotation} * @return orientation of this display. */ diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index 678c80a2094b..6917d664327f 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -318,6 +318,12 @@ public final class DisplayInfo implements Parcelable { @Nullable public RoundedCorners roundedCorners; + /** + * Install orientation of the display relative to its natural orientation. + */ + @Surface.Rotation + public int installOrientation; + public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() { @Override public DisplayInfo createFromParcel(Parcel source) { @@ -389,7 +395,8 @@ public final class DisplayInfo implements Parcelable { && brightnessMaximum == other.brightnessMaximum && brightnessDefault == other.brightnessDefault && Objects.equals(roundedCorners, other.roundedCorners) - && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher; + && shouldConstrainMetricsForLauncher == other.shouldConstrainMetricsForLauncher + && installOrientation == other.installOrientation; } @Override @@ -441,6 +448,7 @@ public final class DisplayInfo implements Parcelable { brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; shouldConstrainMetricsForLauncher = other.shouldConstrainMetricsForLauncher; + installOrientation = other.installOrientation; } public void readFromParcel(Parcel source) { @@ -498,6 +506,7 @@ public final class DisplayInfo implements Parcelable { userDisabledHdrTypes[i] = source.readInt(); } shouldConstrainMetricsForLauncher = source.readBoolean(); + installOrientation = source.readInt(); } @Override @@ -553,6 +562,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(userDisabledHdrTypes[i]); } dest.writeBoolean(shouldConstrainMetricsForLauncher); + dest.writeInt(installOrientation); } @Override @@ -809,6 +819,8 @@ public final class DisplayInfo implements Parcelable { sb.append(brightnessDefault); sb.append(", shouldConstrainMetricsForLauncher "); sb.append(shouldConstrainMetricsForLauncher); + sb.append(", installOrientation "); + sb.append(Surface.rotationToString(installOrientation)); sb.append("}"); return sb.toString(); } diff --git a/core/java/android/view/GestureExclusionTracker.java b/core/java/android/view/GestureExclusionTracker.java deleted file mode 100644 index fffb323e6d50..000000000000 --- a/core/java/android/view/GestureExclusionTracker.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.view; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.graphics.Rect; - -import com.android.internal.util.Preconditions; - -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -/** - * Used by {@link ViewRootImpl} to track system gesture exclusion rects reported by views. - */ -class GestureExclusionTracker { - private boolean mGestureExclusionViewsChanged = false; - private boolean mRootGestureExclusionRectsChanged = false; - private List<Rect> mRootGestureExclusionRects = Collections.emptyList(); - private List<GestureExclusionViewInfo> mGestureExclusionViewInfos = new ArrayList<>(); - private List<Rect> mGestureExclusionRects = Collections.emptyList(); - - public void updateRectsForView(@NonNull View view) { - boolean found = false; - final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator(); - while (i.hasNext()) { - final GestureExclusionViewInfo info = i.next(); - final View v = info.getView(); - if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) { - mGestureExclusionViewsChanged = true; - i.remove(); - continue; - } - if (v == view) { - found = true; - info.mDirty = true; - break; - } - } - if (!found && view.isAttachedToWindow()) { - mGestureExclusionViewInfos.add(new GestureExclusionViewInfo(view)); - mGestureExclusionViewsChanged = true; - } - } - - @Nullable - public List<Rect> computeChangedRects() { - boolean changed = mRootGestureExclusionRectsChanged; - final Iterator<GestureExclusionViewInfo> i = mGestureExclusionViewInfos.iterator(); - final List<Rect> rects = new ArrayList<>(mRootGestureExclusionRects); - while (i.hasNext()) { - final GestureExclusionViewInfo info = i.next(); - switch (info.update()) { - case GestureExclusionViewInfo.CHANGED: - changed = true; - // Deliberate fall-through - case GestureExclusionViewInfo.UNCHANGED: - rects.addAll(info.mExclusionRects); - break; - case GestureExclusionViewInfo.GONE: - mGestureExclusionViewsChanged = true; - i.remove(); - break; - } - } - if (changed || mGestureExclusionViewsChanged) { - mGestureExclusionViewsChanged = false; - mRootGestureExclusionRectsChanged = false; - if (!mGestureExclusionRects.equals(rects)) { - mGestureExclusionRects = rects; - return rects; - } - } - return null; - } - - public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) { - Preconditions.checkNotNull(rects, "rects must not be null"); - mRootGestureExclusionRects = rects; - mRootGestureExclusionRectsChanged = true; - } - - @NonNull - public List<Rect> getRootSystemGestureExclusionRects() { - return mRootGestureExclusionRects; - } - - private static class GestureExclusionViewInfo { - public static final int CHANGED = 0; - public static final int UNCHANGED = 1; - public static final int GONE = 2; - - private final WeakReference<View> mView; - boolean mDirty = true; - List<Rect> mExclusionRects = Collections.emptyList(); - - GestureExclusionViewInfo(View view) { - mView = new WeakReference<>(view); - } - - public View getView() { - return mView.get(); - } - - public int update() { - final View excludedView = getView(); - if (excludedView == null || !excludedView.isAttachedToWindow() - || !excludedView.isAggregatedVisible()) return GONE; - final List<Rect> localRects = excludedView.getSystemGestureExclusionRects(); - final List<Rect> newRects = new ArrayList<>(localRects.size()); - for (Rect src : localRects) { - Rect mappedRect = new Rect(src); - ViewParent p = excludedView.getParent(); - if (p != null && p.getChildVisibleRect(excludedView, mappedRect, null)) { - newRects.add(mappedRect); - } - } - - if (mExclusionRects.equals(localRects)) return UNCHANGED; - mExclusionRects = newRects; - return CHANGED; - } - } -} diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 8524ac846d1a..097d1d0df51b 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -19,7 +19,6 @@ package android.view; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; @@ -75,11 +74,6 @@ public class HandwritingInitiator { @VisibleForTesting public WeakReference<View> mConnectedView = null; - /** The editor bound reported by the connected View. */ - @Nullable - @VisibleForTesting - public Rect mEditorBound = null; - /** * When InputConnection restarts for a View, View#onInputConnectionCreatedInternal * might be called before View#onInputConnectionClosedInternal, so we need to count the input @@ -174,9 +168,8 @@ public class HandwritingInitiator { * @param view the view that created the current InputConnection. * @see #onInputConnectionClosed(View) */ - public void onInputConnectionCreated(@NonNull View view, @NonNull EditorInfo editorInfo) { + public void onInputConnectionCreated(@NonNull View view) { final View connectedView = getConnectedView(); -// updateEditorBound(editorInfo.getInitialEditorBound()); if (connectedView == view) { ++mConnectionCount; } else { @@ -198,33 +191,15 @@ public class HandwritingInitiator { --mConnectionCount; if (mConnectionCount == 0) { mConnectedView = null; - mEditorBound = null; } } else { // Unexpected branch, set mConnectedView to null to avoid further problem. mConnectedView = null; - mEditorBound = null; mConnectionCount = 0; } } /** - * Notify the HandwritingInitiator that editor bound of the connected view(the view with - * active InputConnection) has be updated. - * @param editorBound new the editor bounds of the connected view. - */ - public void updateEditorBound(@NonNull Rect editorBound) { - if (mEditorBound == null) { - mEditorBound = new Rect(editorBound); - } else { - mEditorBound.left = editorBound.left; - mEditorBound.top = editorBound.top; - mEditorBound.right = editorBound.right; - mEditorBound.bottom = editorBound.bottom; - } - } - - /** * Try to initiate handwriting. For this method to successfully send startHandwriting signal, * the following 3 conditions should meet: * a) The stylus movement exceeds the touchSlop. @@ -240,18 +215,19 @@ public class HandwritingInitiator { return; } final View connectedView = getConnectedView(); - if (connectedView == null || mEditorBound == null) { + if (connectedView == null) { return; } final ViewParent viewParent = connectedView.getParent(); // Do a final check before startHandwriting. if (viewParent != null && connectedView.isAttachedToWindow()) { - final Rect editorBounds = new Rect(mEditorBound); + final Rect editorBounds = + new Rect(0, 0, connectedView.getWidth(), connectedView.getHeight()); if (viewParent.getChildVisibleRect(connectedView, editorBounds, null)) { final int roundedInitX = Math.round(mState.mStylusDownX); final int roundedInitY = Math.round(mState.mStylusDownY); if (editorBounds.contains(roundedInitX, roundedInitY)) { - startHandwriting(mConnectedView.get()); + startHandwriting(connectedView); } } } @@ -261,7 +237,7 @@ public class HandwritingInitiator { /** For test only. */ @VisibleForTesting public void startHandwriting(View view) { - // mImm.startHandwriting(view); + mImm.startStylusHandwriting(view); } private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) { diff --git a/core/java/android/view/IDisplayWindowListener.aidl b/core/java/android/view/IDisplayWindowListener.aidl index f95d6b349221..449e9b325904 100644 --- a/core/java/android/view/IDisplayWindowListener.aidl +++ b/core/java/android/view/IDisplayWindowListener.aidl @@ -16,8 +16,11 @@ package android.view; +import android.graphics.Rect; import android.content.res.Configuration; +import java.util.List; + /** * Interface to listen for changes to display window-containers. * @@ -56,4 +59,9 @@ oneway interface IDisplayWindowListener { * Called when the previous fixed rotation on a display is finished. */ void onFixedRotationFinished(int displayId); + + /** + * Called when the keep clear ares on a display have changed. + */ + void onKeepClearAreasChanged(int displayId, in List<Rect> keepClearAreas); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 2c766bd6fd0d..c5ccc18b0cd4 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -25,7 +25,6 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; -import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -66,6 +65,7 @@ import android.view.WindowManager; import android.view.SurfaceControl; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; +import android.window.IOnFpsCallbackListener; /** * System private interface to the window manager. @@ -484,16 +484,6 @@ interface IWindowManager void getStableInsets(int displayId, out Rect outInsets); /** - * Set the forwarded insets on the display. - * <p> - * This is only used in case a virtual display is displayed on another display that has insets, - * and the bounds of the virtual display is overlapping with the insets from the host display. - * In that case, the contents on the virtual display won't be placed over the forwarded insets. - * Only the owner of the display is permitted to set the forwarded insets on it. - */ - void setForwardedInsets(int displayId, in Insets insets); - - /** * Register shortcut key. Shortcut code is packed as: * (MetaState << Integer.SIZE) | KeyCode * @hide @@ -551,6 +541,11 @@ interface IWindowManager void stopWindowTrace(); /** + * If window tracing is active, saves the window trace to file, otherwise does nothing + */ + void saveWindowTraceToFile(); + + /** * Returns true if window trace is enabled. */ boolean isWindowTraceEnabled(); @@ -922,4 +917,28 @@ interface IWindowManager * reverts to using the default task transition with no spec changes. */ void clearTaskTransitionSpec(); + + /** + * Registers the frame rate per second count callback for one given task ID. + * Each callback can only register for receiving FPS callback for one task id until unregister + * is called. If there's no task associated with the given task id, + * {@link IllegalArgumentException} will be thrown. If a task id destroyed after a callback is + * registered, the registered callback will not be unregistered until + * {@link unregisterTaskFpsCallback()} is called + * @param taskId task id of the task. + * @param listener listener to be registered. + * + * @hide + */ + void registerTaskFpsCallback(in int taskId, in IOnFpsCallbackListener listener); + + /** + * Unregisters the frame rate per second count callback which was registered with + * {@link #registerTaskFpsCallback(int,TaskFpsCallback)}. + * + * @param listener listener to be unregistered. + * + * @hide + */ + void unregisterTaskFpsCallback(in IOnFpsCallbackListener listener); } diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 921ce53b3bda..62265663804f 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -37,6 +37,7 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.window.ClientWindowFrames; +import android.window.IOnBackInvokedCallback; import java.util.List; @@ -290,6 +291,11 @@ interface IWindowSession { oneway void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects); /** + * Called when the keep-clear areas for this window have changed. + */ + oneway void reportKeepClearAreasChanged(IWindow window, in List<Rect> keepClearRects); + + /** * Request the server to call setInputWindowInfo on a given Surface, and return * an input channel where the client can receive input. */ @@ -328,4 +334,12 @@ interface IWindowSession { */ oneway void generateDisplayHash(IWindow window, in Rect boundsInWindow, in String hashAlgorithm, in RemoteCallback callback); + + /** + * Sets the {@link IOnBackInvokedCallback} to be invoked for a window when back is triggered. + * + * @param window The token for the window to set the callback to. + * @param callback The {@link IOnBackInvokedCallback} to set. + */ + oneway void setOnBackInvokedCallback(IWindow window, IOnBackInvokedCallback callback); } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 4f1354d7eee6..188d7459f9a7 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -572,6 +572,8 @@ public final class InputDevice implements Parcelable { * @return The identifier object for this device * @hide */ + @TestApi + @NonNull public InputDeviceIdentifier getIdentifier() { return mIdentifier; } @@ -735,6 +737,21 @@ public final class InputDevice implements Parcelable { } /** + * Gets the key code produced by the specified location on a US keyboard layout. + * Key code as defined in {@link android.view.KeyEvent}. + * This API is only functional for devices with {@link InputDevice#SOURCE_KEYBOARD} available + * which can alter their key mapping using country specific keyboard layouts. + * + * @param locationKeyCode The location of a key on a US keyboard layout. + * @return The key code produced when pressing the key at the specified location, given the + * active keyboard layout. Returns {@link KeyEvent#KEYCODE_UNKNOWN} if the requested + * mapping could not be determined, or if an error occurred. + */ + public int getKeyCodeForKeyLocation(int locationKeyCode) { + return InputManager.getInstance().getKeyCodeForKeyLocation(mId, locationKeyCode); + } + + /** * Gets information about the range of values for a particular {@link MotionEvent} axis. * If the device supports multiple sources, the same axis may have different meanings * for each source. Returns information about the first axis found for any source. diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index b7f9be70f7ce..e751720b7f5d 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1831,13 +1831,15 @@ public final class SurfaceControl implements Parcelable { public float density; public boolean secure; public DeviceProductInfo deviceProductInfo; + public @Surface.Rotation int installOrientation; @Override public String toString() { return "StaticDisplayInfo{isInternal=" + isInternal + ", density=" + density + ", secure=" + secure - + ", deviceProductInfo=" + deviceProductInfo + "}"; + + ", deviceProductInfo=" + deviceProductInfo + + ", installOrientation=" + installOrientation + "}"; } @Override @@ -1848,12 +1850,13 @@ public final class SurfaceControl implements Parcelable { return isInternal == that.isInternal && density == that.density && secure == that.secure - && Objects.equals(deviceProductInfo, that.deviceProductInfo); + && Objects.equals(deviceProductInfo, that.deviceProductInfo) + && installOrientation == that.installOrientation; } @Override public int hashCode() { - return Objects.hash(isInternal, density, secure, deviceProductInfo); + return Objects.hash(isInternal, density, secure, deviceProductInfo, installOrientation); } } diff --git a/core/java/android/view/SurfaceControlFpsListener.java b/core/java/android/view/SurfaceControlFpsListener.java deleted file mode 100644 index 20a511a090b5..000000000000 --- a/core/java/android/view/SurfaceControlFpsListener.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2021 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 android.annotation.NonNull; - -/** - * Listener for sampling the frames per second for a SurfaceControl and its children. - * This should only be used by a system component that needs to listen to a SurfaceControl's - * tree's FPS when it is not actively submitting transactions for that SurfaceControl. - * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used. - * - * @hide - */ -public abstract class SurfaceControlFpsListener { - private long mNativeListener; - - public SurfaceControlFpsListener() { - mNativeListener = nativeCreate(this); - } - - protected void destroy() { - if (mNativeListener == 0) { - return; - } - unregister(); - nativeDestroy(mNativeListener); - mNativeListener = 0; - } - - @Override - protected void finalize() throws Throwable { - try { - destroy(); - } finally { - super.finalize(); - } - } - - /** - * Reports the fps from the registered SurfaceControl - */ - public abstract void onFpsReported(float fps); - - /** - * Registers the sampling listener for a particular task ID - */ - public void register(int taskId) { - if (mNativeListener == 0) { - return; - } - - nativeRegister(mNativeListener, taskId); - } - - /** - * Unregisters the sampling listener. - */ - public void unregister() { - if (mNativeListener == 0) { - return; - } - nativeUnregister(mNativeListener); - } - - /** - * Dispatch the collected sample. - * - * Called from native code on a binder thread. - */ - private static void dispatchOnFpsReported( - @NonNull SurfaceControlFpsListener listener, float fps) { - listener.onFpsReported(fps); - } - - private static native long nativeCreate(SurfaceControlFpsListener thiz); - private static native void nativeDestroy(long ptr); - private static native void nativeRegister(long ptr, int taskId); - private static native void nativeUnregister(long ptr); -} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 2126fd523c45..93fdee07b58e 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4734,15 +4734,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback; /** - * This lives here since it's only valid for interactive views. This list is null until the - * first use. + * This lives here since it's only valid for interactive views. This list is null + * until its first use. */ private List<Rect> mSystemGestureExclusionRects = null; + private List<Rect> mKeepClearRects = null; + private boolean mPreferKeepClear = false; /** - * Used to track {@link #mSystemGestureExclusionRects} + * Used to track {@link #mSystemGestureExclusionRects} and {@link #mKeepClearRects} */ public RenderNode.PositionUpdateListener mPositionUpdateListener; + private Runnable mPositionChangedUpdate; /** * Allows the application to implement custom scroll capture support. @@ -6028,6 +6031,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_clipToOutline: setClipToOutline(a.getBoolean(attr, false)); break; + case R.styleable.View_preferKeepClear: + setPreferKeepClear(a.getBoolean(attr, false)); + break; } } @@ -11665,37 +11671,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { info.mSystemGestureExclusionRects = new ArrayList<>(rects); } - if (rects.isEmpty()) { + + updatePositionUpdateListener(); + postUpdate(this::updateSystemGestureExclusionRects); + } + + private void updatePositionUpdateListener() { + final ListenerInfo info = getListenerInfo(); + if (getSystemGestureExclusionRects().isEmpty() + && collectPreferKeepClearRects().isEmpty()) { if (info.mPositionUpdateListener != null) { mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener); + info.mPositionChangedUpdate = null; } } else { if (info.mPositionUpdateListener == null) { + info.mPositionChangedUpdate = () -> { + updateSystemGestureExclusionRects(); + updateKeepClearRects(); + }; info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() { @Override public void positionChanged(long n, int l, int t, int r, int b) { - postUpdateSystemGestureExclusionRects(); + postUpdate(info.mPositionChangedUpdate); } @Override public void positionLost(long frameNumber) { - postUpdateSystemGestureExclusionRects(); + postUpdate(info.mPositionChangedUpdate); } }; mRenderNode.addPositionUpdateListener(info.mPositionUpdateListener); } } - postUpdateSystemGestureExclusionRects(); } /** * WARNING: this can be called by a hwui worker thread, not just the UI thread! */ - void postUpdateSystemGestureExclusionRects() { + private void postUpdate(Runnable r) { // Potentially racey from a background thread. It's ok if it's not perfect. final Handler h = getHandler(); if (h != null) { - h.postAtFrontOfQueue(this::updateSystemGestureExclusionRects); + h.postAtFrontOfQueue(r); } } @@ -11727,6 +11745,106 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Set a preference to keep the bounds of this view clear from floating windows above this + * view's window. This informs the system that the view is considered a vital area for the + * user and that ideally it should not be covered. Setting this is only appropriate for UI + * where the user would likely take action to uncover it. + * <p> + * The system will try to respect this, but when not possible will ignore it. + * <p> + * @see #setPreferKeepClearRects + * @see #isPreferKeepClear + * @attr ref android.R.styleable#View_preferKeepClear + */ + public final void setPreferKeepClear(boolean preferKeepClear) { + getListenerInfo().mPreferKeepClear = preferKeepClear; + updatePositionUpdateListener(); + postUpdate(this::updateKeepClearRects); + } + + /** + * Retrieve the preference for this view to be kept clear. This is set either by + * {@link #setPreferKeepClear} or via the attribute android.R.styleable#View_preferKeepClear. + * <p> + * If this is {@code true}, the system will ignore the Rects set by + * {@link #setPreferKeepClearRects} and try to keep the whole view clear. + * <p> + * @see #setPreferKeepClear + * @attr ref android.R.styleable#View_preferKeepClear + */ + public final boolean isPreferKeepClear() { + return mListenerInfo != null && mListenerInfo.mPreferKeepClear; + } + + /** + * Set a preference to keep the provided rects clear from floating windows above this + * view's window. This informs the system that these rects are considered vital areas for the + * user and that ideally they should not be covered. Setting this is only appropriate for UI + * where the user would likely take action to uncover it. + * <p> + * If the whole view is preferred to be clear ({@link #isPreferKeepClear}), the rects set here + * will be ignored. + * <p> + * The system will try to respect this preference, but when not possible will ignore it. + * <p> + * @see #setPreferKeepClear + * @see #getPreferKeepClearRects + */ + public final void setPreferKeepClearRects(@NonNull List<Rect> rects) { + final ListenerInfo info = getListenerInfo(); + if (info.mKeepClearRects != null) { + info.mKeepClearRects.clear(); + info.mKeepClearRects.addAll(rects); + } else { + info.mKeepClearRects = new ArrayList<>(rects); + } + updatePositionUpdateListener(); + postUpdate(this::updateKeepClearRects); + } + + /** + * @return the list of rects, set by {@link #setPreferKeepClearRects}. + * + * @see #setPreferKeepClearRects + */ + @NonNull + public final List<Rect> getPreferKeepClearRects() { + final ListenerInfo info = mListenerInfo; + if (info != null && info.mKeepClearRects != null) { + return new ArrayList(info.mKeepClearRects); + } + + return Collections.emptyList(); + } + + void updateKeepClearRects() { + final AttachInfo ai = mAttachInfo; + if (ai != null) { + ai.mViewRootImpl.updateKeepClearRectsForView(this); + } + } + + /** + * Retrieve the list of areas within this view's post-layout coordinate space which the + * system will try to not cover with other floating elements, like the pip window. + */ + @NonNull + List<Rect> collectPreferKeepClearRects() { + final ListenerInfo info = mListenerInfo; + if (info != null) { + final List<Rect> list = new ArrayList(); + if (info.mPreferKeepClear) { + list.add(new Rect(0, 0, getWidth(), getHeight())); + } else if (info.mKeepClearRects != null) { + list.addAll(info.mKeepClearRects); + } + return list; + } + + return Collections.emptyList(); + } + + /** * Compute the view's coordinate within the surface. * * <p>Computes the coordinates of this view in its surface. The argument @@ -15120,7 +15238,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible); if (!getSystemGestureExclusionRects().isEmpty()) { - postUpdateSystemGestureExclusionRects(); + postUpdate(this::updateSystemGestureExclusionRects); + } + + if (!collectPreferKeepClearRects().isEmpty()) { + postUpdate(this::updateKeepClearRects); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cec8d4c89c5e..97b5a3181dbf 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -398,6 +398,8 @@ public final class ViewRootImpl implements ViewParent, final DisplayManager mDisplayManager; final String mBasePackageName; + private @Surface.Rotation int mDisplayInstallOrientation; + final int[] mTmpLocation = new int[2]; final TypedValue mTmpValue = new TypedValue(); @@ -749,7 +751,10 @@ public final class ViewRootImpl implements ViewParent, return mImeFocusController; } - private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker(); + private final ViewRootRectTracker mGestureExclusionTracker = + new ViewRootRectTracker(v -> v.getSystemGestureExclusionRects()); + private final ViewRootRectTracker mKeepClearRectsTracker = + new ViewRootRectTracker(v -> v.collectPreferKeepClearRects()); private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; @@ -1025,6 +1030,7 @@ public final class ViewRootImpl implements ViewParent, mView = view; mAttachInfo.mDisplayState = mDisplay.getState(); + mDisplayInstallOrientation = mDisplay.getInstallOrientation(); mViewLayoutDirectionInitial = mView.getRawLayoutDirection(); mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs); @@ -4767,7 +4773,7 @@ public final class ViewRootImpl implements ViewParent, * the root's view hierarchy. */ public void setRootSystemGestureExclusionRects(@NonNull List<Rect> rects) { - mGestureExclusionTracker.setRootSystemGestureExclusionRects(rects); + mGestureExclusionTracker.setRootRects(rects); mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED); } @@ -4777,7 +4783,26 @@ public final class ViewRootImpl implements ViewParent, */ @NonNull public List<Rect> getRootSystemGestureExclusionRects() { - return mGestureExclusionTracker.getRootSystemGestureExclusionRects(); + return mGestureExclusionTracker.getRootRects(); + } + + /** + * Called from View when the position listener is triggered + */ + void updateKeepClearRectsForView(View view) { + mKeepClearRectsTracker.updateRectsForView(view); + mHandler.sendEmptyMessage(MSG_KEEP_CLEAR_RECTS_CHANGED); + } + + void keepClearRectsChanged() { + final List<Rect> rectsForWindowManager = mKeepClearRectsTracker.computeChangedRects(); + if (rectsForWindowManager != null && mView != null) { + try { + mWindowSession.reportKeepClearAreasChanged(mWindow, rectsForWindowManager); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -5273,6 +5298,7 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_HIDE_INSETS = 35; private static final int MSG_REQUEST_SCROLL_CAPTURE = 36; private static final int MSG_WINDOW_TOUCH_MODE_CHANGED = 37; + private static final int MSG_KEEP_CLEAR_RECTS_CHANGED = 38; final class ViewRootHandler extends Handler { @@ -5341,6 +5367,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_HIDE_INSETS"; case MSG_WINDOW_TOUCH_MODE_CHANGED: return "MSG_WINDOW_TOUCH_MODE_CHANGED"; + case MSG_KEEP_CLEAR_RECTS_CHANGED: + return "MSG_KEEP_CLEAR_RECTS_CHANGED"; } return super.getMessageName(message); } @@ -5564,7 +5592,10 @@ public final class ViewRootImpl implements ViewParent, } break; case MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED: { systemGestureExclusionChanged(); - } break; + } break; + case MSG_KEEP_CLEAR_RECTS_CHANGED: { + keepClearRectsChanged(); + } break; case MSG_REQUEST_SCROLL_CAPTURE: handleScrollCaptureRequest((IScrollCaptureResponseListener) msg.obj); break; @@ -7909,6 +7940,10 @@ public final class ViewRootImpl implements ViewParent, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls, mSurfaceSize); + final int transformHint = SurfaceControl.rotationToBufferTransform( + (mDisplayInstallOrientation + mDisplay.getRotation()) % 4); + mSurfaceControl.setTransformHint(transformHint); + if (mAttachInfo.mContentCaptureManager != null) { MainContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager .getMainContentCaptureSession(); @@ -7927,7 +7962,6 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl); mAttachInfo.mThreadedRenderer.setBlastBufferQueue(mBlastBufferQueue); } - int transformHint = mSurfaceControl.getTransformHint(); if (mPreviousTransformHint != transformHint) { mPreviousTransformHint = transformHint; dispatchTransformHintChanged(transformHint); diff --git a/core/java/android/view/ViewRootRectTracker.java b/core/java/android/view/ViewRootRectTracker.java new file mode 100644 index 000000000000..fd9cc1920b39 --- /dev/null +++ b/core/java/android/view/ViewRootRectTracker.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2021 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; + +import com.android.internal.util.Preconditions; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; + +/** + * Abstract class to track a collection of rects reported by the views under the same + * {@link ViewRootImpl}. + */ +class ViewRootRectTracker { + private final Function<View, List<Rect>> mRectCollector; + private boolean mViewsChanged = false; + private boolean mRootRectsChanged = false; + private List<Rect> mRootRects = Collections.emptyList(); + private List<ViewInfo> mViewInfos = new ArrayList<>(); + private List<Rect> mRects = Collections.emptyList(); + + /** + * @param rectCollector given a view returns a list of the rects of interest for this + * ViewRootRectTracker + */ + ViewRootRectTracker(Function<View, List<Rect>> rectCollector) { + mRectCollector = rectCollector; + } + + public void updateRectsForView(@NonNull View view) { + boolean found = false; + final Iterator<ViewInfo> i = mViewInfos.iterator(); + while (i.hasNext()) { + final ViewInfo info = i.next(); + final View v = info.getView(); + if (v == null || !v.isAttachedToWindow() || !v.isAggregatedVisible()) { + mViewsChanged = true; + i.remove(); + continue; + } + if (v == view) { + found = true; + info.mDirty = true; + break; + } + } + if (!found && view.isAttachedToWindow()) { + mViewInfos.add(new ViewInfo(view)); + mViewsChanged = true; + } + } + + /** + * @return all visible rects from all views in the global (root) coordinate system + */ + @Nullable + public List<Rect> computeChangedRects() { + boolean changed = mRootRectsChanged; + final Iterator<ViewInfo> i = mViewInfos.iterator(); + final List<Rect> rects = new ArrayList<>(mRootRects); + while (i.hasNext()) { + final ViewInfo info = i.next(); + switch (info.update()) { + case ViewInfo.CHANGED: + changed = true; + // Deliberate fall-through + case ViewInfo.UNCHANGED: + rects.addAll(info.mRects); + break; + case ViewInfo.GONE: + mViewsChanged = true; + i.remove(); + break; + } + } + if (changed || mViewsChanged) { + mViewsChanged = false; + mRootRectsChanged = false; + if (!mRects.equals(rects)) { + mRects = rects; + return rects; + } + } + return null; + } + + /** + * Sets rects defined in the global (root) coordinate system, i.e. not for a specific view. + */ + public void setRootRects(@NonNull List<Rect> rects) { + Preconditions.checkNotNull(rects, "rects must not be null"); + mRootRects = rects; + mRootRectsChanged = true; + } + + @NonNull + public List<Rect> getRootRects() { + return mRootRects; + } + + @NonNull + private List<Rect> getTrackedRectsForView(@NonNull View v) { + final List<Rect> rects = mRectCollector.apply(v); + return rects == null ? Collections.emptyList() : rects; + } + + private class ViewInfo { + public static final int CHANGED = 0; + public static final int UNCHANGED = 1; + public static final int GONE = 2; + + private final WeakReference<View> mView; + boolean mDirty = true; + List<Rect> mRects = Collections.emptyList(); + + ViewInfo(View view) { + mView = new WeakReference<>(view); + } + + public View getView() { + return mView.get(); + } + + public int update() { + final View view = getView(); + if (view == null || !view.isAttachedToWindow() + || !view.isAggregatedVisible()) return GONE; + final List<Rect> localRects = getTrackedRectsForView(view); + final List<Rect> newRects = new ArrayList<>(localRects.size()); + for (Rect src : localRects) { + Rect mappedRect = new Rect(src); + ViewParent p = view.getParent(); + if (p != null && p.getChildVisibleRect(view, mappedRect, null)) { + newRects.add(mappedRect); + } + } + + if (mRects.equals(localRects)) return UNCHANGED; + mRects = newRects; + return CHANGED; + } + } +} diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 5be3a57e8527..ca7f90080c6c 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -118,6 +118,7 @@ import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityNodeInfo; +import android.window.TaskFpsCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -4858,4 +4859,31 @@ public interface WindowManager extends ViewManager { default boolean isTaskSnapshotSupported() { return false; } + + /** + * Registers the frame rate per second count callback for one given task ID. + * Each callback can only register for receiving FPS callback for one task id until unregister + * is called. If there's no task associated with the given task id, + * {@link IllegalArgumentException} will be thrown. If a task id destroyed after a callback is + * registered, the registered callback will not be unregistered until + * {@link #unregisterTaskFpsCallback(TaskFpsCallback))} is called + * @param taskId task id of the task. + * @param callback callback to be registered. + * + * @hide + */ + @SystemApi + default void registerTaskFpsCallback(@IntRange(from = 0) int taskId, + @NonNull TaskFpsCallback callback) {} + + /** + * Unregisters the frame rate per second count callback which was registered with + * {@link #registerTaskFpsCallback(int,TaskFpsCallback)}. + * + * @param callback callback to be unregistered. + * + * @hide + */ + @SystemApi + default void unregisterTaskFpsCallback(@NonNull TaskFpsCallback callback) {} } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index dd8041684c78..c16703ef50ef 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; import static android.window.WindowProviderService.isWindowProviderService; import android.annotation.CallbackExecutor; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; @@ -37,6 +38,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.StrictMode; +import android.window.TaskFpsCallback; import android.window.WindowContext; import android.window.WindowProvider; @@ -419,4 +421,22 @@ public final class WindowManagerImpl implements WindowManager { } return false; } + + @Override + public void registerTaskFpsCallback(@IntRange(from = 0) int taskId, TaskFpsCallback callback) { + try { + WindowManagerGlobal.getWindowManagerService().registerTaskFpsCallback( + taskId, callback.getListener()); + } catch (RemoteException e) { + } + } + + @Override + public void unregisterTaskFpsCallback(TaskFpsCallback callback) { + try { + WindowManagerGlobal.getWindowManagerService().unregisterTaskFpsCallback( + callback.getListener()); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 5176f9bb0f93..998498b0799a 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -28,6 +28,7 @@ import android.os.RemoteException; import android.util.Log; import android.util.MergedConfiguration; import android.window.ClientWindowFrames; +import android.window.IOnBackInvokedCallback; import java.util.HashMap; import java.util.Objects; @@ -459,6 +460,11 @@ public class WindowlessWindowManager implements IWindowSession { } @Override + public void reportKeepClearAreasChanged(android.view.IWindow window, + java.util.List<android.graphics.Rect> exclusionRects) { + } + + @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, InputChannel outInputChannel) { @@ -496,6 +502,10 @@ public class WindowlessWindowManager implements IWindowSession { } @Override + public void setOnBackInvokedCallback(IWindow iWindow, + IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { } + + @Override public boolean dropForAccessibility(IWindow window, int x, int y) { return false; } diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java index 05c74f2d0111..4f9781b6b6af 100644 --- a/core/java/android/view/accessibility/CaptioningManager.java +++ b/core/java/android/view/accessibility/CaptioningManager.java @@ -254,7 +254,13 @@ public class CaptioningManager { * Returns true if system wide call captioning is enabled for this device. */ public boolean isCallCaptioningEnabled() { - return mResources.getBoolean(R.bool.config_systemCaptionsServiceCallsEnabled); + try { + return mResources.getBoolean( + R.bool.config_systemCaptionsServiceCallsEnabled); + } catch (Resources.NotFoundException e) { + // The resource may not be defined, return false in that case + return false; + } } private void notifyEnabledChanged() { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 6fc246eb2514..6a22023dc9da 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2139,7 +2139,7 @@ public final class InputMethodManager { view.onInputConnectionOpenedInternal(ic, tba, icHandler); final ViewRootImpl viewRoot = view.getViewRootImpl(); if (viewRoot != null) { - viewRoot.getHandwritingInitiator().onInputConnectionCreated(view, tba); + viewRoot.getHandwritingInitiator().onInputConnectionCreated(view); } } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index ac28c31ecf49..dd70d69a8e02 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -17,6 +17,7 @@ package android.widget; import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP; +import static android.widget.TextView.ACCESSIBILITY_ACTION_SMART_START_ID; import android.R; import android.animation.ValueAnimator; @@ -84,6 +85,7 @@ import android.text.style.URLSpan; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.util.TypedValue; import android.view.ActionMode; @@ -112,6 +114,7 @@ import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.LinearInterpolator; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; @@ -449,11 +452,14 @@ public class Editor { private int mLineChangeSlopMax; private int mLineChangeSlopMin; + private final AccessibilitySmartActions mA11ySmartActions; + Editor(TextView textView) { mTextView = textView; // Synchronize the filter list, which places the undo input filter at the end. mTextView.setFilters(mTextView.getFilters()); mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this); + mA11ySmartActions = new AccessibilitySmartActions(mTextView); mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); @@ -4381,6 +4387,7 @@ public class Editor { item.setShowAsAction(showAsAction); mAssistClickHandlers.put(item, TextClassification.createIntentOnClickListener(action.getActionIntent())); + mA11ySmartActions.addAction(action); return item; } @@ -4394,6 +4401,7 @@ public class Editor { } i++; } + mA11ySmartActions.reset(); } private boolean hasLegacyAssistItem(TextClassification classification) { @@ -7656,7 +7664,7 @@ public class Editor { private final PackageManager mPackageManager; private final String mPackageName; private final SparseArray<Intent> mAccessibilityIntents = new SparseArray<>(); - private final SparseArray<AccessibilityNodeInfo.AccessibilityAction> mAccessibilityActions = + private final SparseArray<AccessibilityAction> mAccessibilityActions = new SparseArray<>(); private final List<ResolveInfo> mSupportedActivities = new ArrayList<>(); @@ -7706,8 +7714,7 @@ public class Editor { int actionId = TextView.ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID + i++; mAccessibilityActions.put( actionId, - new AccessibilityNodeInfo.AccessibilityAction( - actionId, getLabel(resolveInfo))); + new AccessibilityAction(actionId, getLabel(resolveInfo))); mAccessibilityIntents.put( actionId, createProcessTextIntentForResolveInfo(resolveInfo)); } @@ -7786,6 +7793,65 @@ public class Editor { } } + /** + * Accessibility helper for "smart" (i.e. textAssist) actions. + * Helps ensure that "smart" actions are shown in the accessibility menu. + * NOTE that these actions are only available when an action mode is live. + * + * @hide + */ + private static final class AccessibilitySmartActions { + + private final TextView mTextView; + private final SparseArray<Pair<AccessibilityAction, RemoteAction>> mActions = + new SparseArray<>(); + + private AccessibilitySmartActions(TextView textView) { + mTextView = Objects.requireNonNull(textView); + } + + private void addAction(RemoteAction action) { + final int actionId = ACCESSIBILITY_ACTION_SMART_START_ID + mActions.size(); + mActions.put(actionId, + new Pair(new AccessibilityAction(actionId, action.getTitle()), action)); + } + + private void reset() { + mActions.clear(); + } + + void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) { + for (int i = 0; i < mActions.size(); i++) { + nodeInfo.addAction(mActions.valueAt(i).first); + } + } + + boolean performAccessibilityAction(int actionId) { + final Pair<AccessibilityAction, RemoteAction> pair = mActions.get(actionId); + if (pair != null) { + TextClassification.createIntentOnClickListener(pair.second.getActionIntent()) + .onClick(mTextView); + return true; + } + return false; + } + } + + /** + * Initializes the nodeInfo with smart actions. + */ + void onInitializeSmartActionsAccessibilityNodeInfo(AccessibilityNodeInfo nodeInfo) { + mA11ySmartActions.onInitializeAccessibilityNodeInfo(nodeInfo); + } + + /** + * Handles the accessibility action if it is an active smart action. + * Return false if this method does not hanle the action. + */ + boolean performSmartActionsAccessibilityAction(int actionId) { + return mA11ySmartActions.performAccessibilityAction(actionId); + } + static void logCursor(String location, @Nullable String msgFormat, Object ... msgArgs) { if (msgFormat == null) { Log.d(TAG, location); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0fe06befa789..41c540116928 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -350,6 +350,7 @@ import java.util.function.Supplier; * @attr ref android.R.styleable#TextView_breakStrategy * @attr ref android.R.styleable#TextView_hyphenationFrequency * @attr ref android.R.styleable#TextView_lineBreakStyle + * @attr ref android.R.styleable#TextView_lineBreakWordStyle * @attr ref android.R.styleable#TextView_autoSizeTextType * @attr ref android.R.styleable#TextView_autoSizeMinTextSize * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize @@ -442,6 +443,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Accessibility action start id for "process text" actions. static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100; + /** Accessibility action start id for "smart" actions. @hide */ + static final int ACCESSIBILITY_ACTION_SMART_START_ID = 0x10001000; + /** * @hide */ @@ -458,6 +462,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; + // The default value of the line break style. + private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE; + + // The default value of the line break word style. + private static final int DEFAULT_LINE_BREAK_WORD_STYLE = + LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + /** * This change ID enables the fallback text line spacing (line height) for BoringLayout. * @hide @@ -1450,6 +1461,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE)); break; + case com.android.internal.R.styleable.TextView_lineBreakWordStyle: + mLineBreakConfig.setLineBreakWordStyle( + a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE)); + break; + case com.android.internal.R.styleable.TextView_autoSizeTextType: mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); break; @@ -3982,6 +3998,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float mLetterSpacing = 0; String mFontFeatureSettings = null; String mFontVariationSettings = null; + boolean mHasLineBreakStyle = false; + boolean mHasLineBreakWordStyle = false; + int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; + int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; @Override public String toString() { @@ -4012,6 +4032,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " mLetterSpacing:" + mLetterSpacing + "\n" + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" + " mFontVariationSettings:" + mFontVariationSettings + "\n" + + " mHasLineBreakStyle:" + mHasLineBreakStyle + "\n" + + " mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n" + + " mLineBreakStyle:" + mLineBreakStyle + "\n" + + " mLineBreakWordStyle:" + mLineBreakWordStyle + "\n" + "}"; } } @@ -4059,6 +4083,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings, com.android.internal.R.styleable.TextAppearance_fontVariationSettings); + sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle, + com.android.internal.R.styleable.TextAppearance_lineBreakStyle); + sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle, + com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle); } /** @@ -4174,6 +4202,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextAppearance_fontVariationSettings: attributes.mFontVariationSettings = appearance.getString(attr); break; + case com.android.internal.R.styleable.TextAppearance_lineBreakStyle: + attributes.mHasLineBreakStyle = true; + attributes.mLineBreakStyle = + appearance.getInt(attr, attributes.mLineBreakStyle); + break; + case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: + attributes.mHasLineBreakWordStyle = true; + attributes.mLineBreakWordStyle = + appearance.getInt(attr, attributes.mLineBreakWordStyle); + break; default: } } @@ -4239,9 +4277,46 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (attributes.mFontVariationSettings != null) { setFontVariationSettings(attributes.mFontVariationSettings); } + + if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) { + updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle, + attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle, + attributes.mLineBreakWordStyle); + } } /** + * Updates the LineBreakConfig from the TextAppearance. + * + * This method updates the given line configuration from the TextAppearance. This method will + * request new layout if line break config has been changed. + * + * @param isLineBreakStyleSpecified true if the line break style is specified. + * @param isLineBreakWordStyleSpecified true if the line break word style is specified. + * @param lineBreakStyle the value of the line break style in the TextAppearance. + * @param lineBreakWordStyle the value of the line break word style in the TextAppearance. + */ + private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified, + boolean isLineBreakWordStyleSpecified, + @LineBreakConfig.LineBreakStyle int lineBreakStyle, + @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { + boolean updated = false; + if (isLineBreakStyleSpecified && mLineBreakConfig.getLineBreakStyle() != lineBreakStyle) { + mLineBreakConfig.setLineBreakStyle(lineBreakStyle); + updated = true; + } + if (isLineBreakWordStyleSpecified + && mLineBreakConfig.getLineBreakWordStyle() != lineBreakWordStyle) { + mLineBreakConfig.setLineBreakWordStyle(lineBreakWordStyle); + updated = true; + } + if (updated && mLayout != null) { + nullLayouts(); + requestLayout(); + invalidate(); + } + } + /** * Get the default primary {@link Locale} of the text in this TextView. This will always be * the first member of {@link #getTextLocales()}. * @return the default primary {@link Locale} of the text in this TextView. @@ -4797,18 +4872,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Sets line break configuration indicates which strategy needs to be used when calculating the - * text wrapping. There are thee strategies for the line break style(lb): + * text wrapping. + * <P> + * There are two types of line break rules that can be configured at the same time. One is + * line break style(lb) and the other is line break word style(lw). The line break style + * affects rule-based breaking. The line break word style affects dictionary-based breaking + * and provide phrase-based breaking opportunities. There are several types for the + * line break style: * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE}, * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}. - * The default value of the line break style is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE}, - * which means no line break style is specified. + * The type for the line break word style is + * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}. + * The default values of the line break style and the line break word style are + * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} and + * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE} respectively, indicating that no line + * breaking rules are specified. * See <a href="https://drafts.csswg.org/css-text/#line-break-property"> * the line-break property</a> * * @param lineBreakConfig the line break config for text wrapping. */ public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) { + Objects.requireNonNull(lineBreakConfig); if (mLineBreakConfig.equals(lineBreakConfig)) { return; } @@ -4855,7 +4941,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTextDir = params.getTextDirection(); mBreakStrategy = params.getBreakStrategy(); mHyphenationFrequency = params.getHyphenationFrequency(); - mLineBreakConfig.set(params.getLineBreakConfig()); + if (params.getLineBreakConfig() != null) { + mLineBreakConfig.set(params.getLineBreakConfig()); + } else { + // Set default value if the line break config in the PrecomputedText.Params is null. + mLineBreakConfig.setLineBreakStyle(DEFAULT_LINE_BREAK_STYLE); + mLineBreakConfig.setLineBreakWordStyle(DEFAULT_LINE_BREAK_WORD_STYLE); + } if (mLayout != null) { nullLayouts(); requestLayout(); @@ -12223,6 +12315,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (canProcessText()) { // also implies mEditor is not null. mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); + mEditor.onInitializeSmartActionsAccessibilityNodeInfo(info); } } @@ -12426,9 +12519,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ @Override public boolean performAccessibilityActionInternal(int action, Bundle arguments) { - if (mEditor != null - && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { - return true; + if (mEditor != null) { + if (mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action) + || mEditor.performSmartActionsAccessibilityAction(action)) { + return true; + } } switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { diff --git a/core/java/android/window/IOnBackInvokedCallback.aidl b/core/java/android/window/IOnBackInvokedCallback.aidl new file mode 100644 index 000000000000..a42863c3126f --- /dev/null +++ b/core/java/android/window/IOnBackInvokedCallback.aidl @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 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.window; + +/** + * Interface that wraps a {@link OnBackInvokedCallback} object, to be stored in window manager + * and called from back handling process when back is invoked. + * + * @hide + */ +oneway interface IOnBackInvokedCallback { + /** + * Called when a back gesture has been started, or back button has been pressed down. + * Wraps {@link OnBackInvokedCallback#onBackStarted()}. + */ + void onBackStarted(); + + /** + * Called on back gesture progress. + * Wraps {@link OnBackInvokedCallback#onBackProgressed()}. + * + * @param touchX Absolute X location of the touch point. + * @param touchY Absolute Y location of the touch point. + * @param progress Value between 0 and 1 on how far along the back gesture is. + */ + void onBackProgressed(int touchX, int touchY, float progress); + + /** + * Called when a back gesture or back button press has been cancelled. + * Wraps {@link OnBackInvokedCallback#onBackCancelled()}. + */ + void onBackCancelled(); + + /** + * Called when a back gesture has been completed and committed, or back button pressed + * has been released and committed. + * Wraps {@link OnBackInvokedCallback#onBackInvoked()}. + */ + void onBackInvoked(); +} diff --git a/core/java/android/window/IOnFpsCallbackListener.aidl b/core/java/android/window/IOnFpsCallbackListener.aidl new file mode 100644 index 000000000000..3091df3b23a3 --- /dev/null +++ b/core/java/android/window/IOnFpsCallbackListener.aidl @@ -0,0 +1,30 @@ +/* + * 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 android.window; + +/** + * @hide + */ +oneway interface IOnFpsCallbackListener { + + /** + * Reports the fps from the registered task + * @param fps The frame rate per second of the task that has the registered task id + * and its children. + */ + void onFpsReported(in float fps); +} diff --git a/core/java/android/window/TaskFpsCallback.java b/core/java/android/window/TaskFpsCallback.java new file mode 100644 index 000000000000..a8e01b6df4b8 --- /dev/null +++ b/core/java/android/window/TaskFpsCallback.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2021 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.window; + +import android.annotation.BinderThread; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.RemoteException; + +import java.util.concurrent.Executor; + +/** + * Callback for sampling the frames per second for a task and its children. + * This should only be used by a system component that needs to listen to a task's + * tree's FPS when it is not actively submitting transactions for that corresponding SurfaceControl. + * Otherwise, ASurfaceTransaction_OnComplete callbacks should be used. + * + * Each callback can only register for receiving FPS report for one task id until + * {@link WindowManager#unregister()} is called. + * + * @hide + */ +@SystemApi +public final class TaskFpsCallback { + + /** + * Listener interface to receive frame per second of a task. + */ + public interface OnFpsCallbackListener { + /** + * Reports the fps from the registered task + * @param fps The frame per second of the task that has the registered task id + * and its children. + */ + void onFpsReported(float fps); + } + + private final IOnFpsCallbackListener mListener; + + public TaskFpsCallback(@NonNull Executor executor, @NonNull OnFpsCallbackListener listener) { + mListener = new IOnFpsCallbackListener.Stub() { + @Override + public void onFpsReported(float fps) { + executor.execute(() -> { + listener.onFpsReported(fps); + }); + } + }; + } + + /** + * @hide + */ + public IOnFpsCallbackListener getListener() { + return mListener; + } + + /** + * Dispatch the collected sample. + * + * Called from native code on a binder thread. + */ + @BinderThread + private static void dispatchOnFpsReported( + @NonNull IOnFpsCallbackListener listener, float fps) { + try { + listener.onFpsReported(fps); + } catch (RemoteException e) { + /* ignore */ + } + } +} diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 53734eb88720..29786046e49c 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -17,11 +17,19 @@ package android.window; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; import android.view.IWindow; import android.view.IWindowSession; import android.view.OnBackInvokedCallback; import android.view.OnBackInvokedDispatcher; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.TreeMap; + /** * Provides window based implementation of {@link OnBackInvokedDispatcher}. * @@ -39,6 +47,18 @@ import android.view.OnBackInvokedDispatcher; public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { private IWindowSession mWindowSession; private IWindow mWindow; + private static final String TAG = "WindowOnBackDispatcher"; + private static final boolean DEBUG = false; + + /** The currently most prioritized callback. */ + @Nullable + private OnBackInvokedCallbackWrapper mTopCallback; + + /** Convenience hashmap to quickly decide if a callback has been added. */ + private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>(); + /** Holds all callbacks by priorities. */ + private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>> + mOnBackInvokedCallbacks = new TreeMap<>(); /** * Sends the pending top callback (if one exists) to WM when the view root @@ -47,7 +67,9 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window) { mWindowSession = windowSession; mWindow = window; - // TODO(b/209867448): Send the top callback to WM (if one exists). + if (mTopCallback != null) { + setTopOnBackInvokedCallback(mTopCallback); + } } /** Detaches the dispatcher instance from its window. */ @@ -56,20 +78,124 @@ public class WindowOnBackInvokedDispatcher extends OnBackInvokedDispatcher { mWindowSession = null; } + // TODO: Take an Executor for the callback to run on. @Override public void registerOnBackInvokedCallback( @NonNull OnBackInvokedCallback callback, @Priority int priority) { - // TODO(b/209867448): To be implemented. + if (!mOnBackInvokedCallbacks.containsKey(priority)) { + mOnBackInvokedCallbacks.put(priority, new ArrayList<>()); + } + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + + // If callback has already been added, remove it and re-add it. + if (mAllCallbacks.containsKey(callback)) { + if (DEBUG) { + Log.i(TAG, "Callback already added. Removing and re-adding it."); + } + Integer prevPriority = mAllCallbacks.get(callback); + mOnBackInvokedCallbacks.get(prevPriority).remove(callback); + } + + callbacks.add(callback); + mAllCallbacks.put(callback, priority); + if (mTopCallback == null || (mTopCallback.getCallback() != callback + && mAllCallbacks.get(mTopCallback.getCallback()) <= priority)) { + setTopOnBackInvokedCallback(new OnBackInvokedCallbackWrapper(callback, priority)); + } } @Override - public void unregisterOnBackInvokedCallback( - @NonNull OnBackInvokedCallback callback) { - // TODO(b/209867448): To be implemented. + public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { + if (!mAllCallbacks.containsKey(callback)) { + if (DEBUG) { + Log.i(TAG, "Callback not found. returning..."); + } + return; + } + Integer priority = mAllCallbacks.get(callback); + mOnBackInvokedCallbacks.get(priority).remove(callback); + mAllCallbacks.remove(callback); + if (mTopCallback != null && mTopCallback.getCallback() == callback) { + findAndSetTopOnBackInvokedCallback(); + } } /** Clears all registered callbacks on the instance. */ public void clear() { - // TODO(b/209867448): To be implemented. + mAllCallbacks.clear(); + mTopCallback = null; + mOnBackInvokedCallbacks.clear(); + } + + /** + * Iterates through all callbacks to find the most prioritized one and pushes it to + * window manager. + */ + private void findAndSetTopOnBackInvokedCallback() { + if (mAllCallbacks.isEmpty()) { + setTopOnBackInvokedCallback(null); + return; + } + + for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) { + ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority); + if (!callbacks.isEmpty()) { + OnBackInvokedCallbackWrapper callback = new OnBackInvokedCallbackWrapper( + callbacks.get(callbacks.size() - 1), priority); + setTopOnBackInvokedCallback(callback); + return; + } + } + setTopOnBackInvokedCallback(null); + } + + // Pushes the top priority callback to window manager. + private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallbackWrapper callback) { + mTopCallback = callback; + if (mWindowSession == null || mWindow == null) { + return; + } + try { + mWindowSession.setOnBackInvokedCallback(mWindow, mTopCallback); + } catch (RemoteException e) { + Log.e(TAG, "Failed to set OnBackInvokedCallback to WM. Error: " + e); + } + } + + private class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { + private final OnBackInvokedCallback mCallback; + private final @Priority int mPriority; + + OnBackInvokedCallbackWrapper( + @NonNull OnBackInvokedCallback callback, @Priority int priority) { + mCallback = callback; + mPriority = priority; + } + + @NonNull + public OnBackInvokedCallback getCallback() { + return mCallback; + } + + @Override + public void onBackStarted() throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackStarted()); + } + + @Override + public void onBackProgressed(int touchX, int touchY, float progress) + throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackProgressed(touchX, touchY, progress)); + } + + @Override + public void onBackCancelled() throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackCancelled()); + } + + @Override + public void onBackInvoked() throws RemoteException { + Handler.getMain().post(() -> mCallback.onBackInvoked()); + } } } diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 14fd4c20e648..40ca9fb22d09 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -90,6 +90,14 @@ public class AccessibilityShortcutController { public static final ComponentName ACCESSIBILITY_BUTTON_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "AccessibilityButton"); + public static final ComponentName COLOR_INVERSION_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "ColorInversionTile"); + public static final ComponentName DALTONIZER_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "ColorCorrectionTile"); + public static final ComponentName ONE_HANDED_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "OneHandedModeTile"); + public static final ComponentName REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "ReduceBrightColorsTile"); private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index b723db287823..4ad232ad25eb 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -467,8 +467,21 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } protected void showEmptyState(ResolverListAdapter activeListAdapter, + @DrawableRes int iconRes, String title, String subtitle) { + showEmptyState(activeListAdapter, iconRes, title, subtitle, /* buttonOnClick */ null); + } + + protected void showEmptyState(ResolverListAdapter activeListAdapter, @DrawableRes int iconRes, @StringRes int titleRes, @StringRes int subtitleRes, View.OnClickListener buttonOnClick) { + String title = titleRes == 0 ? null : mContext.getString(titleRes); + String subtitle = subtitleRes == 0 ? null : mContext.getString(subtitleRes); + showEmptyState(activeListAdapter, iconRes, title, subtitle, buttonOnClick); + } + + protected void showEmptyState(ResolverListAdapter activeListAdapter, + @DrawableRes int iconRes, String title, String subtitle, + View.OnClickListener buttonOnClick) { ProfileDescriptor descriptor = getItem( userHandleToPageIndex(activeListAdapter.getUserHandle())); descriptor.rootView.findViewById(R.id.resolver_list).setVisibility(View.GONE); @@ -479,15 +492,15 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { View container = emptyStateView.findViewById(R.id.resolver_empty_state_container); setupContainerPadding(container); - TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title); - title.setText(titleRes); + TextView titleView = emptyStateView.findViewById(R.id.resolver_empty_state_title); + titleView.setText(title); - TextView subtitle = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle); - if (subtitleRes != 0) { - subtitle.setVisibility(View.VISIBLE); - subtitle.setText(subtitleRes); + TextView subtitleView = emptyStateView.findViewById(R.id.resolver_empty_state_subtitle); + if (subtitle != null) { + subtitleView.setVisibility(View.VISIBLE); + subtitleView.setText(subtitle); } else { - subtitle.setVisibility(View.GONE); + subtitleView.setVisibility(View.GONE); } Button button = emptyStateView.findViewById(R.id.resolver_empty_state_button); diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java index 3b6a877907f2..393bff483a20 100644 --- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java @@ -16,7 +16,17 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_SHARE_WITH_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; + import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.UserHandle; import android.view.LayoutInflater; @@ -184,8 +194,8 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd View.OnClickListener listener) { showEmptyState(activeListAdapter, R.drawable.ic_work_apps_off, - R.string.resolver_turn_on_work_apps, - /* subtitleRes */ 0, + getWorkAppPausedTitle(), + /* subtitle = */ null, listener); } @@ -194,13 +204,13 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd if (mIsSendAction) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_share_with_work_apps_explanation); + getCrossProfileBlockedTitle(), + getCantShareWithWorkMessage()); } else { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_work_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessWorkMessage()); } } @@ -209,13 +219,13 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd if (mIsSendAction) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_share_with_personal_apps_explanation); + getCrossProfileBlockedTitle(), + getCantShareWithPersonalMessage()); } else { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_personal_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessPersonalMessage()); } } @@ -223,8 +233,8 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available, - /* subtitleRes */ 0); + getNoPersonalAppsAvailableMessage(), + /* subtitle= */ null); } @@ -232,10 +242,65 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available, - /* subtitleRes */ 0); + getNoWorkAppsAvailableMessage(), + /* subtitle = */ null); + } + + private String getWorkAppPausedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_PAUSED_TITLE, + () -> getContext().getString(R.string.resolver_turn_on_work_apps)); + } + + private String getCrossProfileBlockedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, + () -> getContext().getString(R.string.resolver_cross_profile_blocked)); + } + + private String getCantShareWithWorkMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_SHARE_WITH_WORK, + () -> getContext().getString( + R.string.resolver_cant_share_with_work_apps_explanation)); + } + + private String getCantShareWithPersonalMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_SHARE_WITH_PERSONAL, + () -> getContext().getString( + R.string.resolver_cant_share_with_personal_apps_explanation)); } + private String getCantAccessWorkMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_WORK, + () -> getContext().getString( + R.string.resolver_cant_access_work_apps_explanation)); + } + + private String getCantAccessPersonalMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_PERSONAL, + () -> getContext().getString( + R.string.resolver_cant_access_personal_apps_explanation)); + } + + private String getNoWorkAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_WORK_APPS, + () -> getContext().getString( + R.string.resolver_no_work_apps_available)); + } + + private String getNoPersonalAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_PERSONAL_APPS, + () -> getContext().getString( + R.string.resolver_no_personal_apps_available)); + } + + void setEmptyStateBottomOffset(int bottomOffset) { mBottomOffset = bottomOffset; } diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index 587876df0df6..9648008274ef 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -21,6 +21,7 @@ import com.android.internal.os.BatteryStatsImpl; import android.bluetooth.BluetoothActivityEnergyInfo; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; +import android.os.BluetoothBatteryStats; import android.os.ParcelFileDescriptor; import android.os.WakeLockStats; import android.os.WorkSource; @@ -162,6 +163,10 @@ interface IBatteryStats { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)") WakeLockStats getWakeLockStats(); + /** {@hide} */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BATTERY_STATS)") + BluetoothBatteryStats getBluetoothBatteryStats(); + HealthStatsParceler takeUidSnapshot(int uid); HealthStatsParceler[] takeUidSnapshots(in int[] uid); diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 0f37dc5949f9..25b8dba2870b 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -16,13 +16,14 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import static com.android.internal.app.ResolverActivity.EXTRA_CALLING_USER; import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE; import android.annotation.Nullable; -import android.annotation.StringRes; import android.app.Activity; import android.app.ActivityThread; import android.app.AppGlobals; @@ -101,16 +102,16 @@ public class IntentForwarderActivity extends Activity { Intent intentReceived = getIntent(); String className = intentReceived.getComponent().getClassName(); final int targetUserId; - final int userMessageId; + final String userMessage; if (className.equals(FORWARD_INTENT_TO_PARENT)) { - userMessageId = com.android.internal.R.string.forward_intent_to_owner; + userMessage = getForwardToPersonalMessage(); targetUserId = getProfileParent(); getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) .setSubtype(MetricsEvent.PARENT_PROFILE)); } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { - userMessageId = com.android.internal.R.string.forward_intent_to_work; + userMessage = getForwardToWorkMessage(); targetUserId = getManagedProfile(); getMetricsLogger().write( @@ -118,7 +119,7 @@ public class IntentForwarderActivity extends Activity { .setSubtype(MetricsEvent.MANAGED_PROFILE)); } else { Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); - userMessageId = -1; + userMessage = null; targetUserId = UserHandle.USER_NULL; } if (targetUserId == UserHandle.USER_NULL) { @@ -156,11 +157,23 @@ public class IntentForwarderActivity extends Activity { return targetResolveInfo; }, mExecutorService) .thenAcceptAsync(result -> { - maybeShowDisclosure(intentReceived, result, userMessageId); + maybeShowDisclosure(intentReceived, result, userMessage); finish(); }, getApplicationContext().getMainExecutor()); } + private String getForwardToPersonalMessage() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_PERSONAL, + () -> getString(com.android.internal.R.string.forward_intent_to_owner)); + } + + private String getForwardToWorkMessage() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_WORK, + () -> getString(com.android.internal.R.string.forward_intent_to_work)); + } + private boolean isIntentForwarderResolveInfo(ResolveInfo resolveInfo) { if (resolveInfo == null) { return false; @@ -183,9 +196,9 @@ public class IntentForwarderActivity extends Activity { } private void maybeShowDisclosure( - Intent intentReceived, ResolveInfo resolveInfo, int messageId) { - if (shouldShowDisclosure(resolveInfo, intentReceived)) { - mInjector.showToast(messageId, Toast.LENGTH_LONG); + Intent intentReceived, ResolveInfo resolveInfo, @Nullable String message) { + if (shouldShowDisclosure(resolveInfo, intentReceived) && message != null) { + mInjector.showToast(message, Toast.LENGTH_LONG); } } @@ -405,8 +418,8 @@ public class IntentForwarderActivity extends Activity { } @Override - public void showToast(int messageId, int duration) { - Toast.makeText(IntentForwarderActivity.this, getString(messageId), duration).show(); + public void showToast(String message, int duration) { + Toast.makeText(IntentForwarderActivity.this, message, duration).show(); } } @@ -419,6 +432,6 @@ public class IntentForwarderActivity extends Activity { CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId); - void showToast(@StringRes int messageId, int duration); + void showToast(String message, int duration); } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index f9a8c7b58897..347153c7e53e 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -17,6 +17,13 @@ package com.android.internal.app; import static android.Manifest.permission.INTERACT_ACROSS_PROFILES; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.FORWARD_INTENT_TO_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_PERSONAL_TAB_ACCESSIBILITY; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PROFILE_NOT_SUPPORTED; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_TAB_ACCESSIBILITY; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.PermissionChecker.PID_UNKNOWN; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -32,6 +39,7 @@ import android.app.VoiceInteractor.PickOptionRequest; import android.app.VoiceInteractor.PickOptionRequest.Option; import android.app.VoiceInteractor.Prompt; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -123,7 +131,7 @@ public class ResolverActivity extends Activity implements protected View mProfileView; private int mLastSelected = AbsListView.INVALID_POSITION; private boolean mResolvingHome = false; - private int mProfileSwitchMessageId = -1; + private String mProfileSwitchMessage; private int mLayoutId; @VisibleForTesting protected final ArrayList<Intent> mIntents = new ArrayList<>(); @@ -441,7 +449,7 @@ public class ResolverActivity extends Activity implements // Determine whether we should show that intent is forwarded // from managed profile to owner or other way around. - setProfileSwitchMessageId(intent.getContentUserHint()); + setProfileSwitchMessage(intent.getContentUserHint()); mLaunchedFromUid = getLaunchedFromUid(); if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) { @@ -674,7 +682,7 @@ public class ResolverActivity extends Activity implements } // Do not show the profile switch message anymore. - mProfileSwitchMessageId = -1; + mProfileSwitchMessage = null; onTargetSelected(dri, false); if (!mAwaitingDelegateResponse) { @@ -828,7 +836,7 @@ public class ResolverActivity extends Activity implements } } - private void setProfileSwitchMessageId(int contentUserHint) { + private void setProfileSwitchMessage(int contentUserHint) { if (contentUserHint != UserHandle.USER_CURRENT && contentUserHint != UserHandle.myUserId()) { UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE); @@ -837,13 +845,25 @@ public class ResolverActivity extends Activity implements : false; boolean targetIsManaged = userManager.isManagedProfile(); if (originIsManaged && !targetIsManaged) { - mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_owner; + mProfileSwitchMessage = getForwardToPersonalMsg(); } else if (!originIsManaged && targetIsManaged) { - mProfileSwitchMessageId = com.android.internal.R.string.forward_intent_to_work; + mProfileSwitchMessage = getForwardToWorkMsg(); } } } + private String getForwardToPersonalMsg() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_PERSONAL, + () -> getString(com.android.internal.R.string.forward_intent_to_owner)); + } + + private String getForwardToWorkMsg() { + return getSystemService(DevicePolicyManager.class).getString( + FORWARD_INTENT_TO_WORK, + () -> getString(com.android.internal.R.string.forward_intent_to_work)); + } + /** * Turn on launch mode that is safe to use when forwarding intents received from * applications and running in system processes. This mode uses Activity.startActivityAsCaller @@ -1095,9 +1115,9 @@ public class ResolverActivity extends Activity implements ResolveInfo ri = mMultiProfilePagerAdapter.getActiveListAdapter() .resolveInfoForPosition(which, hasIndexBeenFiltered); if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) { - Toast.makeText(this, String.format(getResources().getString( - com.android.internal.R.string.activity_resolver_work_profiles_support), - ri.activityInfo.loadLabel(getPackageManager()).toString()), + Toast.makeText(this, + getWorkProfileNotSupportedMsg( + ri.activityInfo.loadLabel(getPackageManager()).toString()), Toast.LENGTH_LONG).show(); return; } @@ -1128,6 +1148,15 @@ public class ResolverActivity extends Activity implements } } + private String getWorkProfileNotSupportedMsg(String launcherName) { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_PROFILE_NOT_SUPPORTED, + () -> getString( + com.android.internal.R.string.activity_resolver_work_profiles_support, + launcherName), + launcherName); + } + /** * Replace me in subclasses! */ @@ -1394,8 +1423,8 @@ public class ResolverActivity extends Activity implements } // If needed, show that intent is forwarded // from managed profile to owner or other way around. - if (mProfileSwitchMessageId != -1) { - Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show(); + if (mProfileSwitchMessage != null) { + Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show(); } if (!mSafeForwardingMode) { if (cti.startAsUser(this, null, user)) { @@ -1742,12 +1771,12 @@ public class ResolverActivity extends Activity implements viewPager.setSaveEnabled(false); TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL) .setContent(R.id.profile_pager) - .setIndicator(getString(R.string.resolver_personal_tab)); + .setIndicator(getPersonalTabLabel()); tabHost.addTab(tabSpec); tabSpec = tabHost.newTabSpec(TAB_TAG_WORK) .setContent(R.id.profile_pager) - .setIndicator(getString(R.string.resolver_work_tab)); + .setIndicator(getWorkTabLabel()); tabHost.addTab(tabSpec); TabWidget tabWidget = tabHost.getTabWidget(); @@ -1799,6 +1828,16 @@ public class ResolverActivity extends Activity implements findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE); } + private String getPersonalTabLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_PERSONAL_TAB, () -> getString(R.string.resolver_personal_tab)); + } + + private String getWorkTabLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_TAB, () -> getString(R.string.resolver_work_tab)); + } + void onHorizontalSwipeStateChanged(int state) {} private void maybeHideDivider() { @@ -1830,8 +1869,6 @@ public class ResolverActivity extends Activity implements } private void resetTabsHeaderStyle(TabWidget tabWidget) { - String workContentDescription = getString(R.string.resolver_work_tab_accessibility); - String personalContentDescription = getString(R.string.resolver_personal_tab_accessibility); for (int i = 0; i < tabWidget.getChildCount(); i++) { View tabView = tabWidget.getChildAt(i); TextView title = tabView.findViewById(android.R.id.title); @@ -1839,14 +1876,26 @@ public class ResolverActivity extends Activity implements title.setTextColor(getAttrColor(this, android.R.attr.textColorTertiary)); title.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(R.dimen.resolver_tab_text_size)); - if (title.getText().equals(getString(R.string.resolver_personal_tab))) { - tabView.setContentDescription(personalContentDescription); - } else if (title.getText().equals(getString(R.string.resolver_work_tab))) { - tabView.setContentDescription(workContentDescription); + if (title.getText().equals(getPersonalTabLabel())) { + tabView.setContentDescription(getPersonalTabAccessibilityLabel()); + } else if (title.getText().equals(getWorkTabLabel())) { + tabView.setContentDescription(getWorkTabAccessibilityLabel()); } } } + private String getPersonalTabAccessibilityLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_PERSONAL_TAB_ACCESSIBILITY, + () -> getString(R.string.resolver_personal_tab_accessibility)); + } + + private String getWorkTabAccessibilityLabel() { + return getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_TAB_ACCESSIBILITY, + () -> getString(R.string.resolver_work_tab_accessibility)); + } + private static int getAttrColor(Context context, int attr) { TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); int colorAccent = ta.getColor(0, 0); diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index 622f1668d052..4da59a3e77d9 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -16,7 +16,15 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_PERSONAL; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CANT_ACCESS_WORK; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_CROSS_PROFILE_BLOCKED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_PERSONAL_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_NO_WORK_APPS; +import static android.app.admin.DevicePolicyResources.Strings.Core.RESOLVER_WORK_PAUSED_TITLE; + import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.Resources; import android.os.UserHandle; @@ -196,8 +204,8 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA View.OnClickListener listener) { showEmptyState(activeListAdapter, R.drawable.ic_work_apps_off, - R.string.resolver_turn_on_work_apps, - /* subtitleRes */ 0, + getWorkAppPausedTitle(), + /* subtitle = */ null, listener); } @@ -205,32 +213,72 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_work_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessWorkMessage()); } @Override protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) { showEmptyState(activeListAdapter, R.drawable.ic_sharing_disabled, - R.string.resolver_cross_profile_blocked, - R.string.resolver_cant_access_personal_apps_explanation); + getCrossProfileBlockedTitle(), + getCantAccessPersonalMessage()); } @Override protected void showNoPersonalAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_personal_apps_available, - /* subtitleRes */ 0); + getNoPersonalAppsAvailableMessage(), + /* subtitle = */ null); } @Override protected void showNoWorkAppsAvailableEmptyState(ResolverListAdapter listAdapter) { showEmptyState(listAdapter, R.drawable.ic_no_apps, - R.string.resolver_no_work_apps_available, - /* subtitleRes */ 0); + getNoWorkAppsAvailableMessage(), + /* subtitle= */ null); + } + + private String getWorkAppPausedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_WORK_PAUSED_TITLE, + () -> getContext().getString(R.string.resolver_turn_on_work_apps)); + } + + private String getCrossProfileBlockedTitle() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CROSS_PROFILE_BLOCKED_TITLE, + () -> getContext().getString(R.string.resolver_cross_profile_blocked)); + } + + private String getCantAccessWorkMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_WORK, + () -> getContext().getString( + R.string.resolver_cant_access_work_apps_explanation)); + } + + private String getCantAccessPersonalMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_CANT_ACCESS_PERSONAL, + () -> getContext().getString( + R.string.resolver_cant_access_personal_apps_explanation)); + } + + private String getNoWorkAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_WORK_APPS, + () -> getContext().getString( + R.string.resolver_no_work_apps_available)); + } + + private String getNoPersonalAppsAvailableMessage() { + return getContext().getSystemService(DevicePolicyManager.class).getString( + RESOLVER_NO_PERSONAL_APPS, + () -> getContext().getString( + R.string.resolver_no_personal_apps_available)); } void setUseLayoutWithDefault(boolean useLayoutWithDefault) { diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index ca0856238b90..3531fad353db 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -16,11 +16,14 @@ package com.android.internal.app; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import android.app.Activity; import android.app.AlertDialog; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.DialogInterface; import android.content.Intent; @@ -70,8 +73,8 @@ public class UnlaunchableAppActivity extends Activity String dialogTitle; String dialogMessage = null; if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE) { - dialogTitle = getResources().getString(R.string.work_mode_off_title); - dialogMessage = getResources().getString(R.string.work_mode_off_message); + dialogTitle = getDialogTitle(); + dialogMessage = getDialogMessage(); } else { Log.wtf(TAG, "Invalid unlaunchable type: " + mReason); finish(); @@ -91,6 +94,17 @@ public class UnlaunchableAppActivity extends Activity builder.show(); } + private String getDialogTitle() { + return getSystemService(DevicePolicyManager.class).getString( + UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, () -> getString(R.string.work_mode_off_title)); + } + + private String getDialogMessage() { + return getSystemService(DevicePolicyManager.class).getString( + UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE, + () -> getString(R.string.work_mode_off_message)); + } + @Override public void onDismiss(DialogInterface dialog) { finish(); diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 2f40d3b457c6..3b6f8f6187e1 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -14,10 +14,13 @@ package com.android.internal.notification; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN; + import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.ParceledListSlice; import android.media.AudioAttributes; @@ -143,7 +146,7 @@ public class SystemNotificationChannels { final NotificationChannel deviceAdmin = new NotificationChannel( DEVICE_ADMIN, - context.getString(R.string.notification_channel_device_admin), + getDeviceAdminNotificationChannelName(context), NotificationManager.IMPORTANCE_HIGH); channelsList.add(deviceAdmin); @@ -209,6 +212,12 @@ public class SystemNotificationChannels { nm.createNotificationChannels(channelsList); } + private static String getDeviceAdminNotificationChannelName(Context context) { + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return dpm.getString(NOTIFICATION_CHANNEL_DEVICE_ADMIN, + () -> context.getString(R.string.notification_channel_device_admin)); + } + /** Remove notification channels which are no longer used */ public static void removeDeprecated(Context context) { final NotificationManager nm = context.getSystemService(NotificationManager.class); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 7c203fbbba3b..8213c863875c 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -45,6 +45,7 @@ import android.os.BatteryConsumer; import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Binder; +import android.os.BluetoothBatteryStats; import android.os.Build; import android.os.Handler; import android.os.IBatteryPropertiesRegistrar; @@ -1196,6 +1197,48 @@ public class BatteryStatsImpl extends BatteryStats { return new WakeLockStats(uidWakeLockStats); } + @Override + @GuardedBy("this") + public BluetoothBatteryStats getBluetoothBatteryStats() { + final long elapsedRealtimeUs = mClock.elapsedRealtime() * 1000; + ArrayList<BluetoothBatteryStats.UidStats> uidStats = new ArrayList<>(); + for (int i = mUidStats.size() - 1; i >= 0; i--) { + final Uid uid = mUidStats.valueAt(i); + final Timer scanTimer = uid.getBluetoothScanTimer(); + final long scanTimeMs = + scanTimer != null ? scanTimer.getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0; + + final Timer unoptimizedScanTimer = uid.getBluetoothUnoptimizedScanTimer(); + final long unoptimizedScanTimeMs = + unoptimizedScanTimer != null ? unoptimizedScanTimer.getTotalTimeLocked( + elapsedRealtimeUs, STATS_SINCE_CHARGED) / 1000 : 0; + + final Counter scanResultCounter = uid.getBluetoothScanResultCounter(); + final int scanResultCount = + scanResultCounter != null ? scanResultCounter.getCountLocked( + STATS_SINCE_CHARGED) : 0; + + final ControllerActivityCounter counter = uid.getBluetoothControllerActivity(); + final long rxTimeMs = counter != null ? counter.getRxTimeCounter().getCountLocked( + STATS_SINCE_CHARGED) : 0; + final long txTimeMs = counter != null ? counter.getTxTimeCounters()[0].getCountLocked( + STATS_SINCE_CHARGED) : 0; + + if (scanTimeMs != 0 || unoptimizedScanTimeMs != 0 || scanResultCount != 0 + || rxTimeMs != 0 || txTimeMs != 0) { + uidStats.add(new BluetoothBatteryStats.UidStats(uid.getUid(), + scanTimeMs, + unoptimizedScanTimeMs, + scanResultCount, + rxTimeMs, + txTimeMs)); + } + } + + return new BluetoothBatteryStats(uidStats); + } + String mLastWakeupReason = null; long mLastWakeupUptimeMs = 0; private final HashMap<String, SamplingTimer> mWakeupReasonStats = new HashMap<>(); @@ -12763,60 +12806,58 @@ public class BatteryStatsImpl extends BatteryStats { long totalTxPackets = 0; long totalRxPackets = 0; if (delta != null) { - NetworkStats.Entry entry = new NetworkStats.Entry(); - final int size = delta.size(); - for (int i = 0; i < size; i++) { - entry = delta.getValues(i, entry); - + for (NetworkStats.Entry entry : delta) { if (DEBUG_ENERGY) { - Slog.d(TAG, "Wifi uid " + entry.uid + ": delta rx=" + entry.rxBytes - + " tx=" + entry.txBytes + " rxPackets=" + entry.rxPackets - + " txPackets=" + entry.txPackets); + Slog.d(TAG, "Wifi uid " + entry.getUid() + + ": delta rx=" + entry.getRxBytes() + + " tx=" + entry.getTxBytes() + + " rxPackets=" + entry.getRxPackets() + + " txPackets=" + entry.getTxPackets()); } - if (entry.rxBytes == 0 && entry.txBytes == 0) { + if (entry.getRxBytes() == 0 && entry.getTxBytes() == 0) { // Skip the lookup below since there is no work to do. continue; } - final int uid = mapUid(entry.uid); + final int uid = mapUid(entry.getUid()); final Uid u = getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs); - if (entry.rxBytes != 0) { - u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.rxBytes, - entry.rxPackets); - if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers - u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.rxBytes, - entry.rxPackets); + if (entry.getRxBytes() != 0) { + u.noteNetworkActivityLocked(NETWORK_WIFI_RX_DATA, entry.getRxBytes(), + entry.getRxPackets()); + if (entry.getSet() == NetworkStats.SET_DEFAULT) { // Background transfers + u.noteNetworkActivityLocked(NETWORK_WIFI_BG_RX_DATA, entry.getRxBytes(), + entry.getRxPackets()); } mNetworkByteActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( - entry.rxBytes); + entry.getRxBytes()); mNetworkPacketActivityCounters[NETWORK_WIFI_RX_DATA].addCountLocked( - entry.rxPackets); + entry.getRxPackets()); - rxPackets.incrementValue(uid, entry.rxPackets); + rxPackets.incrementValue(uid, entry.getRxPackets()); // Sum the total number of packets so that the Rx Power can // be evenly distributed amongst the apps. - totalRxPackets += entry.rxPackets; + totalRxPackets += entry.getRxPackets(); } - if (entry.txBytes != 0) { - u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.txBytes, - entry.txPackets); - if (entry.set == NetworkStats.SET_DEFAULT) { // Background transfers - u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.txBytes, - entry.txPackets); + if (entry.getTxBytes() != 0) { + u.noteNetworkActivityLocked(NETWORK_WIFI_TX_DATA, entry.getTxBytes(), + entry.getTxPackets()); + if (entry.getSet() == NetworkStats.SET_DEFAULT) { // Background transfers + u.noteNetworkActivityLocked(NETWORK_WIFI_BG_TX_DATA, entry.getTxBytes(), + entry.getTxPackets()); } mNetworkByteActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( - entry.txBytes); + entry.getTxBytes()); mNetworkPacketActivityCounters[NETWORK_WIFI_TX_DATA].addCountLocked( - entry.txPackets); + entry.getTxPackets()); - txPackets.incrementValue(uid, entry.txPackets); + txPackets.incrementValue(uid, entry.getTxPackets()); // Sum the total number of packets so that the Tx Power can // be evenly distributed amongst the apps. - totalTxPackets += entry.txPackets; + totalTxPackets += entry.getTxPackets(); } // Calculate consumed energy for this uid. Only do so if WifiReporting isn't @@ -12844,7 +12885,7 @@ public class BatteryStatsImpl extends BatteryStats { uidEstimatedConsumptionMah.incrementValue(u.getUid(), mWifiPowerCalculator.calcPowerWithoutControllerDataMah( - entry.rxPackets, entry.txPackets, + entry.getRxPackets(), entry.getTxPackets(), uidRunningMs, uidScanMs, uidBatchScanMs)); } } diff --git a/core/java/com/android/internal/os/BatteryUsageStatsStore.java b/core/java/com/android/internal/os/BatteryUsageStatsStore.java index fd54b32bd12f..af82f40013d8 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsStore.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsStore.java @@ -58,6 +58,7 @@ public class BatteryUsageStatsStore { new BatteryUsageStatsQuery.Builder() .setMaxStatsAgeMs(0) .includePowerModels() + .includeProcessStateData() .build()); private static final String BATTERY_USAGE_STATS_DIR = "battery-usage-stats"; private static final String SNAPSHOT_FILE_EXTENSION = ".bus"; diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java index 20cf102953e4..e9d55db3a5b4 100644 --- a/core/java/com/android/internal/os/BinderLatencyObserver.java +++ b/core/java/com/android/internal/os/BinderLatencyObserver.java @@ -19,7 +19,6 @@ package com.android.internal.os; import android.annotation.Nullable; import android.os.Binder; import android.os.Handler; -import android.os.Looper; import android.os.SystemClock; import android.util.ArrayMap; import android.util.Slog; @@ -181,7 +180,7 @@ public class BinderLatencyObserver { } public Handler getHandler() { - return new Handler(Looper.getMainLooper()); + return BackgroundThread.getHandler(); } } diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 7f8acccd72bb..54e65e079b83 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -274,16 +274,15 @@ public class PowerProfile { * [31:0] - per Subsystem fields, see {@link ModemPowerProfile}. * */ - private static final int SUBSYSTEM_SHIFT = 32; - private static final long SUBSYSTEM_MASK = 0xF << SUBSYSTEM_SHIFT; + private static final long SUBSYSTEM_MASK = 0xF_0000_0000L; /** * Power constant not associated with a subsystem. */ - public static final long SUBSYSTEM_NONE = 0 << SUBSYSTEM_SHIFT; + public static final long SUBSYSTEM_NONE = 0x0_0000_0000L; /** * Modem power constant. */ - public static final long SUBSYSTEM_MODEM = 1 << SUBSYSTEM_SHIFT; + public static final long SUBSYSTEM_MODEM = 0x1_0000_0000L; @LongDef(prefix = { "SUBSYSTEM_" }, value = { SUBSYSTEM_NONE, @@ -292,7 +291,7 @@ public class PowerProfile { @Retention(RetentionPolicy.SOURCE) public @interface Subsystem {} - private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFFFFFF; + private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF; /** * A map from Power Use Item to its power consumption. @@ -582,7 +581,7 @@ public class PowerProfile { handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP, POWER_MODEM_CONTROLLER_SLEEP, 0); handleDeprecatedModemConstant(ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE, - POWER_MODEM_CONTROLLER_SLEEP, 0); + POWER_MODEM_CONTROLLER_IDLE, 0); handleDeprecatedModemConstant( ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT | ModemPowerProfile.MODEM_DRAIN_TYPE_RX, POWER_MODEM_CONTROLLER_RX, 0); diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java index 456ff4ba8a26..afea69a9c3f2 100644 --- a/core/java/com/android/internal/power/ModemPowerProfile.java +++ b/core/java/com/android/internal/power/ModemPowerProfile.java @@ -22,9 +22,9 @@ import android.telephony.ModemActivityInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseDoubleArray; -import com.android.internal.telephony.util.ArrayUtils; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -64,34 +64,27 @@ public class ModemPowerProfile { */ private final SparseDoubleArray mPowerConstants = new SparseDoubleArray(); - private static final int MODEM_DRAIN_TYPE_SHIFT = 28; - private static final int MODEM_DRAIN_TYPE_MASK = 0xF << MODEM_DRAIN_TYPE_SHIFT; - - private static final int MODEM_TX_LEVEL_SHIFT = 24; - private static final int MODEM_TX_LEVEL_MASK = 0xF << MODEM_TX_LEVEL_SHIFT; - - private static final int MODEM_RAT_TYPE_SHIFT = 20; - private static final int MODEM_RAT_TYPE_MASK = 0xF << MODEM_RAT_TYPE_SHIFT; - - private static final int MODEM_NR_FREQUENCY_RANGE_SHIFT = 16; - private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0xF << MODEM_NR_FREQUENCY_RANGE_SHIFT; + private static final int MODEM_DRAIN_TYPE_MASK = 0xF000_0000; + private static final int MODEM_TX_LEVEL_MASK = 0x0F00_0000; + private static final int MODEM_RAT_TYPE_MASK = 0x00F0_0000; + private static final int MODEM_NR_FREQUENCY_RANGE_MASK = 0x000F_0000; /** * Corresponds to the overall modem battery drain while asleep. */ - public static final int MODEM_DRAIN_TYPE_SLEEP = 0 << MODEM_DRAIN_TYPE_SHIFT; + public static final int MODEM_DRAIN_TYPE_SLEEP = 0x0000_0000; /** * Corresponds to the overall modem battery drain while idle. */ - public static final int MODEM_DRAIN_TYPE_IDLE = 1 << MODEM_DRAIN_TYPE_SHIFT; + public static final int MODEM_DRAIN_TYPE_IDLE = 0x1000_0000; /** * Corresponds to the modem battery drain while receiving data. A specific Rx battery drain * power constant can be selected using a bitwise OR (|) with {@link ModemRatType} and * {@link ModemNrFrequencyRange} (when applicable). */ - public static final int MODEM_DRAIN_TYPE_RX = 2 << MODEM_DRAIN_TYPE_SHIFT; + public static final int MODEM_DRAIN_TYPE_RX = 0x2000_0000; /** * Corresponds to the modem battery drain while receiving data. @@ -99,7 +92,7 @@ public class ModemPowerProfile { * Specific Tx battery drain power constanta can be selected using a bitwise OR (|) with * {@link ModemRatType} and {@link ModemNrFrequencyRange} (when applicable). */ - public static final int MODEM_DRAIN_TYPE_TX = 3 << MODEM_DRAIN_TYPE_SHIFT; + public static final int MODEM_DRAIN_TYPE_TX = 0x3000_0000; @IntDef(prefix = {"MODEM_DRAIN_TYPE_"}, value = { MODEM_DRAIN_TYPE_SLEEP, @@ -111,33 +104,44 @@ public class ModemPowerProfile { public @interface ModemDrainType { } - private static final String[] MODEM_DRAIN_TYPE_NAMES = - new String[]{"SLEEP", "IDLE", "RX", "TX"}; + + private static final SparseArray<String> MODEM_DRAIN_TYPE_NAMES = new SparseArray<>(4); + static { + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_SLEEP, "SLEEP"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_IDLE, "IDLE"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_RX, "RX"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_DRAIN_TYPE_TX, "TX"); + } /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_0}. */ - public static final int MODEM_TX_LEVEL_0 = 0 << MODEM_TX_LEVEL_SHIFT; + + public static final int MODEM_TX_LEVEL_0 = 0x0000_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_1}. */ - public static final int MODEM_TX_LEVEL_1 = 1 << MODEM_TX_LEVEL_SHIFT; + + public static final int MODEM_TX_LEVEL_1 = 0x0100_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_2}. */ - public static final int MODEM_TX_LEVEL_2 = 2 << MODEM_TX_LEVEL_SHIFT; + + public static final int MODEM_TX_LEVEL_2 = 0x0200_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_3}. */ - public static final int MODEM_TX_LEVEL_3 = 3 << MODEM_TX_LEVEL_SHIFT; + + public static final int MODEM_TX_LEVEL_3 = 0x0300_0000; /** * Corresponds to {@link ModemActivityInfo#TX_POWER_LEVEL_4}. */ - public static final int MODEM_TX_LEVEL_4 = 4 << MODEM_TX_LEVEL_SHIFT; + + public static final int MODEM_TX_LEVEL_4 = 0x0400_0000; private static final int MODEM_TX_LEVEL_COUNT = 5; @@ -152,21 +156,37 @@ public class ModemPowerProfile { public @interface ModemTxLevel { } + private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5); + static { + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3"); + MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4"); + } + + private static final int[] MODEM_TX_LEVEL_MAP = new int[]{ + MODEM_TX_LEVEL_0, + MODEM_TX_LEVEL_1, + MODEM_TX_LEVEL_2, + MODEM_TX_LEVEL_3, + MODEM_TX_LEVEL_4}; + /** * Fallback for any active modem usage that does not match specified Radio Access Technology * (RAT) power constants. */ - public static final int MODEM_RAT_TYPE_DEFAULT = 0 << MODEM_RAT_TYPE_SHIFT; + public static final int MODEM_RAT_TYPE_DEFAULT = 0x0000_0000; /** * Corresponds to active modem usage on 4G {@link TelephonyManager#NETWORK_TYPE_LTE} RAT. */ - public static final int MODEM_RAT_TYPE_LTE = 1 << MODEM_RAT_TYPE_SHIFT; + public static final int MODEM_RAT_TYPE_LTE = 0x0010_0000; /** * Corresponds to active modem usage on 5G {@link TelephonyManager#NETWORK_TYPE_NR} RAT. */ - public static final int MODEM_RAT_TYPE_NR = 2 << MODEM_RAT_TYPE_SHIFT; + public static final int MODEM_RAT_TYPE_NR = 0x0020_0000; @IntDef(prefix = {"MODEM_RAT_TYPE_"}, value = { MODEM_RAT_TYPE_DEFAULT, @@ -177,33 +197,38 @@ public class ModemPowerProfile { public @interface ModemRatType { } - private static final String[] MODEM_RAT_TYPE_NAMES = new String[]{"DEFAULT", "LTE", "NR"}; + private static final SparseArray<String> MODEM_RAT_TYPE_NAMES = new SparseArray<>(3); + static { + MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_DEFAULT, "DEFAULT"); + MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_LTE, "LTE"); + MODEM_RAT_TYPE_NAMES.put(MODEM_RAT_TYPE_NR, "NR"); + } /** * Fallback for any active 5G modem usage that does not match specified NR frequency power * constants. */ - public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + public static final int MODEM_NR_FREQUENCY_RANGE_DEFAULT = 0x0000_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_LOW}. */ - public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 1 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + public static final int MODEM_NR_FREQUENCY_RANGE_LOW = 0x0001_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MID}. */ - public static final int MODEM_NR_FREQUENCY_RANGE_MID = 2 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + public static final int MODEM_NR_FREQUENCY_RANGE_MID = 0x0002_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_HIGH}. */ - public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 3 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + public static final int MODEM_NR_FREQUENCY_RANGE_HIGH = 0x0003_0000; /** * Corresponds to active NR modem usage on {@link ServiceState#FREQUENCY_RANGE_MMWAVE}. */ - public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 4 << MODEM_NR_FREQUENCY_RANGE_SHIFT; + public static final int MODEM_NR_FREQUENCY_RANGE_MMWAVE = 0x0004_0000; @IntDef(prefix = {"MODEM_NR_FREQUENCY_RANGE_"}, value = { MODEM_RAT_TYPE_DEFAULT, @@ -215,9 +240,14 @@ public class ModemPowerProfile { @Retention(RetentionPolicy.SOURCE) public @interface ModemNrFrequencyRange { } - - private static final String[] MODEM_NR_FREQUENCY_RANGE_NAMES = - new String[]{"DEFAULT", "LOW", "MID", "HIGH", "MMWAVE"}; + private static final SparseArray<String> MODEM_NR_FREQUENCY_RANGE_NAMES = new SparseArray<>(5); + static { + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_DEFAULT, "DEFAULT"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_LOW, "LOW"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MID, "MID"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_HIGH, "HIGH"); + MODEM_NR_FREQUENCY_RANGE_NAMES.put(MODEM_NR_FREQUENCY_RANGE_MMWAVE, "MMWAVE"); + } public ModemPowerProfile() { } @@ -261,11 +291,10 @@ public class ModemPowerProfile { final int ratType; final int nrfType; try { - ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_SHIFT, - MODEM_RAT_TYPE_NAMES); + ratType = getTypeFromAttribute(parser, ATTR_RAT, MODEM_RAT_TYPE_NAMES); if (ratType == MODEM_RAT_TYPE_NR) { nrfType = getTypeFromAttribute(parser, ATTR_NR_FREQUENCY, - MODEM_NR_FREQUENCY_RANGE_SHIFT, MODEM_NR_FREQUENCY_RANGE_NAMES); + MODEM_NR_FREQUENCY_RANGE_NAMES); } else { nrfType = 0; } @@ -299,10 +328,7 @@ public class ModemPowerProfile { MODEM_TX_LEVEL_COUNT - 1)); continue; } - final int modemTxLevel = level << MODEM_TX_LEVEL_SHIFT; - Slog.d("MWACHENS", - "parsing tx at level:" + level + ", aka 0x" + Integer.toHexString( - modemTxLevel)); + final int modemTxLevel = MODEM_TX_LEVEL_MAP[level]; final int txKey = MODEM_DRAIN_TYPE_TX | modemTxLevel | ratType | nrfType; setPowerConstant(txKey, txDrain); break; @@ -312,20 +338,31 @@ public class ModemPowerProfile { } } - private static int getTypeFromAttribute(XmlResourceParser parser, String attr, int shift, - String[] names) { + private static int getTypeFromAttribute(XmlResourceParser parser, String attr, + SparseArray<String> names) { final String value = XmlUtils.readStringAttribute(parser, attr); - final int index = ArrayUtils.indexOf(names, value); if (value == null) { // Attribute was not specified, just use the default. return 0; } + int index = -1; + final int size = names.size(); + // Manual linear search for string. (SparseArray uses == not equals.) + for (int i = 0; i < size; i++) { + if (value.equals(names.valueAt(i))) { + index = i; + } + } if (index < 0) { + final String[] stringNames = new String[size]; + for (int i = 0; i < size; i++) { + stringNames[i] = names.valueAt(i); + } throw new IllegalArgumentException( "Unexpected " + attr + " value : " + value + ". Acceptable values are " - + Arrays.toString(names)); + + Arrays.toString(stringNames)); } - return index << shift; + return names.keyAt(index); } /** @@ -384,39 +421,35 @@ public class ModemPowerProfile { private static String keyToString(int key) { StringBuilder sb = new StringBuilder(); final int drainType = key & MODEM_DRAIN_TYPE_MASK; - appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, - drainType >> MODEM_DRAIN_TYPE_SHIFT); + appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType); sb.append(","); if (drainType == MODEM_DRAIN_TYPE_TX) { - final int txLevel = (key & MODEM_TX_LEVEL_MASK) >> MODEM_TX_LEVEL_SHIFT; - sb.append("level:"); - sb.append(txLevel); - sb.append(","); + final int txLevel = key & MODEM_TX_LEVEL_MASK; + appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel); } final int ratType = key & MODEM_RAT_TYPE_MASK; - appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType >> MODEM_RAT_TYPE_SHIFT); + appendFieldToString(sb, "RAT", MODEM_RAT_TYPE_NAMES, ratType); if (ratType == MODEM_RAT_TYPE_NR) { sb.append(","); final int nrFreq = key & MODEM_NR_FREQUENCY_RANGE_MASK; - appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, - nrFreq >> MODEM_NR_FREQUENCY_RANGE_SHIFT); + appendFieldToString(sb, "nrFreq", MODEM_NR_FREQUENCY_RANGE_NAMES, nrFreq); } return sb.toString(); } - - private static void appendFieldToString(StringBuilder sb, String fieldName, String[] names, - int index) { + private static void appendFieldToString(StringBuilder sb, String fieldName, + SparseArray<String> names, int key) { sb.append(fieldName); sb.append(":"); - if (index < 0 || index >= names.length) { - sb.append("UNKNOWN("); - sb.append(index); + final String name = names.get(key, null); + if (name == null) { + sb.append("UNKNOWN(0x"); + sb.append(Integer.toHexString(key)); sb.append(")"); } else { - sb.append(names[index]); + sb.append(name); } } diff --git a/core/java/com/android/internal/usb/DumpUtils.java b/core/java/com/android/internal/usb/DumpUtils.java index 32601368f2fb..b06a7f4e56e4 100644 --- a/core/java/com/android/internal/usb/DumpUtils.java +++ b/core/java/com/android/internal/usb/DumpUtils.java @@ -244,7 +244,10 @@ public class DumpUtils { writeContaminantPresenceStatus(dump, "contaminant_presence_status", UsbPortStatusProto.CONTAMINANT_PRESENCE_STATUS, status.getContaminantDetectionStatus()); - + dump.write("usb_data_enabled", UsbPortStatusProto.USB_DATA_ENABLED, + status.getUsbDataStatus()); + dump.write("is_power_transfer_limited", UsbPortStatusProto.IS_POWER_TRANSFER_LIMITED, + status.isPowerTransferLimited()); dump.end(token); } } diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 402fa64036a0..6a626ee6eb8f 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -53,8 +53,6 @@ oneway interface IInputMethod { void setSessionEnabled(IInputMethodSession session, boolean enabled); - void revokeSession(IInputMethodSession session); - void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver); void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver); diff --git a/core/jni/Android.bp b/core/jni/Android.bp index a3ac472bb900..430d84ebb3e1 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -124,7 +124,6 @@ cc_library_shared { "android_view_PointerIcon.cpp", "android_view_Surface.cpp", "android_view_SurfaceControl.cpp", - "android_view_SurfaceControlFpsListener.cpp", "android_view_SurfaceControlHdrLayerInfoListener.cpp", "android_graphics_BLASTBufferQueue.cpp", "android_view_SurfaceSession.cpp", @@ -255,7 +254,6 @@ cc_library_shared { "libandroidicu", "libbattery", "libbpf_android", - "libnetdbpf", "libnetdutils", "libmemtrack", "libandroidfw", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 21ec64bba931..f4296becf484 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -123,7 +123,6 @@ extern int register_android_view_InputApplicationHandle(JNIEnv* env); extern int register_android_view_InputWindowHandle(JNIEnv* env); extern int register_android_view_Surface(JNIEnv* env); extern int register_android_view_SurfaceControl(JNIEnv* env); -extern int register_android_view_SurfaceControlFpsListener(JNIEnv* env); extern int register_android_view_SurfaceControlHdrLayerInfoListener(JNIEnv* env); extern int register_android_view_SurfaceSession(JNIEnv* env); extern int register_android_view_CompositionSamplingListener(JNIEnv* env); @@ -1546,7 +1545,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_view_InputWindowHandle), REG_JNI(register_android_view_Surface), REG_JNI(register_android_view_SurfaceControl), - REG_JNI(register_android_view_SurfaceControlFpsListener), REG_JNI(register_android_view_SurfaceControlHdrLayerInfoListener), REG_JNI(register_android_view_SurfaceSession), REG_JNI(register_android_view_CompositionSamplingListener), diff --git a/core/jni/OWNERS b/core/jni/OWNERS index 4357729095af..9a460f58effe 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -76,6 +76,9 @@ per-file AndroidRuntime.cpp = file:/graphics/java/android/graphics/OWNERS per-file AndroidRuntime.cpp = calin@google.com, ngeoffray@google.com, oth@google.com # Although marked "view" this is mostly graphics stuff per-file android_view_* = file:/graphics/java/android/graphics/OWNERS +# File used for Android Studio layoutlib +per-file LayoutlibLoader.cpp = file:/graphics/java/android/graphics/OWNERS +per-file LayoutlibLoader.cpp = diegoperez@google.com, jgaillard@google.com # Verity per-file com_android_internal_security_Verity* = ebiggers@google.com, victorhsieh@google.com diff --git a/core/jni/android_text_Hyphenator.cpp b/core/jni/android_text_Hyphenator.cpp index 3651dbdb3fa3..571a8e2e64be 100644 --- a/core/jni/android_text_Hyphenator.cpp +++ b/core/jni/android_text_Hyphenator.cpp @@ -127,6 +127,7 @@ static void init() { addHyphenator("or", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Oriya addHyphenator("pa", INDIC_MIN_PREFIX, INDIC_MIN_SUFFIX); // Punjabi addHyphenator("pt", 2, 3); // Portuguese + addHyphenator("ru", 2, 2); // Russian addHyphenator("sk", 2, 2); // Slovak addHyphenator("sl", 2, 2); // Slovenian addHyphenator("sq", 2, 2); // Albanian diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index dd5af0435acc..d5470cc5a888 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -91,6 +91,7 @@ static struct { jfieldID density; jfieldID secure; jfieldID deviceProductInfo; + jfieldID installOrientation; } gStaticDisplayInfoClassInfo; static struct { @@ -1210,6 +1211,8 @@ static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jobject tok env->SetBooleanField(object, gStaticDisplayInfoClassInfo.secure, info.secure); env->SetObjectField(object, gStaticDisplayInfoClassInfo.deviceProductInfo, convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo)); + env->SetIntField(object, gStaticDisplayInfoClassInfo.installOrientation, + static_cast<uint32_t>(info.installOrientation)); return object; } @@ -2152,6 +2155,8 @@ int register_android_view_SurfaceControl(JNIEnv* env) gStaticDisplayInfoClassInfo.deviceProductInfo = GetFieldIDOrDie(env, infoClazz, "deviceProductInfo", "Landroid/hardware/display/DeviceProductInfo;"); + gStaticDisplayInfoClassInfo.installOrientation = + GetFieldIDOrDie(env, infoClazz, "installOrientation", "I"); jclass dynamicInfoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DynamicDisplayInfo"); gDynamicDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, dynamicInfoClazz); diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index 23453876c2d5..11560a5ccd1b 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -225,6 +225,7 @@ message DisplayContentProto { repeated InsetsSourceProviderProto insets_source_providers = 35; optional bool is_sleeping = 36; repeated string sleep_tokens = 37; + repeated .android.graphics.RectProto keep_clear_areas = 38; } @@ -443,6 +444,7 @@ message WindowStateProto { optional bool force_seamless_rotation = 42; optional bool has_compat_scale = 43; optional float global_scale = 44; + repeated .android.graphics.RectProto keep_clear_areas = 45; } message IdentifierProto { diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto index 97097ff91219..b3f54f9afce2 100644 --- a/core/proto/android/service/usb.proto +++ b/core/proto/android/service/usb.proto @@ -196,9 +196,19 @@ message UsbIsHeadsetProto { message UsbPortManagerProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; + enum HalVersion { + V_UNKNOWN = 0; + V1_0 = 10; + V1_1 = 11; + V1_2 = 12; + V1_3 = 13; + V2 = 20; + } + optional bool is_simulation_active = 1; repeated UsbPortInfoProto usb_ports = 2; optional bool enable_usb_data_signaling = 3; + optional HalVersion hal_version = 4; } message UsbPortInfoProto { @@ -254,6 +264,8 @@ message UsbPortStatusProto { optional DataRole data_role = 4; repeated UsbPortStatusRoleCombinationProto role_combinations = 5; optional android.service.ContaminantPresenceStatus contaminant_presence_status = 6; + optional bool usb_data_enabled = 7; + optional bool is_power_transfer_limited = 8; } message UsbPortStatusRoleCombinationProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 9ed11375c7fe..3a842ee6e7f3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4251,7 +4251,8 @@ <!-- Allows low-level access to setting the keyboard layout. <p>Not for use by third-party applications. - @hide --> + @hide + @TestApi --> <permission android:name="android.permission.SET_KEYBOARD_LAYOUT" android:protectionLevel="signature" /> @@ -6043,10 +6044,15 @@ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" android:protectionLevel="signature" /> - <!-- Allows managing the Game Mode - @hide Used internally. --> + <!-- @SystemApi Allows managing the Game Mode + @hide --> <permission android:name="android.permission.MANAGE_GAME_MODE" - android:protectionLevel="signature" /> + android:protectionLevel="signature|privileged" /> + + <!-- @SystemApi Allows accessing the frame rate per second of a given application + @hide --> + <permission android:name="android.permission.ACCESS_FPS_COUNTER" + android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows the holder to register callbacks to inform the RebootReadinessManager when they are performing reboot-blocking work. @@ -6161,6 +6167,19 @@ <permission android:name="android.permission.MANAGE_SAFETY_CENTER" android:protectionLevel="internal|installer|role" /> + <!-- @SystemApi Allows an application to access the AmbientContextEvent service. + @hide + --> + <permission android:name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" + android:protectionLevel="internal|role"/> + + <!-- @SystemApi Required by a AmbientContextEventDetectionService + to ensure that only the service with this permission can bind to it. + @hide + --> + <permission android:name="android.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/drawable/ic_ime_nav_back.xml b/core/res/res/drawable/ic_ime_nav_back.xml new file mode 100644 index 000000000000..ca329aa64bd4 --- /dev/null +++ b/core/res/res/drawable/ic_ime_nav_back.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="28dp" + android:height="28dp" + android:autoMirrored="true" + android:viewportWidth="28" + android:viewportHeight="28"> + <path + android:pathData="M16.78,10.03l-3.97,3.97l3.97,3.97l-1.06,1.06l-5.03,-5.03l5.03,-5.03z" + android:fillColor="#FFFFFFFF" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_ime_switcher.xml b/core/res/res/drawable/ic_ime_switcher.xml new file mode 100644 index 000000000000..6c3b76627323 --- /dev/null +++ b/core/res/res/drawable/ic_ime_switcher.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="20dp" + android:height="20dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M19,7h2v2h-2V7zM15,7h2v2h-2V7zM3,7h2v2H3V7zM7,7h2v2H7V7zM11,7h2v2h-2V7zM19,11h2v2h-2V11zM15,11h2v2h-2V11zM3,11h2v2H3V11zM7,11h2v2H7V11zM11,11h2v2h-2V11zM7,15h10v2H7V15z" + android:fillColor="#FFFFFFFF"/> +</vector> diff --git a/core/res/res/layout/input_method_nav_back.xml b/core/res/res/layout/input_method_nav_back.xml new file mode 100644 index 000000000000..671766aec281 --- /dev/null +++ b/core/res/res/layout/input_method_nav_back.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<android.inputmethodservice.navigationbar.KeyButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_nav_back" + android:layout_width="@dimen/input_method_navigation_key_width" + android:layout_height="match_parent" + android:layout_weight="0" + android:scaleType="center" + android:contentDescription="@string/input_method_nav_back_button_desc" + android:paddingStart="@dimen/input_method_navigation_key_padding" + android:paddingEnd="@dimen/input_method_navigation_key_padding" + /> diff --git a/core/res/res/layout/input_method_nav_home_handle.xml b/core/res/res/layout/input_method_nav_home_handle.xml new file mode 100644 index 000000000000..501f5126aff8 --- /dev/null +++ b/core/res/res/layout/input_method_nav_home_handle.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<android.inputmethodservice.navigationbar.NavigationHandle + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_nav_home_handle" + android:layout_width="72dp" + android:layout_height="match_parent" + android:layout_weight="0" + android:paddingStart="@dimen/input_method_navigation_key_padding" + android:paddingEnd="@dimen/input_method_navigation_key_padding" + /> diff --git a/core/res/res/layout/input_method_nav_ime_switcher.xml b/core/res/res/layout/input_method_nav_ime_switcher.xml new file mode 100644 index 000000000000..b571ba927837 --- /dev/null +++ b/core/res/res/layout/input_method_nav_ime_switcher.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<android.inputmethodservice.navigationbar.KeyButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_nav_ime_switcher" + android:layout_width="@dimen/input_method_navigation_key_width" + android:layout_height="match_parent" + android:layout_weight="0" + android:contentDescription="@string/input_method_ime_switch_button_desc" + android:scaleType="center" + android:paddingStart="@dimen/input_method_navigation_key_padding" + android:paddingEnd="@dimen/input_method_navigation_key_padding" + /> diff --git a/core/res/res/layout/input_method_navigation_bar.xml b/core/res/res/layout/input_method_navigation_bar.xml new file mode 100644 index 000000000000..ce402fbce891 --- /dev/null +++ b/core/res/res/layout/input_method_navigation_bar.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<android.inputmethodservice.navigationbar.NavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/input_method_navigation_bar_view" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:clipChildren="false" + android:clipToPadding="false"> + + <android.inputmethodservice.navigationbar.NavigationBarInflaterView + android:id="@+id/input_method_nav_inflater" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false" /> + +</android.inputmethodservice.navigationbar.NavigationBarView> diff --git a/core/res/res/layout/input_method_navigation_layout.xml b/core/res/res/layout/input_method_navigation_layout.xml new file mode 100644 index 000000000000..05e750ad34ff --- /dev/null +++ b/core/res/res/layout/input_method_navigation_layout.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/input_method_rounded_corner_content_padding" + android:layout_marginEnd="@dimen/input_method_rounded_corner_content_padding" + android:paddingStart="@dimen/input_method_nav_content_padding" + android:paddingEnd="@dimen/input_method_nav_content_padding" + android:clipChildren="false" + android:clipToPadding="false" + android:id="@+id/input_method_nav_horizontal"> + + <FrameLayout + android:id="@+id/input_method_nav_buttons" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false"> + + <LinearLayout + android:id="@+id/input_method_nav_ends_group" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:clipToPadding="false" + android:clipChildren="false" /> + + <LinearLayout + android:id="@+id/input_method_nav_center_group" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center" + android:gravity="center" + android:orientation="horizontal" + android:clipToPadding="false" + android:clipChildren="false" /> + + </FrameLayout> + +</FrameLayout> diff --git a/core/res/res/values-sw360dp/dimens.xml b/core/res/res/values-sw360dp/dimens.xml index 4c74264c676a..00de60efbeb2 100644 --- a/core/res/res/values-sw360dp/dimens.xml +++ b/core/res/res/values-sw360dp/dimens.xml @@ -18,4 +18,8 @@ --> <resources> <dimen name="chooser_grid_padding">16dp</dimen> + + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME --> + <dimen name="input_method_navigation_key_width">80dip</dimen> + </resources> diff --git a/core/res/res/values-sw372dp/dimens.xml b/core/res/res/values-sw372dp/dimens.xml new file mode 100644 index 000000000000..cb29a1944a22 --- /dev/null +++ b/core/res/res/values-sw372dp/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<resources> + <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME --> + <dimen name="input_method_nav_content_padding">8dp</dimen> +</resources> diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml index e8f15fd022d7..4c70ea32bb5b 100644 --- a/core/res/res/values-sw600dp/dimens.xml +++ b/core/res/res/values-sw600dp/dimens.xml @@ -41,6 +41,11 @@ <!-- Size of lockscreen outerring on unsecure unlock LockScreen --> <dimen name="keyguard_lockscreen_outerring_diameter">364dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME --> + <dimen name="input_method_navigation_key_width">128dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME --> + <dimen name="input_method_navigation_key_padding">25dp</dimen> + <!-- Height of FaceUnlock view in keyguard --> <dimen name="face_unlock_height">430dip</dimen> diff --git a/core/res/res/values-sw900dp/dimens.xml b/core/res/res/values-sw900dp/dimens.xml index 11092b2cb9e9..9ec420453f6b 100644 --- a/core/res/res/values-sw900dp/dimens.xml +++ b/core/res/res/values-sw900dp/dimens.xml @@ -24,4 +24,12 @@ the same as @dimen/navigation_bar_height --> <dimen name="navigation_bar_height_landscape">56dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_width">80dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_padding">0dp</dimen> + <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the + IME. --> + <dimen name="input_method_nav_key_button_ripple_max_width">76dp</dimen> + </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 6a7b4aff994f..8696f5a94750 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3347,6 +3347,14 @@ <p>Note that this flag will only be respected if the View's Outline returns true from {@link android.graphics.Outline#canClip()}. --> <attr name="clipToOutline" format="boolean" /> + + <!-- <p> Sets a preference to keep the bounds of this view clear from floating windows + above this view's window. This informs the system that the view is considered a vital + area for the user and that ideally it should not be covered. Setting this is only + appropriate for UI where the user would likely take action to uncover it. + <p>The system will try to respect this, but when not possible will ignore it. + See {@link android.view.View#setPreferKeepClear}. --> + <attr name="preferKeepClear" format="boolean" /> </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> @@ -5030,6 +5038,10 @@ <attr name="fontFeatureSettings" format="string" /> <!-- Font variation settings. --> <attr name="fontVariationSettings" format="string"/> + <!-- Specifies the strictness of line-breaking rules applied within an element. --> + <attr name="lineBreakStyle" /> + <!-- Specifies the phrase-based breaking opportunities. --> + <attr name="lineBreakWordStyle" /> </declare-styleable> <declare-styleable name="TextClock"> <!-- Specifies the formatting pattern used to show the time and/or date @@ -5428,6 +5440,13 @@ <!-- ndicates breaking text with the most strictest line-breaking rules. --> <enum name="strict" value="3" /> </attr> + <!-- Specify the phrase-based line break can be used when calculating the text wrapping.--> + <attr name="lineBreakWordStyle"> + <!-- No line break word style specific. --> + <enum name="none" value="0" /> + <!-- Specify the phrase based breaking. --> + <enum name="phrase" value="1" /> + </attr> <!-- Specify the type of auto-size. Note that this feature is not supported by EditText, works only for TextView. --> <attr name="autoSizeTextType" format="enum"> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index a5954338910d..3a2fb6e70ba8 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -401,6 +401,15 @@ and before. --> <attr name="sharedUserMaxSdkVersion" format="integer" /> + <!-- Whether the application should inherit all AndroidKeyStore keys of its shared user + group in the case of leaving its shared user ID in an upgrade. If set to false, all + AndroidKeyStore keys will remain in the shared user group, and the application will no + longer have access to those keys after the upgrade. If set to true, all AndroidKeyStore + keys owned by the shared user group will be transferred to the upgraded application; + other applications in the shared user group will no longer have access to those keys + after the migration. The default value is false if not explicitly set. --> + <attr name="inheritKeyStoreKeys" format="boolean" /> + <!-- Internal version code. This is the number used to determine whether one version is more recent than another: it has no other meaning than that higher numbers are more recent. You could use this number to @@ -1677,6 +1686,7 @@ <attr name="sharedUserId" /> <attr name="sharedUserLabel" /> <attr name="sharedUserMaxSdkVersion" /> + <attr name="inheritKeyStoreKeys" /> <attr name="installLocation" /> <attr name="isolatedSplits" /> <attr name="isFeatureSplit" /> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d8b3785d7f4e..c0c8618aaf96 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4077,6 +4077,12 @@ --> <string name="config_defaultRotationResolverService" translatable="false"></string> + <!-- The component name for the default system AmbientContextEvent detection service. + This service must be trusted, as it can be activated without explicit consent of the user. + See android.service.ambientcontext.AmbientContextDetectionService. + --> + <string name="config_defaultAmbientContextDetectionService" translatable="false"></string> + <!-- The component name for the system-wide captions service. This service must be trusted, as it controls part of the UI of the volume bar. Example: "com.android.captions/.SystemCaptionsService" diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index a877bd39bceb..3f08e4b9d9ad 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -131,6 +131,19 @@ corners. --> <dimen name="rounded_corner_radius_bottom_adjustment">0px</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_width for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_width">70dp</dimen> + <!-- Copied from SysUI's @dimen/navigation_key_padding for the embedded nav bar in the IME. --> + <dimen name="input_method_navigation_key_padding">0dp</dimen> + <!-- Copied from SysUI's @dimen/nav_content_padding for the embedded nav bar in the IME. --> + <dimen name="input_method_nav_content_padding">0px</dimen> + <!-- Copied from SysUI's @dimen/rounded_corner_content_padding for the embedded nav bar in the + IME. --> + <dimen name="input_method_rounded_corner_content_padding">0px</dimen> + <!-- Copied from SysUI's @dimen/key_button_ripple_max_width for the embedded nav bar in the + IME. --> + <dimen name="input_method_nav_key_button_ripple_max_width">95dp</dimen> + <!-- Width of the window of the divider bar used to resize docked stacks. --> <dimen name="docked_stack_divider_thickness">48dp</dimen> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index cfe65eb00cea..505fe59cfefd 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3212,6 +3212,7 @@ <public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" /> <public type="attr" name="lineBreakStyle" id="0x0101064d" /> + <public type="attr" name="lineBreakWordStyle" id="0x0101064e" /> <staging-public-group-final type="id" first-id="0x01fe0000"> <public name="accessibilityActionDragStart" /> @@ -3256,6 +3257,8 @@ <public name="gameSessionService" /> <public name="localeConfig" /> <public name="showBackground" /> + <public name="inheritKeyStoreKeys" /> + <public name="preferKeepClear" /> </staging-public-group> <staging-public-group type="id" first-id="0x01de0000"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6577ebc8cd3f..1a5d8b7ca8fd 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3277,6 +3277,11 @@ <!-- Title for EditText context menu [CHAR LIMIT=20] --> <string name="editTextMenuTitle">Text actions</string> + <!-- Content description of the back button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="input_method_nav_back_button_desc">Back</string> + <!-- Content description of the switch input method button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="input_method_ime_switch_button_desc">Switch input method</string> + <!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. --> <string name="low_internal_storage_view_title">Storage space running out</string> <!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5e8851976556..8c8ef120f781 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2439,6 +2439,24 @@ <!-- From PinyinIME(!!!) --> <java-symbol type="string" name="inputMethod" /> + <!-- Gestural Nav buttons within InputMethodService --> + <java-symbol type="dimen" name="input_method_nav_key_button_ripple_max_width" /> + <java-symbol type="drawable" name="ic_ime_nav_back" /> + <java-symbol type="drawable" name="ic_ime_switcher" /> + <java-symbol type="id" name="input_method_nav_back" /> + <java-symbol type="id" name="input_method_nav_buttons" /> + <java-symbol type="id" name="input_method_nav_center_group" /> + <java-symbol type="id" name="input_method_nav_ends_group" /> + <java-symbol type="id" name="input_method_nav_home_handle" /> + <java-symbol type="id" name="input_method_nav_horizontal" /> + <java-symbol type="id" name="input_method_nav_ime_switcher" /> + <java-symbol type="id" name="input_method_nav_inflater" /> + <java-symbol type="layout" name="input_method_navigation_bar" /> + <java-symbol type="layout" name="input_method_navigation_layout" /> + <java-symbol type="layout" name="input_method_nav_back" /> + <java-symbol type="layout" name="input_method_nav_home_handle" /> + <java-symbol type="layout" name="input_method_nav_ime_switcher" /> + <!-- From Chromium-WebView --> <java-symbol type="attr" name="actionModeWebSearchDrawable" /> <java-symbol type="string" name="websearch" /> @@ -3652,6 +3670,7 @@ <java-symbol type="string" name="config_defaultRotationResolverService" /> <java-symbol type="string" name="config_defaultSystemCaptionsService" /> <java-symbol type="string" name="config_defaultSystemCaptionsManagerService" /> + <java-symbol type="string" name="config_defaultAmbientContextDetectionService" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index f2b35c72a567..a80424e500c4 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -38,6 +38,7 @@ <uses-permission android:name="android.permission.ACCESS_CACHE_FILESYSTEM" /> <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER" /> <uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" /> + <uses-permission android:name="android.permission.ACCESS_FPS_COUNTER" /> <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> @@ -64,6 +65,7 @@ <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_DREAM_STATE" /> + <uses-permission android:name="android.permission.REAL_GET_TASKS"/> <uses-permission android:name="android.permission.WRITE_DREAM_STATE" /> <uses-permission android:name="android.permission.READ_CLIPBOARD_IN_BACKGROUND" /> <uses-permission android:name="android.permission.READ_LOGS"/> diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml new file mode 100644 index 000000000000..22571142a350 --- /dev/null +++ b/core/tests/coretests/res/xml/power_profile_test.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<device name="Android"> + <!-- This is the battery capacity in mAh --> + <item name="battery.capacity">3000</item> + + <!-- Number of cores each CPU cluster contains --> + <array name="cpu.clusters.cores"> + <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) --> + <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) --> + </array> + + <!-- Power consumption when CPU is suspended --> + <item name="cpu.suspend">5</item> + <!-- Additional power consumption when CPU is in a kernel idle loop --> + <item name="cpu.idle">1.11</item> + <!-- Additional power consumption by CPU excluding cluster and core when running --> + <item name="cpu.active">2.55</item> + + <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it --> + <item name="cpu.cluster_power.cluster0">2.11</item> + <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it --> + <item name="cpu.cluster_power.cluster1">2.22</item> + + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster0"> + <value>300000</value> <!-- 300 MHz CPU speed --> + <value>1000000</value> <!-- 1000 MHz CPU speed --> + <value>2000000</value> <!-- 2000 MHz CPU speed --> + </array> + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster1"> + <value>300000</value> <!-- 300 MHz CPU speed --> + <value>1000000</value> <!-- 1000 MHz CPU speed --> + <value>2500000</value> <!-- 2500 MHz CPU speed --> + <value>3000000</value> <!-- 3000 MHz CPU speed --> + </array> + + <!-- Additional power used by a CPU from cluster 0 when running at different + speeds. Currently this measurement also includes cluster cost. --> + <array name="cpu.core_power.cluster0"> + <value>10</value> <!-- 300 MHz CPU speed --> + <value>20</value> <!-- 1000 MHz CPU speed --> + <value>30</value> <!-- 1900 MHz CPU speed --> + </array> + <!-- Additional power used by a CPU from cluster 1 when running at different + speeds. Currently this measurement also includes cluster cost. --> + <array name="cpu.core_power.cluster1"> + <value>25</value> <!-- 300 MHz CPU speed --> + <value>35</value> <!-- 1000 MHz CPU speed --> + <value>50</value> <!-- 2500 MHz CPU speed --> + <value>60</value> <!-- 3000 MHz CPU speed --> + </array> + + <!-- Power used by display unit in ambient display mode, including back lighting--> + <item name="ambient.on">0.5</item> + <!-- Additional power used when screen is turned on at minimum brightness --> + <item name="screen.on">100</item> + <!-- Additional power used when screen is at maximum brightness, compared to + screen at minimum brightness --> + <item name="screen.full">800</item> + + <!-- Average power used by the camera flash module when on --> + <item name="camera.flashlight">500</item> + <!-- Average power use by the camera subsystem for a typical camera + application. Intended as a rough estimate for an application running a + preview and capturing approximately 10 full-resolution pictures per + minute. --> + <item name="camera.avg">600</item> + + <!-- Additional power used by the audio hardware, probably due to DSP --> + <item name="audio">100.0</item> + + <!-- Additional power used by the video hardware, probably due to DSP --> + <item name="video">150.0</item> <!-- ~50mA --> + + <!-- Additional power used when GPS is acquiring a signal --> + <item name="gps.on">10</item> + + <!-- Additional power used when cellular radio is transmitting/receiving --> + <item name="radio.active">60</item> + <!-- Additional power used when cellular radio is paging the tower --> + <item name="radio.scanning">3</item> + <!-- Additional power used when the cellular radio is on. Multi-value entry, + one per signal strength (no signal, weak, moderate, strong) --> + <array name="radio.on"> <!-- Strength 0 to BINS-1 --> + <value>6</value> <!-- none --> + <value>5</value> <!-- poor --> + <value>4</value> <!-- moderate --> + <value>3</value> <!-- good --> + <value>3</value> <!-- great --> + </array> + + <!-- Cellular modem related values. These constants are deprecated, but still supported and + need to be tested --> + <item name="modem.controller.sleep">123</item> + <item name="modem.controller.idle">456</item> + <item name="modem.controller.rx">789</item> + <array name="modem.controller.tx"> <!-- Strength 0 to 4 --> + <value>10</value> + <value>20</value> + <value>30</value> + <value>40</value> + <value>50</value> + </array> +</device>
\ No newline at end of file diff --git a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java index 0cfcd8f85784..b66642c20808 100644 --- a/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java +++ b/core/tests/coretests/src/android/content/pm/CrossProfileAppsTest.java @@ -16,11 +16,17 @@ package android.content.pm; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_PERSONAL_LABEL; +import static android.app.admin.DevicePolicyResources.Strings.Core.SWITCH_TO_WORK_LABEL; + +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -58,6 +64,8 @@ public class CrossProfileAppsTest { @Mock private UserManager mUserManager; @Mock + private DevicePolicyManager mDevicePolicyManager; + @Mock private ICrossProfileApps mService; @Mock private Resources mResources; @@ -75,6 +83,10 @@ public class CrossProfileAppsTest { when(mContext.getPackageName()).thenReturn(MY_PACKAGE); when(mContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mContext.getSystemServiceName(DevicePolicyManager.class)).thenReturn( + Context.DEVICE_POLICY_SERVICE); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( + mDevicePolicyManager); } @Before @@ -98,7 +110,7 @@ public class CrossProfileAppsTest { setValidTargetProfile(MANAGED_PROFILE); mCrossProfileApps.getProfileSwitchingLabel(MANAGED_PROFILE); - verify(mResources).getString(R.string.managed_profile_label); + verify(mDevicePolicyManager).getString(eq(SWITCH_TO_WORK_LABEL), any()); } @Test @@ -106,7 +118,7 @@ public class CrossProfileAppsTest { setValidTargetProfile(PERSONAL_PROFILE); mCrossProfileApps.getProfileSwitchingLabel(PERSONAL_PROFILE); - verify(mResources).getString(R.string.user_owner_label); + verify(mDevicePolicyManager).getString(eq(SWITCH_TO_PERSONAL_LABEL), any()); } @Test(expected = SecurityException.class) diff --git a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java index 8f044616e323..5ea91997b1f5 100644 --- a/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/HandwritingInitiatorTest.java @@ -33,7 +33,6 @@ import android.app.Instrumentation; import android.content.Context; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -57,7 +56,6 @@ public class HandwritingInitiatorTest { private static final int TOUCH_SLOP = 8; private static final long TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); private static final Rect sHwArea = new Rect(100, 200, 500, 500); - private static final EditorInfo sFakeEditorInfo = new EditorInfo(); private HandwritingInitiator mHandwritingInitiator; private View mTestView; @@ -72,7 +70,6 @@ public class HandwritingInitiatorTest { InputMethodManager inputMethodManager = context.getSystemService(InputMethodManager.class); mHandwritingInitiator = spy(new HandwritingInitiator(viewConfiguration, inputMethodManager)); - mHandwritingInitiator.updateEditorBound(sHwArea); // mock a parent so that HandwritingInitiator can get ViewGroup parent = new ViewGroup(context) { @@ -82,10 +79,7 @@ public class HandwritingInitiatorTest { } @Override public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) { - r.left = sHwArea.left; - r.top = sHwArea.top; - r.right = sHwArea.right; - r.bottom = sHwArea.bottom; + r.set(sHwArea); return true; } }; @@ -97,7 +91,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwriting_when_stylusMoveOnce_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = (sHwArea.left + sHwArea.right) / 2; final int y1 = (sHwArea.top + sHwArea.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -109,13 +103,13 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); - // Stylus movement win HandwritingArea should trigger IMM.startHandwriting once. + // Stylus movement within HandwritingArea should trigger IMM.startHandwriting once. verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); } @Test public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = (sHwArea.left + sHwArea.right) / 2; final int y1 = (sHwArea.top + sHwArea.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -152,14 +146,14 @@ public class HandwritingInitiatorTest { mHandwritingInitiator.onTouchEvent(stylusEvent2); // InputConnection is created after stylus movement. - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView); } @Test public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = 200; final int y1 = 200; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -175,7 +169,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = 10; final int y1 = 10; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); @@ -191,7 +185,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTapTimeOut() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); final int x1 = 10; final int y1 = 10; final long time1 = 10L; @@ -210,18 +204,17 @@ public class HandwritingInitiatorTest { @Test public void onInputConnectionCreated_inputConnectionCreated() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); } @Test public void onInputConnectionCreated_inputConnectionClosed() { - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); mHandwritingInitiator.onInputConnectionClosed(mTestView); assertThat(mHandwritingInitiator.mConnectedView).isNull(); - assertThat(mHandwritingInitiator.mEditorBound).isNull(); } @Test @@ -229,22 +222,14 @@ public class HandwritingInitiatorTest { // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be // called before View#onInputConnectionClosedInternal. As a result, we need to handle the // case where "one view "2 InputConnections". - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); - mHandwritingInitiator.onInputConnectionCreated(mTestView, sFakeEditorInfo); + mHandwritingInitiator.onInputConnectionCreated(mTestView); + mHandwritingInitiator.onInputConnectionCreated(mTestView); mHandwritingInitiator.onInputConnectionClosed(mTestView); assertThat(mHandwritingInitiator.mConnectedView).isNotNull(); assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView); } - @Test - public void updateEditorBound() { - Rect rect = new Rect(1, 2, 3, 4); - mHandwritingInitiator.updateEditorBound(rect); - - assertThat(mHandwritingInitiator.mEditorBound).isEqualTo(rect); - } - private MotionEvent createStylusEvent(int action, int x, int y, long eventTime) { MotionEvent.PointerProperties[] properties = MotionEvent.PointerProperties.createArray(1); properties[0].toolType = MotionEvent.TOOL_TYPE_STYLUS; diff --git a/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java b/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java deleted file mode 100644 index c15fc3a15112..000000000000 --- a/core/tests/coretests/src/android/view/SurfaceControlFpsListenerTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2021 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 android.platform.test.annotations.Presubmit; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@SmallTest -@Presubmit -public class SurfaceControlFpsListenerTest { - - @Test - public void registersAndUnregisters() { - - SurfaceControlFpsListener listener = new SurfaceControlFpsListener() { - @Override - public void onFpsReported(float fps) { - // Ignore - } - }; - - listener.register(0); - - listener.unregister(); - } -} diff --git a/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java new file mode 100644 index 000000000000..bf508db56852 --- /dev/null +++ b/core/tests/coretests/src/android/window/TaskFpsCallbackTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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.window; + +import static org.junit.Assert.assertEquals; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.content.Context; +import android.platform.test.annotations.Presubmit; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class TaskFpsCallbackTest { + + private Context mContext; + private WindowManager mWindowManager; + private ActivityTaskManager mActivityTaskManager; + + @Before + public void setup() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); + mWindowManager = mContext.getSystemService(WindowManager.class); + } + + @Test + public void testRegisterAndUnregister() { + + final TaskFpsCallback.OnFpsCallbackListener listener = fps -> { + // Ignore + }; + final TaskFpsCallback callback = new TaskFpsCallback(Runnable::run, listener); + + final List<ActivityManager.RunningTaskInfo> tasks = mActivityTaskManager.getTasks(1); + assertEquals(tasks.size(), 1); + mWindowManager.registerTaskFpsCallback(tasks.get(0).taskId, callback); + mWindowManager.unregisterTaskFpsCallback(callback); + } +} diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java new file mode 100644 index 000000000000..a1a1e20d6982 --- /dev/null +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2021 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.window; + +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.IWindow; +import android.view.IWindowSession; +import android.view.OnBackInvokedCallback; +import android.view.OnBackInvokedDispatcher; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowOnBackInvokedDispatcherTest} + * + * <p>Build/Install/Run: + * atest FrameworksCoreTests:WindowOnBackInvokedDispatcherTest + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class WindowOnBackInvokedDispatcherTest { + @Mock + private IWindowSession mWindowSession; + @Mock + private IWindow mWindow; + private WindowOnBackInvokedDispatcher mDispatcher; + @Mock + private OnBackInvokedCallback mCallback1; + @Mock + private OnBackInvokedCallback mCallback2; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mDispatcher = new WindowOnBackInvokedDispatcher(); + mDispatcher.attachToWindow(mWindowSession, mWindow); + } + + private void waitForIdle() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + public void propagatesTopCallback_samePriority() throws RemoteException { + ArgumentCaptor<IOnBackInvokedCallback> captor = + ArgumentCaptor.forClass(IOnBackInvokedCallback.class); + + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + verify(mWindowSession, times(2)) + .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture()); + captor.getAllValues().get(0).onBackStarted(); + waitForIdle(); + verify(mCallback1).onBackStarted(); + verifyZeroInteractions(mCallback2); + + captor.getAllValues().get(1).onBackStarted(); + waitForIdle(); + verify(mCallback2).onBackStarted(); + verifyNoMoreInteractions(mCallback1); + } + + @Test + public void propagatesTopCallback_differentPriority() throws RemoteException { + ArgumentCaptor<IOnBackInvokedCallback> captor = + ArgumentCaptor.forClass(IOnBackInvokedCallback.class); + + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_OVERLAY); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + verify(mWindowSession) + .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture()); + verifyNoMoreInteractions(mWindowSession); + captor.getValue().onBackStarted(); + waitForIdle(); + verify(mCallback1).onBackStarted(); + } + + @Test + public void propagatesTopCallback_withRemoval() throws RemoteException { + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + reset(mWindowSession); + mDispatcher.unregisterOnBackInvokedCallback(mCallback1); + verifyZeroInteractions(mWindowSession); + + mDispatcher.unregisterOnBackInvokedCallback(mCallback2); + verify(mWindowSession).setOnBackInvokedCallback(Mockito.eq(mWindow), isNull()); + } + + + @Test + public void propagatesTopCallback_sameInstanceAddedTwice() throws RemoteException { + ArgumentCaptor<IOnBackInvokedCallback> captor = + ArgumentCaptor.forClass(IOnBackInvokedCallback.class); + + mDispatcher.registerOnBackInvokedCallback(mCallback1, + OnBackInvokedDispatcher.PRIORITY_OVERLAY); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + mDispatcher.registerOnBackInvokedCallback( + mCallback1, OnBackInvokedDispatcher.PRIORITY_DEFAULT); + + reset(mWindowSession); + mDispatcher.registerOnBackInvokedCallback( + mCallback2, OnBackInvokedDispatcher.PRIORITY_OVERLAY); + verify(mWindowSession) + .setOnBackInvokedCallback(Mockito.eq(mWindow), captor.capture()); + captor.getValue().onBackStarted(); + waitForIdle(); + verify(mCallback2).onBackStarted(); + } +} diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index 43590bae6770..1f6b57e5d615 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -297,7 +297,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -312,7 +312,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -324,7 +324,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -336,7 +336,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -348,7 +348,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -360,7 +360,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -372,7 +372,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -386,7 +386,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -399,7 +399,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -412,7 +412,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -425,7 +425,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -438,7 +438,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -452,7 +452,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -466,7 +466,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -480,7 +480,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -494,7 +494,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -507,7 +507,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -521,7 +521,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -535,7 +535,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector).showToast(anyInt(), anyInt()); + verify(sInjector).showToast(anyString(), anyInt()); } @Test @@ -551,7 +551,7 @@ public class IntentForwarderActivityTest { mActivityRule.launchActivity(intent); verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt()); - verify(sInjector, never()).showToast(anyInt(), anyInt()); + verify(sInjector, never()).showToast(anyString(), anyInt()); } @Test @@ -692,6 +692,6 @@ public class IntentForwarderActivityTest { } @Override - public void showToast(int messageId, int duration) {} + public void showToast(String message, int duration) {} } } diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java index 388cf6e15e0b..be8045ddc7b2 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java @@ -37,8 +37,12 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.UidTraffic; import android.os.BatteryStats; +import android.os.BluetoothBatteryStats; import android.os.WakeLockStats; +import android.os.WorkSource; import android.util.SparseArray; import android.view.Display; @@ -47,6 +51,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; +import com.google.common.collect.ImmutableList; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,6 +72,8 @@ public class BatteryStatsImplTest { private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader; @Mock private KernelSingleUidTimeReader mKernelSingleUidTimeReader; + @Mock + private PowerProfile mPowerProfile; private final MockClock mMockClock = new MockClock(); private MockBatteryStatsImpl mBatteryStatsImpl; @@ -79,6 +87,7 @@ public class BatteryStatsImplTest { when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true); when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock) + .setPowerProfile(mPowerProfile) .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader) .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader); } @@ -559,4 +568,38 @@ public class BatteryStatsImplTest { assertThat(wakeLock2.timeHeldMs).isEqualTo(3000); // 9000-6000 assertThat(wakeLock2.totalTimeHeldMs).isEqualTo(4000); // (5000-4000) + (9000-6000) } + + @Test + public void testGetBluetoothBatteryStats() { + when(mPowerProfile.getAveragePower( + PowerProfile.POWER_BLUETOOTH_CONTROLLER_OPERATING_VOLTAGE)).thenReturn(3.0); + mBatteryStatsImpl.setOnBatteryInternal(true); + mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0); + + final WorkSource ws = new WorkSource(10042); + mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, false, 1000, 1000); + mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, false, 5000, 5000); + mBatteryStatsImpl.noteBluetoothScanStartedFromSourceLocked(ws, true, 6000, 6000); + mBatteryStatsImpl.noteBluetoothScanStoppedFromSourceLocked(ws, true, 9000, 9000); + mBatteryStatsImpl.noteBluetoothScanResultsFromSourceLocked(ws, 42, 9000, 9000); + + BluetoothActivityEnergyInfo info = new BluetoothActivityEnergyInfo(1000, + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE, 9000, 8000, 12000, 0); + info.setUidTraffic(ImmutableList.of( + new UidTraffic(10042, 3000, 4000), + new UidTraffic(10043, 5000, 8000))); + mBatteryStatsImpl.updateBluetoothStateLocked(info, -1, 1000, 1000); + + BluetoothBatteryStats stats = + mBatteryStatsImpl.getBluetoothBatteryStats(); + assertThat(stats.getUidStats()).hasSize(2); + + final BluetoothBatteryStats.UidStats uidStats = + stats.getUidStats().stream().filter(u -> u.uid == 10042).findFirst().get(); + assertThat(uidStats.scanTimeMs).isEqualTo(7000); // 4000+3000 + assertThat(uidStats.unoptimizedScanTimeMs).isEqualTo(3000); + assertThat(uidStats.scanResultCount).isEqualTo(42); + assertThat(uidStats.rxTimeMs).isEqualTo(7375); // Some scan time is treated as RX + assertThat(uidStats.txTimeMs).isEqualTo(7666); // Some scan time is treated as TX + } } diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java index 1efd78bc13fc..bc3b4229f5e5 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -57,11 +57,13 @@ public class PowerProfileTest extends TestCase { @Before public void setUp() { mContext = InstrumentationRegistry.getContext(); - mProfile = new PowerProfile(mContext, true); + mProfile = new PowerProfile(mContext); } @Test public void testPowerProfile() { + mProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + assertEquals(2, mProfile.getNumCpuClusters()); assertEquals(4, mProfile.getNumCoresInCpuCluster(0)); assertEquals(4, mProfile.getNumCoresInCpuCluster(1)); @@ -83,6 +85,43 @@ public class PowerProfileTest extends TestCase { mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0)); assertEquals(100.0, mProfile.getAveragePower(PowerProfile.POWER_AUDIO)); assertEquals(150.0, mProfile.getAveragePower(PowerProfile.POWER_VIDEO)); + + assertEquals(123.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_SLEEP)); + assertEquals(456.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE)); + assertEquals(789.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX)); + assertEquals(10.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 0)); + assertEquals(20.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 1)); + assertEquals(30.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 2)); + assertEquals(40.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 3)); + assertEquals(50.0, mProfile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX, 4)); + + // Deprecated Modem constants should work with current format. + assertEquals(123.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP)); + assertEquals(456.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE)); + assertEquals(789.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_RX)); + assertEquals(10.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_0)); + assertEquals(20.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_1)); + assertEquals(30.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_2)); + assertEquals(40.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_3)); + assertEquals(50.0, mProfile.getAverageBatteryDrainMa( + PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT + | ModemPowerProfile.MODEM_DRAIN_TYPE_TX + | ModemPowerProfile.MODEM_TX_LEVEL_4)); } @Test diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml index 3fdb0da80875..ddcab6eb76c8 100644 --- a/data/etc/com.android.settings.xml +++ b/data/etc/com.android.settings.xml @@ -30,6 +30,7 @@ <permission name="android.permission.MANAGE_DEBUGGING"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> <permission name="android.permission.MANAGE_FINGERPRINT"/> + <permission name="android.permission.MANAGE_GAME_MODE" /> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MANAGE_USERS"/> <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" /> @@ -59,5 +60,6 @@ <permission name="android.permission.READ_DREAM_STATE"/> <permission name="android.permission.READ_DREAM_SUPPRESSION"/> <permission name="android.permission.RESTART_WIFI_SUBSYSTEM"/> + <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> </privapp-permissions> </permissions> diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index d0bb4dc4d185..d95644a02e69 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -30,6 +30,7 @@ <permission name="android.permission.GET_APP_OPS_STATS"/> <permission name="android.permission.INTERACT_ACROSS_USERS"/> <permission name="android.permission.MANAGE_DEBUGGING"/> + <permission name="android.permission.MANAGE_GAME_MODE" /> <permission name="android.permission.MANAGE_SENSOR_PRIVACY"/> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MANAGE_USERS"/> @@ -72,5 +73,6 @@ <permission name="android.permission.USE_BACKGROUND_BLUR" /> <permission name="android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS" /> <permission name="android.permission.FORCE_STOP_PACKAGES" /> + <permission name="android.permission.ACCESS_FPS_COUNTER" /> </privapp-permissions> </permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 1068c2712fc8..de086df13722 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -333,6 +333,7 @@ applications that come with the platform <permission name="android.permission.LOCAL_MAC_ADDRESS"/> <permission name="android.permission.MANAGE_ACCESSIBILITY"/> <permission name="android.permission.MANAGE_DEVICE_ADMINS"/> + <permission name="android.permission.MANAGE_GAME_MODE"/> <permission name="android.permission.MANAGE_ROLLBACKS"/> <permission name="android.permission.MANAGE_USB"/> <permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/> @@ -574,6 +575,7 @@ applications that come with the platform <privapp-permissions package="com.android.settings"> <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/> <permission name="android.permission.BIND_CELL_BROADCAST_SERVICE"/> + <permission name="android.permission.READ_SAFETY_CENTER_STATUS" /> </privapp-permissions> <privapp-permissions package="com.android.bips"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 9b67cfc3b83b..07523299c353 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -493,12 +493,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, - "-1556507536": { - "message": "Passing transform hint %d for window %s%s", - "level": "VERBOSE", - "group": "WM_DEBUG_ORIENTATION", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "-1554521902": { "message": "showInsets(ime) was requested by different window: %s ", "level": "WARN", @@ -2761,6 +2755,12 @@ "group": "WM_SHOW_SURFACE_ALLOC", "at": "com\/android\/server\/wm\/WindowStateAnimator.java" }, + "751854538": { + "message": "DisplayArea keep clear rects changed name =%s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_ORGANIZER", + "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java" + }, "765395228": { "message": "onAnimationFinished(): controller=%s reorderMode=%d", "level": "DEBUG", @@ -3691,12 +3691,6 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/DisplayAreaPolicyBuilder.java" }, - "1884961873": { - "message": "Sleep still need to stop %d activities", - "level": "VERBOSE", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/Task.java" - }, "1891501279": { "message": "cancelAnimation(): reason=%s", "level": "DEBUG", diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index 4d818583fd23..cffdf28dbc27 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -17,7 +17,7 @@ package android.graphics.text; import android.annotation.IntDef; -import android.annotation.Nullable; +import android.annotation.NonNull; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -58,7 +58,28 @@ public final class LineBreakConfig { @Retention(RetentionPolicy.SOURCE) public @interface LineBreakStyle {} + /** + * No line break word style specified. + */ + public static final int LINE_BREAK_WORD_STYLE_NONE = 0; + + /** + * Indicates the line breaking is based on the phrased. This makes text wrapping only on + * meaningful words. The support of the text wrapping word style varies depending on the + * locales. If the locale does not support the phrase based text wrapping, + * there will be no effect. + */ + public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; + + /** @hide */ + @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = { + LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LineBreakWordStyle {} + private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE; + private @LineBreakWordStyle int mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_NONE; public LineBreakConfig() { } @@ -66,14 +87,12 @@ public final class LineBreakConfig { /** * Set the line break configuration. * - * @param config the new line break configuration. + * @param lineBreakConfig the new line break configuration. */ - public void set(@Nullable LineBreakConfig config) { - if (config != null) { - mLineBreakStyle = config.getLineBreakStyle(); - } else { - mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; - } + public void set(@NonNull LineBreakConfig lineBreakConfig) { + Objects.requireNonNull(lineBreakConfig); + mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); + mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); } /** @@ -94,17 +113,36 @@ public final class LineBreakConfig { mLineBreakStyle = lineBreakStyle; } + /** + * Get the line break word style. + * + * @return The current line break word style to be used for the text wrapping. + */ + public @LineBreakWordStyle int getLineBreakWordStyle() { + return mLineBreakWordStyle; + } + + /** + * Set the line break word style. + * + * @param lineBreakWordStyle the new line break word style. + */ + public void setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) { + mLineBreakWordStyle = lineBreakWordStyle; + } + @Override public boolean equals(Object o) { if (o == null) return false; if (this == o) return true; if (!(o instanceof LineBreakConfig)) return false; LineBreakConfig that = (LineBreakConfig) o; - return mLineBreakStyle == that.mLineBreakStyle; + return (mLineBreakStyle == that.mLineBreakStyle) + && (mLineBreakWordStyle == that.mLineBreakWordStyle); } @Override public int hashCode() { - return Objects.hash(mLineBreakStyle); + return Objects.hash(mLineBreakStyle, mLineBreakWordStyle); } } diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 5f4afb7b9888..6d691c122576 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -264,8 +264,10 @@ public class MeasuredText { Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; - nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end, - isRtl); + int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() : + LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, + mCurrentOffset, end, isRtl); mCurrentOffset = end; return this; } @@ -445,7 +447,8 @@ public class MeasuredText { * * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. * @param paintPtr The native paint pointer to be applied. - * @param lineBreakStyle The line break style of the text. + * @param lineBreakStyle The line break style(lb) of the text. + * @param lineBreakWordStyle The line break word style(lw) of the text. * @param start The start offset in the copied buffer. * @param end The end offset in the copied buffer. * @param isRtl True if the text is RTL. @@ -453,6 +456,7 @@ public class MeasuredText { private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, /* Non Zero */ long paintPtr, int lineBreakStyle, + int lineBreakWordStyle, @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl); diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index a9543443d3f4..8811a7fec932 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -20,7 +20,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.UserHandle; import android.security.maintenance.UserState; -import android.system.keystore2.Domain; /** * @hide This should not be made public in its present form because it @@ -120,15 +119,6 @@ public class KeyStore { } /** - * Forwards the request to clear a UID to Keystore 2.0. - * @hide - */ - public boolean clearUid(int uid) { - return AndroidKeyStoreMaintenance.clearNamespace(Domain.APP, uid) == 0; - } - - - /** * Add an authentication record to the keystore authorization table. * * @param authToken The packed bytes of a hw_auth_token_t to be provided to keymaster. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index 7db49f05a5dc..e2bc36028405 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.common; import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.os.RemoteException; import android.util.Slog; @@ -34,6 +35,7 @@ import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingList import com.android.wm.shell.common.annotations.ShellMainThread; import java.util.ArrayList; +import java.util.List; /** * This module deals with display rotations coming from WM. When WM starts a rotation: after it has @@ -243,6 +245,19 @@ public class DisplayController { } } + private void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) { + synchronized (mDisplays) { + if (mDisplays.get(displayId) == null || getDisplay(displayId) == null) { + Slog.w(TAG, "Skipping onKeepClearAreasChanged on unknown" + + " display, displayId=" + displayId); + return; + } + for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) { + mDisplayChangedListeners.get(i).onKeepClearAreasChanged(displayId, keepClearAreas); + } + } + } + private static class DisplayRecord { private int mDisplayId; private Context mContext; @@ -301,6 +316,13 @@ public class DisplayController { DisplayController.this.onFixedRotationFinished(displayId); }); } + + @Override + public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) { + mMainExecutor.execute(() -> { + DisplayController.this.onKeepClearAreasChanged(displayId, keepClearAreas); + }); + } } /** @@ -335,5 +357,10 @@ public class DisplayController { * Called when fixed rotation on a display is finished. */ default void onFixedRotationFinished(int displayId) {} + + /** + * Called when keep-clear areas on a display have changed. + */ + default void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {} } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt index af629cc9f8ee..f8d14c6e6d04 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ExpandBubbleScreen.kt @@ -24,6 +24,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.helpers.BaseAppHelper +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -59,6 +62,12 @@ class ExpandBubbleScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test } } + @Before + fun setup() { + // This test doesn't work in shell transitions because of b/205288792 + Assume.assumeFalse(BaseAppHelper.isShellTransitionsEnabled()) + } + @Presubmit @Test fun testAppIsAlwaysVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt index add11c10d75f..c93c5ad97bdb 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/MultiBubblesScreen.kt @@ -25,6 +25,9 @@ import com.android.server.wm.flicker.FlickerParametersRunnerFactory import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.annotation.Group4 import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.wm.shell.flicker.helpers.BaseAppHelper +import org.junit.Assume +import org.junit.Before import org.junit.runner.RunWith import org.junit.Test import org.junit.runners.Parameterized @@ -67,6 +70,12 @@ class MultiBubblesScreen(testSpec: FlickerTestParameter) : BaseBubbleScreen(test } } + @Before + fun setup() { + // This test doesn't work in shell transitions because of b/205288792 + Assume.assumeFalse(BaseAppHelper.isShellTransitionsEnabled()) + } + @Presubmit @Test fun testAppIsAlwaysVisible() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt index 2c08b7f5fac2..3a9a0705908d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt @@ -82,6 +82,11 @@ class ExitPipViaExpandButtonClickTest( @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 197726610) + @Test + override fun pipLayerExpands() = super.pipLayerExpands() + companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt index e340f4cd8595..03c8929f9919 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt @@ -101,6 +101,11 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 197726610) + @Test + override fun pipLayerExpands() = super.pipLayerExpands() + companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt index 8adebb8f28c9..976b7c6980a1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithDismissButtonTest.kt @@ -90,6 +90,11 @@ class ExitPipWithDismissButtonTest(testSpec: FlickerTestParameter) : ExitPipTran @Test override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales() + /** {@inheritDoc} */ + @FlakyTest(bugId = 215869110) + @Test + override fun focusDoesNotChange() = super.focusDoesNotChange() + companion object { /** * Creates the test configurations. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index fe66e225ad4a..35e498262707 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -19,7 +19,6 @@ package com.android.wm.shell.draganddrop; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; @@ -111,7 +110,6 @@ public class DragAndDropPolicyTest { private ActivityManager.RunningTaskInfo mHomeTask; private ActivityManager.RunningTaskInfo mFullscreenAppTask; private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask; - private ActivityManager.RunningTaskInfo mSplitPrimaryAppTask; @Before public void setUp() throws RemoteException { @@ -144,8 +142,6 @@ public class DragAndDropPolicyTest { mNonResizeableFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); mNonResizeableFullscreenAppTask.isResizeable = false; - mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, - ACTIVITY_TYPE_STANDARD); setRunningTask(mFullscreenAppTask); } diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp index 09539ecc34b0..76ea2d5c9ff3 100644 --- a/libs/hwui/jni/text/MeasuredText.cpp +++ b/libs/hwui/jni/text/MeasuredText.cpp @@ -65,11 +65,13 @@ static jlong nInitBuilder(CRITICAL_JNI_PARAMS) { // Regular JNI static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr, - jlong paintPtr, jint lbStyle, jint start, jint end, jboolean isRtl) { + jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end, + jboolean isRtl) { Paint* paint = toPaint(paintPtr); const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface()); minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface); - toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl); + toBuilder(builderPtr) + ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl); } // Regular JNI @@ -144,7 +146,7 @@ static jint nGetMemoryUsage(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { static const JNINativeMethod gMTBuilderMethods[] = { // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*)nInitBuilder}, - {"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun}, + {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun}, {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText}, {"nFreeBuilder", "(J)V", (void*)nFreeBuilder}, diff --git a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl index a46229da76ce..2556b68809cc 100644 --- a/media/aidl/android/media/audio/common/AudioOutputFlags.aidl +++ b/media/aidl/android/media/audio/common/AudioOutputFlags.aidl @@ -26,7 +26,7 @@ package android.media.audio.common; */ @VintfStability @Backing(type="int") -enum AudioOutputFlags { +enum AudioOutputFlags{ /** * Output must not be altered by the framework, it bypasses software mixers. */ @@ -98,7 +98,11 @@ enum AudioOutputFlags { */ GAPLESS_OFFLOAD = 15, /** + * Output is used for spatial audio. + */ + SPATIALIZER = 16, + /** * Output is used for transmitting ultrasound audio. */ - ULTRASOUND = 16, + ULTRASOUND = 17, } diff --git a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl index e2f286e775d2..4a512a8049a2 100644 --- a/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl +++ b/media/aidl_api/android.media.audio.common.types/current/android/media/audio/common/AudioOutputFlags.aidl @@ -51,5 +51,6 @@ enum AudioOutputFlags { VOIP_RX = 13, INCALL_MUSIC = 14, GAPLESS_OFFLOAD = 15, - ULTRASOUND = 16, + SPATIALIZER = 16, + ULTRASOUND = 17, } diff --git a/media/java/android/media/midi/MidiDeviceInfo.java b/media/java/android/media/midi/MidiDeviceInfo.java index b888fb00df9b..48c50f01c01c 100644 --- a/media/java/android/media/midi/MidiDeviceInfo.java +++ b/media/java/android/media/midi/MidiDeviceInfo.java @@ -159,7 +159,7 @@ public final class MidiDeviceInfo implements Parcelable { PROTOCOL_UNKNOWN }) @Retention(RetentionPolicy.SOURCE) - public @interface DefaultProtocol {} + public @interface Protocol {} /** * Bundle key for the device's user visible name property. @@ -429,7 +429,7 @@ public final class MidiDeviceInfo implements Parcelable { * * @return the device's default protocol. */ - @DefaultProtocol + @Protocol public int getDefaultProtocol() { return mDefaultProtocol; } diff --git a/media/java/android/media/midi/MidiManager.java b/media/java/android/media/midi/MidiManager.java index 831649e5bae9..5348d4e358d0 100644 --- a/media/java/android/media/midi/MidiManager.java +++ b/media/java/android/media/midi/MidiManager.java @@ -18,7 +18,6 @@ package android.media.midi; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.SystemService; import android.bluetooth.BluetoothDevice; @@ -29,14 +28,17 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArraySet; import android.util.Log; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; // BLE-MIDI @@ -111,18 +113,30 @@ public final class MidiManager { private class DeviceListener extends IMidiDeviceListener.Stub { private final DeviceCallback mCallback; private final Handler mHandler; + private final Executor mExecutor; private final int mTransport; DeviceListener(DeviceCallback callback, Handler handler, int transport) { mCallback = callback; mHandler = handler; + mExecutor = null; + mTransport = transport; + } + + DeviceListener(DeviceCallback callback, Executor executor, int transport) { + mCallback = callback; + mHandler = null; + mExecutor = executor; mTransport = transport; } @Override public void onDeviceAdded(MidiDeviceInfo device) { if (shouldInvokeCallback(device)) { - if (mHandler != null) { + if (mExecutor != null) { + mExecutor.execute(() -> + mCallback.onDeviceAdded(device)); + } else if (mHandler != null) { final MidiDeviceInfo deviceF = device; mHandler.post(new Runnable() { @Override public void run() { @@ -138,7 +152,10 @@ public final class MidiManager { @Override public void onDeviceRemoved(MidiDeviceInfo device) { if (shouldInvokeCallback(device)) { - if (mHandler != null) { + if (mExecutor != null) { + mExecutor.execute(() -> + mCallback.onDeviceRemoved(device)); + } else if (mHandler != null) { final MidiDeviceInfo deviceF = device; mHandler.post(new Runnable() { @Override public void run() { @@ -153,7 +170,10 @@ public final class MidiManager { @Override public void onDeviceStatusChanged(MidiDeviceStatus status) { - if (mHandler != null) { + if (mExecutor != null) { + mExecutor.execute(() -> + mCallback.onDeviceStatusChanged(status)); + } else if (mHandler != null) { final MidiDeviceStatus statusF = status; mHandler.post(new Runnable() { @Override public void run() { @@ -237,7 +257,7 @@ public final class MidiManager { /** * Registers a callback to receive notifications when MIDI 1.0 devices are added and removed. * These are devices that do not default to Universal MIDI Packets. To register for a callback - * for those, call {@link #registerDeviceCallbackForTransport} instead. + * for those, call {@link #registerDeviceCallback} instead. * The {@link DeviceCallback#onDeviceStatusChanged} method will be called immediately * for any devices that have open ports. This allows applications to know which input @@ -250,9 +270,19 @@ public final class MidiManager { * @param handler The {@link android.os.Handler Handler} that will be used for delivering the * device notifications. If handler is null, then the thread used for the * callback is unspecified. + * @deprecated Use the {@link #registerDeviceCallback} + * method with Executor and transport instead. */ + @Deprecated public void registerDeviceCallback(DeviceCallback callback, Handler handler) { - registerDeviceCallbackForTransport(callback, handler, TRANSPORT_MIDI_BYTE_STREAM); + DeviceListener deviceListener = new DeviceListener(callback, handler, + TRANSPORT_MIDI_BYTE_STREAM); + try { + mService.registerListener(mToken, deviceListener); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mDeviceListeners.put(callback, deviceListener); } /** @@ -266,16 +296,16 @@ public final class MidiManager { * Applications should call {@link #getDevicesForTransport} before registering the callback * to get a list of devices already added. * - * @param callback a {@link DeviceCallback} for MIDI device notifications - * @param handler The {@link android.os.Handler Handler} that will be used for delivering the - * device notifications. If handler is null, then the thread used for the - * callback is unspecified. * @param transport The transport to be used. This is either TRANSPORT_MIDI_BYTE_STREAM or * TRANSPORT_UNIVERSAL_MIDI_PACKETS. + * @param executor The {@link Executor} that will be used for delivering the + * device notifications. + * @param callback a {@link DeviceCallback} for MIDI device notifications */ - public void registerDeviceCallbackForTransport(@NonNull DeviceCallback callback, - @Nullable Handler handler, @Transport int transport) { - DeviceListener deviceListener = new DeviceListener(callback, handler, transport); + public void registerDeviceCallback(@Transport int transport, + @NonNull Executor executor, @NonNull DeviceCallback callback) { + Objects.requireNonNull(executor); + DeviceListener deviceListener = new DeviceListener(callback, executor, transport); try { mService.registerListener(mToken, deviceListener); } catch (RemoteException e) { @@ -306,7 +336,9 @@ public final class MidiManager { * {@link #getDevicesForTransport} instead. * * @return an array of MIDI devices + * @deprecated Use {@link #getDevicesForTransport} instead. */ + @Deprecated public MidiDeviceInfo[] getDevices() { try { return mService.getDevices(); @@ -325,14 +357,13 @@ public final class MidiManager { * TRANSPORT_UNIVERSAL_MIDI_PACKETS. * @return a collection of MIDI devices */ - public @NonNull Collection<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) { + public @NonNull Set<MidiDeviceInfo> getDevicesForTransport(@Transport int transport) { try { MidiDeviceInfo[] devices = mService.getDevicesForTransport(transport); - Collection<MidiDeviceInfo> out = new ArrayList<MidiDeviceInfo>(devices.length); - for (int i = 0; i < devices.length; i++) { - out.add(devices[i]); + if (devices == null) { + return Collections.emptySet(); } - return out; + return new ArraySet<>(devices); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/tv/DsmccResponse.java b/media/java/android/media/tv/DsmccResponse.java index 4d496207051a..3ca63e323bcd 100644 --- a/media/java/android/media/tv/DsmccResponse.java +++ b/media/java/android/media/tv/DsmccResponse.java @@ -17,10 +17,13 @@ package android.media.tv; import android.annotation.NonNull; +import android.annotation.StringDef; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -29,6 +32,27 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel public static final @TvInputManager.BroadcastInfoType int responseType = TvInputManager.BROADCAST_INFO_TYPE_DSMCC; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = "BIOP_MESSAGE_TYPE_", value = { + BIOP_MESSAGE_TYPE_DIRECTORY, + BIOP_MESSAGE_TYPE_FILE, + BIOP_MESSAGE_TYPE_STREAM, + BIOP_MESSAGE_TYPE_SERVICE_GATEWAY, + + }) + public @interface BiopMessageType {} + + /** Broadcast Inter-ORB Protocol (BIOP) message types */ + /** BIOP directory message */ + public static final String BIOP_MESSAGE_TYPE_DIRECTORY = "directory"; + /** BIOP file message */ + public static final String BIOP_MESSAGE_TYPE_FILE = "file"; + /** BIOP stream message */ + public static final String BIOP_MESSAGE_TYPE_STREAM = "stream"; + /** BIOP service gateway message */ + public static final String BIOP_MESSAGE_TYPE_SERVICE_GATEWAY = "service_gateway"; + public static final @NonNull Parcelable.Creator<DsmccResponse> CREATOR = new Parcelable.Creator<DsmccResponse>() { @Override @@ -43,39 +67,173 @@ public final class DsmccResponse extends BroadcastInfoResponse implements Parcel } }; + private final @BiopMessageType String mBiopMessageType; private final ParcelFileDescriptor mFileDescriptor; - private final boolean mIsDirectory; - private final List<String> mChildren; + private final List<String> mChildList; + private final int[] mEventIds; + private final String[] mEventNames; public static DsmccResponse createFromParcelBody(Parcel in) { return new DsmccResponse(in); } + /** + * Constructs a BIOP file message response. + */ public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult, - ParcelFileDescriptor file, boolean isDirectory, List<String> children) { + @NonNull ParcelFileDescriptor file) { super(responseType, requestId, sequence, responseResult); + mBiopMessageType = BIOP_MESSAGE_TYPE_FILE; mFileDescriptor = file; - mIsDirectory = isDirectory; - mChildren = children; + mChildList = null; + mEventIds = null; + mEventNames = null; + } + + /** + * Constructs a BIOP service gateway or directory message response. + */ + public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult, + boolean isServiceGateway, @NonNull List<String> childList) { + super(responseType, requestId, sequence, responseResult); + if (isServiceGateway) { + mBiopMessageType = BIOP_MESSAGE_TYPE_SERVICE_GATEWAY; + } else { + mBiopMessageType = BIOP_MESSAGE_TYPE_DIRECTORY; + } + mFileDescriptor = null; + mChildList = childList; + mEventIds = null; + mEventNames = null; + } + + /** + * Constructs a BIOP stream message response. + * + * <p>The current stream message response does not support other stream messages types than + * stream event message type. + */ + public DsmccResponse(int requestId, int sequence, @ResponseResult int responseResult, + @NonNull int[] eventIds, @NonNull String[] eventNames) { + super(responseType, requestId, sequence, responseResult); + mBiopMessageType = BIOP_MESSAGE_TYPE_STREAM; + mFileDescriptor = null; + mChildList = null; + mEventIds = eventIds; + mEventNames = eventNames; + if (mEventIds.length != eventNames.length) { + throw new IllegalStateException("The size of eventIds and eventNames must be equal"); + } } - protected DsmccResponse(Parcel source) { + private DsmccResponse(@NonNull Parcel source) { super(responseType, source); - mFileDescriptor = source.readFileDescriptor(); - mIsDirectory = (source.readInt() == 1); - mChildren = new ArrayList<>(); - source.readStringList(mChildren); + + mBiopMessageType = source.readString(); + switch (mBiopMessageType) { + case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY: + case BIOP_MESSAGE_TYPE_DIRECTORY: + int childNum = source.readInt(); + mChildList = new ArrayList<>(); + for (int i = 0; i < childNum; i++) { + mChildList.add(source.readString()); + } + mFileDescriptor = null; + mEventIds = null; + mEventNames = null; + break; + case BIOP_MESSAGE_TYPE_FILE: + mFileDescriptor = source.readFileDescriptor(); + mChildList = null; + mEventIds = null; + mEventNames = null; + break; + case BIOP_MESSAGE_TYPE_STREAM: + int eventNum = source.readInt(); + mEventIds = new int[eventNum]; + mEventNames = new String[eventNum]; + for (int i = 0; i < eventNum; i++) { + mEventIds[i] = source.readInt(); + mEventNames[i] = source.readString(); + } + mChildList = null; + mFileDescriptor = null; + break; + default: + throw new IllegalStateException("unexpected BIOP message type"); + } + } + + /** Returns the BIOP message type */ + @NonNull + public @BiopMessageType String getBiopMessageType() { + return mBiopMessageType; } + /** Returns the file descriptor for a given file message response */ + @NonNull public ParcelFileDescriptor getFile() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_FILE)) { + throw new IllegalStateException("Not file object"); + } return mFileDescriptor; } + /** + * Returns a list of subobject names for the given service gateway or directory message + * response. + */ + @NonNull + public List<String> getChildList() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_DIRECTORY) + && !mBiopMessageType.equals(BIOP_MESSAGE_TYPE_SERVICE_GATEWAY)) { + throw new IllegalStateException("Not directory object"); + } + return new ArrayList<String>(mChildList); + } + + /** Returns all event IDs carried in a given stream message response. */ + @NonNull + public int[] getStreamEventIds() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) { + throw new IllegalStateException("Not stream event object"); + } + return mEventIds; + } + + /** Returns all event names carried in a given stream message response */ + @NonNull + public String[] getStreamEventNames() { + if (!mBiopMessageType.equals(BIOP_MESSAGE_TYPE_STREAM)) { + throw new IllegalStateException("Not stream event object"); + } + return mEventNames; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - mFileDescriptor.writeToParcel(dest, flags); - dest.writeInt(mIsDirectory ? 1 : 0); - dest.writeStringList(mChildren); + dest.writeString(mBiopMessageType); + switch (mBiopMessageType) { + case BIOP_MESSAGE_TYPE_SERVICE_GATEWAY: + case BIOP_MESSAGE_TYPE_DIRECTORY: + dest.writeInt(mChildList.size()); + for (String child : mChildList) { + dest.writeString(child); + } + break; + case BIOP_MESSAGE_TYPE_FILE: + dest.writeFileDescriptor(mFileDescriptor.getFileDescriptor()); + break; + case BIOP_MESSAGE_TYPE_STREAM: + dest.writeInt(mEventIds.length); + for (int i = 0; i < mEventIds.length; i++) { + dest.writeInt(mEventIds[i]); + dest.writeString(mEventNames[i]); + } + break; + default: + throw new IllegalStateException("unexpected BIOP message type"); + } } } diff --git a/media/java/android/media/tv/StreamEventResponse.java b/media/java/android/media/tv/StreamEventResponse.java index fd7580107c71..903fab5c2268 100644 --- a/media/java/android/media/tv/StreamEventResponse.java +++ b/media/java/android/media/tv/StreamEventResponse.java @@ -39,54 +39,53 @@ public final class StreamEventResponse extends BroadcastInfoResponse implements } }; - private final String mName; - private final String mText; - private final String mData; - private final String mStatus; + private final int mEventId; + private final long mNpt; + private final byte[] mData; public static StreamEventResponse createFromParcelBody(Parcel in) { return new StreamEventResponse(in); } public StreamEventResponse(int requestId, int sequence, @ResponseResult int responseResult, - String name, String text, String data, String status) { + int eventId, long npt, @NonNull byte[] data) { super(responseType, requestId, sequence, responseResult); - mName = name; - mText = text; + mEventId = eventId; + mNpt = npt; mData = data; - mStatus = status; } - protected StreamEventResponse(Parcel source) { + private StreamEventResponse(@NonNull Parcel source) { super(responseType, source); - mName = source.readString(); - mText = source.readString(); - mData = source.readString(); - mStatus = source.readString(); + mEventId = source.readInt(); + mNpt = source.readLong(); + int dataLength = source.readInt(); + mData = new byte[dataLength]; + source.readByteArray(mData); } - public String getName() { - return mName; + /** Returns the event ID */ + public int getEventId() { + return mEventId; } - public String getText() { - return mText; + /** Returns the NPT(Normal Play Time) value when the event occurred or will occur */ + public long getNpt() { + return mNpt; } - public String getData() { + /** Returns the application specific data */ + @NonNull + public byte[] getData() { return mData; } - public String getStatus() { - return mStatus; - } - @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeString(mName); - dest.writeString(mText); - dest.writeString(mData); - dest.writeString(mStatus); + dest.writeInt(mEventId); + dest.writeLong(mNpt); + dest.writeInt(mData.length); + dest.writeByteArray(mData); } } diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.aidl b/media/java/android/media/tv/interactive/AppLinkInfo.aidl new file mode 100644 index 000000000000..7c52d018a3d6 --- /dev/null +++ b/media/java/android/media/tv/interactive/AppLinkInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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.media.tv.interactive; + +parcelable AppLinkInfo;
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/AppLinkInfo.java b/media/java/android/media/tv/interactive/AppLinkInfo.java new file mode 100644 index 000000000000..5cce44319e5d --- /dev/null +++ b/media/java/android/media/tv/interactive/AppLinkInfo.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2021 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.media.tv.interactive; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * App link information used by TV interactive app to launch Android apps. + * @hide + */ +public final class AppLinkInfo implements Parcelable { + private @NonNull String mPackageName; + private @NonNull String mClassName; + private @Nullable String mUriScheme; + private @Nullable String mUriHost; + private @Nullable String mUriPrefix; + + + /** + * Creates a new AppLinkInfo. + */ + private AppLinkInfo( + @NonNull String packageName, + @NonNull String className, + @Nullable String uriScheme, + @Nullable String uriHost, + @Nullable String uriPrefix) { + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mClassName = className; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClassName); + this.mUriScheme = uriScheme; + this.mUriHost = uriHost; + this.mUriPrefix = uriPrefix; + } + + /** + * Gets package name of the App link. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Gets package class of the App link. + */ + @NonNull + public String getClassName() { + return mClassName; + } + + /** + * Gets URI scheme of the App link. + */ + @Nullable + public String getUriScheme() { + return mUriScheme; + } + + /** + * Gets URI host of the App link. + */ + @Nullable + public String getUriHost() { + return mUriHost; + } + + /** + * Gets URI prefix of the App link. + */ + @Nullable + public String getUriPrefix() { + return mUriPrefix; + } + + @Override + public String toString() { + return "AppLinkInfo { " + + "packageName = " + mPackageName + ", " + + "className = " + mClassName + ", " + + "uriScheme = " + mUriScheme + ", " + + "uriHost = " + mUriHost + ", " + + "uriPrefix = " + mUriPrefix + + " }"; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mPackageName); + dest.writeString(mClassName); + dest.writeString(mUriScheme); + dest.writeString(mUriHost); + dest.writeString(mUriPrefix); + } + + @Override + public int describeContents() { + return 0; + } + + /* package-private */ AppLinkInfo(@NonNull Parcel in) { + String packageName = in.readString(); + String className = in.readString(); + String uriScheme = in.readString(); + String uriHost = in.readString(); + String uriPrefix = in.readString(); + + this.mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + this.mClassName = className; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClassName); + this.mUriScheme = uriScheme; + this.mUriHost = uriHost; + this.mUriPrefix = uriPrefix; + } + + @NonNull + public static final Parcelable.Creator<AppLinkInfo> CREATOR = + new Parcelable.Creator<AppLinkInfo>() { + @Override + public AppLinkInfo[] newArray(int size) { + return new AppLinkInfo[size]; + } + + @Override + public AppLinkInfo createFromParcel(@NonNull Parcel in) { + return new AppLinkInfo(in); + } + }; + + /** + * A builder for {@link AppLinkInfo} + */ + public static final class Builder { + private @NonNull String mPackageName; + private @NonNull String mClassName; + private @Nullable String mUriScheme; + private @Nullable String mUriHost; + private @Nullable String mUriPrefix; + + /** + * Creates a new Builder. + */ + public Builder( + @NonNull String packageName, + @NonNull String className) { + mPackageName = packageName; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mPackageName); + mClassName = className; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mClassName); + } + + /** + * Sets package name of the App link. + */ + @NonNull + public Builder setPackageName(@NonNull String value) { + mPackageName = value; + return this; + } + + /** + * Sets app name of the App link. + */ + @NonNull + public Builder setClassName(@NonNull String value) { + mClassName = value; + return this; + } + + /** + * Sets URI scheme of the App link. + */ + @NonNull + public Builder setUriScheme(@Nullable String value) { + mUriScheme = value; + return this; + } + + /** + * Sets URI host of the App link. + */ + @NonNull + public Builder setUriHost(@Nullable String value) { + mUriHost = value; + return this; + } + + /** + * Sets URI prefix of the App link. + */ + @NonNull + public Builder setUriPrefix(@Nullable String value) { + mUriPrefix = value; + return this; + } + + /** Builds the instance. This builder should not be touched after calling this! */ + @NonNull + public AppLinkInfo build() { + AppLinkInfo o = new AppLinkInfo( + mPackageName, + mClassName, + mUriScheme, + mUriHost, + mUriPrefix); + return o; + } + } +} diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl index a8ef0957286c..aaabe342d9f1 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl @@ -20,6 +20,7 @@ import android.graphics.Rect; import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; +import android.media.tv.interactive.AppLinkInfo; import android.media.tv.interactive.ITvInteractiveAppClient; import android.media.tv.interactive.ITvInteractiveAppManagerCallback; import android.media.tv.interactive.TvInteractiveAppInfo; @@ -34,8 +35,8 @@ import android.view.Surface; interface ITvInteractiveAppManager { List<TvInteractiveAppInfo> getTvInteractiveAppServiceList(int userId); void prepare(String tiasId, int type, int userId); - void registerAppLinkInfo(String tiasId, in Bundle info, int userId); - void unregisterAppLinkInfo(String tiasId, in Bundle info, int userId); + void registerAppLinkInfo(String tiasId, in AppLinkInfo info, int userId); + void unregisterAppLinkInfo(String tiasId, in AppLinkInfo info, int userId); void sendAppLinkCommand(String tiasId, in Bundle command, int userId); void startInteractiveApp(in IBinder sessionToken, int userId); void stopInteractiveApp(in IBinder sessionToken, int userId); diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl index 68fae2d96c98..b6d518ff7242 100644 --- a/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl +++ b/media/java/android/media/tv/interactive/ITvInteractiveAppService.aidl @@ -16,6 +16,7 @@ package android.media.tv.interactive; +import android.media.tv.interactive.AppLinkInfo; import android.media.tv.interactive.ITvInteractiveAppServiceCallback; import android.media.tv.interactive.ITvInteractiveAppSessionCallback; import android.os.Bundle; @@ -32,7 +33,7 @@ oneway interface ITvInteractiveAppService { void createSession(in InputChannel channel, in ITvInteractiveAppSessionCallback callback, in String iAppServiceId, int type); void prepare(int type); - void registerAppLinkInfo(in Bundle info); - void unregisterAppLinkInfo(in Bundle info); + void registerAppLinkInfo(in AppLinkInfo info); + void unregisterAppLinkInfo(in AppLinkInfo info); void sendAppLinkCommand(in Bundle command); }
\ No newline at end of file diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java index 15a5f823e144..39be501a072d 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java @@ -757,7 +757,8 @@ public final class TvInteractiveAppManager { * Registers app link info. * @hide */ - public void registerAppLinkInfo(@NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) { + public void registerAppLinkInfo( + @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) { try { mService.registerAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); } catch (RemoteException e) { @@ -770,7 +771,7 @@ public final class TvInteractiveAppManager { * @hide */ public void unregisterAppLinkInfo( - @NonNull String tvIAppServiceId, @NonNull Bundle appLinkInfo) { + @NonNull String tvIAppServiceId, @NonNull AppLinkInfo appLinkInfo) { try { mService.unregisterAppLinkInfo(tvIAppServiceId, appLinkInfo, mUserId); } catch (RemoteException e) { diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 094aabdb5e68..d599d0a60995 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -131,6 +131,13 @@ public abstract class TvInteractiveAppService extends Service { /** @hide */ public static final String COMMAND_PARAMETER_KEY_TRACK_SELECT_MODE = "command_track_select_mode"; + /** + * Command to quiet channel change. No channel banner or channel info is shown. + * <p>Refer to HbbTV Spec 2.0.4 chapter A.2.4.3. + * @hide + */ + public static final String COMMAND_PARAMETER_KEY_CHANGE_CHANNEL_QUIETLY = + "command_change_channel_quietly"; private final Handler mServiceHandler = new ServiceHandler(); private final RemoteCallbackList<ITvInteractiveAppServiceCallback> mCallbacks = @@ -175,12 +182,12 @@ public abstract class TvInteractiveAppService extends Service { } @Override - public void registerAppLinkInfo(Bundle appLinkInfo) { + public void registerAppLinkInfo(AppLinkInfo appLinkInfo) { onRegisterAppLinkInfo(appLinkInfo); } @Override - public void unregisterAppLinkInfo(Bundle appLinkInfo) { + public void unregisterAppLinkInfo(AppLinkInfo appLinkInfo) { onUnregisterAppLinkInfo(appLinkInfo); } @@ -203,7 +210,7 @@ public abstract class TvInteractiveAppService extends Service { * Registers App link info. * @hide */ - public void onRegisterAppLinkInfo(Bundle appLinkInfo) { + public void onRegisterAppLinkInfo(AppLinkInfo appLinkInfo) { // TODO: make it abstract when unhide } @@ -211,7 +218,7 @@ public abstract class TvInteractiveAppService extends Service { * Unregisters App link info. * @hide */ - public void onUnregisterAppLinkInfo(Bundle appLinkInfo) { + public void onUnregisterAppLinkInfo(AppLinkInfo appLinkInfo) { // TODO: make it abstract when unhide } diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index 9590d5d90176..12e21998f226 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.media.tv.TvInputManager; @@ -237,6 +238,7 @@ public class TvInteractiveAppView extends ViewGroup { // The surface view's content should be treated as secure all the time. mSurfaceView.setSecure(true); mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); + mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT); addView(mSurfaceView); } diff --git a/media/java/android/media/tv/tuner/filter/SectionSettings.java b/media/java/android/media/tv/tuner/filter/SectionSettings.java index f123675a8940..83ed8e84e4da 100644 --- a/media/java/android/media/tv/tuner/filter/SectionSettings.java +++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java @@ -82,7 +82,7 @@ public abstract class SectionSettings extends Settings { * The section filter uses this for CRC (Cyclic redundancy check) checking when * {@link #isCrcEnabled()} is {@code true}. */ - public int getBitWidthOfLengthField() { + public int getLengthFieldBitWidth() { return mBitWidthOfLengthField; } diff --git a/packages/ConnectivityT/framework-t/Android.bp b/packages/ConnectivityT/framework-t/Android.bp index d3d8bba16c7c..223bdcdd9c95 100644 --- a/packages/ConnectivityT/framework-t/Android.bp +++ b/packages/ConnectivityT/framework-t/Android.bp @@ -129,6 +129,11 @@ filegroup { "src/android/net/EthernetNetworkSpecifier.java", "src/android/net/IEthernetManager.aidl", "src/android/net/IEthernetServiceListener.aidl", + "src/android/net/IInternalNetworkManagementListener.aidl", + "src/android/net/InternalNetworkUpdateRequest.java", + "src/android/net/InternalNetworkUpdateRequest.aidl", + "src/android/net/InternalNetworkManagementException.java", + "src/android/net/InternalNetworkManagementException.aidl", "src/android/net/ITetheredInterfaceCallback.aidl", ], path: "src", diff --git a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java index 683678ad4671..8813f984519b 100644 --- a/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java +++ b/packages/ConnectivityT/framework-t/src/android/app/usage/NetworkStatsManager.java @@ -157,6 +157,11 @@ public class NetworkStatsManager { setAugmentWithSubscriptionPlan(true); } + /** @hide */ + public INetworkStatsService getBinder() { + return mService; + } + /** * Set poll on open flag to indicate the poll is needed before service gets statistics * result. This is default enabled. However, for any non-privileged caller, the poll might diff --git a/core/java/android/net/IInternalNetworkManagementListener.aidl b/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl index 69cde3bd14e8..69cde3bd14e8 100644 --- a/core/java/android/net/IInternalNetworkManagementListener.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/IInternalNetworkManagementListener.aidl diff --git a/core/java/android/net/InternalNetworkManagementException.aidl b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl index dcce706989f6..dcce706989f6 100644 --- a/core/java/android/net/InternalNetworkManagementException.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.aidl diff --git a/core/java/android/net/InternalNetworkManagementException.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java index 7f4e403f2259..7f4e403f2259 100644 --- a/core/java/android/net/InternalNetworkManagementException.java +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkManagementException.java diff --git a/core/java/android/net/InternalNetworkUpdateRequest.aidl b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl index da00cb97afb4..da00cb97afb4 100644 --- a/core/java/android/net/InternalNetworkUpdateRequest.aidl +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.aidl diff --git a/core/java/android/net/InternalNetworkUpdateRequest.java b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java index f42c4b7c420d..f42c4b7c420d 100644 --- a/core/java/android/net/InternalNetworkUpdateRequest.java +++ b/packages/ConnectivityT/framework-t/src/android/net/InternalNetworkUpdateRequest.java diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java index 9b9d38a36066..d3d5a087ccac 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentity.java @@ -16,6 +16,8 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.NetworkTemplate.NETWORK_TYPE_ALL; @@ -23,6 +25,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.content.Context; import android.net.wifi.WifiInfo; import android.service.NetworkIdentityProto; @@ -30,6 +33,7 @@ import android.telephony.Annotation; import android.telephony.TelephonyManager; import android.util.proto.ProtoOutputStream; +import com.android.net.module.util.CollectionUtils; import com.android.net.module.util.NetworkCapabilitiesUtils; import com.android.net.module.util.NetworkIdentityUtils; @@ -44,8 +48,8 @@ import java.util.Objects; * * @hide */ -// @SystemApi(client = MODULE_LIBRARIES) -public class NetworkIdentity implements Comparable<NetworkIdentity> { +@SystemApi(client = MODULE_LIBRARIES) +public class NetworkIdentity { private static final String TAG = "NetworkIdentity"; /** @hide */ @@ -55,7 +59,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "OEM_MANAGED_" }, value = { + @IntDef(prefix = { "OEM_MANAGED_" }, flag = true, value = { NetworkTemplate.OEM_MANAGED_NO, NetworkTemplate.OEM_MANAGED_PAID, NetworkTemplate.OEM_MANAGED_PRIVATE @@ -71,12 +75,14 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PAID}. * @hide */ - public static final int OEM_PAID = 0x1; + public static final int OEM_PAID = 1 << 0; /** * Network has {@link NetworkCapabilities#NET_CAPABILITY_OEM_PRIVATE}. * @hide */ - public static final int OEM_PRIVATE = 0x2; + public static final int OEM_PRIVATE = 1 << 1; + + private static final long SUPPORTED_OEM_MANAGED_TYPES = OEM_PAID | OEM_PRIVATE; final int mType; final int mRatType; @@ -206,7 +212,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { return mSubscriberId; } - /** Get the Wifi Network Key of this instance. See {@link WifiInfo#getCurrentNetworkKey()}. */ + /** Get the Wifi Network Key of this instance. See {@link WifiInfo#getNetworkKey()}. */ @Nullable public String getWifiNetworkKey() { return mWifiNetworkKey; @@ -218,7 +224,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { return mRoaming; } - /** Return the roaming status of this instance. */ + /** Return whether this network is roaming. */ public boolean isRoaming() { return mRoaming; } @@ -229,7 +235,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { return mMetered; } - /** Return the meteredness of this instance. */ + /** Return whether this network is metered. */ public boolean isMetered() { return mMetered; } @@ -240,7 +246,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { return mDefaultNetwork; } - /** Return the default network status of this instance. */ + /** Return whether this network is the default network. */ public boolean isDefaultNetwork() { return mDefaultNetwork; } @@ -262,7 +268,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { * {@link TelephonyManager#NETWORK_TYPE_UNKNOWN} if not applicable. * See {@code TelephonyManager.NETWORK_TYPE_*}. * @hide - * @deprecated See {@link NetworkIdentity#Builder}. + * @deprecated See {@link NetworkIdentity.Builder}. */ // TODO: Remove this after all callers are migrated to use new Api. @Deprecated @@ -270,8 +276,12 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { public static NetworkIdentity buildNetworkIdentity(Context context, @NonNull NetworkStateSnapshot snapshot, boolean defaultNetwork, @Annotation.NetworkType int ratType) { - return new NetworkIdentity.Builder().setNetworkStateSnapshot(snapshot) - .setDefaultNetwork(defaultNetwork).setRatType(ratType).build(); + final NetworkIdentity.Builder builder = new NetworkIdentity.Builder() + .setNetworkStateSnapshot(snapshot).setDefaultNetwork(defaultNetwork); + if (snapshot.getLegacyType() == TYPE_MOBILE && ratType != NETWORK_TYPE_ALL) { + builder.setRatType(ratType); + } + return builder.build(); } /** @@ -291,30 +301,30 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { return oemManaged; } - @Override - public int compareTo(@NonNull NetworkIdentity another) { - Objects.requireNonNull(another); - int res = Integer.compare(mType, another.mType); + /** @hide */ + public static int compare(@NonNull NetworkIdentity left, @NonNull NetworkIdentity right) { + Objects.requireNonNull(right); + int res = Integer.compare(left.mType, right.mType); if (res == 0) { - res = Integer.compare(mRatType, another.mRatType); + res = Integer.compare(left.mRatType, right.mRatType); } - if (res == 0 && mSubscriberId != null && another.mSubscriberId != null) { - res = mSubscriberId.compareTo(another.mSubscriberId); + if (res == 0 && left.mSubscriberId != null && right.mSubscriberId != null) { + res = left.mSubscriberId.compareTo(right.mSubscriberId); } - if (res == 0 && mWifiNetworkKey != null && another.mWifiNetworkKey != null) { - res = mWifiNetworkKey.compareTo(another.mWifiNetworkKey); + if (res == 0 && left.mWifiNetworkKey != null && right.mWifiNetworkKey != null) { + res = left.mWifiNetworkKey.compareTo(right.mWifiNetworkKey); } if (res == 0) { - res = Boolean.compare(mRoaming, another.mRoaming); + res = Boolean.compare(left.mRoaming, right.mRoaming); } if (res == 0) { - res = Boolean.compare(mMetered, another.mMetered); + res = Boolean.compare(left.mMetered, right.mMetered); } if (res == 0) { - res = Boolean.compare(mDefaultNetwork, another.mDefaultNetwork); + res = Boolean.compare(left.mDefaultNetwork, right.mDefaultNetwork); } if (res == 0) { - res = Integer.compare(mOemManaged, another.mOemManaged); + res = Integer.compare(left.mOemManaged, right.mOemManaged); } return res; } @@ -323,6 +333,11 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { * Builder class for {@link NetworkIdentity}. */ public static final class Builder { + // Need to be synchronized with ConnectivityManager. + // TODO: Use {@link ConnectivityManager#MAX_NETWORK_TYPE} when this file is in the module. + private static final int MAX_NETWORK_TYPE = 18; // TYPE_TEST + private static final int MIN_NETWORK_TYPE = TYPE_MOBILE; + private int mType; private int mRatType; private String mSubscriberId; @@ -349,7 +364,14 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { /** * Add an {@link NetworkStateSnapshot} into the {@link NetworkIdentity} instance. - * This is to read roaming, metered, wifikey... from the snapshot for convenience. + * This is a useful shorthand that will read from the snapshot and set the + * following fields, if they are set in the snapshot : + * - type + * - subscriberId + * - roaming + * - metered + * - oemManaged + * - wifiNetworkKey * * @param snapshot The target {@link NetworkStateSnapshot} object. * @return The builder object. @@ -374,9 +396,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { .getTransportInfo(); if (transportInfo instanceof WifiInfo) { final WifiInfo info = (WifiInfo) transportInfo; - if (info != null) { - setWifiNetworkKey(info.getCurrentNetworkKey()); - } + setWifiNetworkKey(info.getNetworkKey()); } } return this; @@ -391,6 +411,12 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { */ @NonNull public Builder setType(int type) { + // Include TYPE_NONE for compatibility, type field might not be filled by some + // networks such as test networks. + if ((type < MIN_NETWORK_TYPE || MAX_NETWORK_TYPE < type) + && type != ConnectivityManager.TYPE_NONE) { + throw new IllegalArgumentException("Invalid network type: " + type); + } mType = type; return this; } @@ -398,6 +424,8 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { /** * Set the Radio Access Technology(RAT) type of the network. * + * No RAT type is specified by default. Call clearRatType to reset. + * * @param ratType the Radio Access Technology(RAT) type if applicable. See * {@code TelephonyManager.NETWORK_TYPE_*}. * @@ -405,6 +433,10 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { */ @NonNull public Builder setRatType(@Annotation.NetworkType int ratType) { + if (!CollectionUtils.contains(TelephonyManager.getAllNetworkTypes(), ratType) + && ratType != TelephonyManager.NETWORK_TYPE_UNKNOWN) { + throw new IllegalArgumentException("Invalid ratType " + ratType); + } mRatType = ratType; return this; } @@ -436,7 +468,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { * Set the Wifi Network Key. * * @param wifiNetworkKey Wifi Network Key of the network, - * see {@link WifiInfo#getCurrentNetworkKey()}. + * see {@link WifiInfo#getNetworkKey()}. * Or null if not applicable. * @return this builder. */ @@ -447,7 +479,9 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } /** - * Set the roaming. + * Set whether this network is roaming. + * + * This field is false by default. Call with false to reset. * * @param roaming the roaming status of the network. * @return this builder. @@ -459,7 +493,9 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } /** - * Set the meteredness. + * Set whether this network is metered. + * + * This field is false by default. Call with false to reset. * * @param metered the meteredness of the network. * @return this builder. @@ -471,7 +507,9 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { } /** - * Set the default network status. + * Set whether this network is the default network. + * + * This field is false by default. Call with false to reset. * * @param defaultNetwork the default network status of the network. * @return this builder. @@ -491,10 +529,27 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { */ @NonNull public Builder setOemManaged(@OemManaged int oemManaged) { + // Assert input does not contain illegal oemManage bits. + if ((~SUPPORTED_OEM_MANAGED_TYPES & oemManaged) != 0) { + throw new IllegalArgumentException("Invalid value for OemManaged : " + oemManaged); + } mOemManaged = oemManaged; return this; } + private void ensureValidParameters() { + // Assert non-mobile network cannot have a ratType. + if (mType != TYPE_MOBILE && mRatType != NetworkTemplate.NETWORK_TYPE_ALL) { + throw new IllegalArgumentException( + "Invalid ratType " + mRatType + " for type " + mType); + } + + // Assert non-wifi network cannot have a wifi network key. + if (mType != TYPE_WIFI && mWifiNetworkKey != null) { + throw new IllegalArgumentException("Invalid wifi network key for type " + mType); + } + } + /** * Builds the instance of the {@link NetworkIdentity}. * @@ -502,6 +557,7 @@ public class NetworkIdentity implements Comparable<NetworkIdentity> { */ @NonNull public NetworkIdentity build() { + ensureValidParameters(); return new NetworkIdentity(mType, mRatType, mSubscriberId, mWifiNetworkKey, mRoaming, mMetered, mDefaultNetwork, mOemManaged); } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java index 041f070512b0..dfa347f6f12b 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkIdentitySet.java @@ -27,6 +27,7 @@ import java.io.DataOutput; import java.io.IOException; import java.util.HashSet; import java.util.Objects; +import java.util.Set; /** * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity} @@ -34,9 +35,7 @@ import java.util.Objects; * * @hide */ -// @SystemApi(client = MODULE_LIBRARIES) -public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements - Comparable<NetworkIdentitySet> { +public class NetworkIdentitySet extends HashSet<NetworkIdentity> { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_ROAMING = 2; private static final int VERSION_ADD_NETWORK_ID = 3; @@ -52,6 +51,11 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } /** @hide */ + public NetworkIdentitySet(@NonNull Set<NetworkIdentity> ident) { + super(ident); + } + + /** @hide */ public NetworkIdentitySet(DataInput in) throws IOException { final int version = in.readInt(); final int size = in.readInt(); @@ -189,15 +193,15 @@ public class NetworkIdentitySet extends HashSet<NetworkIdentity> implements } } - @Override - public int compareTo(@NonNull NetworkIdentitySet another) { - Objects.requireNonNull(another); - if (isEmpty()) return -1; - if (another.isEmpty()) return 1; + public static int compare(@NonNull NetworkIdentitySet left, @NonNull NetworkIdentitySet right) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); + if (left.isEmpty()) return -1; + if (right.isEmpty()) return 1; - final NetworkIdentity ident = iterator().next(); - final NetworkIdentity anotherIdent = another.iterator().next(); - return ident.compareTo(anotherIdent); + final NetworkIdentity leftIdent = left.iterator().next(); + final NetworkIdentity rightIdent = right.iterator().next(); + return NetworkIdentity.compare(leftIdent, rightIdent); } /** diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java index f169fed6b9b3..58ca21fdfad0 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsCollection.java @@ -72,6 +72,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Objects; +import java.util.Set; /** * Collection of {@link NetworkStatsHistory}, stored based on combined key of @@ -702,7 +703,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W private ArrayList<Key> getSortedKeys() { final ArrayList<Key> keys = new ArrayList<>(); keys.addAll(mStats.keySet()); - Collections.sort(keys); + Collections.sort(keys, (left, right) -> Key.compare(left, right)); return keys; } @@ -812,7 +813,7 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * the identifier that associate with the {@link NetworkStatsHistory} object to identify * a certain record in the {@link NetworkStatsCollection} object. */ - public static class Key implements Comparable<Key> { + public static class Key { /** @hide */ public final NetworkIdentitySet ident; /** @hide */ @@ -832,6 +833,11 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W * @param set Set of the record, see {@code NetworkStats#SET_*}. * @param tag Tag of the record, see {@link TrafficStats#setThreadStatsTag(int)}. */ + public Key(@NonNull Set<NetworkIdentity> ident, int uid, int set, int tag) { + this(new NetworkIdentitySet(Objects.requireNonNull(ident)), uid, set, tag); + } + + /** @hide */ public Key(@NonNull NetworkIdentitySet ident, int uid, int set, int tag) { this.ident = Objects.requireNonNull(ident); this.uid = uid; @@ -855,21 +861,22 @@ public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.W return false; } - @Override - public int compareTo(@NonNull Key another) { - Objects.requireNonNull(another); + /** @hide */ + public static int compare(@NonNull Key left, @NonNull Key right) { + Objects.requireNonNull(left); + Objects.requireNonNull(right); int res = 0; - if (ident != null && another.ident != null) { - res = ident.compareTo(another.ident); + if (left.ident != null && right.ident != null) { + res = NetworkIdentitySet.compare(left.ident, right.ident); } if (res == 0) { - res = Integer.compare(uid, another.uid); + res = Integer.compare(left.uid, right.uid); } if (res == 0) { - res = Integer.compare(set, another.set); + res = Integer.compare(left.set, right.set); } if (res == 0) { - res = Integer.compare(tag, another.tag); + res = Integer.compare(left.tag, right.tag); } return res; } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java index 90054c683de5..78c137073aaa 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkStatsHistory.java @@ -16,6 +16,7 @@ package android.net; +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.TAG_NONE; @@ -31,6 +32,7 @@ import static android.text.format.DateUtils.SECOND_IN_MILLIS; import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -51,7 +53,9 @@ import java.io.DataOutput; import java.io.IOException; import java.io.PrintWriter; import java.net.ProtocolException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Random; /** @@ -65,7 +69,7 @@ import java.util.Random; * * @hide */ -// @SystemApi(client = MODULE_LIBRARIES) +@SystemApi(client = MODULE_LIBRARIES) public final class NetworkStatsHistory implements Parcelable { private static final int VERSION_INIT = 1; private static final int VERSION_ADD_PACKETS = 2; @@ -97,23 +101,157 @@ public final class NetworkStatsHistory implements Parcelable { private long[] operations; private long totalBytes; - public static class Entry { + /** @hide */ + public NetworkStatsHistory(long bucketDuration, long[] bucketStart, long[] activeTime, + long[] rxBytes, long[] rxPackets, long[] txBytes, long[] txPackets, + long[] operations, int bucketCount, long totalBytes) { + this.bucketDuration = bucketDuration; + this.bucketStart = bucketStart; + this.activeTime = activeTime; + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.txBytes = txBytes; + this.txPackets = txPackets; + this.operations = operations; + this.bucketCount = bucketCount; + this.totalBytes = totalBytes; + } + + /** + * An instance to represent a single record in a {@link NetworkStatsHistory} object. + */ + public static final class Entry { + /** @hide */ public static final long UNKNOWN = -1; + /** @hide */ + // TODO: Migrate all callers to get duration from the history object and remove this field. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long bucketDuration; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long bucketStart; + /** @hide */ public long activeTime; + /** @hide */ @UnsupportedAppUsage public long rxBytes; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long rxPackets; + /** @hide */ @UnsupportedAppUsage public long txBytes; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public long txPackets; + /** @hide */ public long operations; + /** @hide */ + Entry() {} + + /** + * Construct a {@link Entry} instance to represent a single record in a + * {@link NetworkStatsHistory} object. + * + * @param bucketStart Start of period for this {@link Entry}, in milliseconds since the + * Unix epoch, see {@link java.lang.System#currentTimeMillis}. + * @param activeTime Active time for this {@link Entry}, in milliseconds. + * @param rxBytes Number of bytes received for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param rxPackets Number of packets received for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param txBytes Number of bytes transmitted for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param txPackets Number of bytes transmitted for this {@link Entry}. Statistics should + * represent the contents of IP packets, including IP headers. + * @param operations count of network operations performed for this {@link Entry}. This can + * be used to derive bytes-per-operation. + */ + public Entry(long bucketStart, long activeTime, long rxBytes, + long rxPackets, long txBytes, long txPackets, long operations) { + this.bucketStart = bucketStart; + this.activeTime = activeTime; + this.rxBytes = rxBytes; + this.rxPackets = rxPackets; + this.txBytes = txBytes; + this.txPackets = txPackets; + this.operations = operations; + } + + /** + * Get start timestamp of the bucket's time interval, in milliseconds since the Unix epoch. + */ + public long getBucketStart() { + return bucketStart; + } + + /** + * Get active time of the bucket's time interval, in milliseconds. + */ + public long getActiveTime() { + return activeTime; + } + + /** Get number of bytes received for this {@link Entry}. */ + public long getRxBytes() { + return rxBytes; + } + + /** Get number of packets received for this {@link Entry}. */ + public long getRxPackets() { + return rxPackets; + } + + /** Get number of bytes transmitted for this {@link Entry}. */ + public long getTxBytes() { + return txBytes; + } + + /** Get number of packets transmitted for this {@link Entry}. */ + public long getTxPackets() { + return txPackets; + } + + /** Get count of network operations performed for this {@link Entry}. */ + public long getOperations() { + return operations; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o.getClass() != getClass()) return false; + Entry entry = (Entry) o; + return bucketStart == entry.bucketStart + && activeTime == entry.activeTime && rxBytes == entry.rxBytes + && rxPackets == entry.rxPackets && txBytes == entry.txBytes + && txPackets == entry.txPackets && operations == entry.operations; + } + + @Override + public int hashCode() { + return (int) (bucketStart * 2 + + activeTime * 3 + + rxBytes * 5 + + rxPackets * 7 + + txBytes * 11 + + txPackets * 13 + + operations * 17); + } + + @Override + public String toString() { + return "Entry{" + + "bucketStart=" + bucketStart + + ", activeTime=" + activeTime + + ", rxBytes=" + rxBytes + + ", rxPackets=" + rxPackets + + ", txBytes=" + txBytes + + ", txPackets=" + txPackets + + ", operations=" + operations + + "}"; + } } /** @hide */ @@ -324,6 +462,22 @@ public final class NetworkStatsHistory implements Parcelable { return entry; } + /** + * Get List of {@link Entry} of the {@link NetworkStatsHistory} instance. + * + * @return + */ + @NonNull + public List<Entry> getEntries() { + // TODO: Return a wrapper that uses this list instead, to prevent the returned result + // from being changed. + final ArrayList<Entry> ret = new ArrayList<>(size()); + for (int i = 0; i < size(); i++) { + ret.add(getValues(i, null /* recycle */)); + } + return ret; + } + /** @hide */ public void setValues(int i, Entry entry) { // Unwind old values @@ -928,4 +1082,80 @@ public final class NetworkStatsHistory implements Parcelable { } } + /** + * Builder class for {@link NetworkStatsHistory}. + */ + public static final class Builder { + private final long mBucketDuration; + private final List<Long> mBucketStart; + private final List<Long> mActiveTime; + private final List<Long> mRxBytes; + private final List<Long> mRxPackets; + private final List<Long> mTxBytes; + private final List<Long> mTxPackets; + private final List<Long> mOperations; + + /** + * Creates a new Builder with given bucket duration and initial capacity to construct + * {@link NetworkStatsHistory} objects. + * + * @param bucketDuration Duration of the buckets of the object, in milliseconds. + * @param initialCapacity Estimated number of records. + */ + public Builder(long bucketDuration, int initialCapacity) { + mBucketDuration = bucketDuration; + mBucketStart = new ArrayList<>(initialCapacity); + mActiveTime = new ArrayList<>(initialCapacity); + mRxBytes = new ArrayList<>(initialCapacity); + mRxPackets = new ArrayList<>(initialCapacity); + mTxBytes = new ArrayList<>(initialCapacity); + mTxPackets = new ArrayList<>(initialCapacity); + mOperations = new ArrayList<>(initialCapacity); + } + + /** + * Add an {@link Entry} into the {@link NetworkStatsHistory} instance. + * + * @param entry The target {@link Entry} object. + * @return The builder object. + */ + @NonNull + public Builder addEntry(@NonNull Entry entry) { + mBucketStart.add(entry.bucketStart); + mActiveTime.add(entry.activeTime); + mRxBytes.add(entry.rxBytes); + mRxPackets.add(entry.rxPackets); + mTxBytes.add(entry.txBytes); + mTxPackets.add(entry.txPackets); + mOperations.add(entry.operations); + return this; + } + + private static long sum(@NonNull List<Long> list) { + long sum = 0; + for (long entry : list) { + sum += entry; + } + return sum; + } + + /** + * Builds the instance of the {@link NetworkStatsHistory}. + * + * @return the built instance of {@link NetworkStatsHistory}. + */ + @NonNull + public NetworkStatsHistory build() { + return new NetworkStatsHistory(mBucketDuration, + CollectionUtils.toLongArray(mBucketStart), + CollectionUtils.toLongArray(mActiveTime), + CollectionUtils.toLongArray(mRxBytes), + CollectionUtils.toLongArray(mRxPackets), + CollectionUtils.toLongArray(mTxBytes), + CollectionUtils.toLongArray(mTxPackets), + CollectionUtils.toLongArray(mOperations), + mBucketStart.size(), + sum(mRxBytes) + sum(mTxBytes)); + } + } } diff --git a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java index a7e48d43631b..cad80752b8e7 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java +++ b/packages/ConnectivityT/framework-t/src/android/net/NetworkTemplate.java @@ -263,7 +263,7 @@ public final class NetworkTemplate implements Parcelable { * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the * given key of the wifi network. * - * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. * @hide */ @@ -283,7 +283,7 @@ public final class NetworkTemplate implements Parcelable { * Call with {@link #WIFI_NETWORK_KEY_ALL} for {@code wifiNetworkKey} to get result regardless * of key of the wifi network. * - * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. * @param subscriberId the IMSI associated to this wifi network. * @@ -593,7 +593,7 @@ public final class NetworkTemplate implements Parcelable { /** * Get the set of Wifi Network Keys of the template. - * See {@link WifiInfo#getCurrentNetworkKey()}. + * See {@link WifiInfo#getNetworkKey()}. */ @NonNull public Set<String> getWifiNetworkKeys() { @@ -729,7 +729,7 @@ public final class NetworkTemplate implements Parcelable { * Returns true when the key matches, or when {@code mMatchWifiNetworkKeys} is * empty. * - * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getCurrentNetworkKey()} + * @param wifiNetworkKey key of the wifi network. see {@link WifiInfo#getNetworkKey()} * to know details about the key. */ private boolean matchesWifiNetworkKey(@NonNull String wifiNetworkKey) { @@ -1059,9 +1059,9 @@ public final class NetworkTemplate implements Parcelable { * the intention of matching any Wifi Network Key. * * @param wifiNetworkKeys the list of Wifi Network Key, - * see {@link WifiInfo#getCurrentNetworkKey()}. + * see {@link WifiInfo#getNetworkKey()}. * Or an empty list to match all networks. - * Note that {@code getCurrentNetworkKey()} might get null key + * Note that {@code getNetworkKey()} might get null key * when wifi disconnects. However, the caller should never invoke * this function with a null Wifi Network Key since such statistics * never exists. diff --git a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java index 1af32bf5524c..c803a723ba83 100644 --- a/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java +++ b/packages/ConnectivityT/framework-t/src/android/net/TrafficStats.java @@ -17,7 +17,6 @@ package android.net; import android.annotation.NonNull; -import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -27,8 +26,8 @@ import android.app.usage.NetworkStatsManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.MediaPlayer; +import android.os.Binder; import android.os.Build; -import android.os.IBinder; import android.os.RemoteException; import com.android.server.NetworkManagementSocketTagger; @@ -37,8 +36,6 @@ import dalvik.system.SocketTagger; import java.io.FileDescriptor; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.DatagramSocket; import java.net.Socket; import java.net.SocketException; @@ -177,25 +174,12 @@ public class TrafficStats { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562) private synchronized static INetworkStatsService getStatsService() { if (sStatsService == null) { - sStatsService = getStatsBinder(); + throw new IllegalStateException("TrafficStats not initialized, uid=" + + Binder.getCallingUid()); } return sStatsService; } - @Nullable - private static INetworkStatsService getStatsBinder() { - try { - final Method getServiceMethod = Class.forName("android.os.ServiceManager") - .getDeclaredMethod("getService", new Class[]{String.class}); - final IBinder binder = (IBinder) getServiceMethod.invoke( - null, Context.NETWORK_STATS_SERVICE); - return INetworkStatsService.Stub.asInterface(binder); - } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException - | InvocationTargetException e) { - throw new NullPointerException("Cannot get INetworkStatsService: " + e); - } - } - /** * Snapshot of {@link NetworkStats} when the currently active profiling * session started, or {@code null} if no session active. @@ -210,6 +194,26 @@ public class TrafficStats { private static final String LOOPBACK_IFACE = "lo"; /** + * Initialization {@link TrafficStats} with the context, to + * allow {@link TrafficStats} to fetch the needed binder. + * + * @param context a long-lived context, such as the application context or system + * server context. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SuppressLint("VisiblySynchronized") + public static synchronized void init(@NonNull final Context context) { + if (sStatsService != null) { + throw new IllegalStateException("TrafficStats is already initialized, uid=" + + Binder.getCallingUid()); + } + final NetworkStatsManager statsManager = + context.getSystemService(NetworkStatsManager.class); + sStatsService = statsManager.getBinder(); + } + + /** * Set active tag to use when accounting {@link Socket} traffic originating * from the current thread. Only one active tag per thread is supported. * <p> diff --git a/packages/ConnectivityT/service/Android.bp b/packages/ConnectivityT/service/Android.bp index b261e165a112..36dd20054c5c 100644 --- a/packages/ConnectivityT/service/Android.bp +++ b/packages/ConnectivityT/service/Android.bp @@ -66,6 +66,7 @@ filegroup { filegroup { name: "services.connectivity-ethernet-sources", srcs: [ + "src/com/android/server/net/DelayedDiskWrite.java", "src/com/android/server/net/IpConfigStore.java", ], path: "src", diff --git a/services/core/java/com/android/server/net/DelayedDiskWrite.java b/packages/ConnectivityT/service/src/com/android/server/net/DelayedDiskWrite.java index 8f09eb7c19ab..35dc4557252c 100644 --- a/services/core/java/com/android/server/net/DelayedDiskWrite.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/DelayedDiskWrite.java @@ -26,21 +26,37 @@ import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; +/** + * This class provides APIs to do a delayed data write to a given {@link OutputStream}. + */ public class DelayedDiskWrite { + private static final String TAG = "DelayedDiskWrite"; + private HandlerThread mDiskWriteHandlerThread; private Handler mDiskWriteHandler; /* Tracks multiple writes on the same thread */ private int mWriteSequence = 0; - private final String TAG = "DelayedDiskWrite"; + /** + * Used to do a delayed data write to a given {@link OutputStream}. + */ public interface Writer { - public void onWriteCalled(DataOutputStream out) throws IOException; + /** + * write data to a given {@link OutputStream}. + */ + void onWriteCalled(DataOutputStream out) throws IOException; } + /** + * Do a delayed data write to a given output stream opened from filePath. + */ public void write(final String filePath, final Writer w) { write(filePath, w, true); } + /** + * Do a delayed data write to a given output stream opened from filePath. + */ public void write(final String filePath, final Writer w, final boolean open) { if (TextUtils.isEmpty(filePath)) { throw new IllegalArgumentException("empty file path"); @@ -77,7 +93,7 @@ public class DelayedDiskWrite { if (out != null) { try { out.close(); - } catch (Exception e) {} + } catch (Exception e) { } } // Quit if no more writes sent diff --git a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java index 9b90f3b54542..1105de3dd92c 100644 --- a/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java +++ b/packages/ConnectivityT/service/src/com/android/server/net/NetworkStatsService.java @@ -106,7 +106,6 @@ import android.os.Binder; import android.os.DropBoxManager; import android.os.Environment; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; @@ -450,7 +449,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { handlerThread.start(); mHandler = new NetworkStatsHandler(handlerThread.getLooper()); mNetworkStatsSubscriptionsMonitor = deps.makeSubscriptionsMonitor(mContext, - new HandlerExecutor(mHandler), this); + (command) -> mHandler.post(command) , this); mContentResolver = mContext.getContentResolver(); mContentObserver = mDeps.makeContentObserver(mHandler, mSettings, mNetworkStatsSubscriptionsMonitor); @@ -557,7 +556,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { // watch for tethering changes final TetheringManager tetheringManager = mContext.getSystemService(TetheringManager.class); tetheringManager.registerTetheringEventCallback( - new HandlerExecutor(mHandler), mTetherListener); + (command) -> mHandler.post(command), mTetherListener); // listen for periodic polling events final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 2c862e685035..389892ed15e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -170,18 +170,6 @@ public class BluetoothEventManager { } @VisibleForTesting - void registerIntentReceiver() { - mContext.registerReceiverAsUser(mBroadcastReceiver, mUserHandle, mAdapterIntentFilter, - null, mReceiverHandler); - } - - @VisibleForTesting - void registerProfileIntentReceiverForTest() { - mContext.registerReceiverAsUser(mProfileBroadcastReceiver, mUserHandle, - mProfileIntentFilter, null, mReceiverHandler); - } - - @VisibleForTesting void addProfileHandler(String action, Handler handler) { mHandlerMap.put(action, handler); mProfileIntentFilter.addAction(action); diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index 3c444f2b95b3..7168f3cf1f9c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -402,7 +402,10 @@ public class DreamBackend { if (dreamInfo == null || dreamInfo.settingsComponentName == null) { return; } - uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName)); + final Intent intent = new Intent() + .setComponent(dreamInfo.settingsComponentName) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + uiContext.startActivity(intent); } public void preview(DreamInfo dreamInfo) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java index bee466d39c23..852ac5ca6abe 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java @@ -129,7 +129,6 @@ public class BluetoothEventManagerTest { @Test public void intentWithExtraState_audioStateChangedShouldDispatchToRegisterCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); mContext.sendBroadcast(mIntent); @@ -143,7 +142,6 @@ public class BluetoothEventManagerTest { @Test public void intentWithExtraState_phoneStateChangedShouldDispatchToRegisterCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED); mContext.sendBroadcast(mIntent); @@ -169,7 +167,6 @@ public class BluetoothEventManagerTest { @Test public void dispatchAclConnectionStateChanged_aclDisconnected_shouldDispatchCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -182,7 +179,6 @@ public class BluetoothEventManagerTest { @Test public void dispatchAclConnectionStateChanged_aclConnected_shouldDispatchCallback() { mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -196,7 +192,6 @@ public class BluetoothEventManagerTest { public void dispatchAclConnectionStateChanged_aclDisconnected_shouldNotCallbackSubDevice() { when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true); mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -210,7 +205,6 @@ public class BluetoothEventManagerTest { public void dispatchAclConnectionStateChanged_aclConnected_shouldNotCallbackSubDevice() { when(mCachedDeviceManager.isSubDevice(mBluetoothDevice)).thenReturn(true); mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -224,7 +218,6 @@ public class BluetoothEventManagerTest { public void dispatchAclConnectionStateChanged_findDeviceReturnNull_shouldNotDispatchCallback() { when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null); mBluetoothEventManager.registerCallback(mBluetoothCallback); - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); @@ -361,7 +354,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); @@ -377,7 +369,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonRemoteDeviceDown_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); @@ -394,7 +385,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonAuthRejected_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); @@ -410,7 +400,6 @@ public class BluetoothEventManagerTest { @Test public void showUnbondMessage_reasonAuthFailed_showCorrectedErrorCode() { - mBluetoothEventManager.registerIntentReceiver(); mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java index 09540d1373ee..4f8fa2fdb96e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java @@ -40,7 +40,6 @@ import android.os.ParcelUuid; import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -85,7 +84,6 @@ public class LocalBluetoothProfileManagerTest { when(mCachedBluetoothDevice.getDevice()).thenReturn(mDevice); mProfileManager = new LocalBluetoothProfileManager(mContext, mLocalBluetoothAdapter, mDeviceManager, mEventManager); - mEventManager.registerProfileIntentReceiverForTest(); } /** @@ -152,7 +150,6 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test - @Ignore public void stateChangedHandler_receiveA2dpConnectionStateChanged_shouldDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.A2DP})); @@ -174,7 +171,6 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test - @Ignore public void stateChangedHandler_receiveHeadsetConnectionStateChanged_shouldDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEADSET})); @@ -196,7 +192,6 @@ public class LocalBluetoothProfileManagerTest { * CachedBluetoothDeviceManager method */ @Test - @Ignore public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchDeviceManager() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEARING_AID})); @@ -219,7 +214,6 @@ public class LocalBluetoothProfileManagerTest { * profile connection state changed callback */ @Test - @Ignore public void stateChangedHandler_receivePanConnectionStateChanged_shouldNotDispatchCallback() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.PAN})); @@ -261,7 +255,6 @@ public class LocalBluetoothProfileManagerTest { * handler and refresh CachedBluetoothDevice */ @Test - @Ignore public void stateChangedHandler_receivePanConnectionStateChangedWithProfile_shouldRefresh() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.PAN})); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java index aa11952397b4..06b6fc8ef73d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminControllerTest.java @@ -22,7 +22,6 @@ import static com.android.settingslib.enterprise.FakeDeviceAdminStringProvider.D import static junit.framework.Assert.assertNotNull; import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertSame; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -37,7 +36,6 @@ import android.provider.Settings; import com.android.settingslib.RestrictedLockUtils; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -67,9 +65,8 @@ public class BiometricActionDisabledByAdminControllerTest { } @Test - @Ignore public void buttonClicked() { - ComponentName componentName = mock(ComponentName.class); + ComponentName componentName = new ComponentName("com.android.test", "AThing"); RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils.EnforcedAdmin( componentName, new UserHandle(UserHandle.myUserId())); @@ -85,6 +82,6 @@ public class BiometricActionDisabledByAdminControllerTest { assertEquals(Settings.SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS, intentCaptor.getValue().getStringExtra( Settings.EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY)); - assertSame(componentName, intentCaptor.getValue().getComponent()); + assertEquals(componentName.getPackageName(), intentCaptor.getValue().getPackage()); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java index a31f24ae5f77..30267f793cd6 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java @@ -19,14 +19,17 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; 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.Context; import android.graphics.drawable.ColorDrawable; +import android.net.wifi.WifiManager; + +import androidx.test.core.app.ApplicationProvider; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -37,7 +40,7 @@ import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class AccessPointPreferenceTest { - private Context mContext = RuntimeEnvironment.application; + private Context mContext; @Mock private AccessPoint mockAccessPoint; @@ -54,12 +57,13 @@ public class AccessPointPreferenceTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mock(WifiManager.class)); when(mockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable()); } @Test - @Ignore public void refresh_openNetwork_updateContentDescription() { final String ssid = "ssid"; final String summary = "connected"; @@ -90,7 +94,6 @@ public class AccessPointPreferenceTest { } @Test - @Ignore public void refresh_setTitle_shouldUseSsidString() { final String ssid = "ssid"; final String summary = "connected"; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java index 5d7f8ba52d2a..e7b3fe9ab8da 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,6 +33,7 @@ import android.net.ScoredNetwork; import android.net.WifiKey; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.os.Bundle; import android.os.Parcelable; @@ -44,7 +46,6 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settingslib.R; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -75,10 +76,10 @@ public class WifiUtilsTest { public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(ApplicationProvider.getApplicationContext()); + when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mock(WifiManager.class)); } @Test - @Ignore public void testVerboseSummaryString_showsScanResultSpeedLabel() { WifiTracker.sVerboseLogging = true; diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index f20057d9f800..5f549fd05e1a 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -325,6 +325,7 @@ public class SecureSettingsValidators { return true; }); VALIDATORS.put(Secure.ODI_CAPTIONS_VOLUME_UI_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.FAST_PAIR_SCAN_ENABLED, BOOLEAN_VALIDATOR); } } diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index e1da74466b55..3ae85e7e2325 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -11,8 +11,10 @@ asc@google.com awickham@google.com beverlyt@google.com brockman@google.com +brzezinski@google.com brycelee@google.com ccassidy@google.com +chrisgollner@google.com cinek@google.com cwren@google.com dupin@google.com @@ -43,6 +45,8 @@ mpietal@google.com mrcasey@google.com mrenouf@google.com nesciosquid@google.com +nickchameyev@google.com +nicomazz@google.com ogunwale@google.com peanutbutter@google.com pinyaoting@google.com diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml index dfc3e63a4e2b..ecb3cb3961a4 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml @@ -22,21 +22,6 @@ android:layout_height="48dp" android:gravity="center_vertical"> - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@android:id/edit" - android:layout_width="0dp" - android:layout_height="@dimen/qs_footer_action_button_size" - android:layout_marginEnd="@dimen/qs_tile_margin_horizontal" - android:layout_weight="1" - android:background="@drawable/qs_footer_action_chip_background" - android:clickable="true" - android:clipToPadding="false" - android:contentDescription="@string/accessibility_quick_settings_edit" - android:focusable="true" - android:padding="@dimen/qs_footer_icon_padding" - android:src="@*android:drawable/ic_mode_edit" - android:tint="?android:attr/textColorPrimary" /> - <com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch" android:layout_width="0dp" diff --git a/packages/SystemUI/res-keyguard/values/bools.xml b/packages/SystemUI/res-keyguard/values/bools.xml index c5bf4ce48188..2b83787172d3 100644 --- a/packages/SystemUI/res-keyguard/values/bools.xml +++ b/packages/SystemUI/res-keyguard/values/bools.xml @@ -17,5 +17,4 @@ <resources> <bool name="kg_show_ime_at_screen_on">true</bool> <bool name="kg_use_all_caps">true</bool> - <bool name="flag_active_unlock">false</bool> </resources> diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml index ed8f61a97c2a..6fa9eac2fc29 100644 --- a/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_background_transition.xml @@ -15,7 +15,7 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="@color/qs_detail_transition"/> + <solid android:color="@android:color/transparent"/> <corners android:radius="?android:attr/dialogCornerRadius" /> </shape> </inset> diff --git a/packages/SystemUI/res/drawable/qs_detail_background.xml b/packages/SystemUI/res/drawable/qs_detail_background.xml index e5c7352807f8..c23649d629bd 100644 --- a/packages/SystemUI/res/drawable/qs_detail_background.xml +++ b/packages/SystemUI/res/drawable/qs_detail_background.xml @@ -17,7 +17,7 @@ Copyright (C) 2014 The Android Open Source Project <item> <inset> <shape> - <solid android:color="@color/qs_detail_transition"/> + <solid android:color="@android:color/transparent"/> <corners android:radius="@dimen/qs_footer_action_corner_radius" /> </shape> </inset> diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index e70084b80308..5cd9e9485fda 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -43,7 +43,6 @@ android:id="@+id/build" android:layout_width="0dp" android:layout_height="match_parent" - android:paddingStart="@dimen/qs_tile_margin_horizontal" android:paddingEnd="4dp" android:layout_weight="1" android:clickable="true" @@ -61,10 +60,23 @@ android:layout_gravity="center_vertical" android:visibility="gone" /> - <View + <FrameLayout android:layout_width="0dp" android:layout_height="match_parent" - android:layout_weight="1" /> + android:layout_weight="1"> + <com.android.systemui.statusbar.AlphaOptimizedImageView + android:id="@android:id/edit" + android:layout_width="@dimen/qs_footer_action_button_size" + android:layout_height="@dimen/qs_footer_action_button_size" + android:layout_gravity="center_vertical|end" + android:background="?android:attr/selectableItemBackground" + android:clickable="true" + android:contentDescription="@string/accessibility_quick_settings_edit" + android:focusable="true" + android:padding="@dimen/qs_footer_icon_padding" + android:src="@*android:drawable/ic_mode_edit" + android:tint="?android:attr/textColorPrimary" /> + </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index fc28f0976013..461a598e9341 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -23,7 +23,6 @@ <color name="system_bar_background_transparent">#00000000</color> <color name="qs_tile_divider">#29ffffff</color><!-- 16% white --> <color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white --> - <color name="qs_detail_transition">#66FFFFFF</color> <color name="status_bar_clock_color">#FFFFFFFF</color> <color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index a348b423d3c7..f2d0427c39d3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -37,8 +37,6 @@ import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.AlarmManager; -import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.UserSwitchObserver; import android.app.admin.DevicePolicyManager; @@ -104,8 +102,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; @@ -113,7 +109,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.telephony.TelephonyListenerManager; import com.android.systemui.util.Assert; -import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.RingerModeTracker; import com.google.android.collect.Lists; @@ -338,7 +333,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID; private final Executor mBackgroundExecutor; private SensorPrivacyManager mSensorPrivacyManager; - private FeatureFlags mFeatureFlags; private int mFaceAuthUserId; /** @@ -443,7 +437,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } @Override - public void onTrustChanged(boolean enabled, int userId, int flags) { + public void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages) { Assert.isMainThread(); boolean wasTrusted = mUserHasTrust.get(userId, false); mUserHasTrust.put(userId, enabled); @@ -465,6 +460,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } } + + if (KeyguardUpdateMonitor.getCurrentUser() == userId && getUserHasTrust(userId)) { + CharSequence message = null; + if (trustGrantedMessages != null && trustGrantedMessages.size() > 0) { + message = trustGrantedMessages.get(0); // for now only shows the first in the list + } + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.showTrustGrantedMessage(message); + } + } + } } @Override @@ -1790,8 +1798,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab AuthController authController, TelephonyListenerManager telephonyListenerManager, InteractionJankMonitor interactionJankMonitor, - LatencyTracker latencyTracker, - FeatureFlags featureFlags) { + LatencyTracker latencyTracker) { mContext = context; mSubscriptionManager = SubscriptionManager.from(context); mTelephonyListenerManager = telephonyListenerManager; @@ -1809,7 +1816,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mAuthController = authController; dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); - mFeatureFlags = featureFlags; mHandler = new Handler(mainLooper) { @Override @@ -2253,34 +2259,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return; } - if (shouldTriggerActiveUnlock() && mFeatureFlags.isEnabled(Flags.ACTIVE_UNLOCK)) { - // TODO (b/192405661): call new TrustManager API - mNumActiveUnlockTriggers++; - Log.d("ActiveUnlock", "would have triggered times=" + mNumActiveUnlockTriggers); - showActiveUnlockNotification(mNumActiveUnlockTriggers); + if (shouldTriggerActiveUnlock()) { + mTrustManager.reportUserRequestedUnlock(KeyguardUpdateMonitor.getCurrentUser()); } } - /** - * TODO (b/192405661): Only for testing. Remove before release. - */ - private void showActiveUnlockNotification(int times) { - final String message = "Active unlock triggered " + times + " times."; - final Notification.Builder nb = - new Notification.Builder(mContext, NotificationChannels.GENERAL) - .setSmallIcon(R.drawable.ic_volume_ringer) - .setContentTitle(message) - .setStyle(new Notification.BigTextStyle().bigText(message)); - mContext.getSystemService(NotificationManager.class).notifyAsUser( - "active_unlock", - 0, - nb.build(), - UserHandle.ALL); - } - private boolean shouldTriggerActiveUnlock() { - // TODO: check if active unlock is ENABLED / AVAILABLE - // Triggers: final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant(); final boolean awakeKeyguard = mKeyguardIsVisible && mDeviceInteractive && !mGoingToSleep @@ -2294,7 +2278,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab final boolean userCanDismissLockScreen = getUserCanSkipBouncer(user) || !mLockPatternUtils.isSecure(user); - // Don't trigger active unlock if fp is locked out TODO: confirm this one + // Don't trigger active unlock if fp is locked out final boolean fpLockedout = mFingerprintLockedOut || mFingerprintLockedOutPermanent; // Don't trigger active unlock if primary auth is required diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index a74fd15ab11b..47e1035fbfef 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,6 +23,8 @@ import android.os.SystemClock; import android.telephony.TelephonyManager; import android.view.WindowManagerPolicyConstants; +import androidx.annotation.Nullable; + import com.android.settingslib.fuelgauge.BatteryStatus; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -216,6 +218,11 @@ public class KeyguardUpdateMonitorCallback { public void onTrustGrantedWithFlags(int flags, int userId) { } /** + * Called when setting the trust granted message. + */ + public void showTrustGrantedMessage(@Nullable CharSequence message) { } + + /** * Called when a biometric has been acquired. * <p> * It is guaranteed that either {@link #onBiometricAuthenticated} or diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java index 099e37960ad6..e5d63192f156 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/ComplicationProvider.java @@ -16,8 +16,14 @@ package com.android.systemui.dreams; +import android.annotation.IntDef; import android.content.Context; +import com.android.settingslib.dream.DreamBackend; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * {@link ComplicationProvider} is an interface for defining entities that can supply complications * to show over a dream. Presentation components such as the {@link DreamOverlayService} supply @@ -25,6 +31,27 @@ import android.content.Context; */ public interface ComplicationProvider { /** + * The type of dream complications which can be provided by a {@link ComplicationProvider}. + */ + @IntDef(prefix = {"COMPLICATION_TYPE_"}, flag = true, value = { + COMPLICATION_TYPE_NONE, + COMPLICATION_TYPE_TIME, + COMPLICATION_TYPE_DATE, + COMPLICATION_TYPE_WEATHER, + COMPLICATION_TYPE_AIR_QUALITY, + COMPLICATION_TYPE_CAST_INFO + }) + @Retention(RetentionPolicy.SOURCE) + @interface ComplicationType {} + + int COMPLICATION_TYPE_NONE = 0; + int COMPLICATION_TYPE_TIME = 1; + int COMPLICATION_TYPE_DATE = 1 << 1; + int COMPLICATION_TYPE_WEATHER = 1 << 2; + int COMPLICATION_TYPE_AIR_QUALITY = 1 << 3; + int COMPLICATION_TYPE_CAST_INFO = 1 << 4; + + /** * Called when the {@link ComplicationHost} requests the associated complication be produced. * * @param context The {@link Context} used to construct the view. @@ -33,4 +60,26 @@ public interface ComplicationProvider { */ void onCreateComplication(Context context, ComplicationHost.CreationCallback creationCallback, ComplicationHost.InteractionCallback interactionCallback); + + /** + * Converts a {@link com.android.settingslib.dream.DreamBackend.ComplicationType} to + * {@link ComplicationType}. + */ + @ComplicationType + default int convertComplicationType(@DreamBackend.ComplicationType int type) { + switch (type) { + case DreamBackend.COMPLICATION_TYPE_TIME: + return COMPLICATION_TYPE_TIME; + case DreamBackend.COMPLICATION_TYPE_DATE: + return COMPLICATION_TYPE_DATE; + case DreamBackend.COMPLICATION_TYPE_WEATHER: + return COMPLICATION_TYPE_WEATHER; + case DreamBackend.COMPLICATION_TYPE_AIR_QUALITY: + return COMPLICATION_TYPE_AIR_QUALITY; + case DreamBackend.COMPLICATION_TYPE_CAST_INFO: + return COMPLICATION_TYPE_CAST_INFO; + default: + return COMPLICATION_TYPE_NONE; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 5d6c2a247df3..4be819a49772 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -74,9 +74,6 @@ public class Flags { public static final ResourceBooleanFlag BOUNCER_USER_SWITCHER = new ResourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher); - public static final ResourceBooleanFlag ACTIVE_UNLOCK = - new ResourceBooleanFlag(205, R.bool.flag_active_unlock); - /***************************************/ // 300 - power menu public static final BooleanFlag POWER_MENU_LITE = diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 9e350ee60bff..4f4bd1e86e7c 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -36,6 +36,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.provider.DeviceConfig; import android.util.DisplayMetrics; import android.util.Log; @@ -578,7 +579,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f); if (mBackGestureTfClassifierProvider.isActive()) { + Trace.beginSection("EdgeBackGestureHandler#loadVocab"); mVocab = mBackGestureTfClassifierProvider.loadVocab(mContext.getAssets()); + Trace.endSection(); mUseMLModel = true; return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index e10e4d8a825c..7ac9205c7922 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -56,7 +56,6 @@ import javax.inject.Named */ class FooterActionsController @Inject constructor( view: FooterActionsView, - private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, private val userTracker: UserTracker, @@ -82,7 +81,6 @@ class FooterActionsController @Inject constructor( private val settingsButton: SettingsButton = view.findViewById(R.id.settings_button) private val settingsButtonContainer: View? = view.findViewById(R.id.settings_button_container) - private val editButton: View = view.findViewById(android.R.id.edit) private val powerMenuLite: View = view.findViewById(R.id.pm_lite) private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ -> @@ -176,13 +174,6 @@ class FooterActionsController @Inject constructor( powerMenuLite.visibility = View.GONE } settingsButton.setOnClickListener(onClickListener) - editButton.setOnClickListener(View.OnClickListener { view: View? -> - if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - return@OnClickListener - } - activityStarter.postQSRunnableDismissingKeyguard { qsPanelController.showEdit(view) } - }) - updateView() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt index dd4dc87d8a9f..7694be51cba6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsControllerBuilder.kt @@ -36,7 +36,6 @@ import javax.inject.Inject import javax.inject.Named class FooterActionsControllerBuilder @Inject constructor( - private val qsPanelController: QSPanelController, private val activityStarter: ActivityStarter, private val userManager: UserManager, private val userTracker: UserTracker, @@ -66,7 +65,7 @@ class FooterActionsControllerBuilder @Inject constructor( } fun build(): FooterActionsController { - return FooterActionsController(view, qsPanelController, activityStarter, userManager, + return FooterActionsController(view, activityStarter, userManager, userTracker, userInfoController, multiUserSwitchControllerFactory.create(view), deviceProvisionedController, falsingManager, metricsLogger, tunerService, globalActionsDialog, uiEventLogger, showPMLiteButton, buttonsVisibleState, diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt index f81f7bf73f64..e6fa2ae8dad1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt @@ -43,7 +43,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private lateinit var multiUserSwitch: MultiUserSwitch private lateinit var multiUserAvatar: ImageView private lateinit var tunerIcon: View - private lateinit var editTilesButton: View private var settingsCogAnimator: TouchAnimator? = null @@ -52,7 +51,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( override fun onFinishInflate() { super.onFinishInflate() - editTilesButton = requireViewById(android.R.id.edit) settingsButton = findViewById(R.id.settings_button) settingsContainer = findViewById(R.id.settings_button_container) multiUserSwitch = findViewById(R.id.multi_user_switch) @@ -130,7 +128,6 @@ class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout( private fun updateClickabilities() { multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE - editTilesButton.isClickable = editTilesButton.visibility == VISIBLE settingsButton.isClickable = settingsButton.visibility == VISIBLE } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 066a286b271d..4622660a6c15 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -47,6 +47,7 @@ public class QSFooterView extends FrameLayout { private PageIndicator mPageIndicator; private TextView mBuildText; private View mActionsContainer; + private View mEditButton; @Nullable protected TouchAnimator mFooterAnimator; @@ -79,6 +80,7 @@ public class QSFooterView extends FrameLayout { mPageIndicator = findViewById(R.id.footer_page_indicator); mActionsContainer = requireViewById(R.id.qs_footer_actions); mBuildText = findViewById(R.id.build); + mEditButton = findViewById(android.R.id.edit); updateResources(); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); @@ -130,6 +132,7 @@ public class QSFooterView extends FrameLayout { .addFloat(mActionsContainer, "alpha", 0, 1) .addFloat(mPageIndicator, "alpha", 0, 1) .addFloat(mBuildText, "alpha", 0, 1) + .addFloat(mEditButton, "alpha", 0, 1) .setStartDelay(0.9f); return builder.build(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index e7c06e3c7ede..5327b7e3ba26 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -26,6 +26,8 @@ import android.widget.TextView; import android.widget.Toast; import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.dagger.QSScope; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.ViewController; @@ -45,10 +47,15 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final FooterActionsController mFooterActionsController; private final TextView mBuildText; private final PageIndicator mPageIndicator; + private final View mEditButton; + private final FalsingManager mFalsingManager; + private final ActivityStarter mActivityStarter; @Inject QSFooterViewController(QSFooterView view, UserTracker userTracker, + FalsingManager falsingManager, + ActivityStarter activityStarter, QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController, @Named(QS_FOOTER) FooterActionsController footerActionsController) { @@ -57,9 +64,12 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mQsPanelController = qsPanelController; mQuickQSPanelController = quickQSPanelController; mFooterActionsController = footerActionsController; + mFalsingManager = falsingManager; + mActivityStarter = activityStarter; mBuildText = mView.findViewById(R.id.build); mPageIndicator = mView.findViewById(R.id.footer_page_indicator); + mEditButton = mView.findViewById(android.R.id.edit); } @Override @@ -91,6 +101,14 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme } return false; }); + + mEditButton.setOnClickListener(view -> { + if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { + return; + } + mActivityStarter + .postQSRunnableDismissingKeyguard(() -> mQsPanelController.showEdit(view)); + }); mQsPanelController.setFooterPageIndicator(mPageIndicator); mView.updateEverything(); } @@ -103,6 +121,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme @Override public void setVisibility(int visibility) { mView.setVisibility(visibility); + mEditButton.setClickable(visibility == View.VISIBLE); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt index 3449bd8e2686..5aeab84b677c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt @@ -23,7 +23,6 @@ import android.graphics.drawable.AnimatedImageDrawable import android.os.Handler import android.service.notification.NotificationListenerService.Ranking import android.service.notification.NotificationListenerService.RankingMap -import com.android.internal.statusbar.NotificationVisibility import com.android.internal.widget.ConversationLayout import com.android.internal.widget.MessagingImageMessage import com.android.internal.widget.MessagingLayout @@ -31,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener @@ -72,7 +72,8 @@ class ConversationNotificationProcessor @Inject constructor( */ @SysUISingleton class AnimatedImageNotificationManager @Inject constructor( - private val notificationEntryManager: NotificationEntryManager, + private val notifCollection: CommonNotifCollection, + private val bindEventManager: BindEventManager, private val headsUpManager: HeadsUpManager, private val statusBarStateController: StatusBarStateController ) { @@ -83,33 +84,23 @@ class AnimatedImageNotificationManager @Inject constructor( fun bind() { headsUpManager.addListener(object : OnHeadsUpChangedListener { override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) { - entry.row?.let { row -> - updateAnimatedImageDrawables(row, animating = isHeadsUp || isStatusBarExpanded) - } + updateAnimatedImageDrawables(entry) } }) statusBarStateController.addCallback(object : StatusBarStateController.StateListener { override fun onExpandedChanged(isExpanded: Boolean) { isStatusBarExpanded = isExpanded - notificationEntryManager.activeNotificationsForCurrentUser.forEach { entry -> - entry.row?.let { row -> - updateAnimatedImageDrawables(row, animating = isExpanded || row.isHeadsUp) - } - } + notifCollection.allNotifs.forEach(::updateAnimatedImageDrawables) } }) - notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { - override fun onEntryInflated(entry: NotificationEntry) { - entry.row?.let { row -> - updateAnimatedImageDrawables( - row, - animating = isStatusBarExpanded || row.isHeadsUp) - } - } - override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) - }) + bindEventManager.addListener(::updateAnimatedImageDrawables) } + private fun updateAnimatedImageDrawables(entry: NotificationEntry) = + entry.row?.let { row -> + updateAnimatedImageDrawables(row, animating = row.isHeadsUp || isStatusBarExpanded) + } + private fun updateAnimatedImageDrawables(row: ExpandableNotificationRow, animating: Boolean) = (row.layouts?.asSequence() ?: emptySequence()) .flatMap { layout -> layout.allViews.asSequence() } @@ -138,7 +129,7 @@ class AnimatedImageNotificationManager @Inject constructor( */ @SysUISingleton class ConversationNotificationManager @Inject constructor( - private val notificationEntryManager: NotificationEntryManager, + private val bindEventManager: BindEventManager, private val notificationGroupManager: NotificationGroupManagerLegacy, private val context: Context, private val notifCollection: CommonNotifCollection, @@ -151,35 +142,12 @@ class ConversationNotificationManager @Inject constructor( private var notifPanelCollapsed = true - private val entryManagerListener = object : NotificationEntryListener { - override fun onNotificationRankingUpdated(rankingMap: RankingMap) = - updateNotificationRanking(rankingMap) - override fun onEntryInflated(entry: NotificationEntry) = - onEntryViewBound(entry) - override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry) - override fun onEntryRemoved( - entry: NotificationEntry, - visibility: NotificationVisibility?, - removedByUser: Boolean, - reason: Int - ) = removeTrackedEntry(entry) - } - - private val notifCollectionListener = object : NotifCollectionListener { - override fun onRankingUpdate(ranking: RankingMap) = - updateNotificationRanking(ranking) - - override fun onEntryRemoved(entry: NotificationEntry, reason: Int) { - removeTrackedEntry(entry) - } - } - private fun updateNotificationRanking(rankingMap: RankingMap) { fun getLayouts(view: NotificationContentView) = sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild) val ranking = Ranking() val activeConversationEntries = states.keys.asSequence() - .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) } + .mapNotNull { notifCollection.getEntry(it) } for (entry in activeConversationEntries) { if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) { val important = ranking.channel.isImportantConversation @@ -204,7 +172,7 @@ class ConversationNotificationManager @Inject constructor( layout.setIsImportantConversation(important, false) } } - if (changed) { + if (changed && !featureFlags.isNewPipelineEnabled()) { notificationGroupManager.updateIsolation(entry) } } @@ -233,11 +201,14 @@ class ConversationNotificationManager @Inject constructor( } init { - if (featureFlags.isNewPipelineEnabled()) { - notifCollection.addCollectionListener(notifCollectionListener) - } else { - notificationEntryManager.addNotificationEntryListener(entryManagerListener) - } + notifCollection.addCollectionListener(object : NotifCollectionListener { + override fun onRankingUpdate(ranking: RankingMap) = + updateNotificationRanking(ranking) + + override fun onEntryRemoved(entry: NotificationEntry, reason: Int) = + removeTrackedEntry(entry) + }) + bindEventManager.addListener(::onEntryViewBound) } private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) = @@ -265,11 +236,10 @@ class ConversationNotificationManager @Inject constructor( val expanded = states .asSequence() .mapNotNull { (key, _) -> - notificationEntryManager.getActiveNotificationUnfiltered(key) - ?.let { entry -> - if (entry.row?.isExpanded == true) key to entry - else null - } + notifCollection.getEntry(key)?.let { entry -> + if (entry.row?.isExpanded == true) key to entry + else null + } } .toMap() states.replaceAll { key, state -> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java index 09ae7eb38a06..87e531c01a5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java @@ -260,7 +260,8 @@ public class GroupCoalescer implements Dumpable { } events.sort(mEventComparator); - mLogger.logEmitBatch(batch.mGroupKey); + long batchAge = mClock.uptimeMillis() - batch.mCreatedTimestamp; + mLogger.logEmitBatch(batch.mGroupKey, batch.mMembers.size(), batchAge); mHandler.onNotificationBatchPosted(events); } @@ -337,6 +338,6 @@ public class GroupCoalescer implements Dumpable { void onNotificationBatchPosted(List<CoalescedEvent> events); } - private static final int MIN_GROUP_LINGER_DURATION = 50; + private static final int MIN_GROUP_LINGER_DURATION = 200; private static final int MAX_GROUP_LINGER_DURATION = 500; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt index d4d5b64240c2..211e37473a70 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -32,11 +32,13 @@ class GroupCoalescerLogger @Inject constructor( }) } - fun logEmitBatch(groupKey: String) { + fun logEmitBatch(groupKey: String, batchSize: Int, batchAgeMs: Long) { buffer.log(TAG, LogLevel.DEBUG, { str1 = groupKey + int1 = batchSize + long1 = batchAgeMs }, { - "Emitting event batch for group $str1" + "Emitting batch for group $str1 size=$int1 age=${long1}ms" }) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java index 195f3672dc56..35fe0ee7cdb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java @@ -31,13 +31,13 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; -import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.ShadeListBuilder; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustment; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; @@ -99,7 +99,7 @@ public class PreparationCoordinator implements Coordinator { /** How long we can delay a group while waiting for all children to inflate */ private final long mMaxGroupInflationDelay; - private final ConversationNotificationManager mConversationManager; + private final BindEventManagerImpl mBindEventManager; @Inject public PreparationCoordinator( @@ -109,7 +109,7 @@ public class PreparationCoordinator implements Coordinator { NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service, - ConversationNotificationManager conversationManager) { + BindEventManagerImpl bindEventManager) { this( logger, notifInflater, @@ -117,7 +117,7 @@ public class PreparationCoordinator implements Coordinator { viewBarn, adjustmentProvider, service, - conversationManager, + bindEventManager, CHILD_BIND_CUTOFF, MAX_GROUP_INFLATION_DELAY); } @@ -130,7 +130,7 @@ public class PreparationCoordinator implements Coordinator { NotifViewBarn viewBarn, NotifUiAdjustmentProvider adjustmentProvider, IStatusBarService service, - ConversationNotificationManager conversationManager, + BindEventManagerImpl bindEventManager, int childBindCutoff, long maxGroupInflationDelay) { mLogger = logger; @@ -141,7 +141,7 @@ public class PreparationCoordinator implements Coordinator { mStatusBarService = service; mChildBindCutoff = childBindCutoff; mMaxGroupInflationDelay = maxGroupInflationDelay; - mConversationManager = conversationManager; + mBindEventManager = bindEventManager; } @Override @@ -369,9 +369,7 @@ public class PreparationCoordinator implements Coordinator { mInflatingNotifs.remove(entry); mViewBarn.registerViewForEntry(entry, controller); mInflationStates.put(entry, STATE_INFLATED); - // NOTE: under the new pipeline there's no way to register for an inflation callback, - // so this one method is called by the PreparationCoordinator directly. - mConversationManager.onEntryViewBound(entry); + mBindEventManager.notifyViewBound(entry); mNotifInflatingFilter.invalidateList(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt new file mode 100644 index 000000000000..51bdd00d09be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt @@ -0,0 +1,45 @@ +/* + * 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.statusbar.notification.collection.inflation + +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.util.ListenerSet + +/** + * Helper class that allows distributing bind events regardless of the pipeline. + * + * NOTE: This class isn't ideal; this exposes the concept of view inflation as something that can be + * globally registered for. This is built as it is to provide compatibility with patterns developed + * for the legacy pipeline. Ideally we'd have functionality that needs to know this information be + * handled by events that go through the ViewController itself. + */ +open class BindEventManager { + protected val listeners = ListenerSet<Listener>() + + /** Register a listener */ + fun addListener(listener: Listener) = + listeners.addIfAbsent(listener) + + /** Deregister a listener */ + fun removeListener(listener: Listener) = + listeners.remove(listener) + + /** Listener interface for view bind events */ + fun interface Listener { + fun onViewBound(entry: NotificationEntry) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt new file mode 100644 index 000000000000..9d5b859ef29c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt @@ -0,0 +1,42 @@ +/* + * 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.statusbar.notification.collection.inflation + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.NotificationEntryListener +import com.android.systemui.statusbar.notification.NotificationEntryManager +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager.Listener +import javax.inject.Inject + +/** + * Helper class that allows distributing bind events regardless of the pipeline. + */ +@SysUISingleton +class BindEventManagerImpl @Inject constructor() : BindEventManager() { + /** Emit the [Listener.onViewBound] event to all registered listeners. */ + fun notifyViewBound(entry: NotificationEntry) = + listeners.forEach { listener -> listener.onViewBound(entry) } + + /** Initialize this for the legacy pipeline. */ + fun attachToLegacyPipeline(notificationEntryManager: NotificationEntryManager) { + notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener { + override fun onEntryInflated(entry: NotificationEntry) = notifyViewBound(entry) + override fun onEntryReinflated(entry: NotificationEntry) = notifyViewBound(entry) + }) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt index 289dacbca69e..26ba12c97575 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt @@ -41,17 +41,29 @@ interface NodeController { fun getChildCount(): Int = 0 + /** Called to add a child to this view */ fun addChildAt(child: NodeController, index: Int) { throw RuntimeException("Not supported") } + /** Called to move one of this view's current children to a new position */ fun moveChildTo(child: NodeController, index: Int) { throw RuntimeException("Not supported") } + /** Called to remove one of this view's current children */ fun removeChild(child: NodeController, isTransfer: Boolean) { throw RuntimeException("Not supported") } + + /** Called when this view has been added */ + fun onViewAdded() {} + + /** Called when this view has been moved */ + fun onViewMoved() {} + + /** Called when this view has been removed */ + fun onViewRemoved() {} } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt index 4e9017e05ecd..2c9508e84aef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt @@ -94,6 +94,10 @@ internal class SectionHeaderNodeControllerImpl @Inject constructor( _view?.setOnClearAllClickListener(listener) } + override fun onViewAdded() { + headerView?.isContentVisible = true + } + override val view: View get() = _view!! }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt index 6d4ae4b1a869..28cd28594c3e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt @@ -215,13 +215,16 @@ private class ShadeNode( fun addChildAt(child: ShadeNode, index: Int) { controller.addChildAt(child.controller, index) + child.controller.onViewAdded() } fun moveChildTo(child: ShadeNode, index: Int) { controller.moveChildTo(child.controller, index) + child.controller.onViewMoved() } fun removeChild(child: ShadeNode, isTransfer: Boolean) { controller.removeChild(child.controller, isTransfer) + child.controller.onViewRemoved() } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index f1cba34158d1..05c40b204c86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -50,6 +50,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator; import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl; @@ -358,5 +360,9 @@ public interface NotificationsModule { /** */ @Binds + BindEventManager bindBindEventManagerImpl(BindEventManagerImpl bindEventManagerImpl); + + /** */ + @Binds NotifLiveDataStore bindNotifLiveDataStore(NotifLiveDataStoreImpl notifLiveDataStoreImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 38f3c39b5b1a..48f2dafedcbb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationRankingManager import com.android.systemui.statusbar.notification.collection.TargetSdkResolver +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy @@ -76,6 +77,7 @@ class NotificationsControllerImpl @Inject constructor( private val notifBindPipelineInitializer: NotifBindPipelineInitializer, private val deviceProvisionedController: DeviceProvisionedController, private val notificationRowBinder: NotificationRowBinderImpl, + private val bindEventManagerImpl: BindEventManagerImpl, private val remoteInputUriController: RemoteInputUriController, private val groupManagerLegacy: Lazy<NotificationGroupManagerLegacy>, private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, @@ -131,6 +133,7 @@ class NotificationsControllerImpl @Inject constructor( targetSdkResolver.initialize(entryManager) remoteInputUriController.attach(entryManager) groupAlertTransferHelper.bind(entryManager, groupManagerLegacy.get()) + bindEventManagerImpl.attachToLegacyPipeline(entryManager) headsUpManager.addListener(groupManagerLegacy.get()) headsUpManager.addListener(groupAlertTransferHelper) headsUpController.attach(entryManager, headsUpManager) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index b28fb58967bd..46efef66de43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -268,6 +268,18 @@ public class ExpandableNotificationRowController implements NotifViewController } @Override + public void onViewAdded() { + } + + @Override + public void onViewMoved() { + } + + @Override + public void onViewRemoved() { + } + + @Override public int getChildCount() { final List<ExpandableNotificationRow> mChildren = mView.getAttachedChildren(); return mChildren != null ? mChildren.size() : 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 624e7416d3ee..6eff7993c59c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -552,14 +552,8 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { final ViewGroup transientContainer = getTransientContainer(); if (parent == null || parent == newParent) { // If this view's current parent is null or the same as the new parent, the add will - // succeed, so just make sure the tracked transient container is in sync with the - // current parent. - if (transientContainer != null && transientContainer != parent) { - Log.w(TAG, "Expandable view " + this - + " has transient container " + transientContainer - + " but different parent" + parent); - setTransientContainer(null); - } + // succeed as long as it's a true child, so just make sure the view isn't transient. + removeFromTransientContainer(); return; } if (transientContainer == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index 9c755e970a0f..3cdaa9ad5f87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -105,6 +105,9 @@ public abstract class StackScrollerDecorView extends ExpandableView { runAfter.run(); }; setViewVisible(mContent, visible, animate, endRunnable); + } else if (runAfter != null) { + // Execute the runAfter runnable immediately if there's no animation to perform. + runAfter.run(); } if (!mContentAnimating) { @@ -228,7 +231,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration - setContentVisible(false); + setContentVisible(false, true /* animate */, onFinishedRunnable); return 0; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java index 5d6e807729fa..aff73e456b2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java @@ -349,6 +349,9 @@ public class NotificationIconAreaController implements } private void updateShelfIcons() { + if (mShelfIcons == null) { + return; + } updateIconsForLayout(entry -> entry.getIcons().getShelfIcon(), mShelfIcons, true /* showAmbient */, true /* showLowPriority */, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 8afa63719564..d2e1650056ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -153,7 +153,7 @@ public enum ScrimState { // to make sure correct color is returned before "prepare" is called @Override public int getBehindTint() { - return Color.BLACK; + return DEBUG_MODE ? DEBUG_BEHIND_TINT : Color.BLACK; } }, @@ -264,6 +264,12 @@ public enum ScrimState { } }; + private static final boolean DEBUG_MODE = false; + + private static final int DEBUG_NOTIFICATIONS_TINT = Color.RED; + private static final int DEBUG_FRONT_TINT = Color.GREEN; + private static final int DEBUG_BEHIND_TINT = Color.BLUE; + boolean mBlankScreen = false; long mAnimationDuration = ScrimController.ANIMATION_DURATION; int mFrontTint = Color.TRANSPARENT; @@ -323,15 +329,15 @@ public enum ScrimState { } public int getFrontTint() { - return mFrontTint; + return DEBUG_MODE ? DEBUG_FRONT_TINT : mFrontTint; } public int getBehindTint() { - return mBehindTint; + return DEBUG_MODE ? DEBUG_BEHIND_TINT : mBehindTint; } public int getNotifTint() { - return mNotifTint; + return DEBUG_MODE ? DEBUG_NOTIFICATIONS_TINT : mNotifTint; } public long getAnimationDuration() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 07ae33c42c21..9a9e3bc58774 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -619,6 +619,8 @@ public class StatusBar extends CoreStartable implements protected boolean mDozing; private boolean mIsFullscreen; + boolean mCloseQsBeforeScreenOff; + private final NotificationMediaManager mMediaManager; private final NotificationLockscreenUserManager mLockscreenUserManager; private final NotificationRemoteInputManager mRemoteInputManager; @@ -1124,6 +1126,15 @@ public class StatusBar extends CoreStartable implements } if (leaveOpen) { mStatusBarStateController.setLeaveOpenOnKeyguardHide(true); + if (mIsKeyguard) { + // When device state changes on keyguard we don't want to keep the state of + // the shade and instead we open clean state of keyguard with shade closed. + // Normally some parts of QS state (like expanded/collapsed) are persisted and + // that causes incorrect UI rendering, especially when changing state with QS + // expanded. To prevent that we can close QS which resets QS and some parts of + // the shade to its default state. Read more in b/201537421 + mCloseQsBeforeScreenOff = true; + } } } @@ -2923,10 +2934,10 @@ public class StatusBar extends CoreStartable implements } boolean updateIsKeyguard() { - return updateIsKeyguard(false /* force */); + return updateIsKeyguard(false /* forceStateChange */); } - boolean updateIsKeyguard(boolean force) { + boolean updateIsKeyguard(boolean forceStateChange) { boolean wakeAndUnlocking = mBiometricUnlockController.getMode() == BiometricUnlockController.MODE_WAKE_AND_UNLOCK; @@ -2959,7 +2970,7 @@ public class StatusBar extends CoreStartable implements // as the animation could prepare 'fake AOD' interface (without actually // transitioning to keyguard state) and this might reset the view states if (!mScreenOffAnimationController.isKeyguardHideDelayed()) { - return hideKeyguardImpl(force); + return hideKeyguardImpl(forceStateChange); } } return false; @@ -3087,12 +3098,12 @@ public class StatusBar extends CoreStartable implements /** * @return true if we would like to stay in the shade, false if it should go away entirely */ - public boolean hideKeyguardImpl(boolean force) { + public boolean hideKeyguardImpl(boolean forceStateChange) { mIsKeyguard = false; Trace.beginSection("StatusBar#hideKeyguard"); boolean staying = mStatusBarStateController.leaveOpenOnKeyguardHide(); int previousState = mStatusBarStateController.getState(); - if (!(mStatusBarStateController.setState(StatusBarState.SHADE, force))) { + if (!(mStatusBarStateController.setState(StatusBarState.SHADE, forceStateChange))) { //TODO: StatusBarStateController should probably know about hiding the keyguard and // notify listeners. @@ -3654,6 +3665,10 @@ public class StatusBar extends CoreStartable implements public void onScreenTurnedOff() { mFalsingCollector.onScreenOff(); mScrimController.onScreenTurnedOff(); + if (mCloseQsBeforeScreenOff) { + mNotificationPanelViewController.closeQs(); + mCloseQsBeforeScreenOff = false; + } updateIsKeyguard(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 6746b3e8883a..dab37e0da57f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -219,7 +219,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( // even if we're going from SHADE to SHADE or KEYGUARD to KEYGUARD, since we might have // changed parts of the UI (such as showing AOD in the shade) without actually changing // the StatusBarState. This ensures that the UI definitely reflects the desired state. - statusBar.updateIsKeyguard(true /* force */) + statusBar.updateIsKeyguard(true /* forceStateChange */) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 7266e41ad7ca..08d881ff96aa 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -88,7 +88,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -108,6 +107,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; @@ -174,10 +174,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { private InteractionJankMonitor mInteractionJankMonitor; @Mock private LatencyTracker mLatencyTracker; - @Mock - private FeatureFlags mFeatureFlags; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateListenerCaptor; + @Mock + private KeyguardUpdateMonitorCallback mTestCallback; // Direct executor private Executor mBackgroundExecutor = Runnable::run; private Executor mMainExecutor = Runnable::run; @@ -255,11 +255,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture()); mStatusBarStateListener = mStatusBarStateListenerCaptor.getValue(); + mKeyguardUpdateMonitor.registerCallback(mTestCallback); } @After public void tearDown() { mMockitoSession.finishMocking(); + mKeyguardUpdateMonitor.removeCallback(mTestCallback); mKeyguardUpdateMonitor.destroy(); } @@ -599,7 +601,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mTestableLooper.processAllMessages(); when(mKeyguardBypassController.canBypass()).thenReturn(true); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */); + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, + new ArrayList<>()); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); } @@ -609,7 +612,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mKeyguardUpdateMonitor.dispatchStartedWakingUp(); mTestableLooper.processAllMessages(); mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */); + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>()); mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true); verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean()); @@ -754,7 +757,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Test public void testGetUserCanSkipBouncer_whenTrust() { int user = KeyguardUpdateMonitor.getCurrentUser(); - mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */); + mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */, + new ArrayList<>()); assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue(); } @@ -985,7 +989,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { // WHEN trust is enabled (ie: via smartlock) mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, - KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */); + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, new ArrayList<>()); // THEN we shouldn't listen for udfps assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false); @@ -1069,6 +1073,17 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { anyBoolean()); } + @Test + public void testShowTrustGrantedMessage_onTrustGranted() { + // WHEN trust is enabled (ie: via some trust agent) with a trustGranted string + mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, + KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */, + Arrays.asList("Unlocked by wearable")); + + // THEN the showTrustGrantedMessage should be called with the first message + verify(mTestCallback).showTrustGrantedMessage("Unlocked by wearable"); + } + private void setKeyguardBouncerVisibility(boolean isVisible) { mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(isVisible); mTestableLooper.processAllMessages(); @@ -1108,7 +1123,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { mRingerModeTracker, mBackgroundExecutor, mMainExecutor, mStatusBarStateController, mLockPatternUtils, mAuthController, mTelephonyListenerManager, - mInteractionJankMonitor, mLatencyTracker, mFeatureFlags); + mInteractionJankMonitor, mLatencyTracker); setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index dcb7307250fe..1dd5e227a909 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -34,7 +34,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; @@ -44,6 +43,7 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.ValueAnimator; import android.app.Instrumentation; import android.content.Context; import android.content.pm.ActivityInfo; @@ -55,6 +55,7 @@ import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableResources; import android.text.TextUtils; import android.view.Display; @@ -74,6 +75,7 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.model.SysUiState; import com.android.systemui.util.leak.ReferenceTestUtils; +import com.android.systemui.utils.os.FakeHandler; import org.junit.After; import org.junit.Before; @@ -88,13 +90,12 @@ import org.mockito.MockitoAnnotations; import java.util.List; @LargeTest +@TestableLooper.RunWithLooper @RunWith(AndroidTestingRunner.class) public class WindowMagnificationControllerTest extends SysuiTestCase { private static final int LAYOUT_CHANGE_TIMEOUT_MS = 5000; @Mock - private Handler mHandler; - @Mock private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; @Mock private MirrorWindowControl mMirrorWindowControl; @@ -102,17 +103,21 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { private WindowMagnifierCallback mWindowMagnifierCallback; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + private Handler mHandler; private TestableWindowManager mWindowManager; private SysUiState mSysUiState = new SysUiState(); private Resources mResources; private WindowMagnificationAnimationController mWindowMagnificationAnimationController; private WindowMagnificationController mWindowMagnificationController; private Instrumentation mInstrumentation; + private final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 1.0f).setDuration(0); @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = Mockito.spy(getContext()); + mHandler = new FakeHandler(TestableLooper.get(this).getLooper()); mInstrumentation = InstrumentationRegistry.getInstrumentation(); final WindowManager wm = mContext.getSystemService(WindowManager.class); mWindowManager = spy(new TestableWindowManager(wm)); @@ -124,17 +129,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { return null; }).when(mSfVsyncFrameProvider).postFrameCallback( any(FrameCallback.class)); - doAnswer(invocation -> { - final Runnable runnable = invocation.getArgument(0); - runnable.run(); - return null; - }).when(mHandler).post( - any(Runnable.class)); mSysUiState.addCallback(Mockito.mock(SysUiState.SysUiStateCallback.class)); mResources = getContext().getOrCreateTestableResources().getResources(); mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( - mContext); + mContext, mValueAnimator); mWindowMagnificationController = new WindowMagnificationController(mContext, mHandler, mWindowMagnificationAnimationController, mSfVsyncFrameProvider, mMirrorWindowControl, mTransaction, mWindowMagnifierCallback, mSysUiState); @@ -147,6 +146,7 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { public void tearDown() { mInstrumentation.runOnMainSync( () -> mWindowMagnificationController.deleteWindowMagnification()); + mValueAnimator.cancel(); } @Test @@ -287,12 +287,6 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void setScale_enabled_expectedValueAndUpdateStateDescription() { - doAnswer(invocation -> { - final Runnable runnable = invocation.getArgument(0); - runnable.run(); - return null; - }).when(mHandler).postDelayed(any(Runnable.class), anyLong()); - mInstrumentation.runOnMainSync( () -> mWindowMagnificationController.enableWindowMagnificationInternal(2.0f, Float.NaN, Float.NaN)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java new file mode 100644 index 000000000000..ada7ddbdb287 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/ComplicationProviderTest.java @@ -0,0 +1,66 @@ +/* + * 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.dreams; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.settingslib.dream.DreamBackend; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class ComplicationProviderTest { + private TestComplicationProvider mComplicationProvider; + + @Before + public void setup() { + mComplicationProvider = new TestComplicationProvider(); + } + + @Test + public void testConvertComplicationType() { + assertEquals(ComplicationProvider.COMPLICATION_TYPE_TIME, + mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_TIME)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_DATE, + mComplicationProvider.convertComplicationType(DreamBackend.COMPLICATION_TYPE_DATE)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_WEATHER, + mComplicationProvider.convertComplicationType( + DreamBackend.COMPLICATION_TYPE_WEATHER)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_AIR_QUALITY, + mComplicationProvider.convertComplicationType( + DreamBackend.COMPLICATION_TYPE_AIR_QUALITY)); + assertEquals(ComplicationProvider.COMPLICATION_TYPE_CAST_INFO, + mComplicationProvider.convertComplicationType( + DreamBackend.COMPLICATION_TYPE_CAST_INFO)); + } + + private static class TestComplicationProvider implements ComplicationProvider { + @Override + public void onCreateComplication(Context context, + ComplicationHost.CreationCallback creationCallback, + ComplicationHost.InteractionCallback interactionCallback) { + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt index 26f04fc4e7b5..354bb5192251 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt @@ -54,8 +54,6 @@ class FooterActionsControllerTest : LeakCheckedTest() { @Mock private lateinit var userInfoController: UserInfoController @Mock - private lateinit var qsPanelController: QSPanelController - @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite @@ -81,7 +79,7 @@ class FooterActionsControllerTest : LeakCheckedTest() { view = LayoutInflater.from(context) .inflate(R.layout.footer_actions, null) as FooterActionsView - controller = FooterActionsController(view, qsPanelController, activityStarter, + controller = FooterActionsController(view, activityStarter, userManager, userTracker, userInfoController, multiUserSwitchController, deviceProvisionedController, falsingManager, metricsLogger, fakeTunerService, globalActionsDialog, uiEventLogger, showPMLiteButton = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index 8b19c50f915e..f43e68f3e575 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -18,7 +18,11 @@ package com.android.systemui.qs; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.ClipData; @@ -31,6 +35,8 @@ import android.widget.TextView; import androidx.test.filters.SmallTest; import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -60,13 +66,20 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { private TextView mBuildText; @Mock private FooterActionsController mFooterActionsController; + @Mock + private FalsingManager mFalsingManager; + @Mock + private ActivityStarter mActivityStarter; private QSFooterViewController mController; + private View mEditButton; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + mEditButton = new View(mContext); + injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mContext.addMockSystemService(ClipboardManager.class, mClipboardManager); @@ -77,9 +90,11 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { when(mView.isAttachedToWindow()).thenReturn(true); when(mView.findViewById(R.id.build)).thenReturn(mBuildText); + when(mView.findViewById(android.R.id.edit)).thenReturn(mEditButton); - mController = new QSFooterViewController(mView, mUserTracker, mQSPanelController, - mQuickQSPanelController, mFooterActionsController); + mController = new QSFooterViewController(mView, mUserTracker, mFalsingManager, + mActivityStarter, mQSPanelController, mQuickQSPanelController, + mFooterActionsController); mController.init(); } @@ -99,4 +114,27 @@ public class QSFooterViewControllerTest extends LeakCheckedTest { verify(mClipboardManager).setPrimaryClip(captor.capture()); assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text); } + + @Test + public void testEditButton_falseTap() { + when(mFalsingManager.isFalseTap(anyInt())).thenReturn(true); + + mEditButton.performClick(); + + verify(mQSPanelController, never()).showEdit(any()); + verifyZeroInteractions(mActivityStarter); + } + + @Test + public void testEditButton_realTap() { + when(mFalsingManager.isFalseTap(anyInt())).thenReturn(false); + + mEditButton.performClick(); + + ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); + + verify(mActivityStarter).postQSRunnableDismissingKeyguard(captor.capture()); + captor.getValue().run(); + verify(mQSPanelController).showEdit(mEditButton); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index bde6734bbf92..3b034f7af9a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static java.util.Objects.requireNonNull; @@ -40,7 +41,6 @@ import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.RankingBuilder; -import com.android.systemui.statusbar.notification.ConversationNotificationManager; import com.android.systemui.statusbar.notification.SectionClassifier; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.notification.collection.ListEntry; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl; import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater; import com.android.systemui.statusbar.notification.collection.inflation.NotifUiAdjustmentProvider; import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection; @@ -93,7 +94,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { @Mock private NotifSection mNotifSection; @Mock private NotifPipeline mNotifPipeline; @Mock private IStatusBarService mService; - @Mock private ConversationNotificationManager mConvoManager; + @Mock private BindEventManagerImpl mBindEventManagerImpl; @Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater(); private final SectionClassifier mSectionClassifier = new SectionClassifier(); private final NotifUiAdjustmentProvider mAdjustmentProvider = @@ -121,7 +122,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { mock(NotifViewBarn.class), mAdjustmentProvider, mService, - mConvoManager, + mBindEventManagerImpl, TEST_CHILD_BIND_CUTOFF, TEST_MAX_GROUP_DELAY); @@ -411,7 +412,8 @@ public class PreparationCoordinatorTest extends SysuiTestCase { public void testCallConversationManagerBindWhenInflated() { mBeforeFilterListener.onBeforeFinalizeFilter(List.of(mEntry)); mNotifInflater.getInflateCallback(mEntry).onInflationFinished(mEntry, null); - verify(mConvoManager, times(1)).onEntryViewBound(eq(mEntry)); + verify(mBindEventManagerImpl, times(1)).notifyViewBound(eq(mEntry)); + verifyNoMoreInteractions(mBindEventManagerImpl); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java index bbe92f67ca2e..15ff5551703b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java @@ -274,6 +274,18 @@ public class ShadeViewDifferTest extends SysuiTestCase { public void removeChild(@NonNull NodeController child, boolean isTransfer) { view.removeView(child.getView()); } + + @Override + public void onViewAdded() { + } + + @Override + public void onViewMoved() { + } + + @Override + public void onViewRemoved() { + } } private static class SpecBuilder { diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java index 93fc0e7262aa..2b7b97737e0f 100644 --- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java +++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java @@ -254,9 +254,14 @@ class AssociationRequestsProcessor { private AssociationInfo createAssociationAndNotifyApplication( @NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId, @Nullable MacAddress macAddress, @NonNull IAssociationRequestCallback callback) { - final AssociationInfo association = mService.createAssociation(userId, packageName, - macAddress, request.getDisplayName(), request.getDeviceProfile(), - request.isSelfManaged()); + final AssociationInfo association; + final long callingIdentity = Binder.clearCallingIdentity(); + try { + association = mService.createAssociation(userId, packageName, macAddress, + request.getDisplayName(), request.getDeviceProfile(), request.isSelfManaged()); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } try { callback.onAssociationCreated(association); diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b2b55765f178..cfd37988d234 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -787,6 +787,12 @@ public class CompanionDeviceManagerService extends SystemService Slog.i(LOG_TAG, "New CDM association created=" + association); mAssociationStore.addAssociation(association); + // If the "Device Profile" is specified, make the companion application a holder of the + // corresponding role. + if (deviceProfile != null) { + addRoleHolderForAssociation(getContext(), association); + } + updateSpecialAccessPermissionForAssociatedPackage(association); return association; @@ -930,14 +936,8 @@ public class CompanionDeviceManagerService extends SystemService exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - if (!association.isSelfManaged()) { - if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) { - addRoleHolderForAssociation(getContext(), association); - } - - if (association.isNotifyOnDeviceNearby()) { - restartBleScan(); - } + if (association.isNotifyOnDeviceNearby()) { + restartBleScan(); } } @@ -983,19 +983,7 @@ public class CompanionDeviceManagerService extends SystemService void onDeviceConnected(String address) { Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); - mCurrentlyConnectedDevices.add(address); - - for (AssociationInfo association : mAssociationStore.getAssociationsByAddress(address)) { - if (association.getDeviceProfile() != null) { - Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile() - + " to " + association.getPackageName() - + " due to device connected: " + association.getDeviceMacAddress()); - - addRoleHolderForAssociation(getContext(), association); - } - } - onDeviceNearby(address); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 9b2bd82fcfed..f2e660779e9e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -73,20 +73,12 @@ class CompanionDeviceShellCommand extends android.os.ShellCommand { } break; - case "simulate_connect": { - mService.onDeviceConnected(getNextArgRequired()); - } - break; - - case "simulate_disconnect": { - mService.onDeviceDisconnected(getNextArgRequired()); - } - break; case "clear-association-memory-cache": { mService.persistState(); mService.loadAssociationsFromDisk(); } break; + default: return handleDefaultCommands(cmd); } diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java new file mode 100644 index 000000000000..0eb6b8d24768 --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java @@ -0,0 +1,436 @@ +/* + * 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.server.companion.presence; + +import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE; +import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; +import static android.bluetooth.BluetoothAdapter.STATE_BLE_ON; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static android.bluetooth.BluetoothAdapter.nameForState; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES; +import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY; +import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; +import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH; +import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST; +import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; + +import static com.android.server.companion.presence.Utils.btDeviceToString; + +import static java.util.Objects.requireNonNull; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.companion.AssociationInfo; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.util.Slog; + +import com.android.server.companion.AssociationStore; +import com.android.server.companion.AssociationStore.ChangeType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@SuppressLint("LongLogTag") +class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { + private static final boolean DEBUG = false; + private static final String TAG = "CompanionDevice_PresenceMonitor_BLE"; + + /** + * When using {@link ScanSettings#SCAN_MODE_LOW_POWER}, it usually takes from 20 seconds to + * 2 minutes for the BLE scanner to find advertisements sent from the same device. + * On the other hand, {@link android.bluetooth.BluetoothAdapter.LeScanCallback} will report + * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST} 10 sec after it finds the + * advertisement for the first time (add reports + * {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH FIRST_MATCH}). + * To avoid constantly reporting {@link Callback#onBleCompanionDeviceFound(int) onDeviceFound()} + * and {@link Callback#onBleCompanionDeviceLost(int) onDeviceLost()} (while the device is + * actually present) to its clients, {@link BleCompanionDeviceScanner}, will add 1-minute delay + * after it receives {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST MATCH_LOST}. + */ + private static final int NOTIFY_DEVICE_LOST_DELAY = 2 * 60 * 1000; // 2 Min. + + interface Callback { + void onBleCompanionDeviceFound(int associationId); + + void onBleCompanionDeviceLost(int associationId); + } + + private final @NonNull AssociationStore mAssociationStore; + private final @NonNull Callback mCallback; + private final @NonNull MainThreadHandler mMainThreadHandler; + + // Non-null after init(). + private @Nullable BluetoothAdapter mBtAdapter; + // Non-null after init() and when BLE is available. Otherwise - null. + private @Nullable BluetoothLeScanner mBleScanner; + // Only accessed from the Main thread. + private boolean mScanning = false; + + BleCompanionDeviceScanner( + @NonNull AssociationStore associationStore, @NonNull Callback callback) { + mAssociationStore = associationStore; + mCallback = callback; + mMainThreadHandler = new MainThreadHandler(); + } + + @MainThread + void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) { + if (DEBUG) Log.i(TAG, "init()"); + + if (mBtAdapter != null) { + throw new IllegalStateException(getClass().getSimpleName() + " is already initialized"); + } + mBtAdapter = requireNonNull(btAdapter); + + checkBleState(); + registerBluetoothStateBroadcastReceiver(context); + + mAssociationStore.registerListener(this); + } + + @MainThread + final void restartScan() { + enforceInitialized(); + + if (DEBUG) Log.i(TAG , "restartScan()"); + if (mBleScanner == null) { + if (DEBUG) Log.d(TAG, " > BLE is not available"); + return; + } + + stopScanIfNeeded(); + startScan(); + } + + @Override + public void onAssociationChanged(@ChangeType int changeType, AssociationInfo association) { + // Simply restart scanning. + if (Looper.getMainLooper().isCurrentThread()) { + restartScan(); + } else { + mMainThreadHandler.post(this::restartScan); + } + } + + @MainThread + private void checkBleState() { + enforceInitialized(); + + final boolean bleAvailable = isBleAvailable(); + if (DEBUG) { + Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable); + } + if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) { + // Nothing changed. + if (DEBUG) Log.i(TAG, " > BLE status did not change"); + return; + } + + if (bleAvailable) { + mBleScanner = mBtAdapter.getBluetoothLeScanner(); + if (mBleScanner == null) { + // Oops, that's a race condition. Can return. + return; + } + if (DEBUG) Log.i(TAG, " > BLE is now available"); + + startScan(); + } else { + if (DEBUG) Log.i(TAG, " > BLE is now unavailable"); + + stopScanIfNeeded(); + mBleScanner = null; + } + } + + /** + * A duplicate of {@code BluetoothAdapter.getLeAccess()} method which has the package-private + * access level, so it's not accessible from here. + */ + private boolean isBleAvailable() { + final int state = mBtAdapter.getLeState(); + if (DEBUG) Log.d(TAG, "getLeAccess() state=" + nameForBtState(state)); + return state == STATE_ON || state == STATE_BLE_ON; + } + + @MainThread + private void startScan() { + enforceInitialized(); + // This method should not be called if scan is already in progress. + if (mScanning) throw new IllegalStateException("Scan is already in progress."); + // Neither should this method be called if the adapter is not available. + if (mBleScanner == null) throw new IllegalStateException("BLE is not available."); + + if (DEBUG) Log.i(TAG, "startScan()"); + + // Collect MAC addresses from all associations. + final Set<String> macAddresses = new HashSet<>(); + for (AssociationInfo association : mAssociationStore.getAssociations()) { + // Beware that BT stack does not consider low-case MAC addresses valid, while + // MacAddress.toString() return a low-case String. + final String macAddress = association.getDeviceMacAddressAsString(); + if (macAddress != null) { + macAddresses.add(macAddress); + } + } + if (macAddresses.isEmpty()) { + if (DEBUG) Log.i(TAG, " > there are no (associated) devices to Scan for."); + return; + } else { + if (DEBUG) { + Log.d(TAG, " > addresses=(n=" + macAddresses.size() + ")" + + "[" + String.join(", ", macAddresses) + "]"); + } + } + + final List<ScanFilter> filters = new ArrayList<>(macAddresses.size()); + for (String macAddress : macAddresses) { + final ScanFilter filter = new ScanFilter.Builder() + .setDeviceAddress(macAddress) + .build(); + filters.add(filter); + } + + mBleScanner.startScan(filters, SCAN_SETTINGS, mScanCallback); + mScanning = true; + } + + private void stopScanIfNeeded() { + enforceInitialized(); + + if (DEBUG) Log.i(TAG, "stopScan()"); + if (!mScanning) { + Log.d(TAG, " > not scanning."); + return; + } + + mBleScanner.stopScan(mScanCallback); + mScanning = false; + } + + @MainThread + private void notifyDeviceFound(@NonNull BluetoothDevice device) { + if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device)); + + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); + + for (AssociationInfo association : associations) { + mCallback.onBleCompanionDeviceFound(association.getId()); + } + } + + @MainThread + private void notifyDeviceLost(@NonNull BluetoothDevice device) { + if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device)); + + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); + + for (AssociationInfo association : associations) { + mCallback.onBleCompanionDeviceLost(association.getId()); + } + } + + private void registerBluetoothStateBroadcastReceiver(Context context) { + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1); + final int state = intent.getIntExtra(EXTRA_STATE, -1); + + if (DEBUG) { + // The action is either STATE_CHANGED or BLE_STATE_CHANGED. + final String action = + intent.getAction().replace("android.bluetooth.adapter.", "bt."); + Log.d(TAG, "on(Broadcast)Receive() " + action + ": " + + nameForBtState(prevState) + "->" + nameForBtState(state)); + } + + checkBleState(); + } + }; + + final IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_STATE_CHANGED); + filter.addAction(ACTION_BLE_STATE_CHANGED); + + context.registerReceiver(receiver, filter); + } + + private void enforceInitialized() { + if (mBtAdapter != null) return; + throw new IllegalStateException(getClass().getSimpleName() + " is not initialized"); + } + + private final ScanCallback mScanCallback = new ScanCallback() { + @MainThread + @Override + public void onScanResult(int callbackType, ScanResult result) { + final BluetoothDevice device = result.getDevice(); + + if (DEBUG) { + Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType) + + " device=" + btDeviceToString(device)); + Log.v(TAG, " > scanResult=" + result); + + final List<AssociationInfo> associations = + mAssociationStore.getAssociationsByAddress(device.getAddress()); + Log.v(TAG, " > associations=" + Arrays.toString(associations.toArray())); + } + + switch (callbackType) { + case CALLBACK_TYPE_FIRST_MATCH: + if (mMainThreadHandler.hasNotifyDeviceLostMessages(device)) { + mMainThreadHandler.removeNotifyDeviceLostMessages(device); + return; + } + + notifyDeviceFound(device); + break; + + case CALLBACK_TYPE_MATCH_LOST: + mMainThreadHandler.sendNotifyDeviceLostDelayedMessage(device); + break; + + default: + Slog.wtf(TAG, "Unexpected callback " + + nameForBleScanCallbackType(callbackType)); + break; + } + } + + @MainThread + @Override + public void onScanFailed(int errorCode) { + if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode)); + mScanning = false; + } + }; + + @SuppressLint("HandlerLeak") + private class MainThreadHandler extends Handler { + private static final int NOTIFY_DEVICE_LOST = 1; + + MainThreadHandler() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(@NonNull Message message) { + if (message.what != NOTIFY_DEVICE_LOST) return; + + final BluetoothDevice device = (BluetoothDevice) message.obj; + notifyDeviceLost(device); + } + + void sendNotifyDeviceLostDelayedMessage(BluetoothDevice device) { + final Message message = obtainMessage(NOTIFY_DEVICE_LOST, device); + sendMessageDelayed(message, NOTIFY_DEVICE_LOST_DELAY); + } + + boolean hasNotifyDeviceLostMessages(BluetoothDevice device) { + return hasEqualMessages(NOTIFY_DEVICE_LOST, device); + } + + void removeNotifyDeviceLostMessages(BluetoothDevice device) { + removeEqualMessages(NOTIFY_DEVICE_LOST, device); + } + } + + private static String nameForBtState(int state) { + return nameForState(state) + "(" + state + ")"; + } + + private static String nameForBleScanCallbackType(int callbackType) { + final String name; + switch (callbackType) { + case CALLBACK_TYPE_ALL_MATCHES: + name = "ALL_MATCHES"; + break; + case CALLBACK_TYPE_FIRST_MATCH: + name = "FIRST_MATCH"; + break; + case CALLBACK_TYPE_MATCH_LOST: + name = "MATCH_LOST"; + break; + default: + name = "Unknown"; + } + return name + "(" + callbackType + ")"; + } + + private static String nameForBleScanErrorCode(int errorCode) { + final String name; + switch (errorCode) { + case SCAN_FAILED_ALREADY_STARTED: + name = "ALREADY_STARTED"; + break; + case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED: + name = "APPLICATION_REGISTRATION_FAILED"; + break; + case SCAN_FAILED_INTERNAL_ERROR: + name = "INTERNAL_ERROR"; + break; + case SCAN_FAILED_FEATURE_UNSUPPORTED: + name = "FEATURE_UNSUPPORTED"; + break; + case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES: + name = "OUT_OF_HARDWARE_RESOURCES"; + break; + case SCAN_FAILED_SCANNING_TOO_FREQUENTLY: + name = "SCANNING_TOO_FREQUENTLY"; + break; + default: + name = "Unknown"; + } + return name + "(" + errorCode + ")"; + } + + private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder() + .setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST) + .setScanMode(SCAN_MODE_LOW_POWER) + .build(); +} diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java index a4fa1c12b29b..dbe866b374f1 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java @@ -16,6 +16,8 @@ package com.android.server.companion.presence; +import static com.android.server.companion.presence.Utils.btDeviceToString; + import android.annotation.NonNull; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; @@ -71,11 +73,11 @@ class BluetoothCompanionDeviceConnectionListener */ @Override public void onDeviceConnected(@NonNull BluetoothDevice device) { - if (DEBUG) Log.i(TAG, "onDevice_Connected() " + toString(device)); + if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device)); final MacAddress macAddress = MacAddress.fromString(device.getAddress()); if (mAllConnectedDevices.put(macAddress, device) != null) { - if (DEBUG) Log.w(TAG, "Device " + toString(device) + " is already connected."); + if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected."); return; } @@ -91,13 +93,15 @@ class BluetoothCompanionDeviceConnectionListener public void onDeviceDisconnected(@NonNull BluetoothDevice device, @DisconnectReason int reason) { if (DEBUG) { - Log.i(TAG, "onDevice_Disconnected() " + toString(device)); + Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device)); Log.d(TAG, " reason=" + disconnectReasonText(reason)); } final MacAddress macAddress = MacAddress.fromString(device.getAddress()); if (mAllConnectedDevices.remove(macAddress) == null) { - if (DEBUG) Log.w(TAG, "The device wasn't tracked as connected " + toString(device)); + if (DEBUG) { + Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device)); + } return; } @@ -109,7 +113,7 @@ class BluetoothCompanionDeviceConnectionListener mAssociationStore.getAssociationsByAddress(device.getAddress()); if (DEBUG) { - Log.d(TAG, "onDevice_ConnectivityChanged() " + toString(device) + Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device) + " connected=" + connected); if (associations.isEmpty()) { Log.d(TAG, " > No CDM associations"); @@ -138,6 +142,12 @@ class BluetoothCompanionDeviceConnectionListener } @Override + public void onAssociationRemoved(AssociationInfo association) { + // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping + // required. + } + + @Override public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) { if (DEBUG) { Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged @@ -153,23 +163,4 @@ class BluetoothCompanionDeviceConnectionListener // This will be implemented when CDM support updating addresses. throw new IllegalArgumentException("Address changes are not supported."); } - - private static String toString(@NonNull BluetoothDevice btDevice) { - final StringBuilder sb = new StringBuilder(btDevice.getAddress()); - - sb.append(" [name="); - final String name = btDevice.getName(); - if (name != null) { - sb.append('\'').append(name).append('\''); - } else { - sb.append("null"); - } - - final String alias = btDevice.getAlias(); - if (alias != null) { - sb.append(", alias='").append(alias).append("'"); - } - - return sb.append(']').toString(); - } } diff --git a/services/companion/java/com/android/server/companion/presence/Utils.java b/services/companion/java/com/android/server/companion/presence/Utils.java new file mode 100644 index 000000000000..583b443c8cb7 --- /dev/null +++ b/services/companion/java/com/android/server/companion/presence/Utils.java @@ -0,0 +1,49 @@ +/* + * 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.server.companion.presence; + +import android.annotation.NonNull; +import android.bluetooth.BluetoothDevice; + +/** Utilities for working with Bluetooth and BLE devices. */ +class Utils { + + /** + * @return short String representation of {@link BluetoothDevice}. + */ + static String btDeviceToString(@NonNull BluetoothDevice btDevice) { + final StringBuilder sb = new StringBuilder(btDevice.getAddress()); + + sb.append(" [name="); + final String name = btDevice.getName(); + if (name != null) { + sb.append('\'').append(name).append('\''); + } else { + sb.append("null"); + } + + final String alias = btDevice.getAlias(); + if (alias != null) { + sb.append(", alias='").append(alias).append("'"); + } + + return sb.append(']').toString(); + } + + private Utils() { + } +} diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java index 0fd29675d469..75acf81a4a3c 100644 --- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java +++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java @@ -23,14 +23,18 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import android.annotation.NonNull; import android.annotation.Nullable; import android.app.compat.CompatChanges; +import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledSince; import android.content.ComponentName; import android.content.pm.ActivityInfo; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; +import android.view.Display; import android.window.DisplayWindowPolicyController; import java.util.List; @@ -60,15 +64,29 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>(); + @Nullable private final ActivityListener mActivityListener; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + /** + * Creates a window policy controller that is generic to the different use cases of virtual + * device. + * + * @param windowFlags The window flags that this controller is interested in. + * @param systemWindowFlags The system window flags that this controller is interested in. + * @param allowedUsers The set of users that are allowed to stream in this display. + * @param activityListener Activity listener to listen for activity changes. The display ID + * is not populated in this callback and is always {@link Display#INVALID_DISPLAY}. + */ GenericWindowPolicyController(int windowFlags, int systemWindowFlags, @NonNull ArraySet<UserHandle> allowedUsers, @Nullable Set<ComponentName> allowedActivities, - @Nullable Set<ComponentName> blockedActivities) { + @Nullable Set<ComponentName> blockedActivities, + @NonNull ActivityListener activityListener) { mAllowedUsers = allowedUsers; mAllowedActivities = allowedActivities == null ? null : new ArraySet<>(allowedActivities); mBlockedActivities = blockedActivities == null ? null : new ArraySet<>(blockedActivities); setInterestedWindowFlags(windowFlags, systemWindowFlags); + mActivityListener = activityListener; } @Override @@ -92,13 +110,21 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @Override public void onTopActivityChanged(ComponentName topActivity, int uid) { - + if (mActivityListener != null) { + // Post callback on the main thread so it doesn't block activity launching + mHandler.post(() -> + mActivityListener.onTopActivityChanged(Display.INVALID_DISPLAY, topActivity)); + } } @Override public void onRunningAppsChanged(ArraySet<Integer> runningUids) { mRunningUids.clear(); mRunningUids.addAll(runningUids); + if (mActivityListener != null && mRunningUids.isEmpty()) { + // Post callback on the main thread so it doesn't block activity launching + mHandler.post(() -> mActivityListener.onDisplayEmpty(Display.INVALID_DISPLAY)); + } } /** diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 067edcc0b08d..ae39d7ef0b83 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -16,8 +16,11 @@ package com.android.server.companion.virtual; +import android.annotation.IntDef; import android.annotation.NonNull; import android.graphics.Point; +import android.graphics.PointF; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -26,25 +29,40 @@ import android.hardware.input.VirtualTouchEvent; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; +import android.util.Slog; +import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; /** Controls virtual input devices, including device lifecycle and event dispatch. */ class InputController { + private static final String TAG = "VirtualInputController"; + private final Object mLock; /* Token -> file descriptor associations. */ @VisibleForTesting @GuardedBy("mLock") - final Map<IBinder, Integer> mInputDeviceFds = new ArrayMap<>(); + final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>(); private final NativeWrapper mNativeWrapper; + /** + * Because the pointer is a singleton, it can only be targeted at one display at a time. Because + * multiple mice could be concurrently registered, mice that are associated with a different + * display than the current target display should not be allowed to affect the current target. + */ + @VisibleForTesting int mActivePointerDisplayId; + InputController(@NonNull Object lock) { this(lock, new NativeWrapper()); } @@ -53,32 +71,39 @@ class InputController { InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) { mLock = lock; mNativeWrapper = nativeWrapper; + mActivePointerDisplayId = Display.INVALID_DISPLAY; } void close() { synchronized (mLock) { - for (int fd : mInputDeviceFds.values()) { - mNativeWrapper.closeUinput(fd); + for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) { + mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); } - mInputDeviceFds.clear(); + mInputDeviceDescriptors.clear(); + resetMouseValuesLocked(); } } void createKeyboard(@NonNull String deviceName, int vendorId, int productId, - @NonNull IBinder deviceToken) { + @NonNull IBinder deviceToken, + int displayId) { final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating keyboard: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_KEYBOARD, displayId)); } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual keyboard", e); } } @@ -86,18 +111,27 @@ class InputController { void createMouse(@NonNull String deviceName, int vendorId, int productId, - @NonNull IBinder deviceToken) { + @NonNull IBinder deviceToken, + int displayId) { final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId); if (fd < 0) { throw new RuntimeException( "A native error occurred when creating mouse: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_MOUSE, displayId)); + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId(displayId); + mActivePointerDisplayId = displayId; } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual mouse", e); } } @@ -106,6 +140,7 @@ class InputController { int vendorId, int productId, @NonNull IBinder deviceToken, + int displayId, @NonNull Point screenSize) { final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, screenSize.y, screenSize.x); @@ -113,93 +148,177 @@ class InputController { throw new RuntimeException( "A native error occurred when creating touchscreen: " + -fd); } + final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); synchronized (mLock) { - mInputDeviceFds.put(deviceToken, fd); + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, + InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId)); } try { - deviceToken.linkToDeath(new BinderDeathRecipient(deviceToken), /* flags= */ 0); + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { + // TODO(b/215608394): remove and close InputDeviceDescriptor throw new RuntimeException("Could not create virtual touchscreen", e); } } void unregisterInputDevice(@NonNull IBinder token) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.remove(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not unregister input device for given token"); } - mNativeWrapper.closeUinput(fd); + token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); + mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor()); + + // Reset values to the default if all virtual mice are unregistered, or set display + // id if there's another mouse (choose the most recent). + if (inputDeviceDescriptor.isMouse()) { + updateMouseValuesLocked(); + } } } + @GuardedBy("mLock") + private void updateMouseValuesLocked() { + InputDeviceDescriptor mostRecentlyCreatedMouse = null; + for (InputDeviceDescriptor otherInputDeviceDescriptor : + mInputDeviceDescriptors.values()) { + if (otherInputDeviceDescriptor.isMouse()) { + if (mostRecentlyCreatedMouse == null + || (otherInputDeviceDescriptor.getCreationOrderNumber() + > mostRecentlyCreatedMouse.getCreationOrderNumber())) { + mostRecentlyCreatedMouse = otherInputDeviceDescriptor; + } + } + } + if (mostRecentlyCreatedMouse != null) { + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId( + mostRecentlyCreatedMouse.getDisplayId()); + mActivePointerDisplayId = mostRecentlyCreatedMouse.getDisplayId(); + } else { + // All mice have been unregistered; reset all values. + resetMouseValuesLocked(); + } + } + + private void resetMouseValuesLocked() { + final InputManagerInternal inputManagerInternal = + LocalServices.getService(InputManagerInternal.class); + inputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); + mActivePointerDisplayId = Display.INVALID_DISPLAY; + } + boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send key event to input device for given token"); } - return mNativeWrapper.writeKeyEvent(fd, event.getKeyCode(), event.getAction()); + return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getKeyCode(), event.getAction()); } } boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send button event to input device for given token"); } - return mNativeWrapper.writeButtonEvent(fd, event.getButtonCode(), event.getAction()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getButtonCode(), event.getAction()); } } boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send touch event to input device for given token"); } - return mNativeWrapper.writeTouchEvent(fd, event.getPointerId(), event.getToolType(), - event.getAction(), event.getX(), event.getY(), event.getPressure(), - event.getMajorAxisSize()); + return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getPointerId(), event.getToolType(), event.getAction(), event.getX(), + event.getY(), event.getPressure(), event.getMajorAxisSize()); } } boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send relative event to input device for given token"); } - return mNativeWrapper.writeRelativeEvent(fd, event.getRelativeX(), - event.getRelativeY()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getRelativeX(), event.getRelativeY()); } } boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) { synchronized (mLock) { - final Integer fd = mInputDeviceFds.get(token); - if (fd == null) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { throw new IllegalArgumentException( "Could not send scroll event to input device for given token"); } - return mNativeWrapper.writeScrollEvent(fd, event.getXAxisMovement(), - event.getYAxisMovement()); + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getFileDescriptor(), + event.getXAxisMovement(), event.getYAxisMovement()); + } + } + + public PointF getCursorPosition(@NonNull IBinder token) { + synchronized (mLock) { + final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( + token); + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException( + "Could not get cursor position for input device for given token"); + } + if (inputDeviceDescriptor.getDisplayId() != mActivePointerDisplayId) { + throw new IllegalStateException( + "Display id associated with this mouse is not currently targetable"); + } + return LocalServices.getService(InputManagerInternal.class).getCursorPosition(); } } public void dump(@NonNull PrintWriter fout) { fout.println(" InputController: "); synchronized (mLock) { - fout.println(" Active file descriptors: "); - for (int inputDeviceFd : mInputDeviceFds.values()) { - fout.println(inputDeviceFd); + fout.println(" Active descriptors: "); + for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) { + fout.println(" fd: " + inputDeviceDescriptor.getFileDescriptor()); + fout.println(" displayId: " + inputDeviceDescriptor.getDisplayId()); + fout.println(" creationOrder: " + + inputDeviceDescriptor.getCreationOrderNumber()); + fout.println(" type: " + inputDeviceDescriptor.getType()); } + fout.println(" Active mouse display id: " + mActivePointerDisplayId); } } @@ -267,6 +386,63 @@ class InputController { } } + @VisibleForTesting static final class InputDeviceDescriptor { + + static final int TYPE_KEYBOARD = 1; + static final int TYPE_MOUSE = 2; + static final int TYPE_TOUCHSCREEN = 3; + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_KEYBOARD, + TYPE_MOUSE, + TYPE_TOUCHSCREEN, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Type { + } + + private static final AtomicLong sNextCreationOrderNumber = new AtomicLong(1); + + private final int mFd; + private final IBinder.DeathRecipient mDeathRecipient; + private final @Type int mType; + private final int mDisplayId; + // Monotonically increasing number; devices with lower numbers were created earlier. + private final long mCreationOrderNumber; + + InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, + @Type int type, int displayId) { + mFd = fd; + mDeathRecipient = deathRecipient; + mType = type; + mDisplayId = displayId; + mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement(); + } + + public int getFileDescriptor() { + return mFd; + } + + public int getType() { + return mType; + } + + public boolean isMouse() { + return mType == TYPE_MOUSE; + } + + public IBinder.DeathRecipient getDeathRecipient() { + return mDeathRecipient; + } + + public int getDisplayId() { + return mDisplayId; + } + + public long getCreationOrderNumber() { + return mCreationOrderNumber; + } + } + private final class BinderDeathRecipient implements IBinder.DeathRecipient { private final IBinder mDeviceToken; @@ -277,6 +453,10 @@ class InputController { @Override public void binderDied() { + // All callers are expected to call {@link VirtualDevice#unregisterInputDevice} before + // quitting, which removes this death recipient. If this is invoked, the remote end + // died, or they disposed of the object without properly unregistering. + Slog.e(TAG, "Virtual input controller binder died"); unregisterInputDevice(mDeviceToken); } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 59c9d8c625b5..5c8fb2ecec0f 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -29,10 +29,15 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.IVirtualDeviceActivityListener; +import android.companion.virtual.VirtualDeviceManager.ActivityListener; import android.companion.virtual.VirtualDeviceParams; +import android.content.ComponentName; import android.content.Context; import android.graphics.Point; +import android.graphics.PointF; import android.hardware.display.DisplayManager; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -50,6 +55,7 @@ import android.util.SparseArray; import android.window.DisplayWindowPolicyController; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -72,6 +78,30 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub private final OnDeviceCloseListener mListener; private final IBinder mAppToken; private final VirtualDeviceParams mParams; + private final IVirtualDeviceActivityListener mActivityListener; + + private ActivityListener createListenerAdapter(int displayId) { + return new ActivityListener() { + + @Override + public void onTopActivityChanged(int unusedDisplayId, ComponentName topActivity) { + try { + mActivityListener.onTopActivityChanged(displayId, topActivity); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to call mActivityListener", e); + } + } + + @Override + public void onDisplayEmpty(int unusedDisplayId) { + try { + mActivityListener.onDisplayEmpty(displayId); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to call mActivityListener", e); + } + } + }; + } /** * A mapping from the virtual display ID to its corresponding @@ -82,18 +112,22 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token, int ownerUid, OnDeviceCloseListener listener, - PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) { + PendingTrampolineCallback pendingTrampolineCallback, + IVirtualDeviceActivityListener activityListener, + VirtualDeviceParams params) { this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener, - pendingTrampolineCallback, params); + pendingTrampolineCallback, activityListener, params); } @VisibleForTesting VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token, int ownerUid, InputController inputController, OnDeviceCloseListener listener, - PendingTrampolineCallback pendingTrampolineCallback, VirtualDeviceParams params) { + PendingTrampolineCallback pendingTrampolineCallback, + IVirtualDeviceActivityListener activityListener, VirtualDeviceParams params) { mContext = context; mAssociationInfo = associationInfo; mPendingTrampolineCallback = pendingTrampolineCallback; + mActivityListener = activityListener; mOwnerUid = ownerUid; mAppToken = token; mParams = params; @@ -201,7 +235,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } final long token = Binder.clearCallingIdentity(); try { - mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken); + mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken, + displayId); } finally { Binder.restoreCallingIdentity(token); } @@ -226,7 +261,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } final long token = Binder.clearCallingIdentity(); try { - mInputController.createMouse(deviceName, vendorId, productId, deviceToken); + mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId); } finally { Binder.restoreCallingIdentity(token); } @@ -253,7 +288,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final long token = Binder.clearCallingIdentity(); try { mInputController.createTouchscreen(deviceName, vendorId, productId, - deviceToken, screenSize); + deviceToken, displayId, screenSize); } finally { Binder.restoreCallingIdentity(token); } @@ -323,6 +358,16 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } + @Override // Binder call + public PointF getCursorPosition(IBinder token) { + final long binderToken = Binder.clearCallingIdentity(); + try { + return mInputController.getCursorPosition(token); + } finally { + Binder.restoreCallingIdentity(binderToken); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { fout.println(" VirtualDevice: "); @@ -343,11 +388,15 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device already have a virtual display with ID " + displayId); } mVirtualDisplayIds.add(displayId); + LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture( + displayId, false); final GenericWindowPolicyController dwpc = new GenericWindowPolicyController(FLAG_SECURE, - SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles(), + SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, + getAllowedUserHandles(), mParams.getAllowedActivities(), - mParams.getBlockedActivities()); + mParams.getBlockedActivities(), + createListenerAdapter(displayId)); mWindowPolicyControllers.put(displayId, dwpc); return dwpc; } @@ -376,6 +425,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub "Virtual device doesn't have a virtual display with ID " + displayId); } mVirtualDisplayIds.remove(displayId); + LocalServices.getService(InputManagerInternal.class).setDisplayEligibilityForPointerCapture( + displayId, true); mWindowPolicyControllers.remove(displayId); } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 7e0c2fc37da6..b507110d5473 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -27,6 +27,7 @@ import android.companion.AssociationInfo; import android.companion.CompanionDeviceManager; import android.companion.CompanionDeviceManager.OnAssociationsChangedListener; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.IVirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; @@ -176,7 +177,8 @@ public class VirtualDeviceManagerService extends SystemService { IBinder token, String packageName, int associationId, - @NonNull VirtualDeviceParams params) { + @NonNull VirtualDeviceParams params, + @NonNull IVirtualDeviceActivityListener activityListener) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "createVirtualDevice"); @@ -206,7 +208,7 @@ public class VirtualDeviceManagerService extends SystemService { } } }, - this, params); + this, activityListener, params); mVirtualDevices.put(associationInfo.getId(), virtualDevice); return virtualDevice; } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 1666d15b2387..f56bfab7055f 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -105,9 +105,10 @@ public abstract class PackageManagerInternal implements PackageSettingsSnapshotP public static final int PACKAGE_COMPANION = 15; public static final int PACKAGE_RETAIL_DEMO = 16; public static final int PACKAGE_RECENTS = 17; + public static final int PACKAGE_AMBIENT_CONTEXT_DETECTION = 18; // Integer value of the last known package ID. Increases as new ID is added to KnownPackage. // Please note the numbers should be continuous. - public static final int LAST_KNOWN_PACKAGE = PACKAGE_RECENTS; + public static final int LAST_KNOWN_PACKAGE = PACKAGE_AMBIENT_CONTEXT_DETECTION; @LongDef(flag = true, prefix = "RESOLVE_", value = { RESOLVE_NON_BROWSER_ONLY, diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java index 62dc27330f87..be2b7f72bd4f 100644 --- a/services/core/java/com/android/server/MasterClearReceiver.java +++ b/services/core/java/com/android/server/MasterClearReceiver.java @@ -16,11 +16,14 @@ package com.android.server; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; + import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.Notification; import android.app.NotificationManager; import android.app.ProgressDialog; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -155,7 +158,7 @@ public class MasterClearReceiver extends BroadcastReceiver { final Notification notification = new Notification.Builder(context, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(android.R.drawable.stat_sys_warning) - .setContentTitle(context.getString(R.string.work_profile_deleted)) + .setContentTitle(getWorkProfileDeletedTitle(context)) .setContentText(wipeReason) .setColor(context.getColor(R.color.system_notification_accent_color)) .setStyle(new Notification.BigTextStyle().bigText(wipeReason)) @@ -164,6 +167,12 @@ public class MasterClearReceiver extends BroadcastReceiver { SystemMessageProto.SystemMessage.NOTE_PROFILE_WIPED, notification); } + private String getWorkProfileDeletedTitle(Context context) { + final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return dpm.getString(WORK_PROFILE_DELETED_TITLE, + () -> context.getString(R.string.work_profile_deleted)); + } + private @UserIdInt int getCurrentForegroundUserId() { try { return ActivityManager.getCurrentUser(); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 98764e02d803..f71f02a6ec4e 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -160,8 +160,6 @@ import com.android.server.storage.StorageSessionController.ExternalStorageServic import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -173,6 +171,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.PrintWriter; import java.math.BigInteger; import java.security.GeneralSecurityException; @@ -3698,16 +3698,29 @@ class StorageManagerService extends IStorageManager.Stub @Nullable public PendingIntent getManageSpaceActivityIntent( @NonNull String packageName, int requestCode) { - // Only Apps with MANAGE_EXTERNAL_STORAGE permission should be able to call this API. - enforcePermission(android.Manifest.permission.MANAGE_EXTERNAL_STORAGE); - - // We want to call the manageSpaceActivity as a SystemService and clear identity - // of the calling App + // Only Apps with MANAGE_EXTERNAL_STORAGE permission which have package visibility for + // packageName should be able to call this API. int originalUid = Binder.getCallingUidOrThrow(); - final long token = Binder.clearCallingIdentity(); + try { + // Get package name for calling app and verify it has MANAGE_EXTERNAL_STORAGE permission + final String[] packagesFromUid = mIPackageManager.getPackagesForUid(originalUid); + if (packagesFromUid == null) { + throw new SecurityException("Unknown uid " + originalUid); + } + // Checking first entry in packagesFromUid is enough as using "sharedUserId" + // mechanism is rare and discouraged. Also, Apps that share same UID share the same + // permissions. + if (!mStorageManagerInternal.hasExternalStorageAccess(originalUid, + packagesFromUid[0])) { + throw new SecurityException("Only File Manager Apps permitted"); + } + } catch (RemoteException re) { + throw new SecurityException("Unknown uid " + originalUid, re); + } + ApplicationInfo appInfo; try { - ApplicationInfo appInfo = mIPackageManager.getApplicationInfo(packageName, 0, + appInfo = mIPackageManager.getApplicationInfo(packageName, 0, UserHandle.getUserId(originalUid)); if (appInfo == null) { throw new IllegalArgumentException( @@ -3717,8 +3730,15 @@ class StorageManagerService extends IStorageManager.Stub Log.i(TAG, packageName + " doesn't have a manageSpaceActivity"); return null; } - Context targetAppContext = mContext.createPackageContext(packageName, 0); + } catch (RemoteException e) { + throw new SecurityException("Only File Manager Apps permitted"); + } + // We want to call the manageSpaceActivity as a SystemService and clear identity + // of the calling App + final long token = Binder.clearCallingIdentity(); + try { + Context targetAppContext = mContext.createPackageContext(packageName, 0); Intent intent = new Intent(Intent.ACTION_DEFAULT); intent.setClassName(packageName, appInfo.manageSpaceActivityName); @@ -3728,8 +3748,6 @@ class StorageManagerService extends IStorageManager.Stub intent, FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); return activity; - } catch (RemoteException e) { - throw e.rethrowAsRuntimeException(); } catch (PackageManager.NameNotFoundException e) { throw new IllegalArgumentException( "packageName not found"); @@ -4955,19 +4973,17 @@ class StorageManagerService extends IStorageManager.Stub @Override public boolean hasExternalStorageAccess(int uid, String packageName) { try { - if (mIPackageManager.checkUidPermission( - MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED) { - return true; + final int opMode = mIAppOpsService.checkOperation( + OP_MANAGE_EXTERNAL_STORAGE, uid, packageName); + if (opMode == AppOpsManager.MODE_DEFAULT) { + return mIPackageManager.checkUidPermission( + MANAGE_EXTERNAL_STORAGE, uid) == PERMISSION_GRANTED; } - if (mIAppOpsService.checkOperation( - OP_MANAGE_EXTERNAL_STORAGE, uid, packageName) == MODE_ALLOWED) { - return true; - } + return opMode == AppOpsManager.MODE_ALLOWED; } catch (RemoteException e) { Slog.w("Failed to check MANAGE_EXTERNAL_STORAGE access for " + packageName, e); } - return false; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index f67e732b47dd..b1b4c4447ec8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2871,13 +2871,51 @@ public class ActivityManagerService extends IActivityManager.Stub return mode == AppOpsManager.MODE_ALLOWED; } - @Override - public int getPackageProcessState(String packageName, String callingPackage) { + /** + * Checks whether the calling package is trusted. + * + * The calling package is trusted if it's from system or the supposed package name matches the + * UID making the call. + * + * @throws SecurityException if the package name and UID don't match. + */ + private void verifyCallingPackage(String callingPackage) { + final int callingUid = Binder.getCallingUid(); + // The caller is System or Shell. + if (callingUid == SYSTEM_UID || isCallerShell()) { + return; + } + + // Handle the special UIDs that don't have real package (audioserver, cameraserver, etc). + final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, + null /* packageName */); + if (resolvedPackage != null && resolvedPackage.equals(callingPackage)) { + return; + } + + final int claimedUid = getPackageManagerInternal().getPackageUid(callingPackage, + 0 /* flags */, UserHandle.getUserId(callingUid)); + if (callingUid == claimedUid) { + return; + } + + throw new SecurityException( + "Claimed calling package " + callingPackage + " does not match the calling UID " + + Binder.getCallingUid()); + } + + private void enforceUsageStatsPermission(String callingPackage, String func) { + verifyCallingPackage(callingPackage); + // Since the protection level of PACKAGE_USAGE_STATS has 'appop', apps may grant this + // permission via that way. We need to check both app-ops and permission. if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "getPackageProcessState"); + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, func); } + } + @Override + public int getPackageProcessState(String packageName, String callingPackage) { + enforceUsageStatsPermission(callingPackage, "getPackageProcessState"); final int[] procState = {PROCESS_STATE_NONEXISTENT}; synchronized (mProcLock) { mProcessList.forEachLruProcessesLOSP(false, proc -> { @@ -6938,11 +6976,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public int getUidProcessState(int uid, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "getUidProcessState"); - } - + enforceUsageStatsPermission(callingPackage, "getUidProcessState"); synchronized (mProcLock) { return mProcessList.getUidProcStateLOSP(uid); } @@ -6950,11 +6984,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public @ProcessCapability int getUidProcessCapabilities(int uid, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "getUidProcessState"); - } - + enforceUsageStatsPermission(callingPackage, "getUidProcessCapabilities"); synchronized (mProcLock) { return mProcessList.getUidProcessCapabilityLOSP(uid); } @@ -6963,10 +6993,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void registerUidObserver(IUidObserver observer, int which, int cutpoint, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "registerUidObserver"); - } + enforceUsageStatsPermission(callingPackage, "registerUidObserver"); mUidObserverController.register(observer, which, cutpoint, callingPackage, Binder.getCallingUid()); } @@ -6978,10 +7005,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public boolean isUidActive(int uid, String callingPackage) { - if (!hasUsageStatsPermission(callingPackage)) { - enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, - "isUidActive"); - } + enforceUsageStatsPermission(callingPackage, "isUidActive"); synchronized (mProcLock) { if (isUidActiveLOSP(uid)) { return true; diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 921208cbfa3d..0b92954e7932 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -42,6 +42,7 @@ import android.os.BatteryStatsInternal; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Binder; +import android.os.BluetoothBatteryStats; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -2627,6 +2628,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub } /** + * Gets a snapshot of Bluetooth stats + * @hide + */ + public BluetoothBatteryStats getBluetoothBatteryStats() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BATTERY_STATS, null); + + // Wait for the completion of pending works if there is any + awaitCompletion(); + synchronized (mStats) { + return mStats.getBluetoothBatteryStats(); + } + } + + /** * Gets a snapshot of the system health for a particular uid. */ @Override diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index c08cf6485855..6f22c61d2b2a 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -962,7 +962,7 @@ public final class CachedAppOptimizer { } opt.setFreezerOverride(false); - if (!opt.isFrozen()) { + if (pid == 0 || !opt.isFrozen()) { return; } diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java new file mode 100644 index 000000000000..6982513f8f2f --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2021 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.ambientcontext; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.app.AppGlobals; +import android.app.BroadcastOptions; +import android.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.app.ambientcontext.AmbientContextManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.Binder; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.service.ambientcontext.AmbientContextDetectionService; +import android.util.IndentingPrintWriter; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.infra.AbstractPerUserSystemService; + +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Set; + +/** + * Per-user manager service for {@link AmbientContextEvent}s. + */ +final class AmbientContextManagerPerUserService extends + AbstractPerUserSystemService<AmbientContextManagerPerUserService, + AmbientContextManagerService> { + private static final String TAG = AmbientContextManagerPerUserService.class.getSimpleName(); + + @Nullable + @VisibleForTesting + RemoteAmbientContextDetectionService mRemoteService; + + private ComponentName mComponentName; + private Context mContext; + private Set<PendingIntent> mExistingPendingIntents; + + AmbientContextManagerPerUserService( + @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) { + super(master, lock, userId); + mContext = master.getContext(); + mExistingPendingIntents = new HashSet<>(); + } + + void destroyLocked() { + if (isVerbose()) { + Slog.v(TAG, "destroyLocked()"); + } + + Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed."); + if (mRemoteService != null) { + synchronized (mLock) { + mRemoteService.unbind(); + mRemoteService = null; + } + } + } + + @GuardedBy("mLock") + private void ensureRemoteServiceInitiated() { + if (mRemoteService == null) { + mRemoteService = new RemoteAmbientContextDetectionService( + getContext(), mComponentName, getUserId()); + } + } + + /** + * get the currently bound component name. + */ + @VisibleForTesting + ComponentName getComponentName() { + return mComponentName; + } + + + /** + * Resolves and sets up the service if it had not been done yet. Returns true if the service + * is available. + */ + @GuardedBy("mLock") + @VisibleForTesting + boolean setUpServiceIfNeeded() { + if (mComponentName == null) { + mComponentName = updateServiceInfoLocked(); + } + return mComponentName != null; + } + + @Override + protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent) + throws PackageManager.NameNotFoundException { + ServiceInfo serviceInfo; + try { + serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent, + 0, mUserId); + if (serviceInfo != null) { + final String permission = serviceInfo.permission; + if (!Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE.equals( + permission)) { + throw new SecurityException(String.format( + "Service %s requires %s permission. Found %s permission", + serviceInfo.getComponentName(), + Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE, + serviceInfo.permission)); + } + } + } catch (RemoteException e) { + throw new PackageManager.NameNotFoundException( + "Could not get service for " + serviceComponent); + } + return serviceInfo; + } + + @Override + protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) { + synchronized (super.mLock) { + super.dumpLocked(prefix, pw); + } + if (mRemoteService != null) { + mRemoteService.dump("", new IndentingPrintWriter(pw, " ")); + } + } + + /** + * Handles client registering as an observer. Only one registration is supported per app + * package. A new registration from the same package will overwrite the previous registration. + */ + public void onRegisterObserver(AmbientContextEventRequest request, + PendingIntent pendingIntent) { + synchronized (mLock) { + if (!setUpServiceIfNeeded()) { + Slog.w(TAG, "Service is not available at this moment."); + sendStatusUpdateIntent( + pendingIntent, AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE); + return; + } + + // Remove any existing intent and unregister for this package before adding a new one. + String callingPackage = pendingIntent.getCreatorPackage(); + PendingIntent duplicatePendingIntent = findExistingRequestByPackage(callingPackage); + if (duplicatePendingIntent != null) { + Slog.d(TAG, "Unregister duplicate request from " + callingPackage); + onUnregisterObserver(callingPackage); + mExistingPendingIntents.remove(duplicatePendingIntent); + } + + // Register new package and add request to mExistingRequests + startDetection(request, callingPackage, createRemoteCallback()); + mExistingPendingIntents.add(pendingIntent); + } + } + + @VisibleForTesting + void startDetection(AmbientContextEventRequest request, String callingPackage, + RemoteCallback callback) { + Slog.d(TAG, "Requested detection of " + request.getEventTypes()); + synchronized (mLock) { + ensureRemoteServiceInitiated(); + mRemoteService.startDetection(request, callingPackage, callback); + } + } + + /** + * Sends an intent with a status code and empty events. + */ + void sendStatusUpdateIntent(PendingIntent pendingIntent, int statusCode) { + AmbientContextEventResponse response = new AmbientContextEventResponse.Builder() + .setStatusCode(statusCode) + .build(); + sendResponseIntent(pendingIntent, response); + } + + /** + * Unregisters the client from all previously registered events by removing from the + * mExistingRequests map, and unregister events from the service if those events are not + * requested by other apps. + */ + public void onUnregisterObserver(String callingPackage) { + synchronized (mLock) { + PendingIntent pendingIntent = findExistingRequestByPackage(callingPackage); + if (pendingIntent == null) { + Slog.d(TAG, "No registration found for " + callingPackage); + return; + } + + // Remove from existing requests + mExistingPendingIntents.remove(pendingIntent); + stopDetection(pendingIntent.getCreatorPackage()); + } + } + + @VisibleForTesting + void stopDetection(String packageName) { + Slog.d(TAG, "Stop detection for " + packageName); + synchronized (mLock) { + ensureRemoteServiceInitiated(); + mRemoteService.stopDetection(packageName); + } + } + + @Nullable + private PendingIntent findExistingRequestByPackage(String callingPackage) { + for (PendingIntent pendingIntent : mExistingPendingIntents) { + if (pendingIntent.getCreatorPackage().equals(callingPackage)) { + return pendingIntent; + } + } + return null; + } + + /** + * Sends out the Intent to the client after the event is detected. + * + * @param pendingIntent Client's PendingIntent for callback + * @param response Response with status code and detection result + */ + private void sendResponseIntent(PendingIntent pendingIntent, + AmbientContextEventResponse response) { + Intent intent = new Intent(); + intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENT_RESPONSE, response); + // Explicitly disallow the receiver from starting activities, to prevent apps from utilizing + // the PendingIntent as a backdoor to do this. + BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setPendingIntentBackgroundActivityLaunchAllowed(false); + try { + pendingIntent.send(getContext(), 0, intent, null, null, null, + options.toBundle()); + Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": " + + response); + } catch (PendingIntent.CanceledException e) { + Slog.w(TAG, "Couldn't deliver pendingIntent:" + pendingIntent); + } + } + + @NonNull + private RemoteCallback createRemoteCallback() { + return new RemoteCallback(result -> { + AmbientContextEventResponse response = (AmbientContextEventResponse) result.get( + AmbientContextDetectionService.RESPONSE_BUNDLE_KEY); + final long token = Binder.clearCallingIdentity(); + try { + Set<PendingIntent> pendingIntentForFailedRequests = new HashSet<>(); + for (PendingIntent pendingIntent : mExistingPendingIntents) { + // Send PendingIntent if a requesting package matches the response packages. + if (response.getPackageName().equals(pendingIntent.getCreatorPackage())) { + sendResponseIntent(pendingIntent, response); + + int statusCode = response.getStatusCode(); + if (statusCode != AmbientContextEventResponse.STATUS_SUCCESS) { + pendingIntentForFailedRequests.add(pendingIntent); + } + Slog.i(TAG, "Got response of " + response.getEvents() + " for " + + pendingIntent.getCreatorPackage() + ". Status: " + statusCode); + } + } + + // Removes the failed requests from the existing requests. + for (PendingIntent pendingIntent : pendingIntentForFailedRequests) { + mExistingPendingIntents.remove(pendingIntent); + } + } finally { + Binder.restoreCallingIdentity(token); + } + }); + } +} diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java new file mode 100644 index 000000000000..33905f2d1aa3 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2021 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.ambientcontext; + +import static android.provider.DeviceConfig.NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.UserIdInt; +import android.app.PendingIntent; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.app.ambientcontext.IAmbientContextEventObserver; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.os.RemoteCallback; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.util.DumpUtils; +import com.android.server.LocalServices; +import com.android.server.SystemService; +import com.android.server.infra.AbstractMasterSystemService; +import com.android.server.infra.FrameworkResourcesServiceNameResolver; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Objects; +import java.util.Set; + +/** + * System service for managing {@link AmbientContextEvent}s. + */ +public class AmbientContextManagerService extends + AbstractMasterSystemService<AmbientContextManagerService, + AmbientContextManagerPerUserService> { + private static final String TAG = AmbientContextManagerService.class.getSimpleName(); + private static final String KEY_SERVICE_ENABLED = "service_enabled"; + + /** Default value in absence of {@link DeviceConfig} override. */ + private static final boolean DEFAULT_SERVICE_ENABLED = true; + + private final Context mContext; + boolean mIsServiceEnabled; + + public AmbientContextManagerService(Context context) { + super(context, + new FrameworkResourcesServiceNameResolver( + context, + R.string.config_defaultAmbientContextDetectionService), + /*disallowProperty=*/null, + PACKAGE_UPDATE_POLICY_REFRESH_EAGER + | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER); + mContext = context; + } + + @Override + public void onStart() { + publishBinderService(Context.AMBIENT_CONTEXT_SERVICE, new AmbientContextEventObserver()); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, + getContext().getMainExecutor(), + (properties) -> onDeviceConfigChange(properties.getKeyset())); + + mIsServiceEnabled = DeviceConfig.getBoolean( + NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, + KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + } + } + + private void onDeviceConfigChange(@NonNull Set<String> keys) { + if (keys.contains(KEY_SERVICE_ENABLED)) { + mIsServiceEnabled = DeviceConfig.getBoolean( + NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE, + KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + } + } + + @Override + protected AmbientContextManagerPerUserService newServiceLocked(int resolvedUserId, + boolean disabled) { + return new AmbientContextManagerPerUserService(this, mLock, resolvedUserId); + } + + @Override + protected void onServiceRemoved( + AmbientContextManagerPerUserService service, @UserIdInt int userId) { + service.destroyLocked(); + } + + /** Returns {@code true} if the detection service is configured on this device. */ + public static boolean isDetectionServiceConfigured() { + final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + final String[] packageNames = pmi.getKnownPackageNames( + PackageManagerInternal.PACKAGE_AMBIENT_CONTEXT_DETECTION, UserHandle.USER_SYSTEM); + boolean isServiceConfigured = (packageNames.length != 0); + Slog.i(TAG, "Detection service configured: " + isServiceConfigured); + return isServiceConfigured; + } + + /** + * Send request to the remote AmbientContextDetectionService impl to start detecting the + * specified events. Intended for use by shell command for testing. + * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. + */ + void startAmbientContextEvent(@UserIdInt int userId, AmbientContextEventRequest request, + String packageName, RemoteCallback callback) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + synchronized (mLock) { + final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.startDetection(request, packageName, callback); + } else { + Slog.i(TAG, "service not available for user_id: " + userId); + } + } + } + + /** + * Send request to the remote AmbientContextDetectionService impl to stop detecting the + * specified events. Intended for use by shell command for testing. + * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission. + */ + void stopAmbientContextEvent(@UserIdInt int userId, String packageName) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + synchronized (mLock) { + final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + service.stopDetection(packageName); + } else { + Slog.i(TAG, "service not available for user_id: " + userId); + } + } + } + + /** + * Returns the AmbientContextManagerPerUserService component for this user. + */ + public ComponentName getComponentName(@UserIdInt int userId) { + synchronized (mLock) { + final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId); + if (service != null) { + return service.getComponentName(); + } + } + return null; + } + + private final class AmbientContextEventObserver extends IAmbientContextEventObserver.Stub { + final AmbientContextManagerPerUserService mService = getServiceForUserLocked( + UserHandle.getCallingUserId()); + + @Override + public void registerObserver( + AmbientContextEventRequest request, PendingIntent pendingIntent) { + Objects.requireNonNull(request); + Objects.requireNonNull(pendingIntent); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available."); + mService.sendStatusUpdateIntent(pendingIntent, + AmbientContextEventResponse.STATUS_SERVICE_UNAVAILABLE); + return; + } + mService.onRegisterObserver(request, pendingIntent); + } + + @Override + public void unregisterObserver(String callingPackage) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG); + mService.onUnregisterObserver(callingPackage); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } + synchronized (mLock) { + dumpLocked("", pw); + } + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + new AmbientContextShellCommand(AmbientContextManagerService.this).exec( + this, in, out, err, args, callback, resultReceiver); + } + } +} diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java new file mode 100644 index 000000000000..b5cd985fa097 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 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.ambientcontext; + +import static java.lang.System.out; + +import android.annotation.NonNull; +import android.app.ambientcontext.AmbientContextEvent; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.app.ambientcontext.AmbientContextEventResponse; +import android.content.ComponentName; +import android.os.Binder; +import android.os.RemoteCallback; +import android.os.ShellCommand; +import android.service.ambientcontext.AmbientContextDetectionService; + +import java.io.PrintWriter; + +/** + * Shell command for {@link AmbientContextManagerService}. + */ +final class AmbientContextShellCommand extends ShellCommand { + + @NonNull + private final AmbientContextManagerService mService; + + AmbientContextShellCommand(@NonNull AmbientContextManagerService service) { + mService = service; + } + + /** Callbacks for AmbientContextEventService results used internally for testing. */ + static class TestableCallbackInternal { + private AmbientContextEventResponse mLastResponse; + + public AmbientContextEventResponse getLastResponse() { + return mLastResponse; + } + + @NonNull + private RemoteCallback createRemoteCallback() { + return new RemoteCallback(result -> { + AmbientContextEventResponse response = + (AmbientContextEventResponse) result.get( + AmbientContextDetectionService.RESPONSE_BUNDLE_KEY); + final long token = Binder.clearCallingIdentity(); + try { + mLastResponse = response; + out.println("Response available: " + response); + } finally { + Binder.restoreCallingIdentity(token); + } + }); + } + } + + static final TestableCallbackInternal sTestableCallbackInternal = + new TestableCallbackInternal(); + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + switch (cmd) { + case "start-detection": + return runStartDetection(); + case "stop-detection": + return runStopDetection(); + case "get-last-status-code": + return getLastStatusCode(); + case "get-bound-package": + return getBoundPackageName(); + case "set-temporary-service": + return setTemporaryService(); + default: + return handleDefaultCommands(cmd); + } + } + + private int runStartDetection() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + AmbientContextEventRequest request = new AmbientContextEventRequest.Builder() + .addEventType(AmbientContextEvent.EVENT_COUGH) + .addEventType(AmbientContextEvent.EVENT_SNORE) + .build(); + + mService.startAmbientContextEvent(userId, request, packageName, + sTestableCallbackInternal.createRemoteCallback()); + return 0; + } + + private int runStopDetection() { + final int userId = Integer.parseInt(getNextArgRequired()); + final String packageName = getNextArgRequired(); + mService.stopAmbientContextEvent(userId, packageName); + return 0; + } + + private int getLastStatusCode() { + AmbientContextEventResponse lastResponse = sTestableCallbackInternal.getLastResponse(); + if (lastResponse == null) { + return -1; + } + return lastResponse.getStatusCode(); + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("AmbientContextEvent commands: "); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(); + pw.println(" start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection."); + pw.println(" stop-detection USER_ID: Stops AmbientContextEvent detection."); + pw.println(" get-last-status-code: Prints the latest request status code."); + pw.println(" get-bound-package USER_ID:" + + " Print the bound package that implements the service."); + pw.println(" set-temporary-service USER_ID [COMPONENT_NAME DURATION]"); + pw.println(" Temporarily (for DURATION ms) changes the service implementation."); + pw.println(" To reset, call with just the USER_ID argument."); + } + + private int getBoundPackageName() { + final PrintWriter out = getOutPrintWriter(); + final int userId = Integer.parseInt(getNextArgRequired()); + final ComponentName componentName = mService.getComponentName(userId); + out.println(componentName == null ? "" : componentName.getPackageName()); + return 0; + } + + private int setTemporaryService() { + final PrintWriter out = getOutPrintWriter(); + final int userId = Integer.parseInt(getNextArgRequired()); + final String serviceName = getNextArg(); + if (serviceName == null) { + mService.resetTemporaryService(userId); + out.println("AmbientContextDetectionService temporary reset. "); + return 0; + } + + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryService(userId, serviceName, duration); + out.println("AmbientContextDetectionService temporarily set to " + serviceName + + " for " + duration + "ms"); + return 0; + } +} diff --git a/services/core/java/com/android/server/ambientcontext/OWNERS b/services/core/java/com/android/server/ambientcontext/OWNERS new file mode 100644 index 000000000000..a863297b84e8 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/OWNERS @@ -0,0 +1,3 @@ +enxun@google.com +kxchen@google.com +tgadh@google.com diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java new file mode 100644 index 000000000000..5cc29b3c34c0 --- /dev/null +++ b/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 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.ambientcontext; + +import static android.content.Context.BIND_FOREGROUND_SERVICE; +import static android.content.Context.BIND_INCLUDE_CAPABILITIES; + +import android.annotation.NonNull; +import android.app.ambientcontext.AmbientContextEventRequest; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.RemoteCallback; +import android.service.ambientcontext.AmbientContextDetectionService; +import android.service.ambientcontext.IAmbientContextDetectionService; +import android.util.Slog; + +import com.android.internal.infra.ServiceConnector; + +/** Manages the connection to the remote service. */ +final class RemoteAmbientContextDetectionService + extends ServiceConnector.Impl<IAmbientContextDetectionService> { + private static final String TAG = + RemoteAmbientContextDetectionService.class.getSimpleName(); + + RemoteAmbientContextDetectionService(Context context, ComponentName serviceName, + int userId) { + super(context, new Intent( + AmbientContextDetectionService.SERVICE_INTERFACE).setComponent(serviceName), + BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId, + IAmbientContextDetectionService.Stub::asInterface); + + // Bind right away + connect(); + } + + /** + * Asks the implementation to start detection. + * + * @param request The request with events to detect, and optional detection options. + * @param packageName The app package that requested the detection + * @param callback callback for detection results + */ + public void startDetection( + @NonNull AmbientContextEventRequest request, String packageName, + RemoteCallback callback) { + Slog.i(TAG, "Start detection for " + request.getEventTypes()); + post(service -> service.startDetection(request, packageName, callback)); + } + + /** + * Asks the implementation to stop detection. + * + * @param packageName stop detection for the given package + */ + public void stopDetection(String packageName) { + Slog.i(TAG, "Stop detection for " + packageName); + post(service -> service.stopDetection(packageName)); + } +} diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 53f651bfdf08..f5f7bb3428bf 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -43,6 +43,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; +import android.app.GameModeInfo; import android.app.GameState; import android.app.IGameManagerService; import android.app.compat.PackageOverride; @@ -694,29 +695,43 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - /** - * Get an array of game modes available for a given package. - * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}. - */ - @Override - @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) - public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException { - checkPermission(Manifest.permission.MANAGE_GAME_MODE); + private @GameMode int[] getAvailableGameModesUnchecked(String packageName) { GamePackageConfiguration config = null; synchronized (mOverrideConfigLock) { config = mOverrideConfigs.get(packageName); } if (config == null) { - synchronized (mDeviceConfigLock) { + synchronized (mDeviceConfigLock) { config = mConfigs.get(packageName); } } if (config == null) { - return new int[]{GameManager.GAME_MODE_UNSUPPORTED}; + return new int[]{}; } return config.getAvailableGameModes(); } + private boolean isPackageGame(String packageName, @UserIdInt int userId) { + try { + final ApplicationInfo applicationInfo = mPackageManager + .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); + return applicationInfo.category == ApplicationInfo.CATEGORY_GAME; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + /** + * Get an array of game modes available for a given package. + * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}. + */ + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException { + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + return getAvailableGameModesUnchecked(packageName); + } + private @GameMode int getGameModeFromSettings(String packageName, @UserIdInt int userId) { synchronized (mLock) { if (!mSettings.containsKey(userId)) { @@ -735,28 +750,22 @@ public final class GameManagerService extends IGameManagerService.Stub { * {@link android.Manifest.permission#MANAGE_GAME_MODE}. */ @Override - public @GameMode int getGameMode(String packageName, int userId) + public @GameMode int getGameMode(@NonNull String packageName, @UserIdInt int userId) throws SecurityException { userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "getGameMode", "com.android.server.app.GameManagerService"); // Restrict to games only. - try { - final ApplicationInfo applicationInfo = mPackageManager - .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); - if (applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { - // The game mode for applications that are not identified as game is always - // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)} - return GameManager.GAME_MODE_UNSUPPORTED; - } - } catch (PackageManager.NameNotFoundException e) { + if (!isPackageGame(packageName, userId)) { + // The game mode for applications that are not identified as game is always + // UNSUPPORTED. See {@link PackageManager#setApplicationCategoryHint(String, int)} return GameManager.GAME_MODE_UNSUPPORTED; } // This function handles two types of queries: - // 1.) A normal, non-privileged app querying its own Game Mode. - // 2.) A privileged system service querying the Game Mode of another package. + // 1) A normal, non-privileged app querying its own Game Mode. + // 2) A privileged system service querying the Game Mode of another package. // The least privileged case is a normal app performing a query, so check that first and // return a value if the package name is valid. Next, check if the caller has the necessary // permission and return a value. Do this check last, since it can throw an exception. @@ -769,14 +778,32 @@ public final class GameManagerService extends IGameManagerService.Stub { return getGameModeFromSettings(packageName, userId); } - private boolean isPackageGame(String packageName, int userId) { - try { - final ApplicationInfo applicationInfo = mPackageManager - .getApplicationInfoAsUser(packageName, PackageManager.MATCH_ALL, userId); - return applicationInfo.category == ApplicationInfo.CATEGORY_GAME; - } catch (PackageManager.NameNotFoundException e) { - return false; + /** + * Get the GameModeInfo for the package name. + * Verifies that the calling process is for the matching package UID or has + * {@link android.Manifest.permission#MANAGE_GAME_MODE}. If the package is not a game, + * null is always returned. + */ + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) + @Nullable + public GameModeInfo getGameModeInfo(@NonNull String packageName, @UserIdInt int userId) { + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, true, "getGameModeInfo", + "com.android.server.app.GameManagerService"); + + // Check the caller has the necessary permission. + checkPermission(Manifest.permission.MANAGE_GAME_MODE); + + // Restrict to games only. + if (!isPackageGame(packageName, userId)) { + return null; } + + final @GameMode int activeGameMode = getGameModeFromSettings(packageName, userId); + final @GameMode int[] availableGameModes = getAvailableGameModesUnchecked(packageName); + + return new GameModeInfo(activeGameMode, availableGameModes); } /** diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java index 48e66b6c6aeb..b4c43f6f1b93 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceFactoryImpl.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.app.ActivityTaskManager; import android.content.Context; import android.content.Intent; +import android.os.ServiceManager; import android.service.games.GameService; import android.service.games.GameSessionService; import android.service.games.IGameService; @@ -29,6 +30,7 @@ import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerService; final class GameServiceProviderInstanceFactoryImpl implements GameServiceProviderInstanceFactory { private final Context mContext; @@ -46,6 +48,7 @@ final class GameServiceProviderInstanceFactoryImpl implements GameServiceProvide BackgroundThread.getExecutor(), new GameClassifierImpl(mContext.getPackageManager()), ActivityTaskManager.getService(), + (WindowManagerService) ServiceManager.getService(Context.WINDOW_SERVICE), LocalServices.getService(WindowManagerInternal.class), new GameServiceConnector(mContext, gameServiceProviderConfiguration), new GameSessionServiceConnector(mContext, gameServiceProviderConfiguration)); diff --git a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java index 31eb8c1c9429..43c9839a04d8 100644 --- a/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java +++ b/services/core/java/com/android/server/app/GameServiceProviderInstanceImpl.java @@ -19,27 +19,33 @@ package com.android.server.app; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.TaskStackListener; import android.content.ComponentName; +import android.graphics.Bitmap; import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.service.games.CreateGameSessionRequest; import android.service.games.CreateGameSessionResult; +import android.service.games.GameScreenshotResult; import android.service.games.GameSessionViewHostConfiguration; import android.service.games.GameStartedEvent; import android.service.games.IGameService; import android.service.games.IGameServiceController; import android.service.games.IGameSession; +import android.service.games.IGameSessionController; import android.service.games.IGameSessionService; import android.util.Slog; import android.view.SurfaceControlViewHost.SurfacePackage; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerService; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -70,6 +76,13 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan }); } + @Override + public void onTaskFocusChanged(int taskId, boolean focused) { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.onTaskFocusChanged(taskId, focused); + }); + } + // TODO(b/204503192): Limit the lifespan of the game session in the Game Service provider // to only when the associated task is running. Right now it is possible for a task to // move into the background and for all associated processes to die and for the Game Session @@ -87,11 +100,24 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } }; + private final IGameSessionController mGameSessionController = + new IGameSessionController.Stub() { + @Override + public void takeScreenshot(int taskId, + @NonNull AndroidFuture gameScreenshotResultFuture) { + mBackgroundExecutor.execute(() -> { + GameServiceProviderInstanceImpl.this.takeScreenshot(taskId, + gameScreenshotResultFuture); + }); + } + }; + private final Object mLock = new Object(); private final UserHandle mUserHandle; private final Executor mBackgroundExecutor; private final GameClassifier mGameClassifier; private final IActivityTaskManager mActivityTaskManager; + private final WindowManagerService mWindowManagerService; private final WindowManagerInternal mWindowManagerInternal; private final ServiceConnector<IGameService> mGameServiceConnector; private final ServiceConnector<IGameSessionService> mGameSessionServiceConnector; @@ -107,6 +133,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan @NonNull Executor backgroundExecutor, @NonNull GameClassifier gameClassifier, @NonNull IActivityTaskManager activityTaskManager, + @NonNull WindowManagerService windowManagerService, @NonNull WindowManagerInternal windowManagerInternal, @NonNull ServiceConnector<IGameService> gameServiceConnector, @NonNull ServiceConnector<IGameSessionService> gameSessionServiceConnector) { @@ -114,6 +141,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan mBackgroundExecutor = backgroundExecutor; mGameClassifier = gameClassifier; mActivityTaskManager = activityTaskManager; + mWindowManagerService = windowManagerService; mWindowManagerInternal = windowManagerInternal; mGameServiceConnector = gameServiceConnector; mGameSessionServiceConnector = gameSessionServiceConnector; @@ -192,6 +220,30 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } + private void onTaskFocusChanged(int taskId, boolean focused) { + synchronized (mLock) { + onTaskFocusChangedLocked(taskId, focused); + } + } + + @GuardedBy("mLock") + private void onTaskFocusChangedLocked(int taskId, boolean focused) { + if (DEBUG) { + Slog.d(TAG, "onTaskFocusChangedLocked() id: " + taskId + " focused: " + focused); + } + + final GameSessionRecord gameSessionRecord = mGameSessions.get(taskId); + if (gameSessionRecord == null || gameSessionRecord.getGameSession() == null) { + return; + } + + try { + gameSessionRecord.getGameSession().onTaskFocusChanged(focused); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify session of task focus change: " + gameSessionRecord); + } + } + @GuardedBy("mLock") private void gameTaskStartedLocked(int taskId, @NonNull ComponentName componentName) { if (DEBUG) { @@ -291,6 +343,12 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan synchronized (mLock) { attachGameSessionLocked(taskId, createGameSessionResult); } + + // The TaskStackListener may have made its task focused call for the + // game session's task before the game session was created, so check if + // the task is already focused so that the game session can be notified. + setGameSessionFocusedIfNecessary(taskId, + createGameSessionResult.getGameSession()); }, mBackgroundExecutor); AndroidFuture<Void> unusedPostCreateGameSessionFuture = @@ -300,12 +358,25 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan taskId, existingGameSessionRecord.getComponentName().getPackageName()); gameService.create( + mGameSessionController, createGameSessionRequest, gameSessionViewHostConfiguration, createGameSessionResultFuture); }); } + private void setGameSessionFocusedIfNecessary(int taskId, IGameSession gameSession) { + try { + final ActivityTaskManager.RootTaskInfo rootTaskInfo = + mActivityTaskManager.getFocusedRootTaskInfo(); + if (rootTaskInfo != null && rootTaskInfo.taskId == taskId) { + gameSession.onTaskFocusChanged(true); + } + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to set task focused for ID: " + taskId); + } + } + @GuardedBy("mLock") private void attachGameSessionLocked( int taskId, @@ -347,7 +418,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan int taskId, CreateGameSessionResult createGameSessionResult) { try { - createGameSessionResult.getGameSession().destroy(); + createGameSessionResult.getGameSession().onDestroyed(); } catch (RemoteException ex) { Slog.w(TAG, "Failed to destroy session: " + taskId); } @@ -387,7 +458,7 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan IGameSession gameSession = gameSessionRecord.getGameSession(); if (gameSession != null) { try { - gameSession.destroy(); + gameSession.onDestroyed(); } catch (RemoteException ex) { Slog.w(TAG, "Failed to destroy session: " + gameSessionRecord, ex); } @@ -404,7 +475,6 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan } } - @Nullable private GameSessionViewHostConfiguration createViewHostConfigurationForTask(int taskId) { RunningTaskInfo runningTaskInfo = getRunningTaskInfoForTask(taskId); @@ -440,4 +510,26 @@ final class GameServiceProviderInstanceImpl implements GameServiceProviderInstan return null; } + + @VisibleForTesting + void takeScreenshot(int taskId, @NonNull AndroidFuture callback) { + synchronized (mLock) { + boolean isTaskAssociatedWithGameSession = mGameSessions.containsKey(taskId); + if (!isTaskAssociatedWithGameSession) { + Slog.w(TAG, "No game session found for id: " + taskId); + callback.complete(GameScreenshotResult.createInternalErrorResult()); + return; + } + } + + mBackgroundExecutor.execute(() -> { + final Bitmap bitmap = mWindowManagerService.captureTaskBitmap(taskId); + if (bitmap == null) { + Slog.w(TAG, "Could not get bitmap for id: " + taskId); + callback.complete(GameScreenshotResult.createInternalErrorResult()); + } else { + callback.complete(GameScreenshotResult.createSuccessResult(bitmap)); + } + }); + } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 0961fcb31ace..2dd6bf575579 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -1360,6 +1360,9 @@ public class AudioDeviceInventory { case AudioSystem.DEVICE_OUT_USB_HEADSET: connType = AudioRoutesInfo.MAIN_USB; break; + case AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET: + connType = AudioRoutesInfo.MAIN_DOCK_SPEAKERS; + break; } synchronized (mCurAudioRoutes) { diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 38fe73fc9b88..1694bd92c73c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -414,7 +414,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage } @Override - public void onTrustChanged(boolean enabled, int userId, int flags) { + public void onTrustChanged(boolean enabled, int userId, int flags, + List<String> trustGrantedMessages) { mUserHasTrust.put(userId, enabled); } diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 33a26baea90e..1e00ea9161a8 100644 --- a/services/core/java/com/android/server/camera/CameraServiceProxy.java +++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java @@ -36,6 +36,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.res.Configuration; +import android.graphics.Rect; import android.hardware.CameraSessionStats; import android.hardware.CameraStreamStats; import android.hardware.ICameraService; @@ -305,6 +306,9 @@ public class CameraServiceProxy extends SystemService @Override public void onFixedRotationFinished(int displayId) { } + + @Override + public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearArea) { } } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index fd4cd8e6ec88..35e3db7832f1 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -358,6 +358,12 @@ final class DisplayDeviceInfo { public float brightnessMaximum; public float brightnessDefault; + /** + * Install orientation of display panel relative to its natural orientation. + */ + @Surface.Rotation + public int installOrientation = Surface.ROTATION_0; + public void setAssumedDensityForExternalDisplay(int width, int height) { densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080; // Technically, these values should be smaller than the apparent density @@ -417,7 +423,8 @@ final class DisplayDeviceInfo { || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum) || !BrightnessSynchronizer.floatEquals(brightnessDefault, other.brightnessDefault) - || !Objects.equals(roundedCorners, other.roundedCorners)) { + || !Objects.equals(roundedCorners, other.roundedCorners) + || installOrientation != other.installOrientation) { diff |= DIFF_OTHER; } return diff; @@ -461,6 +468,7 @@ final class DisplayDeviceInfo { brightnessMaximum = other.brightnessMaximum; brightnessDefault = other.brightnessDefault; roundedCorners = other.roundedCorners; + installOrientation = other.installOrientation; } // For debugging purposes @@ -508,6 +516,7 @@ final class DisplayDeviceInfo { sb.append(", roundedCorners ").append(roundedCorners); } sb.append(flagsToString(flags)); + sb.append(", installOrientation ").append(installOrientation); sb.append("}"); return sb.toString(); } diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 84de8229f37b..3a9ef0a83f6b 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -641,6 +641,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.roundedCorners = RoundedCorners.fromResources( res, mInfo.uniqueId, mInfo.width, mInfo.height); + mInfo.installOrientation = mStaticDisplayInfo.installOrientation; if (mStaticDisplayInfo.isInternal) { mInfo.type = Display.TYPE_INTERNAL; diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 4d1367a3d083..e3ecf498fbb0 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -429,6 +429,7 @@ final class LogicalDisplay { mBaseDisplayInfo.brightnessMaximum = deviceInfo.brightnessMaximum; mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault; mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners; + mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo.set(null); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 261aa32f093e..2dd7a10ffe29 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -16,6 +16,8 @@ package com.android.server.input; +import static android.view.KeyEvent.KEYCODE_UNKNOWN; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -38,6 +40,7 @@ import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.ContentObserver; +import android.graphics.PointF; import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayViewport; @@ -269,6 +272,10 @@ public class InputManagerService extends IInputManager.Stub private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<String, Integer>(); @GuardedBy("mAssociationLock") private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>(); + private final Object mPointerDisplayIdLock = new Object(); + // Forces the MouseCursorController to target a specific display id. + @GuardedBy("mPointerDisplayIdLock") + private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY; private static native long nativeInit(InputManagerService service, Context context, MessageQueue messageQueue); @@ -284,6 +291,8 @@ public class InputManagerService extends IInputManager.Stub int deviceId, int sourceMask, int sw); private static native boolean nativeHasKeys(long ptr, int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists); + private static native int nativeGetKeyCodeForKeyLocation(long ptr, int deviceId, + int locationKeyCode); private static native InputChannel nativeCreateInputChannel(long ptr, String name); private static native InputChannel nativeCreateInputMonitor(long ptr, int displayId, boolean isGestureMonitor, String name, int pid); @@ -341,6 +350,9 @@ public class InputManagerService extends IInputManager.Stub private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId); private static native void nativeNotifyPortAssociationsChanged(long ptr); private static native void nativeChangeUniqueIdAssociation(long ptr); + private static native void nativeNotifyPointerDisplayIdChanged(long ptr); + private static native void nativeSetDisplayEligibilityForPointerCapture(long ptr, int displayId, + boolean enabled); private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled); private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId); private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType); @@ -658,6 +670,22 @@ public class InputManagerService extends IInputManager.Stub } /** + * Returns the keyCode generated by the specified location on a US keyboard layout. + * This takes into consideration the currently active keyboard layout. + * + * @param deviceId The input device id. + * @param locationKeyCode The location of a key on a US keyboard layout. + * @return The KeyCode this physical key location produces. + */ + @Override // Binder call + public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) { + if (locationKeyCode <= KEYCODE_UNKNOWN || locationKeyCode > KeyEvent.getMaxKeyCode()) { + return KEYCODE_UNKNOWN; + } + return nativeGetKeyCodeForKeyLocation(mPtr, deviceId, locationKeyCode); + } + + /** * Transfer the current touch gesture to the provided window. * * @param destChannelToken The token of the window or input channel that should receive the @@ -1902,6 +1930,18 @@ public class InputManagerService extends IInputManager.Stub return result; } + private void setVirtualMousePointerDisplayId(int displayId) { + synchronized (mPointerDisplayIdLock) { + mOverriddenPointerDisplayId = displayId; + } + // TODO(b/215597605): trigger MousePositionTracker update + nativeNotifyPointerDisplayIdChanged(mPtr); + } + + private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { + nativeSetDisplayEligibilityForPointerCapture(mPtr, displayId, isEligible); + } + private static class VibrationInfo { private final long[] mPattern; private final int[] mAmplitudes; @@ -2575,6 +2615,7 @@ public class InputManagerService extends IInputManager.Stub synchronized (mInputFilterLock) { } synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */} synchronized (mLidSwitchLock) { /* Test if blocked by lid switch lock. */ } + synchronized (mPointerDisplayIdLock) { /* Test if blocked by pointer display id lock */ } nativeMonitor(mPtr); } @@ -2965,6 +3006,12 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private int getPointerDisplayId() { + synchronized (mPointerDisplayIdLock) { + // Prefer the override to all other displays. + if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { + return mOverriddenPointerDisplayId; + } + } return mWindowManagerCallbacks.getPointerDisplayId(); } @@ -3109,6 +3156,9 @@ public class InputManagerService extends IInputManager.Stub int getPointerDisplayId(); + /** Gets the x and y coordinates of the cursor's current position. */ + PointF getCursorPosition(); + /** * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event * occurred on a window that did not have focus. @@ -3427,6 +3477,21 @@ public class InputManagerService extends IInputManager.Stub } @Override + public void setVirtualMousePointerDisplayId(int pointerDisplayId) { + InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId); + } + + @Override + public PointF getCursorPosition() { + return mWindowManagerCallbacks.getCursorPosition(); + } + + @Override + public void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { + InputManagerService.this.setDisplayEligibilityForPointerCapture(displayId, isEligible); + } + + @Override public void registerLidSwitchCallback(LidSwitchCallback callbacks) { registerLidSwitchCallbackInternal(callbacks); } diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java new file mode 100644 index 000000000000..c86ebd26d871 --- /dev/null +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -0,0 +1,224 @@ +/* + * 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.server.inputmethod; + +import android.annotation.AnyThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Binder; +import android.os.DeadObjectException; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.util.Slog; +import android.view.InputChannel; +import android.view.MotionEvent; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputBinding; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.view.IInlineSuggestionsRequestCallback; +import com.android.internal.view.IInputContext; +import com.android.internal.view.IInputMethod; +import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.IInputSessionCallback; +import com.android.internal.view.InlineSuggestionsRequestInfo; + +import java.util.List; + +/** + * A wrapper class to invoke IPCs defined in {@link IInputMethod}. + */ +final class IInputMethodInvoker { + private static final String TAG = InputMethodManagerService.TAG; + private static final boolean DEBUG = InputMethodManagerService.DEBUG; + + @AnyThread + @Nullable + static IInputMethodInvoker create(@Nullable IInputMethod inputMethod) { + if (inputMethod == null) { + return null; + } + if (!Binder.isProxy(inputMethod)) { + // IInputMethodInvoker must be used only within the system_server and InputMethodService + // must not be running in the system_server. Therefore, "inputMethod" must be a Proxy. + throw new UnsupportedOperationException(inputMethod + " must have been a BinderProxy."); + } + return new IInputMethodInvoker(inputMethod); + } + + /** + * A simplified version of {@link android.os.Debug#getCaller()}. + * + * @return method name of the caller. + */ + @AnyThread + private static String getCallerMethodName() { + final StackTraceElement[] callStack = Thread.currentThread().getStackTrace(); + if (callStack.length <= 4) { + return "<bottom of call stack>"; + } + return callStack[4].getMethodName(); + } + + @AnyThread + private static void logRemoteException(@NonNull RemoteException e) { + if (DEBUG || !(e instanceof DeadObjectException)) { + Slog.w(TAG, "IPC failed at IInputMethodInvoker#" + getCallerMethodName(), e); + } + } + + @AnyThread + static int getBinderIdentityHashCode(@Nullable IInputMethodInvoker obj) { + if (obj == null) { + return 0; + } + + return System.identityHashCode(obj.mTarget); + } + + @NonNull + private final IInputMethod mTarget; + + private IInputMethodInvoker(@NonNull IInputMethod target) { + mTarget = target; + } + + @AnyThread + @NonNull + IBinder asBinder() { + return mTarget.asBinder(); + } + + @AnyThread + void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps, + int configChanges, boolean stylusHwSupported) { + try { + mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, + IInlineSuggestionsRequestCallback cb) { + try { + mTarget.onCreateInlineSuggestionsRequest(requestInfo, cb); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void bindInput(InputBinding binding) { + try { + mTarget.bindInput(binding); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void unbindInput() { + try { + mTarget.unbindInput(); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, + boolean restarting) { + try { + mTarget.startInput(startInputToken, inputContext, attribute, restarting); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void createSession(InputChannel channel, IInputSessionCallback callback) { + try { + mTarget.createSession(channel, callback); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void setSessionEnabled(IInputMethodSession session, boolean enabled) { + try { + mTarget.setSessionEnabled(session, enabled); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + // TODO(b/192412909): Convert this back to void method + @AnyThread + boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { + try { + mTarget.showSoftInput(showInputToken, flags, resultReceiver); + } catch (RemoteException e) { + logRemoteException(e); + return false; + } + return true; + } + + // TODO(b/192412909): Convert this back to void method + @AnyThread + boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) { + try { + mTarget.hideSoftInput(hideInputToken, flags, resultReceiver); + } catch (RemoteException e) { + logRemoteException(e); + return false; + } + return true; + } + + @AnyThread + void changeInputMethodSubtype(InputMethodSubtype subtype) { + try { + mTarget.changeInputMethodSubtype(subtype); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void canStartStylusHandwriting(int requestId) { + try { + mTarget.canStartStylusHandwriting(requestId); + } catch (RemoteException e) { + logRemoteException(e); + } + } + + @AnyThread + void startStylusHandwriting(InputChannel channel, List<MotionEvent> events) { + try { + mTarget.startStylusHandwriting(channel, events); + } catch (RemoteException e) { + logRemoteException(e); + } + } +} diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index db13deba1972..2230dcde0869 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -75,7 +75,7 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") @Nullable private String mCurId; @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId; @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent; - @GuardedBy("ImfLock.class") @Nullable private IInputMethod mCurMethod; + @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod; @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID; @GuardedBy("ImfLock.class") private IBinder mCurToken; @GuardedBy("ImfLock.class") private int mCurSeq; @@ -241,7 +241,7 @@ final class InputMethodBindingController { */ @GuardedBy("ImfLock.class") @Nullable - IInputMethod getCurMethod() { + IInputMethodInvoker getCurMethod() { return mCurMethod; } @@ -298,7 +298,7 @@ final class InputMethodBindingController { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected"); synchronized (ImfLock.class) { if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { - mCurMethod = IInputMethod.Stub.asInterface(service); + mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service)); updateCurrentMethodUid(); if (mCurToken == null) { Slog.w(TAG, "Service connected without a token!"); @@ -309,8 +309,8 @@ final class InputMethodBindingController { if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); final InputMethodInfo info = mMethodMap.get(mSelectedMethodId); mSupportsStylusHw = info.supportsStylusHandwriting(); - mService.executeOrSendInitializeIme(mCurMethod, mCurToken, - info.getConfigChanges(), mSupportsStylusHw); + mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges(), + mSupportsStylusHw); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); mService.reRequestCurrentClientSessionLocked(); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4da26f65e1da..0d41a37caf0d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -167,7 +167,6 @@ import com.android.internal.util.DumpUtils; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInlineSuggestionsResponseCallback; import com.android.internal.view.IInputContext; -import com.android.internal.view.IInputMethod; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; @@ -219,17 +218,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; - private static final int MSG_SHOW_IM_CONFIG = 3; private static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; - private static final int MSG_INITIALIZE_IME = 1040; - private static final int MSG_CREATE_SESSION = 1050; private static final int MSG_REMOVE_IME_SURFACE = 1060; private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; private static final int MSG_UPDATE_IME_WINDOW_STATUS = 1070; - private static final int MSG_START_HANDWRITING = 1100; - - private static final int MSG_START_INPUT = 2000; private static final int MSG_UNBIND_CLIENT = 3000; private static final int MSG_BIND_CLIENT = 3010; @@ -242,8 +235,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final int MSG_SYSTEM_UNLOCK_USER = 5000; private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; - private static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000; - private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; @@ -337,7 +328,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub static class SessionState { final ClientState client; - final IInputMethod method; + final IInputMethodInvoker method; IInputMethodSession session; InputChannel channel; @@ -346,14 +337,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public String toString() { return "SessionState{uid " + client.uid + " pid " + client.pid + " method " + Integer.toHexString( - System.identityHashCode(method)) + IInputMethodInvoker.getBinderIdentityHashCode(method)) + " session " + Integer.toHexString( System.identityHashCode(session)) + " channel " + channel + "}"; } - SessionState(ClientState _client, IInputMethod _method, + SessionState(ClientState _client, IInputMethodInvoker _method, IInputMethodSession _session, InputChannel _channel) { client = _client; method = _method; @@ -621,7 +612,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ @GuardedBy("ImfLock.class") @Nullable - private IInputMethod getCurMethodLocked() { + private IInputMethodInvoker getCurMethodLocked() { return mBindingController.getCurMethod(); } @@ -648,9 +639,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean mBoundToMethod; /** - * Currently enabled session. Only touched by service thread, not - * protected by a lock. + * Currently enabled session. */ + @GuardedBy("ImfLock.class") SessionState mEnabledSession; /** @@ -699,11 +690,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub new CopyOnWriteArrayList<>(); /** - * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the - * internal message queue. Any subsequent state change inside {@link InputMethodManagerService} - * will not affect those tasks that are already posted. + * Internal state snapshot when + * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IInputContext, EditorInfo, + * boolean)} is about to be called. * - * <p>Posting {@link #MSG_START_INPUT} message basically means that + * <p>Calling that IPC endpoint basically means that * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called * back in the current IME process shortly, which will also affect what the current IME starts * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this @@ -1959,15 +1950,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) { final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked()); try { - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (userId == mSettings.getCurrentUserId() && imi != null && imi.isInlineSuggestionsEnabled() && curMethod != null) { - executeOrSendMessage(curMethod, - mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, curMethod, - requestInfo, new InlineSuggestionsRequestCallbackDecorator(callback, - imi.getPackageName(), mCurTokenDisplayId, - getCurTokenLocked(), - this))); + final IInlineSuggestionsRequestCallback callbackImpl = + new InlineSuggestionsRequestCallbackDecorator(callback, + imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(), + this); + curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl); } else { callback.onInlineSuggestionsUnsupported(); } @@ -2195,13 +2185,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); if (mBoundToMethod) { mBoundToMethod = false; - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { - try { - curMethod.unbindInput(); - } catch (RemoteException e) { - // There is nothing interesting about the method dying. - } + curMethod.unbindInput(); } } mCurClient = null; @@ -2213,16 +2199,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - // TODO(b/215609403): This method will be removed soon! - private void executeOrSendMessage(IInputMethod target, Message msg) { - if (target.asBinder() instanceof Binder) { - throw new UnsupportedOperationException( - "InputMethodService is not supported to run in the system_server"); - } - handleMessage(msg); - msg.recycle(); - } - private void executeOrSendMessage(IInputMethodClient target, Message msg) { if (target.asBinder() instanceof Binder) { // This is supposed to be emulating the one-way semantics when the IME client is @@ -2245,13 +2221,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + mCurClient.client.asBinder()); if (mBoundToMethod) { mBoundToMethod = false; - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { - try { - curMethod.unbindInput(); - } catch (RemoteException e) { - // There is nothing interesting about the method dying. - } + curMethod.unbindInput(); } } @@ -2300,18 +2272,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @NonNull InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { if (!mBoundToMethod) { - IInputMethod curMethod = getCurMethodLocked(); - try { - curMethod.bindInput(mCurClient.binding); - } catch (RemoteException e) { - } + getCurMethodLocked().bindInput(mCurClient.binding); mBoundToMethod = true; } + final boolean restarting = !initial; final Binder startInputToken = new Binder(); final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), getCurTokenLocked(), - mCurTokenDisplayId, getCurIdLocked(), startInputReason, !initial, + mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode, getSequenceNumberLocked()); @@ -2330,9 +2299,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } final SessionState session = mCurClient.curSession; - executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO( - MSG_START_INPUT, 0 /* unused */, initial ? 0 : 1 /* restarting */, - startInputToken, session, mCurInputContext, mCurAttribute)); + setEnabledSessionLocked(session); + session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting); + if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null, @@ -2348,11 +2317,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub curId, getSequenceNumberLocked(), suppressesSpellChecker); } + /** + * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the + * selected InputMethod to the given focused IME client. + * + * Note that this should be called after validating if the IME client has IME focus. + * + * @see WindowManagerInternal#hasInputMethodClientFocus(IBinder, int, int, int) + */ @GuardedBy("ImfLock.class") @NonNull - InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext, - @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags, - @StartInputReason int startInputReason, int unverifiedTargetSdkVersion) { + private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, + IInputContext inputContext, @NonNull EditorInfo attribute, + @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, + int unverifiedTargetSdkVersion) { // If no method is currently selected, do nothing. String selectedMethodId = getSelectedMethodIdLocked(); if (selectedMethodId == null) { @@ -2374,10 +2352,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return InputBindResult.INVALID_PACKAGE_NAME; } - if (!mWindowManagerInternal.isUidAllowedOnDisplay(cs.selfReportedDisplayId, cs.uid)) { - // Wait, the client no longer has access to the display. - return InputBindResult.INVALID_DISPLAY_ID; - } // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. mDisplayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId, @@ -2519,11 +2493,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - @AnyThread - void executeOrSendInitializeIme(@NonNull IInputMethod inputMethod, @NonNull IBinder token, + @GuardedBy("ImfLock.class") + void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token, @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) { - executeOrSendMessage(inputMethod, mCaller.obtainMessageIOOO(MSG_INITIALIZE_IME, - configChanges, inputMethod, token, supportStylusHw)); + if (DEBUG) { + Slog.v(TAG, "Sending attach of token: " + token + " for display: " + + mCurTokenDisplayId); + } + inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), + configChanges, supportStylusHw); } @AnyThread @@ -2533,7 +2511,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) { + void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session, + InputChannel channel) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated"); try { synchronized (ImfLock.class) { @@ -2543,7 +2522,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub channel.dispose(); return; } - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null && method != null && curMethod.asBinder() == method.asBinder()) { if (mCurClient != null) { @@ -2597,22 +2576,38 @@ public class InputMethodManagerService extends IInputMethodManager.Stub void requestClientSessionLocked(ClientState cs) { if (!cs.sessionRequested) { if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); - InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); + final InputChannel serverChannel; + final InputChannel clientChannel; + { + final InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); + serverChannel = channels[0]; + clientChannel = channels[1]; + } + cs.sessionRequested = true; - IInputMethod curMethod = getCurMethodLocked(); - executeOrSendMessage(curMethod, mCaller.obtainMessageOOO( - MSG_CREATE_SESSION, curMethod, channels[1], - new IInputSessionCallback.Stub() { - @Override - public void sessionCreated(IInputMethodSession session) { - final long ident = Binder.clearCallingIdentity(); - try { - onSessionCreated(curMethod, session, channels[0]); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - })); + + final IInputMethodInvoker curMethod = getCurMethodLocked(); + final IInputSessionCallback.Stub callback = new IInputSessionCallback.Stub() { + @Override + public void sessionCreated(IInputMethodSession session) { + final long ident = Binder.clearCallingIdentity(); + try { + onSessionCreated(curMethod, session, serverChannel); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + }; + + try { + curMethod.createSession(clientChannel, callback); + } finally { + // Dispose the channel because the remote proxy will get its own copy when + // unparceled. + if (clientChannel != null) { + clientChannel.dispose(); + } + } } } @@ -2993,14 +2988,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } if (newSubtype != oldSubtype) { setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { - try { - updateSystemUiLocked(mImeWindowVis, mBackDisposition); - curMethod.changeInputMethodSubtype(newSubtype); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to call changeInputMethodSubtype"); - } + updateSystemUiLocked(mImeWindowVis, mBackDisposition); + curMethod.changeInputMethodSubtype(newSubtype); } } return; @@ -3077,9 +3068,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started"); - if (getCurMethodLocked() != null) { - executeOrSendMessage(getCurMethodLocked(), mCaller.obtainMessageIO( - MSG_START_HANDWRITING, ++mHwRequestId, getCurMethodLocked())); + final IInputMethodInvoker curMethod = getCurMethodLocked(); + if (curMethod != null) { + curMethod.canStartStylusHandwriting(++mHwRequestId); } } finally { Binder.restoreCallingIdentity(ident); @@ -3130,21 +3121,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } mBindingController.setCurrentMethodVisible(); - final IInputMethod curMethod = getCurMethodLocked(); + final IInputMethodInvoker curMethod = getCurMethodLocked(); if (curMethod != null) { // create a placeholder token for IMS so that IMS cannot inject windows into client app. Binder showInputToken = new Binder(); mShowRequestWindowMap.put(showInputToken, windowToken); final int showFlags = getImeShowFlagsLocked(); - try { - if (DEBUG) { - Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken - + ", " + showFlags + ", " + resultReceiver + ") for reason: " - + InputMethodDebug.softInputDisplayReasonToString(reason)); - } - curMethod.showSoftInput(showInputToken, showFlags, resultReceiver); + if (DEBUG) { + Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken + + ", " + showFlags + ", " + resultReceiver + ") for reason: " + + InputMethodDebug.softInputDisplayReasonToString(reason)); + } + // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. + if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) { onShowHideSoftInputRequested(true /* show */, windowToken, reason); - } catch (RemoteException e) { } mInputShown = true; return true; @@ -3172,14 +3162,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // be made before input is started in it. final ClientState cs = mClients.get(client.asBinder()); if (cs == null) { - throw new IllegalArgumentException( - "unknown client " + client.asBinder()); + throw new IllegalArgumentException("unknown client " + client.asBinder()); } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { + if (!isImeClientFocused(windowToken, cs)) { if (DEBUG) { - Slog.w(TAG, - "Ignoring hideSoftInput of uid " + uid + ": " + client); + Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); } return false; } @@ -3216,7 +3203,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only // IMMS#InputShown indicates that the software keyboard is shown. // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested. - IInputMethod curMethod = getCurMethodLocked(); + IInputMethodInvoker curMethod = getCurMethodLocked(); final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); boolean res; @@ -3232,10 +3219,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + ", " + resultReceiver + ") for reason: " + InputMethodDebug.softInputDisplayReasonToString(reason)); } - try { - curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver); + // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. + if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) { onShowHideSoftInputRequested(false /* show */, windowToken, reason); - } catch (RemoteException e) { } res = true; } else { @@ -3249,6 +3235,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return res; } + private boolean isImeClientFocused(IBinder windowToken, ClientState cs) { + final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus( + windowToken, cs.uid, cs.pid, cs.selfReportedDisplayId); + return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS; + } + @NonNull @Override public InputBindResult startInputOrWindowGainedFocus( @@ -3342,31 +3334,30 @@ public class InputMethodManagerService extends IInputMethodManager.Stub + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion); } - final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); - final ClientState cs = mClients.get(client.asBinder()); if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } - if (cs.selfReportedDisplayId != windowDisplayId) { - Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch." - + " from client:" + cs.selfReportedDisplayId - + " from window:" + windowDisplayId); - return InputBindResult.DISPLAY_ID_MISMATCH; - } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { - // Check with the window manager to make sure this client actually - // has a window with focus. If not, reject. This is thread safe - // because if the focus changes some time before or after, the - // next client receiving focus that has any interest in input will - // be calling through here after that change happens. - if (DEBUG) { - Slog.w(TAG, "Focus gain on non-focused client " + cs.client - + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); - } - return InputBindResult.NOT_IME_TARGET_WINDOW; + final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus( + windowToken, cs.uid, cs.pid, cs.selfReportedDisplayId); + switch (imeClientFocus) { + case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH: + Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch."); + return InputBindResult.DISPLAY_ID_MISMATCH; + case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW: + // Check with the window manager to make sure this client actually + // has a window with focus. If not, reject. This is thread safe + // because if the focus changes some time before or after, the + // next client receiving focus that has any interest in input will + // be calling through here after that change happens. + if (DEBUG) { + Slog.w(TAG, "Focus gain on non-focused client " + cs.client + + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); + } + return InputBindResult.NOT_IME_TARGET_WINDOW; + case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID: + return InputBindResult.INVALID_DISPLAY_ID; } if (mUserSwitchHandlerTask != null) { @@ -3594,8 +3585,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } - if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, - cs.selfReportedDisplayId)) { + if (!isImeClientFocused(mCurFocusedWindow, cs)) { Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); return false; } @@ -4136,7 +4126,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - /** Called right after {@link IInputMethod#showSoftInput}. */ + /** Called right after {@link com.android.internal.view.IInputMethod#showSoftInput}. */ @GuardedBy("ImfLock.class") private void onShowHideSoftInputRequested(boolean show, IBinder requestToken, @SoftInputShowHideReason int reason) { @@ -4187,22 +4177,17 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } - void setEnabledSessionInHandlerThread(SessionState session) { + @GuardedBy("ImfLock.class") + void setEnabledSessionLocked(SessionState session) { if (mEnabledSession != session) { if (mEnabledSession != null && mEnabledSession.session != null) { - try { - if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); - mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false); - } catch (RemoteException e) { - } + if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); + mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false); } mEnabledSession = session; if (mEnabledSession != null && mEnabledSession.session != null) { - try { - if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); - mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true); - } catch (RemoteException e) { - } + if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); + mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true); } } } @@ -4235,10 +4220,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mMenuController.showInputMethodMenu(showAuxSubtypes, displayId); return true; - case MSG_SHOW_IM_CONFIG: - showConfigureInputMethods(); - return true; - // --------------------------------------------------------- case MSG_HIDE_CURRENT_INPUT_METHOD: @@ -4248,40 +4229,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; - case MSG_INITIALIZE_IME: - args = (SomeArgs)msg.obj; - try { - if (DEBUG) { - synchronized (ImfLock.class) { - Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: " - + mCurTokenDisplayId); - } - } - final IBinder token = (IBinder) args.arg2; - ((IInputMethod) args.arg1).initializeInternal(token, - new InputMethodPrivilegedOperationsImpl(this, token), - msg.arg1, (boolean) args.arg3); - } catch (RemoteException e) { - } - args.recycle(); - return true; - case MSG_CREATE_SESSION: { - args = (SomeArgs)msg.obj; - IInputMethod method = (IInputMethod)args.arg1; - InputChannel channel = (InputChannel)args.arg2; - try { - method.createSession(channel, (IInputSessionCallback)args.arg3); - } catch (RemoteException e) { - } finally { - // Dispose the channel if the input method is not local to this process - // because the remote proxy will get its own copy when unparceled. - if (channel != null && Binder.isProxy(method)) { - channel.dispose(); - } - } - args.recycle(); - return true; - } case MSG_REMOVE_IME_SURFACE: { synchronized (ImfLock.class) { try { @@ -4313,25 +4260,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // --------------------------------------------------------- - case MSG_START_INPUT: { - final boolean restarting = msg.arg2 != 0; - args = (SomeArgs) msg.obj; - final IBinder startInputToken = (IBinder) args.arg1; - final SessionState session = (SessionState) args.arg2; - final IInputContext inputContext = (IInputContext) args.arg3; - final EditorInfo editorInfo = (EditorInfo) args.arg4; - try { - setEnabledSessionInHandlerThread(session); - session.method.startInput(startInputToken, inputContext, editorInfo, - restarting); - } catch (RemoteException e) { - } - args.recycle(); - return true; - } - - // --------------------------------------------------------- - case MSG_UNBIND_CLIENT: try { ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2); @@ -4405,23 +4333,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } // --------------------------------------------------------------- - case MSG_INLINE_SUGGESTIONS_REQUEST: { - args = (SomeArgs) msg.obj; - final InlineSuggestionsRequestInfo requestInfo = - (InlineSuggestionsRequestInfo) args.arg2; - final IInlineSuggestionsRequestCallback callback = - (IInlineSuggestionsRequestCallback) args.arg3; - try { - ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(requestInfo, - callback); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e); - } - args.recycle(); - return true; - } - - // --------------------------------------------------------------- case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: { if (mAudioManagerInternal == null) { mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); @@ -4431,13 +4342,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; } - case MSG_START_HANDWRITING: - try { - (((IInputMethod) msg.obj)).canStartStylusHandwriting(msg.arg1); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e); - } - return true; } return false; } @@ -4450,12 +4354,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return; } - try { - // TODO: replace null with actual Channel, MotionEvents - getCurMethodLocked().startStylusHandwriting(null, null); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e); - } + // TODO: replace null with actual Channel, MotionEvents + getCurMethodLocked().startStylusHandwriting(null, null); } } @@ -4716,14 +4616,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mContext.startActivityAsUser(intent, null, UserHandle.of(userId)); } - private void showConfigureInputMethods() { - Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); - } - // ---------------------------------------------------------------------- /** @@ -5196,7 +5088,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args, boolean isCritical) { - IInputMethod method; + IInputMethodInvoker method; ClientState client; ClientState focusedWindowClient; diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 682a27adc15f..8f051303bf03 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE; import static android.Manifest.permission.MANAGE_BIOMETRIC; import static android.Manifest.permission.READ_CONTACTS; import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE; import static android.content.Context.KEYGUARD_SERVICE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_ALL; @@ -117,6 +120,7 @@ import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; @@ -642,12 +646,9 @@ public class LockSettingsService extends ILockSettings.Stub { private void showEncryptionNotificationForProfile(UserHandle user) { Resources r = mContext.getResources(); - CharSequence title = r.getText( - com.android.internal.R.string.profile_encrypted_title); - CharSequence message = r.getText( - com.android.internal.R.string.profile_encrypted_message); - CharSequence detail = r.getText( - com.android.internal.R.string.profile_encrypted_detail); + CharSequence title = getEncryptionNotificationTitle(); + CharSequence message = getEncryptionNotificationMessage(); + CharSequence detail = getEncryptionNotificationDetail(); final KeyguardManager km = (KeyguardManager) mContext.getSystemService(KEYGUARD_SERVICE); final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null, @@ -663,6 +664,24 @@ public class LockSettingsService extends ILockSettings.Stub { showEncryptionNotification(user, title, message, detail, intent); } + private String getEncryptionNotificationTitle() { + return mInjector.getDevicePolicyManager().getString( + PROFILE_ENCRYPTED_TITLE, + () -> mContext.getString(R.string.profile_encrypted_title)); + } + + private String getEncryptionNotificationDetail() { + return mInjector.getDevicePolicyManager().getString( + PROFILE_ENCRYPTED_DETAIL, + () -> mContext.getString(R.string.profile_encrypted_detail)); + } + + private String getEncryptionNotificationMessage() { + return mInjector.getDevicePolicyManager().getString( + PROFILE_ENCRYPTED_MESSAGE, + () -> mContext.getString(R.string.profile_encrypted_message)); + } + private void showEncryptionNotification(UserHandle user, CharSequence title, CharSequence message, CharSequence detail, PendingIntent intent) { if (DEBUG) Slog.v(TAG, "showing encryption notification, user: " + user.getIdentifier()); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index a8383b612941..e555c1356df7 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -63,7 +63,6 @@ import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkIdentity.OEM_NONE; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.SNOOZE_NEVER; import static android.net.NetworkPolicy.WARNING_DISABLED; @@ -1498,13 +1497,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int i = 0; i < mSubIdToSubscriberId.size(); i++) { final int subId = mSubIdToSubscriberId.keyAt(i); final String subscriberId = mSubIdToSubscriberId.valueAt(i); - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, - true, OEM_NONE); - /* While OEM_NONE indicates "any non OEM managed network", OEM_NONE is meant to be a - * placeholder value here. The probeIdent is matched against a NetworkTemplate which - * should have its OEM managed value set to OEM_MANAGED_ALL, which will cause the - * template to match probeIdent without regard to OEM managed status. */ + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); if (template.matches(probeIdent)) { return subId; } @@ -1737,9 +1734,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // find and update the carrier NetworkPolicy for this subscriber id boolean policyUpdated = false; - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true, - OEM_NONE); + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { final NetworkTemplate template = mNetworkPolicy.keyAt(i); if (template.matches(probeIdent)) { @@ -1967,10 +1966,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (int i = 0; i < mSubIdToSubscriberId.size(); i++) { final int subId = mSubIdToSubscriberId.keyAt(i); final String subscriberId = mSubIdToSubscriberId.valueAt(i); - - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, - true, OEM_NONE); + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); // Template is matched when subscriber id matches. if (template.matches(probeIdent)) { matchingSubIds.add(subId); @@ -2074,11 +2074,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { for (final NetworkStateSnapshot snapshot : snapshots) { mNetIdToSubId.put(snapshot.getNetwork().getNetId(), parseSubId(snapshot)); - // Policies matched by NPMS only match by subscriber ID or by network ID. Thus subtype - // in the object created here is never used and its value doesn't matter, so use - // NETWORK_TYPE_UNKNOWN. - final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, snapshot, - true, TelephonyManager.NETWORK_TYPE_UNKNOWN /* subType */); + // Policies matched by NPMS only match by subscriber ID or by network ID. + final NetworkIdentity ident = new NetworkIdentity.Builder() + .setNetworkStateSnapshot(snapshot).setDefaultNetwork(true).build(); identified.put(snapshot, ident); } @@ -2275,9 +2273,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @GuardedBy("mNetworkPoliciesSecondLock") private boolean ensureActiveCarrierPolicyAL(int subId, String subscriberId) { // Poke around to see if we already have a policy - final NetworkIdentity probeIdent = new NetworkIdentity(TYPE_MOBILE, - TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false, true, true, - OEM_NONE); + final NetworkIdentity probeIdent = new NetworkIdentity.Builder() + .setType(TYPE_MOBILE) + .setSubscriberId(subscriberId) + .setMetered(true) + .setDefaultNetwork(true).build(); for (int i = mNetworkPolicy.size() - 1; i >= 0; i--) { final NetworkTemplate template = mNetworkPolicy.keyAt(i); if (template.matches(probeIdent)) { @@ -2687,7 +2687,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final List<WifiConfiguration> configs = wm.getConfiguredNetworks(); for (int i = 0; i < configs.size(); ++i) { final WifiConfiguration config = configs.get(i); - for (String key : config.getAllPersistableNetworkKeys()) { + for (String key : config.getAllNetworkKeys()) { final Boolean metered = wifiNetworkKeys.get(key); if (metered != null) { Slog.d(TAG, "Found network " + key + "; upgrading metered hint"); diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java index bdb48bdbf6d9..4b999e9fac7f 100644 --- a/services/core/java/com/android/server/pm/AppDataHelper.java +++ b/services/core/java/com/android/server/pm/AppDataHelper.java @@ -24,7 +24,6 @@ import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.PackageManager; -import com.android.server.pm.pkg.SELinuxUtil; import android.content.pm.UserInfo; import android.os.CreateAppDataArgs; import android.os.Environment; @@ -35,6 +34,9 @@ import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; import android.os.storage.VolumeInfo; +import android.security.AndroidKeyStoreMaintenance; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; import android.text.TextUtils; import android.util.Log; import android.util.Slog; @@ -46,6 +48,7 @@ import com.android.server.SystemServerInitThreadPool; import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.pkg.SELinuxUtil; import dalvik.system.VMRuntime; @@ -156,8 +159,7 @@ final class AppDataHelper { * <ul> * <li>If previousAppId < 0, app data will be migrated to the new app ID * <li>If previousAppId == 0, no migration will happen and data will be wiped and recreated - * <li>If previousAppId > 0, it will migrate all data owned by previousAppId - * to the new app ID + * <li>If previousAppId > 0, app data owned by previousAppId will be migrated to the new app ID * </ul> */ private @NonNull CompletableFuture<?> prepareAppData(@NonNull Installer.Batch batch, @@ -545,6 +547,22 @@ final class AppDataHelper { return prepareAppDataFuture; } + public void migrateKeyStoreData(int previousAppId, int appId) { + for (int userId : mPm.resolveUserIds(UserHandle.USER_ALL)) { + int srcUid = UserHandle.getUid(userId, previousAppId); + int destUid = UserHandle.getUid(userId, appId); + final KeyDescriptor[] keys = AndroidKeyStoreMaintenance.listEntries(Domain.APP, srcUid); + if (keys == null) continue; + for (final KeyDescriptor key : keys) { + KeyDescriptor dest = new KeyDescriptor(); + dest.domain = Domain.APP; + dest.nspace = destUid; + dest.alias = key.alias; + AndroidKeyStoreMaintenance.migrateKeyNamespace(key, dest); + } + } + } + void clearAppDataLIF(AndroidPackage pkg, int userId, int flags) { if (pkg == null) { return; @@ -629,4 +647,18 @@ final class AppDataHelper { pkg.getProperties().get(PackageManager.PROPERTY_NO_APP_DATA_STORAGE); return noAppDataProp == null || !noAppDataProp.getBoolean(); } + + /** + * Remove entries from the keystore daemon. Will only remove if the {@code appId} is valid. + */ + public void clearKeystoreData(int userId, int appId) { + if (appId < 0) { + return; + } + + for (int realUserId : mPm.resolveUserIds(userId)) { + AndroidKeyStoreMaintenance.clearNamespace( + Domain.APP, UserHandle.getUid(realUserId, appId)); + } + } } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 9a80a4e558c4..48689a8da335 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -485,8 +485,7 @@ final class DeletePackageHelper { mAppDataHelper.destroyAppDataLIF(pkg, nextUserId, FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); } - PackageManagerService.removeKeystoreDataIfNeeded(mUserManagerInternal, nextUserId, - ps.getAppId()); + mAppDataHelper.clearKeystoreData(nextUserId, ps.getAppId()); preferredActivityHelper.clearPackagePreferredActivities(ps.getPackageName(), nextUserId); mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 6a5d76bc248b..80699ac5dd82 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2245,6 +2245,17 @@ final class InstallPackageHelper { if (reconciledPkg.mScanResult.needsNewAppId()) { // Only set previousAppId if the app is migrating out of shared UID previousAppId = reconciledPkg.mScanResult.mPreviousAppId; + + if (pkg.shouldInheritKeyStoreKeys()) { + // Migrate keystore data + mAppDataHelper.migrateKeyStoreData( + previousAppId, reconciledPkg.mPkgSetting.getAppId()); + } + + if (reconciledPkg.mInstallResult.mRemovedInfo.mRemovedAppId == previousAppId) { + // If the previous app ID is removed, clear the keys + mAppDataHelper.clearKeystoreData(UserHandle.USER_ALL, previousAppId); + } } mAppDataHelper.prepareAppDataPostCommitLIF(pkg, previousAppId); if (reconciledPkg.mPrepareResult.mClearCodeCache) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 1f10d77086d3..ccc375ff85f2 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -16,6 +16,8 @@ package com.android.server.pm; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -29,6 +31,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PackageDeleteObserver; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.Context; import android.content.Intent; @@ -1312,7 +1315,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mPackageName = packageName; if (showNotification) { mNotification = buildSuccessNotification(mContext, - mContext.getResources().getString(R.string.package_deleted_device_owner), + getDeviceOwnerDeletedPackageMsg(), packageName, userId); } else { @@ -1320,6 +1323,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + private String getDeviceOwnerDeletedPackageMsg() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PACKAGE_DELETED_BY_DO, + () -> mContext.getString(R.string.package_updated_device_owner)); + } + @Override public void onUserActionRequired(Intent intent) { if (mTarget == null) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index e0f1b0b44cf8..d9ade967d0b4 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -17,6 +17,8 @@ package com.android.server.pm; import static android.app.AppOpsManager.MODE_DEFAULT; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTALLED_BY_DO; +import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO; import static android.content.pm.DataLoaderType.INCREMENTAL; import static android.content.pm.DataLoaderType.STREAMING; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; @@ -56,6 +58,7 @@ import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.app.admin.DevicePolicyEventLogger; +import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.content.ComponentName; import android.content.Context; @@ -91,7 +94,6 @@ import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ApkLite; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; -import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.graphics.Bitmap; @@ -156,6 +158,7 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.dex.DexManager; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import libcore.io.IoUtils; import libcore.util.EmptyArray; @@ -4336,9 +4339,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (INSTALL_SUCCEEDED == returnCode && showNotification) { boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING); Notification notification = PackageInstallerService.buildSuccessNotification(context, - context.getResources() - .getString(update ? R.string.package_updated_device_owner : - R.string.package_installed_device_owner), + getDeviceOwnerInstalledPackageMsg(context, update), basePackageName, userId); if (notification != null) { @@ -4370,6 +4371,15 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private static String getDeviceOwnerInstalledPackageMsg(Context context, boolean update) { + DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return update + ? dpm.getString(PACKAGE_UPDATED_BY_DO, + () -> context.getString(R.string.package_updated_device_owner)) + : dpm.getString(PACKAGE_INSTALLED_BY_DO, + () -> context.getString(R.string.package_installed_device_owner)); + } + /** * This method doesn't change internal states and is safe to call outside the lock. */ diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 81a2c0046adf..e00f4f591d54 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -167,7 +167,6 @@ import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.provider.Settings.Global; import android.provider.Settings.Secure; -import android.security.KeyStore; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -951,6 +950,7 @@ public class PackageManagerService extends IPackageManager.Stub final @Nullable String mRetailDemoPackage; final @Nullable String mOverlayConfigSignaturePackage; final @Nullable String mRecentsPackage; + final @Nullable String mAmbientContextDetectionPackage; @GuardedBy("mLock") private final PackageUsage mPackageUsage = new PackageUsage(); @@ -1669,6 +1669,7 @@ public class PackageManagerService extends IPackageManager.Stub mSystemTextClassifierPackageName = testParams.systemTextClassifierPackage; mRetailDemoPackage = testParams.retailDemoPackage; mRecentsPackage = testParams.recentsPackage; + mAmbientContextDetectionPackage = testParams.ambientContextDetectionPackage; mConfiguratorPackage = testParams.configuratorPackage; mAppPredictionServicePackage = testParams.appPredictionServicePackage; mIncidentReportApproverPackage = testParams.incidentReportApproverPackage; @@ -1996,6 +1997,7 @@ public class PackageManagerService extends IPackageManager.Stub mRetailDemoPackage = getRetailDemoPackageName(); mOverlayConfigSignaturePackage = getOverlayConfigSignaturePackageName(); mRecentsPackage = getRecentsPackageName(); + mAmbientContextDetectionPackage = getAmbientContextDetectionPackageName(); // Now that we know all of the shared libraries, update all clients to have // the correct library paths. @@ -5108,7 +5110,7 @@ public class PackageManagerService extends IPackageManager.Stub FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL); final int appId = UserHandle.getAppId(pkg.getUid()); - removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), userId, appId); + mAppDataHelper.clearKeystoreData(userId, appId); UserManagerInternal umInternal = mInjector.getUserManagerInternal(); StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class); @@ -5181,30 +5183,6 @@ public class PackageManagerService extends IPackageManager.Stub } } - /** - * Remove entries from the keystore daemon. Will only remove it if the - * {@code appId} is valid. - */ - static void removeKeystoreDataIfNeeded(UserManagerInternal um, @UserIdInt int userId, - @AppIdInt int appId) { - if (appId < 0) { - return; - } - - final KeyStore keyStore = KeyStore.getInstance(); - if (keyStore != null) { - if (userId == UserHandle.USER_ALL) { - for (final int individual : um.getUserIds()) { - keyStore.clearUid(UserHandle.getUid(individual, appId)); - } - } else { - keyStore.clearUid(UserHandle.getUid(userId, appId)); - } - } else { - Slog.w(TAG, "Could not contact keystore to clear entries for app id " + appId); - } - } - @Override public void deleteApplicationCacheFiles(final String packageName, final IPackageDataObserver observer) { @@ -5643,6 +5621,11 @@ public class PackageManagerService extends IPackageManager.Stub return mPmInternal.getSetupWizardPackageName(); } + public @Nullable String getAmbientContextDetectionPackageName() { + return ensureSystemPackageName(getPackageFromComponentString( + R.string.config_defaultAmbientContextDetectionService)); + } + public String getIncidentReportApproverPackageName() { return ensureSystemPackageName(mContext.getString( R.string.config_incidentReportApproverPackage)); @@ -8718,6 +8701,8 @@ public class PackageManagerService extends IPackageManager.Stub return mComputer.filterOnlySystemPackages(mConfiguratorPackage); case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER: return mComputer.filterOnlySystemPackages(mIncidentReportApproverPackage); + case PackageManagerInternal.PACKAGE_AMBIENT_CONTEXT_DETECTION: + return mComputer.filterOnlySystemPackages(mAmbientContextDetectionPackage); case PackageManagerInternal.PACKAGE_APP_PREDICTOR: return mComputer.filterOnlySystemPackages(mAppPredictionServicePackage); case PackageManagerInternal.PACKAGE_COMPANION: diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index 0d6555c52623..db606863248a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -91,6 +91,7 @@ public final class PackageManagerServiceTestParams { public ViewCompiler viewCompiler; public @Nullable String retailDemoPackage; public @Nullable String recentsPackage; + public @Nullable String ambientContextDetectionPackage; public ComponentName resolveComponentName; public ArrayMap<String, AndroidPackage> packages; public boolean enableFreeCacheV2; diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java index b0e03403b653..7e898cbe86b0 100644 --- a/services/core/java/com/android/server/pm/RemovePackageHelper.java +++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java @@ -29,7 +29,6 @@ import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.content.pm.PackageManager; -import com.android.server.pm.pkg.component.ParsedInstrumentation; import android.os.UserHandle; import android.os.incremental.IncrementalManager; import android.util.Log; @@ -43,6 +42,7 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.pm.pkg.component.ParsedInstrumentation; import java.io.File; import java.util.Collections; @@ -330,8 +330,7 @@ final class RemovePackageHelper { if (removedAppId != -1) { // A user ID was deleted here. Go through all users and remove it // from KeyStore. - mPm.removeKeystoreDataIfNeeded( - mUserManagerInternal, UserHandle.USER_ALL, removedAppId); + mAppDataHelper.clearKeystoreData(UserHandle.USER_ALL, removedAppId); } } } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index d29dbbc7c04a..d6e88f40d05d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3675,9 +3675,18 @@ public class UserManagerService extends IUserManager.Stub { @UserInfoFlag int flags, @UserIdInt int parentId, @Nullable String[] disallowedPackages) throws UserManager.CheckedUserOperationException { - String restriction = (UserManager.isUserTypeManagedProfile(userType)) - ? UserManager.DISALLOW_ADD_MANAGED_PROFILE - : UserManager.DISALLOW_ADD_USER; + + // Checking user restriction before creating new user, + // default check is for DISALLOW_ADD_USER + // If new user is of type CLONE, check if creation of clone profile is allowed + // If new user is of type MANAGED, check if creation of managed profile is allowed + String restriction = UserManager.DISALLOW_ADD_USER; + if (UserManager.isUserTypeCloneProfile(userType)) { + restriction = UserManager.DISALLOW_ADD_CLONE_PROFILE; + } else if (UserManager.isUserTypeManagedProfile(userType)) { + restriction = UserManager.DISALLOW_ADD_MANAGED_PROFILE; + } + enforceUserRestriction(restriction, UserHandle.getCallingUserId(), "Cannot add user"); return createUserInternalUnchecked(name, userType, flags, parentId, diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 0f3b4bcfac56..1fa901352c3d 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -101,6 +101,7 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_FACTORY_RESET, UserManager.DISALLOW_ADD_USER, UserManager.DISALLOW_ADD_MANAGED_PROFILE, + UserManager.DISALLOW_ADD_CLONE_PROFILE, UserManager.ENSURE_VERIFY_APPS, UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, diff --git a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java index 4bbe3733719e..d455be7e4a69 100644 --- a/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java +++ b/services/core/java/com/android/server/pm/permission/OneTimePermissionUserManager.java @@ -162,7 +162,10 @@ public class OneTimePermissionUserManager { * The delay to wait before revoking on the event an app is terminated. Recommended to be long * enough so that apps don't lose permission on an immediate restart */ - private static long getKilledDelayMillis() { + private long getKilledDelayMillis(boolean isSelfRevokedPermissionSession) { + if (isSelfRevokedPermissionSession) { + return 0; + } return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS); } @@ -175,6 +178,18 @@ public class OneTimePermissionUserManager { mContext.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED)); } + void setSelfRevokedPermissionSession(int uid) { + synchronized (mLock) { + PackageInactivityListener listener = mListeners.get(uid); + if (listener == null) { + Log.e(LOG_TAG, "Could not set session for uid " + uid + + " as self-revoke session: session not found"); + return; + } + listener.setSelfRevokedPermissionSession(); + } + } + /** * A class which watches a package for inactivity and notifies the permission controller when * the package becomes inactive @@ -189,6 +204,7 @@ public class OneTimePermissionUserManager { private final int mImportanceToResetTimer; private final int mImportanceToKeepSessionAlive; + private boolean mIsSelfRevokedPermissionSession; private boolean mIsAlarmSet; private boolean mIsFinished; @@ -255,7 +271,7 @@ public class OneTimePermissionUserManager { } onImportanceChanged(mUid, imp); } - }, mToken, getKilledDelayMillis()); + }, mToken, getKilledDelayMillis(mIsSelfRevokedPermissionSession)); return; } if (importance > mImportanceToResetTimer) { @@ -291,6 +307,14 @@ public class OneTimePermissionUserManager { } /** + * Marks the session as a self-revoke session, which does not delay the revocation when + * the app is restarting. + */ + public void setSelfRevokedPermissionSession() { + mIsSelfRevokedPermissionSession = true; + } + + /** * Set the alarm which will callback when the package is inactive */ @GuardedBy("mInnerLock") diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index 8e16835f8b62..317730a9f606 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -66,6 +66,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.AndroidFuture; import com.android.internal.util.Preconditions; import com.android.internal.util.function.TriFunction; import com.android.server.LocalServices; @@ -560,7 +561,16 @@ public class PermissionManagerService extends IPermissionManager.Stub { @Override public void revokeOwnPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions) { - mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions); + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + AndroidFuture<Void> future = new AndroidFuture<>(); + future.whenComplete((result, err) -> { + if (err == null) { + getOneTimePermissionUserManager(callingUserId) + .setSelfRevokedPermissionSession(callingUid); + } + }); + mPermissionManagerServiceImpl.revokeOwnPermissionsOnKill(packageName, permissions, future); } @Override diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index c46503829080..9b3d6d6eb3de 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -113,6 +113,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.infra.AndroidFuture; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.RoSystemProperties; @@ -1592,7 +1593,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } @Override - public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions) { + public void revokeOwnPermissionsOnKill(String packageName, List<String> permissions, + AndroidFuture<Void> callback) { final int callingUid = Binder.getCallingUid(); int callingUserId = UserHandle.getUserId(callingUid); int targetPackageUid = mPackageManagerInt.getPackageUid(packageName, 0, callingUserId); @@ -1607,7 +1609,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt + permName + " because it does not hold that permission"); } } - mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions); + mPermissionControllerManager.revokeOwnPermissionsOnKill(packageName, permissions, + callback); } private boolean mayManageRolePermission(int uid) { @@ -3181,9 +3184,13 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt ps.updatePermissionFlags(bp, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED | FLAG_PERMISSION_REVOKE_WHEN_REQUESTED, PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED); - // TODO(b/205888750): remove revoke once propagated through droidfood - if (ps.isPermissionGranted(newPerm)) { + // TODO(b/205888750): remove if/else block once propagated through droidfood + if (ps.isPermissionGranted(newPerm) + && pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M) { ps.revokePermission(bp); + } else if (!ps.isPermissionGranted(newPerm) + && pkg.getTargetSdkVersion() < Build.VERSION_CODES.M) { + ps.grantPermission(bp); } } } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java index b558e3dee623..91c558b2f35e 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java @@ -27,6 +27,7 @@ import android.content.pm.permission.SplitPermissionInfoParcelable; import android.permission.IOnPermissionsChangeListener; import android.permission.PermissionManagerInternal; +import com.android.internal.infra.AndroidFuture; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.FileDescriptor; @@ -343,8 +344,10 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte * * @param packageName The name of the package for which the permissions will be revoked. * @param permissions List of permissions to be revoked. + * @param callback Callback called when the revocation request has been completed. */ - void revokeOwnPermissionsOnKill(String packageName, List<String> permissions); + void revokeOwnPermissionsOnKill(String packageName, List<String> permissions, + AndroidFuture<Void> callback); /** * Get whether you should show UI with rationale for requesting a permission. You should do this diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java index 18a6435d17c8..52d9b7a3abc1 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java @@ -26,6 +26,10 @@ import android.content.pm.FeatureGroupInfo; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager.Property; import android.content.pm.SigningDetails; +import android.os.Bundle; +import android.util.SparseArray; +import android.util.SparseIntArray; + import com.android.server.pm.pkg.component.ParsedActivity; import com.android.server.pm.pkg.component.ParsedApexSystemService; import com.android.server.pm.pkg.component.ParsedAttribution; @@ -37,9 +41,6 @@ import com.android.server.pm.pkg.component.ParsedProcess; import com.android.server.pm.pkg.component.ParsedProvider; import com.android.server.pm.pkg.component.ParsedService; import com.android.server.pm.pkg.component.ParsedUsesPermission; -import android.os.Bundle; -import android.util.SparseArray; -import android.util.SparseIntArray; import java.security.PublicKey; import java.util.Map; @@ -286,6 +287,9 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage setInstallLocation(int installLocation); + /** @see R#styleable.AndroidManifest_inheritKeyStoreKeys */ + ParsingPackage setInheritKeyStoreKeys(boolean inheritKeyStoreKeys); + ParsingPackage setLabelRes(int labelRes); ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp); diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java index c4de862bccd9..1f21938fc706 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageImpl.java @@ -492,6 +492,10 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, ENABLED, DISALLOW_PROFILING, REQUEST_FOREGROUND_SERVICE_EXEMPTION, + ATTRIBUTIONS_ARE_USER_VISIBLE, + RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED, + SDK_LIBRARY, + INHERIT_KEYSTORE_KEYS, }) public @interface Values {} private static final long EXTERNAL_STORAGE = 1L; @@ -544,6 +548,7 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, private static final long ATTRIBUTIONS_ARE_USER_VISIBLE = 1L << 47; private static final long RESET_ENABLED_SETTINGS_ON_APP_DATA_CLEARED = 1L << 48; private static final long SDK_LIBRARY = 1L << 49; + private static final long INHERIT_KEYSTORE_KEYS = 1L << 50; } private ParsingPackageImpl setBoolean(@Booleans.Values long flag, boolean value) { @@ -2371,6 +2376,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, } @Override + public boolean shouldInheritKeyStoreKeys() { + return getBoolean(Booleans.INHERIT_KEYSTORE_KEYS); + } + + @Override public ParsingPackageImpl setBaseRevisionCode(int value) { baseRevisionCode = value; return this; @@ -2514,6 +2524,11 @@ public class ParsingPackageImpl implements ParsingPackage, ParsingPackageHidden, } @Override + public ParsingPackageImpl setInheritKeyStoreKeys(boolean value) { + return setBoolean(Booleans.INHERIT_KEYSTORE_KEYS, value); + } + + @Override public ParsingPackageImpl setLabelRes(int value) { labelRes = value; return this; diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java index 149711287f32..4b659a14418f 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageRead.java @@ -350,4 +350,9 @@ public interface ParsingPackageRead extends PkgWithoutStateAppInfo, PkgWithoutSt * @see R.styleable#AndroidManifestApplication_localeConfig */ int getLocaleConfigRes(); + + /** + * @see R.styleable#AndroidManifest_inheritKeyStoreKeys + */ + boolean shouldInheritKeyStoreKeys(); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 1ce01f633791..bf7c55f0f59e 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -868,7 +868,9 @@ public class ParsingPackageUtils { .setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX, R.styleable.AndroidManifest_targetSandboxVersion, sa)) /* Set the global "on SD card" flag */ - .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0); + .setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0) + .setInheritKeyStoreKeys(bool(false, + R.styleable.AndroidManifest_inheritKeyStoreKeys, sa)); boolean foundApp = false; final int depth = parser.getDepth(); diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index 593250cb9293..06ce4a41afec 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -16,6 +16,8 @@ package com.android.server.trust; +import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_DISPLAY_MESSAGE; + import android.annotation.TargetApi; import android.app.AlarmManager; import android.app.PendingIntent; @@ -99,6 +101,7 @@ public class TrustAgentWrapper { // Trust state private boolean mTrusted; private CharSequence mMessage; + private boolean mDisplayTrustGrantedMessage; private boolean mTrustDisabledByDpm; private boolean mManagingTrust; private IBinder mSetTrustAgentFeaturesToken; @@ -132,6 +135,7 @@ public class TrustAgentWrapper { mTrusted = true; mMessage = (CharSequence) msg.obj; int flags = msg.arg1; + mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0; long durationMs = msg.getData().getLong(DATA_DURATION); if (durationMs > 0) { final long duration; @@ -166,6 +170,7 @@ public class TrustAgentWrapper { // Fall through. case MSG_REVOKE_TRUST: mTrusted = false; + mDisplayTrustGrantedMessage = false; mMessage = null; mHandler.removeMessages(MSG_TRUST_TIMEOUT); if (msg.what == MSG_REVOKE_TRUST) { @@ -199,6 +204,7 @@ public class TrustAgentWrapper { mManagingTrust = msg.arg1 != 0; if (!mManagingTrust) { mTrusted = false; + mDisplayTrustGrantedMessage = false; mMessage = null; } mTrustManagerService.mArchive.logManagingTrust(mUserId, mName, mManagingTrust); @@ -271,12 +277,13 @@ public class TrustAgentWrapper { private ITrustAgentServiceCallback mCallback = new ITrustAgentServiceCallback.Stub() { @Override - public void grantTrust(CharSequence userMessage, long durationMs, int flags) { - if (DEBUG) Slog.d(TAG, "enableTrust(" + userMessage + ", durationMs = " + durationMs + public void grantTrust(CharSequence message, long durationMs, int flags) { + if (DEBUG) { + Slog.d(TAG, "enableTrust(" + message + ", durationMs = " + durationMs + ", flags = " + flags + ")"); + } - Message msg = mHandler.obtainMessage( - MSG_GRANT_TRUST, flags, 0, userMessage); + Message msg = mHandler.obtainMessage(MSG_GRANT_TRUST, flags, 0, message); msg.getData().putLong(DATA_DURATION, durationMs); msg.sendToTarget(); } @@ -592,6 +599,14 @@ public class TrustAgentWrapper { return mMessage; } + /** + * Whether the trust agent would like to display {@link #getMessage()} to the user when trust + * is granted. + */ + public boolean shouldDisplayTrustGrantedMessage() { + return mDisplayTrustGrantedMessage; + } + public void destroy() { mHandler.removeMessages(MSG_RESTART_TIMEOUT); diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 150eebb8276e..9bed24d05f3d 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -75,7 +75,6 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -392,7 +391,6 @@ public class TrustManagerService extends SystemService { } } - public void updateTrust(int userId, int flags) { updateTrust(userId, flags, false /* isFromUnlock */); } @@ -432,7 +430,7 @@ public class TrustManagerService extends SystemService { changed = mUserIsTrusted.get(userId) != trusted; mUserIsTrusted.put(userId, trusted); } - dispatchOnTrustChanged(trusted, userId, flags); + dispatchOnTrustChanged(trusted, userId, flags, getTrustGrantedMessages(userId)); if (changed) { refreshDeviceLockedForUser(userId); if (!trusted) { @@ -952,6 +950,24 @@ public class TrustManagerService extends SystemService { return false; } + private List<String> getTrustGrantedMessages(int userId) { + if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) { + return new ArrayList<>(); + } + + List<String> trustGrantedMessages = new ArrayList<>(); + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == userId + && info.agent.isTrusted() + && info.agent.shouldDisplayTrustGrantedMessage() + && !TextUtils.isEmpty(info.agent.getMessage())) { + trustGrantedMessages.add(info.agent.getMessage().toString()); + } + } + return trustGrantedMessages; + } + private boolean aggregateIsTrustManaged(int userId) { if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) { return false; @@ -1021,7 +1037,8 @@ public class TrustManagerService extends SystemService { } } - private void dispatchOnTrustChanged(boolean enabled, int userId, int flags) { + private void dispatchOnTrustChanged(boolean enabled, int userId, int flags, + @NonNull List<String> trustGrantedMessages) { if (DEBUG) { Log.i(TAG, "onTrustChanged(" + enabled + ", " + userId + ", 0x" + Integer.toHexString(flags) + ")"); @@ -1029,7 +1046,7 @@ public class TrustManagerService extends SystemService { if (!enabled) flags = 0; for (int i = 0; i < mTrustListeners.size(); i++) { try { - mTrustListeners.get(i).onTrustChanged(enabled, userId, flags); + mTrustListeners.get(i).onTrustChanged(enabled, userId, flags, trustGrantedMessages); } catch (DeadObjectException e) { Slog.d(TAG, "Removing dead TrustListener."); mTrustListeners.remove(i); diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java index 35cc43faa4e9..b3649a75ee5b 100644 --- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java +++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java @@ -34,6 +34,7 @@ import android.media.tv.AdResponse; import android.media.tv.BroadcastInfoRequest; import android.media.tv.BroadcastInfoResponse; import android.media.tv.TvTrackInfo; +import android.media.tv.interactive.AppLinkInfo; import android.media.tv.interactive.ITvInteractiveAppClient; import android.media.tv.interactive.ITvInteractiveAppManager; import android.media.tv.interactive.ITvInteractiveAppManagerCallback; @@ -95,6 +96,10 @@ public class TvInteractiveAppManagerService extends SystemService { @GuardedBy("mLock") private final SparseArray<UserState> mUserStates = new SparseArray<>(); + // TODO: remove mGetServiceListCalled if onBootPhrase work correctly + @GuardedBy("mLock") + private boolean mGetServiceListCalled = false; + private final UserManager mUserManager; /** @@ -638,6 +643,10 @@ public class TvInteractiveAppManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { + if (!mGetServiceListCalled) { + buildTvInteractiveAppServiceListLocked(userId, null); + mGetServiceListCalled = true; + } UserState userState = getOrCreateUserStateLocked(resolvedUserId); List<TvInteractiveAppInfo> iAppList = new ArrayList<>(); for (TvInteractiveAppState state : userState.mIAppMap.values()) { @@ -687,9 +696,9 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override - public void registerAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) { + public void registerAppLinkInfo(String tiasId, AppLinkInfo appLinkInfo, int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), - Binder.getCallingUid(), userId, "registerAppLinkInfo"); + Binder.getCallingUid(), userId, "registerAppLinkInfo: " + appLinkInfo); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -723,9 +732,9 @@ public class TvInteractiveAppManagerService extends SystemService { } @Override - public void unregisterAppLinkInfo(String tiasId, Bundle appLinkInfo, int userId) { + public void unregisterAppLinkInfo(String tiasId, AppLinkInfo appLinkInfo, int userId) { final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), - Binder.getCallingUid(), userId, "unregisterAppLinkInfo"); + Binder.getCallingUid(), userId, "unregisterAppLinkInfo: " + appLinkInfo); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -1811,7 +1820,7 @@ public class TvInteractiveAppManagerService extends SystemService { private final ServiceConnection mConnection; private final ComponentName mComponent; private final String mIAppServiceId; - private final List<Pair<Bundle, Boolean>> mPendingAppLinkInfo = new ArrayList<>(); + private final List<Pair<AppLinkInfo, Boolean>> mPendingAppLinkInfo = new ArrayList<>(); private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>(); private boolean mPendingPrepare = false; @@ -1834,7 +1843,7 @@ public class TvInteractiveAppManagerService extends SystemService { mIAppServiceId = tias; } - private void addPendingAppLink(Bundle info, boolean register) { + private void addPendingAppLink(AppLinkInfo info, boolean register) { mPendingAppLinkInfo.add(Pair.create(info, register)); } @@ -1891,10 +1900,10 @@ public class TvInteractiveAppManagerService extends SystemService { } if (!serviceState.mPendingAppLinkInfo.isEmpty()) { - for (Iterator<Pair<Bundle, Boolean>> it = + for (Iterator<Pair<AppLinkInfo, Boolean>> it = serviceState.mPendingAppLinkInfo.iterator(); it.hasNext(); ) { - Pair<Bundle, Boolean> appLinkInfoPair = it.next(); + Pair<AppLinkInfo, Boolean> appLinkInfoPair = it.next(); final long identity = Binder.clearCallingIdentity(); try { if (appLinkInfoPair.second) { diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 344270427569..6aa06e8ee068 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -104,7 +104,8 @@ import java.util.List; import java.util.Objects; /** Manages uri grants. */ -public class UriGrantsManagerService extends IUriGrantsManager.Stub { +public class UriGrantsManagerService extends IUriGrantsManager.Stub implements + UriMetricsHelper.PersistentUriGrantsProvider { private static final boolean DEBUG = false; private static final String TAG = "UriGrantsManagerService"; // Maximum number of persisted Uri grants a package is allowed @@ -115,6 +116,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { private final H mH; ActivityManagerInternal mAmInternal; PackageManagerInternal mPmInternal; + UriMetricsHelper mMetricsHelper; /** File storing persisted {@link #mGrantedUriPermissions}. */ private final AtomicFile mGrantFile; @@ -168,16 +170,19 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { } public static final class Lifecycle extends SystemService { + private final Context mContext; private final UriGrantsManagerService mService; public Lifecycle(Context context) { super(context); + mContext = context; mService = new UriGrantsManagerService(); } @Override public void onStart() { publishBinderService(Context.URI_GRANTS_SERVICE, mService); + mService.mMetricsHelper = new UriMetricsHelper(mContext, mService); mService.start(); } @@ -186,6 +191,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { if (phase == PHASE_SYSTEM_SERVICES_READY) { mService.mAmInternal = LocalServices.getService(ActivityManagerInternal.class); mService.mPmInternal = LocalServices.getService(PackageManagerInternal.class); + mService.mMetricsHelper.registerPuller(); } } @@ -1298,20 +1304,50 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { return false; } + @Override + public ArrayList<UriPermission> providePersistentUriGrants() { + final ArrayList<UriPermission> result = new ArrayList<>(); + + synchronized (mLock) { + final int size = mGrantedUriPermissions.size(); + for (int i = 0; i < size; i++) { + final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i); + + final int permissionsForPackageSize = perms.size(); + for (int j = 0; j < permissionsForPackageSize; j++) { + final UriPermission permission = perms.valueAt(j); + + if (permission.persistedModeFlags != 0) { + result.add(permission); + } + } + } + } + + return result; + } + private void writeGrantedUriPermissions() { if (DEBUG) Slog.v(TAG, "writeGrantedUriPermissions()"); final long startTime = SystemClock.uptimeMillis(); + int persistentUriPermissionsCount = 0; + // Snapshot permissions so we can persist without lock ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList(); synchronized (mLock) { final int size = mGrantedUriPermissions.size(); for (int i = 0; i < size; i++) { final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i); - for (UriPermission perm : perms.values()) { - if (perm.persistedModeFlags != 0) { - persist.add(perm.snapshot()); + + final int permissionsForPackageSize = perms.size(); + for (int j = 0; j < permissionsForPackageSize; j++) { + final UriPermission permission = perms.valueAt(j); + + if (permission.persistedModeFlags != 0) { + persistentUriPermissionsCount++; + persist.add(permission.snapshot()); } } } @@ -1345,6 +1381,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { mGrantFile.failWrite(fos); } } + + mMetricsHelper.reportPersistentUriFlushed(persistentUriPermissionsCount); } final class H extends Handler { diff --git a/services/core/java/com/android/server/uri/UriMetricsHelper.java b/services/core/java/com/android/server/uri/UriMetricsHelper.java new file mode 100644 index 000000000000..dbc959928a3b --- /dev/null +++ b/services/core/java/com/android/server/uri/UriMetricsHelper.java @@ -0,0 +1,101 @@ +/* + * 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.server.uri; + +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + +import android.app.StatsManager; +import android.content.Context; +import android.util.SparseArray; +import android.util.StatsEvent; + +import com.android.internal.util.FrameworkStatsLog; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +final class UriMetricsHelper { + + private static final StatsManager.PullAtomMetadata DAILY_PULL_METADATA = + new StatsManager.PullAtomMetadata.Builder() + .setCoolDownMillis(TimeUnit.DAYS.toMillis(1)) + .build(); + + + private final Context mContext; + private final PersistentUriGrantsProvider mPersistentUriGrantsProvider; + + UriMetricsHelper(Context context, PersistentUriGrantsProvider provider) { + mContext = context; + mPersistentUriGrantsProvider = provider; + } + + void registerPuller() { + final StatsManager statsManager = mContext.getSystemService(StatsManager.class); + statsManager.setPullAtomCallback( + FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_AMOUNT_PER_PACKAGE, + DAILY_PULL_METADATA, + DIRECT_EXECUTOR, + (atomTag, data) -> { + reportPersistentUriPermissionsPerPackage(data); + return StatsManager.PULL_SUCCESS; + }); + } + + void reportPersistentUriFlushed(int amount) { + FrameworkStatsLog.write( + FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_FLUSHED, + amount + ); + } + + private void reportPersistentUriPermissionsPerPackage(List<StatsEvent> data) { + final ArrayList<UriPermission> persistentUriGrants = + mPersistentUriGrantsProvider.providePersistentUriGrants(); + + final SparseArray<Integer> perUidCount = new SparseArray<>(); + + final int persistentUriGrantsSize = persistentUriGrants.size(); + for (int i = 0; i < persistentUriGrantsSize; i++) { + final UriPermission uriPermission = persistentUriGrants.get(i); + + perUidCount.put( + uriPermission.targetUid, + perUidCount.get(uriPermission.targetUid, 0) + 1 + ); + } + + final int perUidCountSize = perUidCount.size(); + for (int i = 0; i < perUidCountSize; i++) { + final int uid = perUidCount.keyAt(i); + final int amount = perUidCount.valueAt(i); + + data.add( + FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.PERSISTENT_URI_PERMISSIONS_AMOUNT_PER_PACKAGE, + uid, + amount + ) + ); + } + } + + interface PersistentUriGrantsProvider { + ArrayList<UriPermission> providePersistentUriGrants(); + } +} diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 6c5d9520151b..eafd9d7f0b6c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -179,7 +179,7 @@ final class VibrationSettings { try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE, - ActivityManager.PROCESS_STATE_UNKNOWN, null); + ActivityManager.PROCESS_STATE_UNKNOWN, mContext.getOpPackageName()); } catch (RemoteException e) { // ignored; both services live in system_server } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 86ef8d2b26fc..76434c71d342 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7364,12 +7364,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @VisibleForTesting void clearSizeCompatMode() { + final float lastSizeCompatScale = mSizeCompatScale; mInSizeCompatModeForBounds = false; mSizeCompatScale = 1f; mSizeCompatBounds = null; mCompatDisplayInsets = null; + if (mSizeCompatScale != lastSizeCompatScale) { + forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); + } - onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); + // Clear config override in #updateCompatDisplayInsets(). + onRequestedOverrideConfigurationChanged(EMPTY); } @Override @@ -7924,6 +7929,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); final int viewportH = containerAppBounds.height(); + final float lastSizeCompatScale = mSizeCompatScale; // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); @@ -7942,6 +7948,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { mSizeCompatBounds = null; } + if (mSizeCompatScale != lastSizeCompatScale) { + forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); + } // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal // decor if needed. Horizontal position is adjusted in diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c65ca0847563..e449dde15c67 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -122,6 +122,7 @@ import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGE import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET; import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS; import static com.android.server.wm.DisplayContentProto.IS_SLEEPING; +import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS; import static com.android.server.wm.DisplayContentProto.OPENING_APPS; import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY; import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA; @@ -169,6 +170,7 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.ColorSpace; import android.graphics.Insets; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -838,7 +840,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } w.mSurfacePlacementNeeded = true; w.mLayoutNeeded = false; - w.prelayout(); final boolean firstLayout = !w.isLaidOut(); getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames); w.mLayoutSeq = mLayoutSeq; @@ -881,7 +882,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } w.mSurfacePlacementNeeded = true; w.mLayoutNeeded = false; - w.prelayout(); getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames); w.mLayoutSeq = mLayoutSeq; if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame() @@ -2001,6 +2001,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void configureDisplayPolicy() { + mRootWindowContainer.updateDisplayImePolicyCache(); mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); mDisplayRotation.configure(mBaseDisplayWidth, mBaseDisplayHeight); } @@ -2718,6 +2719,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void onDisplayChanged(DisplayContent dc) { super.onDisplayChanged(dc); updateSystemGestureExclusionLimit(); + updateKeepClearAreas(); } void updateSystemGestureExclusionLimit() { @@ -3327,6 +3329,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } proto.write(IME_POLICY, getImePolicy()); + for (Rect r : getKeepClearAreas()) { + r.dumpDebug(proto, KEEP_CLEAR_AREAS); + } proto.end(token); } @@ -3386,6 +3391,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp pw.println(mSystemGestureExclusion); } + final List<Rect> keepClearAreas = getKeepClearAreas(); + if (!keepClearAreas.isEmpty()) { + pw.println(); + pw.print(" keepClearAreas="); + pw.println(keepClearAreas); + } + pw.println(); pw.println(prefix + "Display areas in top down Z order:"); dumpChildDisplayArea(pw, subPrefix, dumpAll); @@ -3613,6 +3625,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } adjustForImeIfNeeded(); + updateKeepClearAreas(); // We may need to schedule some toast windows to be removed. The toasts for an app that // does not have input focus are removed within a timeout to prevent apps to redress @@ -3939,6 +3952,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } + // IMPORTANT: When introducing new dependencies in this method, make sure that + // changes to those result in RootWindowContainer.updateDisplayImePolicyCache() + // being called. @DisplayImePolicy int getImePolicy() { if (!isTrusted()) { return DISPLAY_IME_POLICY_FALLBACK_DISPLAY; @@ -3987,11 +4003,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (target == mImeLayeringTarget) { return; } - // Prepare the IME screenshot for the last IME target when its task is applying app - // transition. This is for the better IME transition to keep IME visibility when - // transitioning to the next task. + // If the IME target is the input target, before it changes, prepare the IME screenshot + // for the last IME target when its task is applying app transition. This is for the + // better IME transition to keep IME visibility when transitioning to the next task. if (mImeLayeringTarget != null && mImeLayeringTarget.isAnimating(PARENTS | TRANSITION, - ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) { + ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS) + && mImeLayeringTarget == mImeInputTarget) { attachAndShowImeScreenshotOnTarget(); } @@ -5477,19 +5494,28 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mSystemGestureExclusionListeners.unregister(listener); } + void updateKeepClearAreas() { + mWmService.mDisplayNotificationController.dispatchKeepClearAreasChanged( + this, getKeepClearAreas()); + } + /** - * @see IWindowManager#setForwardedInsets + * Returns all keep-clear areas from visible windows on this display. */ - public void setForwardedInsets(Insets insets) { - if (insets == null) { - insets = Insets.NONE; - } - if (mDisplayPolicy.getForwardedInsets().equals(insets)) { - return; - } - mDisplayPolicy.setForwardedInsets(insets); - setLayoutNeeded(); - mWmService.mWindowPlacerLocked.requestTraversal(); + ArrayList<Rect> getKeepClearAreas() { + final ArrayList<Rect> keepClearAreas = new ArrayList<Rect>(); + final Matrix tmpMatrix = new Matrix(); + final float[] tmpFloat9 = new float[9]; + forAllWindows(w -> { + if (w.isVisible() && !w.inPinnedWindowingMode()) { + keepClearAreas.addAll(w.getKeepClearAreas(tmpMatrix, tmpFloat9)); + } + + // We stop traversing when we reach the base of a fullscreen app. + return w.getWindowType() == TYPE_BASE_APPLICATION + && w.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; + }, true); + return keepClearAreas; } protected MetricsLogger getMetricsLogger() { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 0745b3b6d971..18885541b1fe 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -305,10 +305,10 @@ public class DisplayPolicy { private WindowState mRoundedCornerWindow; /** - * Windows to determine the color of status bar. See {@link #mNavBarColorWindowCandidate} for - * the conditions of being candidate window. + * A collection of {@link AppearanceRegion} to indicate that which region of status bar applies + * which appearance. */ - private final ArrayList<WindowState> mStatusBarColorWindows = new ArrayList<>(); + private final ArrayList<AppearanceRegion> mStatusBarAppearanceRegionList = new ArrayList<>(); /** * Windows to determine opacity and background of translucent status bar. The window needs to be @@ -323,7 +323,7 @@ public class DisplayPolicy { private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities(); private AppearanceRegion[] mLastStatusBarAppearanceRegions; - /** The union of checked bounds while fetching {@link #mStatusBarColorWindows}. */ + /** The union of checked bounds while building {@link #mStatusBarAppearanceRegionList}. */ private final Rect mStatusBarColorCheckedBounds = new Rect(); /** The union of checked bounds while fetching {@link #mStatusBarBackgroundWindows}. */ @@ -336,6 +336,7 @@ public class DisplayPolicy { private long mPendingPanicGestureUptime; private static final Rect sTmpRect = new Rect(); + private static final Rect sTmpRect2 = new Rect(); private static final Rect sTmpLastParentFrame = new Rect(); private static final Rect sTmpDisplayCutoutSafe = new Rect(); private static final Rect sTmpDisplayFrame = new Rect(); @@ -359,16 +360,6 @@ public class DisplayPolicy { private int mDisplayCutoutTouchableRegionSize; - /** - * The area covered by system windows which belong to another display. Forwarded insets is set - * in case this is a virtual display, this is displayed on another display that has insets, and - * the bounds of this display is overlapping with the insets of the host display (e.g. IME is - * displayed on the host display, and it covers a part of this virtual display.) - * The forwarded insets is used to compute display frames of this virtual display, which will - * be then used to layout windows in the virtual display. - */ - @NonNull private Insets mForwardedInsets = Insets.NONE; - private RefreshRatePolicy mRefreshRatePolicy; /** @@ -1442,33 +1433,6 @@ public class DisplayPolicy { return mForceShowSystemBars; } - // TODO: Should probably be moved into DisplayFrames. - /** - * Return the layout hints for a newly added window. These values are computed on the - * most recent layout, so they are not guaranteed to be correct. - * - * @param attrs The LayoutParams of the window. - * @param windowToken The token of the window. - * @param outInsetsState The insets state of this display from the client's perspective. - * @param localClient Whether the client is from the our process. - * @return Whether to always consume the system bars. - * See {@link #areSystemBarsForcedShownLw()}. - */ - boolean getLayoutHint(LayoutParams attrs, WindowToken windowToken, InsetsState outInsetsState, - boolean localClient) { - final InsetsState state = - mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(attrs); - final boolean hasCompatScale = WindowState.hasCompatScale(attrs, windowToken); - outInsetsState.set(state, hasCompatScale || localClient); - if (hasCompatScale) { - final float compatScale = windowToken != null - ? windowToken.getSizeCompatScale() - : mDisplayContent.mCompatibleScreenScale; - outInsetsState.scale(1f / compatScale); - } - return mForceShowSystemBars; - } - /** * Computes the frames of display (its logical size, rotation and cutout should already be set) * used to layout window. This method only changes the given display frames, insets state and @@ -1563,7 +1527,7 @@ public class DisplayPolicy { mTopFullscreenOpaqueWindowState = null; mNavBarColorWindowCandidate = null; mNavBarBackgroundWindow = null; - mStatusBarColorWindows.clear(); + mStatusBarAppearanceRegionList.clear(); mStatusBarBackgroundWindows.clear(); mStatusBarColorCheckedBounds.setEmpty(); mStatusBarBackgroundCheckedBounds.setEmpty(); @@ -1643,7 +1607,9 @@ public class DisplayPolicy { mStatusBarBackgroundWindows.add(win); mStatusBarBackgroundCheckedBounds.union(sTmpRect); if (!mStatusBarColorCheckedBounds.contains(sTmpRect)) { - mStatusBarColorWindows.add(win); + mStatusBarAppearanceRegionList.add(new AppearanceRegion( + win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS, + new Rect(win.getFrame()))); mStatusBarColorCheckedBounds.union(sTmpRect); } } @@ -1663,13 +1629,10 @@ public class DisplayPolicy { } } } else if (win.isDimming()) { - // For dimming window whose host bounds is overlapping with system bars, it can be - // used to determine colors but not opacity of system bars. - if (mStatusBar != null - && sTmpRect.setIntersect(win.getBounds(), mStatusBar.getFrame()) - && !mStatusBarColorCheckedBounds.contains(sTmpRect)) { - mStatusBarColorWindows.add(win); - mStatusBarColorCheckedBounds.union(sTmpRect); + if (mStatusBar != null) { + addStatusBarAppearanceRegionsForDimmingWindow( + win.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS, + mStatusBar.getFrame(), win.getBounds(), win.getFrame()); } if (isOverlappingWithNavBar && mNavBarColorWindowCandidate == null) { mNavBarColorWindowCandidate = win; @@ -1677,6 +1640,48 @@ public class DisplayPolicy { } } + private void addStatusBarAppearanceRegionsForDimmingWindow(int appearance, Rect statusBarFrame, + Rect winBounds, Rect winFrame) { + if (!sTmpRect.setIntersect(winBounds, statusBarFrame)) { + return; + } + if (mStatusBarColorCheckedBounds.contains(sTmpRect)) { + return; + } + if (appearance == 0 || !sTmpRect2.setIntersect(winFrame, statusBarFrame)) { + mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect(winBounds))); + mStatusBarColorCheckedBounds.union(sTmpRect); + return; + } + // A dimming window can divide status bar into different appearance regions (up to 3). + // +---------+-------------+---------+ + // |/////////| |/////////| <-- Status Bar + // +---------+-------------+---------+ + // |/////////| |/////////| + // |/////////| |/////////| + // |/////////| |/////////| + // |/////////| |/////////| + // |/////////| |/////////| + // +---------+-------------+---------+ + // ^ ^ ^ + // dim layer window dim layer + mStatusBarAppearanceRegionList.add(new AppearanceRegion(appearance, new Rect(winFrame))); + if (!sTmpRect.equals(sTmpRect2)) { + if (sTmpRect.height() == sTmpRect2.height()) { + if (sTmpRect.left != sTmpRect2.left) { + mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect( + winBounds.left, winBounds.top, sTmpRect2.left, winBounds.bottom))); + } + if (sTmpRect.right != sTmpRect2.right) { + mStatusBarAppearanceRegionList.add(new AppearanceRegion(0, new Rect( + sTmpRect2.right, winBounds.top, winBounds.right, winBounds.bottom))); + } + } + // We don't have vertical status bar yet, so we don't handle the other orientation. + } + mStatusBarColorCheckedBounds.union(sTmpRect); + } + /** * Called following layout of all windows and after policy has been applied * to each window. If in this function you do @@ -2149,18 +2154,6 @@ public class DisplayPolicy { } } - /** - * @see IWindowManager#setForwardedInsets - */ - public void setForwardedInsets(@NonNull Insets forwardedInsets) { - mForwardedInsets = forwardedInsets; - } - - @NonNull - public Insets getForwardedInsets() { - return mForwardedInsets; - } - @NavigationBarPosition int navigationBarPosition(int displayRotation) { if (mNavigationBar != null) { @@ -2319,14 +2312,9 @@ public class DisplayPolicy { final String focusedApp = win.mAttrs.packageName; final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR) || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR); - final AppearanceRegion[] appearanceRegions = - new AppearanceRegion[mStatusBarColorWindows.size()]; - for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) { - final WindowState windowState = mStatusBarColorWindows.get(i); - appearanceRegions[i] = new AppearanceRegion( - getStatusBarAppearance(windowState, windowState), - new Rect(windowState.getFrame())); - } + final AppearanceRegion[] statusBarAppearanceRegions = + new AppearanceRegion[mStatusBarAppearanceRegionList.size()]; + mStatusBarAppearanceRegionList.toArray(statusBarAppearanceRegions); if (mLastDisableFlags != disableFlags) { mLastDisableFlags = disableFlags; final String cause = win.toString(); @@ -2338,7 +2326,7 @@ public class DisplayPolicy { && mRequestedVisibilities.equals(win.getRequestedVisibilities()) && Objects.equals(mFocusedApp, focusedApp) && mLastFocusIsFullscreen == isFullscreen - && Arrays.equals(mLastStatusBarAppearanceRegions, appearanceRegions)) { + && Arrays.equals(mLastStatusBarAppearanceRegions, statusBarAppearanceRegions)) { return; } if (mDisplayContent.isDefaultDisplay && mLastFocusIsFullscreen != isFullscreen @@ -2353,20 +2341,12 @@ public class DisplayPolicy { mRequestedVisibilities = requestedVisibilities; mFocusedApp = focusedApp; mLastFocusIsFullscreen = isFullscreen; - mLastStatusBarAppearanceRegions = appearanceRegions; + mLastStatusBarAppearanceRegions = statusBarAppearanceRegions; callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId, - appearance, appearanceRegions, isNavbarColorManagedByIme, behavior, + appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior, requestedVisibilities, focusedApp)); } - private int getStatusBarAppearance(WindowState opaque, WindowState opaqueOrDimming) { - final boolean onKeyguard = isKeyguardShowing() && !isKeyguardOccluded(); - final WindowState colorWin = onKeyguard ? mNotificationShade : opaqueOrDimming; - return isLightBarAllowed(colorWin, Type.statusBars()) && (colorWin == opaque || onKeyguard) - ? (colorWin.mAttrs.insetsFlags.appearance & APPEARANCE_LIGHT_STATUS_BARS) - : 0; - } - private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) { mHandler.post(() -> { StatusBarManagerInternal statusBar = getStatusBarManagerInternal(); @@ -2760,11 +2740,10 @@ public class DisplayPolicy { pw.print(prefix); pw.print("mNavBarBackgroundWindow="); pw.println(mNavBarBackgroundWindow); } - if (!mStatusBarColorWindows.isEmpty()) { - pw.print(prefix); pw.println("mStatusBarColorWindows="); - for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) { - final WindowState win = mStatusBarColorWindows.get(i); - pw.print(prefixInner); pw.println(win); + if (mLastStatusBarAppearanceRegions != null) { + pw.print(prefix); pw.println("mLastStatusBarAppearanceRegions="); + for (int i = mLastStatusBarAppearanceRegions.length - 1; i >= 0; i--) { + pw.print(prefixInner); pw.println(mLastStatusBarAppearanceRegions[i]); } } if (!mStatusBarBackgroundWindows.isEmpty()) { diff --git a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java index 4141090f7fa0..276dbe9c844a 100644 --- a/services/core/java/com/android/server/wm/DisplayWindowListenerController.java +++ b/services/core/java/com/android/server/wm/DisplayWindowListenerController.java @@ -17,11 +17,14 @@ package com.android.server.wm; import android.content.res.Configuration; +import android.graphics.Rect; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.IntArray; import android.view.IDisplayWindowListener; +import java.util.List; + /** * Manages dispatch of relevant hierarchy changes to interested listeners. Listeners are assumed * to be remote. @@ -116,4 +119,16 @@ class DisplayWindowListenerController { } mDisplayListeners.finishBroadcast(); } + + void dispatchKeepClearAreasChanged(DisplayContent display, List<Rect> keepClearAreas) { + int count = mDisplayListeners.beginBroadcast(); + for (int i = 0; i < count; ++i) { + try { + mDisplayListeners.getBroadcastItem(i).onKeepClearAreasChanged( + display.mDisplayId, keepClearAreas); + } catch (RemoteException e) { + } + } + mDisplayListeners.finishBroadcast(); + } } diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index 963f3265757d..8d3e07116693 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -97,7 +97,7 @@ class EnsureActivitiesVisibleHelper { // activities are actually behind other fullscreen activities, but still required // to be visible (such as performing Recents animation). final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind - && mTaskFragment.isTopActivityFocusable() + && mTaskFragment.canBeResumed(starting) && (starting == null || !starting.isDescendantOf(mTaskFragment)); ArrayList<TaskFragment> adjacentTaskFragments = null; diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index e02e7c5ab15d..f91969b2c558 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -24,6 +24,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS; import android.annotation.NonNull; +import android.graphics.PointF; import android.os.Debug; import android.os.IBinder; import android.util.Slog; @@ -219,6 +220,11 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } @Override + public PointF getCursorPosition() { + return mService.getLatestMousePosition(); + } + + @Override public void onPointerDownOutsideFocus(IBinder touchedToken) { mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 1a1101e45f45..a1468cc60682 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -366,6 +366,7 @@ class InsetsStateController { if (changed) { notifyInsetsChanged(); mDisplayContent.updateSystemGestureExclusion(); + mDisplayContent.updateKeepClearAreas(); mDisplayContent.getDisplayPolicy().updateSystemBarAttributes(); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 5a420caa176c..d031bec5443f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -165,6 +165,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; @@ -986,6 +987,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> forAllDisplays(dc -> { dc.getInputMonitor().updateInputWindowsLw(true /*force*/); dc.updateSystemGestureExclusion(); + dc.updateKeepClearAreas(); dc.updateTouchExcludeRegion(); }); @@ -2530,9 +2532,16 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Drop any cached DisplayInfos associated with this display id - the values are now // out of date given this display changed event. mWmService.mPossibleDisplayInfoMapper.removePossibleDisplayInfos(displayId); + updateDisplayImePolicyCache(); } } + void updateDisplayImePolicyCache() { + ArrayMap<Integer, Integer> displayImePolicyMap = new ArrayMap<>(); + forAllDisplays(dc -> displayImePolicyMap.put(dc.getDisplayId(), dc.getImePolicy())); + mWmService.mDisplayImePolicyCache = Collections.unmodifiableMap(displayImePolicyMap); + } + /** Update lists of UIDs that are present on displays and have access to them. */ void updateUIDsPresentOnDisplay() { mDisplayAccessUIDs.clear(); @@ -3665,7 +3674,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> try { if (mTaskSupervisor.realStartActivityLocked(r, mApp, - mTop == r && r.isFocusable() /* andResume */, true /* checkConfig */)) { + mTop == r && r.getTask().canBeResumed(r) /* andResume */, + true /* checkConfig */)) { mHasActivityStarted = true; } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 005544b961c3..7acc0c52cc4c 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -73,6 +73,7 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; import android.window.ClientWindowFrames; +import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.logging.MetricsLoggerWrapper; @@ -490,6 +491,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } + @Override + public void reportKeepClearAreasChanged(IWindow window, List<Rect> keepClearAreas) { + final long ident = Binder.clearCallingIdentity(); + try { + mService.reportKeepClearAreasChanged(this, window, keepClearAreas); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void actionOnWallpaper(IBinder window, BiConsumer<WallpaperController, WindowState> action) { final WindowState windowState = mService.windowForClientLocked(this, window, true); @@ -863,4 +874,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { Binder.restoreCallingIdentity(origId); } } + + @Override + public void setOnBackInvokedCallback(IWindow iWindow, + IOnBackInvokedCallback iOnBackInvokedCallback) throws RemoteException { + // TODO: Set the callback to the WindowState of the window. + } } diff --git a/services/core/java/com/android/server/wm/TaskFpsCallbackController.java b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java new file mode 100644 index 000000000000..d9dc9aa9e5e2 --- /dev/null +++ b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java @@ -0,0 +1,69 @@ +/* + * 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.server.wm; + +import android.content.Context; +import android.os.IBinder; +import android.os.RemoteException; +import android.window.IOnFpsCallbackListener; + +import java.util.HashMap; + +final class TaskFpsCallbackController { + + private final Context mContext; + private final HashMap<IOnFpsCallbackListener, Long> mTaskFpsCallbackListeners; + private final HashMap<IOnFpsCallbackListener, IBinder.DeathRecipient> mDeathRecipients; + + TaskFpsCallbackController(Context context) { + mContext = context; + mTaskFpsCallbackListeners = new HashMap<>(); + mDeathRecipients = new HashMap<>(); + } + + void registerCallback(int taskId, IOnFpsCallbackListener listener) { + if (mTaskFpsCallbackListeners.containsKey(listener)) { + return; + } + + final long nativeListener = nativeRegister(listener, taskId); + mTaskFpsCallbackListeners.put(listener, nativeListener); + + final IBinder.DeathRecipient deathRecipient = () -> unregisterCallback(listener); + try { + listener.asBinder().linkToDeath(deathRecipient, 0); + mDeathRecipients.put(listener, deathRecipient); + } catch (RemoteException e) { + // ignore + } + } + + void unregisterCallback(IOnFpsCallbackListener listener) { + if (!mTaskFpsCallbackListeners.containsKey(listener)) { + return; + } + + listener.asBinder().unlinkToDeath(mDeathRecipients.get(listener), 0); + mDeathRecipients.remove(listener); + + nativeUnregister(mTaskFpsCallbackListeners.get(listener)); + mTaskFpsCallbackListeners.remove(listener); + } + + private static native long nativeRegister(IOnFpsCallbackListener listener, int taskId); + private static native void nativeUnregister(long ptr); +} diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index c8781ae62384..177d2e61d5f0 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -24,8 +24,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -796,13 +794,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { return TASK_FRAGMENT_VISIBILITY_VISIBLE; } - boolean gotRootSplitScreenFragment = false; - boolean gotOpaqueSplitScreenPrimary = false; - boolean gotOpaqueSplitScreenSecondary = false; boolean gotTranslucentFullscreen = false; boolean gotTranslucentAdjacent = false; - boolean gotTranslucentSplitScreenPrimary = false; - boolean gotTranslucentSplitScreenSecondary = false; boolean shouldBeVisible = true; // This TaskFragment is only considered visible if all its parent TaskFragments are @@ -821,8 +814,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final List<TaskFragment> adjacentTaskFragments = new ArrayList<>(); - final int windowingMode = getWindowingMode(); - final boolean isAssistantType = isActivityTypeAssistant(); for (int i = parent.getChildCount() - 1; i >= 0; --i) { final WindowContainer other = parent.getChildAt(i); if (other == null) continue; @@ -870,37 +861,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } // Multi-window TaskFragment that matches parent bounds would occlude other children return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && !gotOpaqueSplitScreenPrimary) { - gotRootSplitScreenFragment = true; - gotTranslucentSplitScreenPrimary = isTranslucent(other, starting); - gotOpaqueSplitScreenPrimary = !gotTranslucentSplitScreenPrimary; - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - && gotOpaqueSplitScreenPrimary) { - // Can't be visible behind another opaque TaskFragment in split-screen-primary. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - && !gotOpaqueSplitScreenSecondary) { - gotRootSplitScreenFragment = true; - gotTranslucentSplitScreenSecondary = isTranslucent(other, starting); - gotOpaqueSplitScreenSecondary = !gotTranslucentSplitScreenSecondary; - if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - && gotOpaqueSplitScreenSecondary) { - // Can't be visible behind another opaque TaskFragment in split-screen-secondary - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - } - if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) { - // Can not be visible if we are in split-screen windowing mode and both halves of - // the screen are opaque. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - if (isAssistantType && gotRootSplitScreenFragment) { - // Assistant TaskFragment can't be visible behind split-screen. In addition to - // this not making sense, it also works around an issue here we boost the z-order - // of the assistant window surfaces in window manager whenever it is visible. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } final TaskFragment otherTaskFrag = other.asTaskFragment(); @@ -926,34 +886,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return TASK_FRAGMENT_VISIBILITY_INVISIBLE; } - // Handle cases when there can be a translucent split-screen TaskFragment on top. - switch (windowingMode) { - case WINDOWING_MODE_FULLSCREEN: - if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) { - // At least one of the split-screen TaskFragment that covers this one is - // translucent. - // When in split mode, home will be reparented to the secondary split while - // leaving TaskFragments not supporting split below. Due to - // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to - // the bottom, this makes sure TaskFragments not in split roots won't occlude - // home task unexpectedly. - return TASK_FRAGMENT_VISIBILITY_INVISIBLE; - } - break; - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: - if (gotTranslucentSplitScreenPrimary) { - // Covered by translucent primary split-screen on top. - return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; - } - break; - case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: - if (gotTranslucentSplitScreenSecondary) { - // Covered by translucent secondary split-screen on top. - return TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; - } - break; - } - // Lastly - check if there is a translucent fullscreen TaskFragment on top. return gotTranslucentFullscreen ? TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 1ab191bb6650..b9fa29733aa6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -16,6 +16,9 @@ package com.android.server.wm; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ClipData; @@ -40,6 +43,7 @@ import com.android.internal.policy.KeyInterceptionInfo; import com.android.server.input.InputManagerService; import com.android.server.policy.WindowManagerPolicy; +import java.lang.annotation.Retention; import java.util.List; import java.util.Set; @@ -609,6 +613,7 @@ public abstract class WindowManagerInternal { /** * Checks whether the specified IME client has IME focus or not. * + * @param windowToken The window token of the input method client * @param uid UID of the process to be queried * @param pid PID of the process to be queried * @param displayId Display ID reported from the client. Note that this method also verifies @@ -616,7 +621,22 @@ public abstract class WindowManagerInternal { * @return {@code true} if the IME client specified with {@code uid}, {@code pid}, and * {@code displayId} has IME focus */ - public abstract boolean isInputMethodClientFocus(int uid, int pid, int displayId); + public abstract @ImeClientFocusResult int hasInputMethodClientFocus(IBinder windowToken, + int uid, int pid, int displayId); + + @Retention(SOURCE) + @IntDef({ + ImeClientFocusResult.HAS_IME_FOCUS, + ImeClientFocusResult.NOT_IME_TARGET_WINDOW, + ImeClientFocusResult.DISPLAY_ID_MISMATCH, + ImeClientFocusResult.INVALID_DISPLAY_ID + }) + public @interface ImeClientFocusResult { + int HAS_IME_FOCUS = 0; + int NOT_IME_TARGET_WINDOW = -1; + int DISPLAY_ID_MISMATCH = -2; + int INVALID_DISPLAY_ID = -3; + } /** * Checks whether the given {@code uid} is allowed to use the given {@code displayId} or not. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 2f0ef4a8ee1b..026b9e1094d0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -143,6 +143,7 @@ import android.Manifest; import android.Manifest.permission; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -170,14 +171,12 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Bitmap; -import android.graphics.Insets; import android.graphics.Point; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.Region; import android.hardware.configstore.V1_0.OptionalBool; -import android.hardware.configstore.V1_1.DisplayOrientation; import android.hardware.configstore.V1_1.ISurfaceFlingerConfigs; -import android.hardware.configstore.V1_1.OptionalDisplayOrientation; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.InputManager; @@ -229,7 +228,6 @@ import android.util.TypedValue; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; import android.view.Display; -import android.view.DisplayAddress; import android.view.DisplayInfo; import android.view.Gravity; import android.view.IAppTransitionAnimationSpecsFuture; @@ -282,6 +280,7 @@ import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; import android.window.ClientWindowFrames; +import android.window.IOnFpsCallbackListener; import android.window.TaskSnapshot; import com.android.internal.R; @@ -439,8 +438,6 @@ public class WindowManagerService extends IWindowManager.Stub */ static final boolean ENABLE_FIXED_ROTATION_TRANSFORM = SystemProperties.getBoolean("persist.wm.fixed_rotation_transform", true); - private @Surface.Rotation int mPrimaryDisplayOrientation = Surface.ROTATION_0; - private DisplayAddress mPrimaryDisplayPhysicalAddress; // Enums for animation scale update types. @Retention(RetentionPolicy.SOURCE) @@ -589,6 +586,13 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowState> mResizingWindows = new ArrayList<>(); /** + * Mapping of displayId to {@link DisplayImePolicy}. + * Note that this can be accessed without holding the lock. + */ + volatile Map<Integer, Integer> mDisplayImePolicyCache = Collections.unmodifiableMap( + new ArrayMap<>()); + + /** * Windows whose surface should be destroyed. */ final ArrayList<WindowState> mDestroySurface = new ArrayList<>(); @@ -708,6 +712,7 @@ public class WindowManagerService extends IWindowManager.Stub final TaskSnapshotController mTaskSnapshotController; final BlurController mBlurController; + final TaskFpsCallbackController mTaskFpsCallbackController; boolean mIsTouchDevice; boolean mIsFakeTouchDevice; @@ -1354,6 +1359,7 @@ public class WindowManagerService extends IWindowManager.Stub mStartingSurfaceController = new StartingSurfaceController(this); mBlurController = new BlurController(mContext, mPowerManager); + mTaskFpsCallbackController = new TaskFpsCallbackController(mContext); mAccessibilityController = new AccessibilityController(this); } @@ -1788,8 +1794,7 @@ public class WindowManagerService extends IWindowManager.Stub prepareNoneTransitionForRelaunching(activity); } - if (displayPolicy.getLayoutHint(win.mAttrs, token, outInsetsState, - win.isClientLocal())) { + if (displayPolicy.areSystemBarsForcedShownLw()) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; } @@ -1836,6 +1841,7 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.getInsetsStateController().updateAboveInsetsState( win, false /* notifyInsetsChanged */); + outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal()); getInsetsSourceControls(win, outActiveControls); } @@ -2438,23 +2444,6 @@ public class WindowManagerService extends IWindowManager.Stub configChanged = displayContent.updateOrientation(); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - final DisplayInfo displayInfo = win.getDisplayInfo(); - int transformHint = displayInfo.rotation; - // If the window is on the primary display, use the panel orientation to adjust the - // transform hint - final boolean isPrimaryDisplay = displayInfo.address != null && - displayInfo.address.equals(mPrimaryDisplayPhysicalAddress); - if (isPrimaryDisplay) { - transformHint = (transformHint + mPrimaryDisplayOrientation) % 4; - } - outSurfaceControl.setTransformHint( - SurfaceControl.rotationToBufferTransform(transformHint)); - ProtoLog.v(WM_DEBUG_ORIENTATION, - "Passing transform hint %d for window %s%s", - transformHint, win, - isPrimaryDisplay ? " on primary display with orientation " - + mPrimaryDisplayOrientation : ""); - if (toBeDisplayed && win.mIsWallpaper) { displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */); } @@ -3838,6 +3827,40 @@ public class WindowManagerService extends IWindowManager.Stub } /** + * Generates and returns an up-to-date {@link Bitmap} for the specified taskId. The returned + * bitmap will be full size and will not include any secure content. + * + * @param taskId The task ID of the task for which a snapshot is requested. + * @return The Bitmap, or null if no task with the specified ID can be found or the bitmap could + * not be generated. + */ + @Nullable public Bitmap captureTaskBitmap(int taskId) { + if (mTaskSnapshotController.shouldDisableSnapshots()) { + return null; + } + + synchronized (mGlobalLock) { + final Task task = mRoot.anyTaskForId(taskId); + if (task == null) { + return null; + } + + task.getBounds(mTmpRect); + final SurfaceControl sc = task.getSurfaceControl(); + final SurfaceControl.ScreenshotHardwareBuffer buffer = SurfaceControl.captureLayers( + new SurfaceControl.LayerCaptureArgs.Builder(sc) + .setSourceCrop(mTmpRect) + .build()); + if (buffer == null) { + Slog.w(TAG, "Could not get screenshot buffer for taskId: " + taskId); + return null; + } + + return buffer.asBitmap(); + } + } + + /** * In case a task write/delete operation was lost because the system crashed, this makes sure to * clean up the directory to remove obsolete files. * @@ -4316,6 +4339,15 @@ public class WindowManagerService extends IWindowManager.Stub } } + void reportKeepClearAreasChanged(Session session, IWindow window, List<Rect> keepClearAreas) { + synchronized (mGlobalLock) { + final WindowState win = windowForClientLocked(session, window, true); + if (win.setKeepClearAreas(keepClearAreas)) { + win.getDisplayContent().updateKeepClearAreas(); + } + } + } + @Override public void registerDisplayFoldListener(IDisplayFoldListener listener) { mPolicy.registerDisplayFoldListener(listener); @@ -4888,9 +4920,6 @@ public class WindowManagerService extends IWindowManager.Stub mTaskSnapshotController.systemReady(); mHasWideColorGamutSupport = queryWideColorGamutSupport(); mHasHdrSupport = queryHdrSupport(); - mPrimaryDisplayOrientation = queryPrimaryDisplayOrientation(); - mPrimaryDisplayPhysicalAddress = - DisplayAddress.fromPhysicalDisplayId(SurfaceControl.getPrimaryPhysicalDisplayId()); UiThread.getHandler().post(mSettingsObserver::loadSettings); IVrManager vrManager = IVrManager.Stub.asInterface( ServiceManager.getService(Context.VR_SERVICE)); @@ -4953,39 +4982,6 @@ public class WindowManagerService extends IWindowManager.Stub return false; } - private static @Surface.Rotation int queryPrimaryDisplayOrientation() { - Optional<SurfaceFlingerProperties.primary_display_orientation_values> prop = - SurfaceFlingerProperties.primary_display_orientation(); - if (prop.isPresent()) { - switch (prop.get()) { - case ORIENTATION_90: return Surface.ROTATION_90; - case ORIENTATION_180: return Surface.ROTATION_180; - case ORIENTATION_270: return Surface.ROTATION_270; - case ORIENTATION_0: - default: - return Surface.ROTATION_0; - } - } - try { - ISurfaceFlingerConfigs surfaceFlinger = ISurfaceFlingerConfigs.getService(); - OptionalDisplayOrientation primaryDisplayOrientation = - surfaceFlinger.primaryDisplayOrientation(); - if (primaryDisplayOrientation != null && primaryDisplayOrientation.specified) { - switch (primaryDisplayOrientation.value) { - case DisplayOrientation.ORIENTATION_90: return Surface.ROTATION_90; - case DisplayOrientation.ORIENTATION_180: return Surface.ROTATION_180; - case DisplayOrientation.ORIENTATION_270: return Surface.ROTATION_270; - case DisplayOrientation.ORIENTATION_0: - default: - return Surface.ROTATION_0; - } - } - } catch (Exception e) { - // Use default value if we can't talk to config store. - } - return Surface.ROTATION_0; - } - // Returns an input target which is mapped to the given input token. This can be a WindowState // or an embedded window. @Nullable InputTarget getInputTargetFromToken(IBinder inputToken) { @@ -5684,6 +5680,11 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void saveWindowTraceToFile() { + mWindowTracing.saveForBugreport(null /* printwriter */); + } + + @Override public boolean isWindowTraceEnabled() { return mWindowTracing.isEnabled(); } @@ -6908,6 +6909,7 @@ public class WindowManagerService extends IWindowManager.Stub void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) { synchronized (mGlobalLock) { mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays; + mRoot.updateDisplayImePolicyCache(); } } @@ -6963,23 +6965,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - @Override - public void setForwardedInsets(int displayId, Insets insets) throws RemoteException { - synchronized (mGlobalLock) { - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { - return; - } - final int callingUid = Binder.getCallingUid(); - final int displayOwnerUid = dc.getDisplay().getOwnerUid(); - if (callingUid != displayOwnerUid) { - throw new SecurityException( - "Only owner of the display can set ForwardedInsets to it."); - } - dc.setForwardedInsets(insets); - } - } - MousePositionTracker mMousePositionTracker = new MousePositionTracker(); private static class MousePositionTracker implements PointerEventListener { @@ -7066,6 +7051,13 @@ public class WindowManagerService extends IWindowManager.Stub } } + PointF getLatestMousePosition() { + synchronized (mMousePositionTracker) { + return new PointF(mMousePositionTracker.mLatestMouseX, + mMousePositionTracker.mLatestMouseY); + } + } + /** * Update a tap exclude region in the window identified by the provided id. Touches down on this * region will not: @@ -7332,16 +7324,14 @@ public class WindowManagerService extends IWindowManager.Stub if (!checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "getDisplayImePolicy()")) { throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission"); } - final DisplayContent dc = mRoot.getDisplayContent(displayId); - if (dc == null) { + final Map<Integer, Integer> displayImePolicyCache = mDisplayImePolicyCache; + if (!displayImePolicyCache.containsKey(displayId)) { ProtoLog.w(WM_ERROR, "Attempted to get IME policy of a display that does not exist: %d", displayId); return DISPLAY_IME_POLICY_FALLBACK_DISPLAY; } - synchronized (mGlobalLock) { - return dc.getImePolicy(); - } + return displayImePolicyCache.get(displayId); } @Override @@ -7690,19 +7680,32 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean isInputMethodClientFocus(int uid, int pid, int displayId) { + public @ImeClientFocusResult int hasInputMethodClientFocus(IBinder windowToken, + int uid, int pid, int displayId) { if (displayId == Display.INVALID_DISPLAY) { - return false; + return ImeClientFocusResult.INVALID_DISPLAY_ID; } synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getTopFocusedDisplayContent(); + final WindowState window = mWindowMap.get(windowToken); + if (window == null) { + return ImeClientFocusResult.NOT_IME_TARGET_WINDOW; + } + final int tokenDisplayId = window.getDisplayContent().getDisplayId(); + if (tokenDisplayId != displayId) { + Slog.e(TAG, "isInputMethodClientFocus: display ID mismatch." + + " from client: " + displayId + + " from window: " + tokenDisplayId); + return ImeClientFocusResult.DISPLAY_ID_MISMATCH; + } if (displayContent == null || displayContent.getDisplayId() != displayId || !displayContent.hasAccess(uid)) { - return false; + return ImeClientFocusResult.INVALID_DISPLAY_ID; } + if (displayContent.isInputMethodClientFocus(uid, pid)) { - return true; + return ImeClientFocusResult.HAS_IME_FOCUS; } // Okay, how about this... what is the current focus? // It seems in some cases we may not have moved the IM @@ -7715,10 +7718,11 @@ public class WindowManagerService extends IWindowManager.Stub final WindowState currentFocus = displayContent.mCurrentFocus; if (currentFocus != null && currentFocus.mSession.mUid == uid && currentFocus.mSession.mPid == pid) { - return currentFocus.canBeImeTarget(); + return currentFocus.canBeImeTarget() ? ImeClientFocusResult.HAS_IME_FOCUS + : ImeClientFocusResult.NOT_IME_TARGET_WINDOW; } } - return false; + return ImeClientFocusResult.NOT_IME_TARGET_WINDOW; } @Override @@ -7817,9 +7821,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public @DisplayImePolicy int getDisplayImePolicy(int displayId) { - synchronized (mGlobalLock) { - return WindowManagerService.this.getDisplayImePolicy(displayId); - } + return WindowManagerService.this.getDisplayImePolicy(displayId); } @Override @@ -8475,6 +8477,7 @@ public class WindowManagerService extends IWindowManager.Stub public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId, InsetsState outInsetsState) { final boolean fromLocal = Binder.getCallingPid() == myPid(); + final int uid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { @@ -8483,9 +8486,20 @@ public class WindowManagerService extends IWindowManager.Stub throw new WindowManager.InvalidDisplayException("Display#" + displayId + "could not be found!"); } - final WindowToken windowToken = dc.getWindowToken(attrs.token); - return dc.getDisplayPolicy().getLayoutHint(attrs, windowToken, outInsetsState, - fromLocal); + final WindowToken token = dc.getWindowToken(attrs.token); + final float overrideScale = mAtmService.mCompatModePackages.getCompatScale( + attrs.packageName, uid); + final InsetsState state = dc.getInsetsPolicy().getInsetsForWindowMetrics(attrs); + final boolean hasCompatScale = + WindowState.hasCompatScale(attrs, token, overrideScale); + outInsetsState.set(state, hasCompatScale || fromLocal); + if (hasCompatScale) { + final float compatScale = token != null && token.hasSizeCompatBounds() + ? token.getSizeCompatScale() * overrideScale + : overrideScale; + outInsetsState.scale(1f / compatScale); + } + return dc.getDisplayPolicy().areSystemBarsForcedShownLw(); } } finally { Binder.restoreCallingIdentity(origId); @@ -8714,20 +8728,21 @@ public class WindowManagerService extends IWindowManager.Stub } boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { + final Task imeTargetWindowTask; synchronized (mGlobalLock) { final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken); if (imeTargetWindow == null) { return false; } - final Task imeTargetWindowTask = imeTargetWindow.getTask(); + imeTargetWindowTask = imeTargetWindow.getTask(); if (imeTargetWindowTask == null) { return false; } - final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId, - imeTargetWindowTask.mUserId, false /* isLowResolution */, - false /* restoreFromDisk */); - return snapshot != null && snapshot.hasImeSurface(); } + final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId, + imeTargetWindowTask.mUserId, false /* isLowResolution */, + false /* restoreFromDisk */); + return snapshot != null && snapshot.hasImeSurface(); } @Override @@ -8772,4 +8787,34 @@ public class WindowManagerService extends IWindowManager.Stub mTaskTransitionSpec = null; } + @Override + @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER) + public void registerTaskFpsCallback(@IntRange(from = 0) int taskId, + IOnFpsCallbackListener listener) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER) + != PackageManager.PERMISSION_GRANTED) { + final int pid = Binder.getCallingPid(); + throw new SecurityException("Access denied to process: " + pid + + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER); + } + + if (mRoot.anyTaskForId(taskId) == null) { + throw new IllegalArgumentException("no task with taskId: " + taskId); + } + + mTaskFpsCallbackController.registerCallback(taskId, listener); + } + + @Override + @RequiresPermission(Manifest.permission.ACCESS_FPS_COUNTER) + public void unregisterTaskFpsCallback(IOnFpsCallbackListener listener) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.ACCESS_FPS_COUNTER) + != PackageManager.PERMISSION_GRANTED) { + final int pid = Binder.getCallingPid(); + throw new SecurityException("Access denied to process: " + pid + + ", must have permission " + Manifest.permission.ACCESS_FPS_COUNTER); + } + + mTaskFpsCallbackController.unregisterCallback(listener); + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e4d3e05c09ff..1f837677ab36 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -172,6 +172,7 @@ import static com.android.server.wm.WindowStateProto.HAS_SURFACE; import static com.android.server.wm.WindowStateProto.IS_ON_SCREEN; import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY; import static com.android.server.wm.WindowStateProto.IS_VISIBLE; +import static com.android.server.wm.WindowStateProto.KEEP_CLEAR_AREAS; import static com.android.server.wm.WindowStateProto.PENDING_SEAMLESS_ROTATION; import static com.android.server.wm.WindowStateProto.REMOVED; import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT; @@ -196,6 +197,7 @@ import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Region; import android.gui.TouchOcclusionMode; import android.os.Binder; @@ -442,9 +444,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Current transformation being applied. float mGlobalScale=1; - float mLastGlobalScale=1; float mInvGlobalScale=1; - float mOverrideScale = 1; + final float mOverrideScale; float mHScale=1, mVScale=1; float mLastHScale=1, mLastVScale=1; @@ -471,6 +472,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Coordinates are relative to the window's position. */ private final List<Rect> mExclusionRects = new ArrayList<>(); + /** + * List of rects which should ideally not be covered by floating windows like Pip. + * + * Coordinates are relative to the window's position. + */ + private final List<Rect> mKeepClearAreas = new ArrayList<>(); // 0 = left, 1 = right private final int[] mLastRequestedExclusionHeight = {0, 0}; @@ -1012,6 +1019,55 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + /** + * @return a list of rects that should ideally not be covered by floating windows like pip. + * The returned rect coordinates are relative to the display origin. + */ + List<Rect> getKeepClearAreas() { + final Matrix tmpMatrix = new Matrix(); + final float[] tmpFloat9 = new float[9]; + return getKeepClearAreas(tmpMatrix, tmpFloat9); + } + + /** + * @param tmpMatrix a temporary matrix to be used for transformations + * @param float9 a temporary array of 9 floats + * + * @return a list of rects that should ideally not be covered by floating windows like pip. + * The returned rect coordinates are relative to the display origin. + */ + List<Rect> getKeepClearAreas(Matrix tmpMatrix, float[] float9) { + getTransformationMatrix(float9, tmpMatrix); + + // Translate all keep-clear rects to screen coordinates. + final List<Rect> transformedKeepClearAreas = new ArrayList<Rect>(); + final RectF tmpRect = new RectF(); + Rect curr; + for (Rect r : mKeepClearAreas) { + tmpRect.set(r); + tmpMatrix.mapRect(tmpRect); + curr = new Rect(); + tmpRect.roundOut(curr); + transformedKeepClearAreas.add(curr); + } + return transformedKeepClearAreas; + } + + /** + * @param keepClearAreas the new keep-clear areas for this window. The rects should be defined + * in window coordinate space + * + * @return true if there is a change in the list of keep-clear areas; false otherwise + */ + boolean setKeepClearAreas(List<Rect> keepClearAreas) { + if (mKeepClearAreas.equals(keepClearAreas)) { + return false; + } + mKeepClearAreas.clear(); + mKeepClearAreas.addAll(keepClearAreas); + return true; + } + interface PowerManagerWrapper { void wakeUp(long time, @WakeReason int reason, String details); @@ -1091,6 +1147,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSubLayer = 0; mWinAnimator = null; mWpcForDisplayAreaConfigChanges = null; + mOverrideScale = 1f; return; } mDeathRecipient = deathRecipient; @@ -1138,6 +1195,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mLayer = 0; mOverrideScale = mWmService.mAtmService.mCompatModePackages.getCompatScale( mAttrs.packageName, s.mUid); + updateGlobalScale(); // Make sure we initial all fields before adding to parentWindow, to prevent exception // during onDisplayChanged. @@ -1167,6 +1225,23 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mSession.windowAddedLocked(); } + boolean updateGlobalScale() { + if (hasCompatScale()) { + if (mOverrideScale != 1f) { + mGlobalScale = mToken.hasSizeCompatBounds() + ? mToken.getSizeCompatScale() * mOverrideScale + : mOverrideScale; + } else { + mGlobalScale = mToken.getSizeCompatScale(); + } + mInvGlobalScale = 1f / mGlobalScale; + return true; + } + + mGlobalScale = mInvGlobalScale = 1f; + return false; + } + /** * @return {@code true} if the application runs in size compatibility mode or has an app level * scaling override set. @@ -1175,7 +1250,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * @see ActivityRecord#hasSizeCompatBounds() */ boolean hasCompatScale() { - return mOverrideScale != 1f || hasCompatScale(mAttrs, mActivityRecord); + return hasCompatScale(mAttrs, mActivityRecord, mOverrideScale); } /** @@ -1183,11 +1258,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * @see android.content.res.CompatibilityInfo#supportsScreen * @see ActivityRecord#hasSizeCompatBounds() */ - static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken windowToken) { - return (attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0 - || (windowToken != null && windowToken.hasSizeCompatBounds() - // Exclude starting window because it is not displayed by the application. - && attrs.type != TYPE_APPLICATION_STARTING); + static boolean hasCompatScale(WindowManager.LayoutParams attrs, WindowToken token, + float overrideScale) { + if ((attrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0) { + return true; + } + if (attrs.type == TYPE_APPLICATION_STARTING) { + // Exclude starting window because it is not displayed by the application. + return false; + } + return token != null && token.hasSizeCompatBounds() || overrideScale != 1f; } /** @@ -1691,21 +1771,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed); } - void prelayout() { - if (hasCompatScale()) { - if (mOverrideScale != 1f) { - mGlobalScale = mToken.hasSizeCompatBounds() - ? mToken.getSizeCompatScale() * mOverrideScale - : mOverrideScale; - } else { - mGlobalScale = mToken.getSizeCompatScale(); - } - mInvGlobalScale = 1 / mGlobalScale; - } else { - mGlobalScale = mInvGlobalScale = 1; - } - } - @Override boolean hasContentToDisplay() { if (!mAppFreezing && isDrawn() && (mViewVisibility == View.VISIBLE @@ -2928,7 +2993,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void binderDied() { try { - boolean resetSplitScreenResizing = false; synchronized (mWmService.mGlobalLock) { final WindowState win = mWmService .windowForClientLocked(mSession, mClient, false); @@ -2944,16 +3008,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP WindowState.this.removeIfPossible(); } } - if (resetSplitScreenResizing) { - try { - // Note: this calls into ActivityManager, so we must *not* hold the window - // manager lock while calling this. - mWmService.mActivityTaskManager.setSplitScreenResizing(false); - } catch (RemoteException e) { - // Local call, shouldn't return RemoteException. - throw e.rethrowAsRuntimeException(); - } - } } catch (IllegalArgumentException ex) { // This will happen if the window has already been removed. } @@ -4063,6 +4117,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP proto.write(FORCE_SEAMLESS_ROTATION, mForceSeamlesslyRotate); proto.write(HAS_COMPAT_SCALE, hasCompatScale()); proto.write(GLOBAL_SCALE, mGlobalScale); + for (Rect r : getKeepClearAreas()) { + r.dumpDebug(proto, KEEP_CLEAR_AREAS); + } proto.end(token); } @@ -4231,6 +4288,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } pw.println(prefix + "isOnScreen=" + isOnScreen()); pw.println(prefix + "isVisible=" + isVisible()); + pw.println(prefix + "keepClearAreas=" + getKeepClearAreas()); if (dumpAll) { final String visibilityString = mRequestedVisibilities.toString(); if (!visibilityString.isEmpty()) { @@ -5259,7 +5317,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mLastVScale != newVScale ) { getPendingTransaction().setMatrix(getSurfaceControl(), newHScale, 0, 0, newVScale); - mLastGlobalScale = mGlobalScale; mLastHScale = newHScale; mLastVScale = newVScale; } diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index b6438832399a..79a980f0d9ee 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -68,6 +68,7 @@ cc_library_static { "com_android_server_am_LowMemDetector.cpp", "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp", "com_android_server_sensor_SensorService.cpp", + "com_android_server_wm_TaskFpsCallbackController.cpp", "onload.cpp", ":lib_cachedAppOptimizer_native", ":lib_networkStatsFactory_native", diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3cd4e5ee82cf..c71686abe9de 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -286,6 +286,7 @@ public: void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled); void setCustomPointerIcon(const SpriteIcon& icon); void setMotionClassifierEnabled(bool enabled); + void notifyPointerDisplayIdChanged(); /* --- InputReaderPolicyInterface implementation --- */ @@ -1494,6 +1495,18 @@ void NativeInputManager::setMotionClassifierEnabled(bool enabled) { mInputManager->getClassifier().setMotionClassifierEnabled(enabled); } +void NativeInputManager::notifyPointerDisplayIdChanged() { + int32_t pointerDisplayId = getPointerDisplayId(); + + { // acquire lock + AutoMutex _l(mLock); + mLocked.pointerDisplayId = pointerDisplayId; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + // ---------------------------------------------------------------------------- static jlong nativeInit(JNIEnv* env, jclass /* clazz */, @@ -1573,6 +1586,13 @@ static jboolean nativeHasKeys(JNIEnv* env, jclass /* clazz */, return result; } +static jint nativeGetKeyCodeForKeyLocation(JNIEnv* env, jclass /* clazz */, jlong ptr, + jint deviceId, jint locationKeyCode) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + return (jint)im->getInputManager()->getReader().getKeyCodeForKeyLocation(deviceId, + locationKeyCode); +} + static void handleInputChannelDisposed(JNIEnv* env, jobject /* inputChannelObj */, const std::shared_ptr<InputChannel>& inputChannel, void* data) { @@ -2186,6 +2206,18 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, InputReaderConfiguration::CHANGE_DISPLAY_INFO); } +static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + im->notifyPointerDisplayIdChanged(); +} + +static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jclass /* clazz */, jlong ptr, + jint displayId, jboolean isEligible) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + im->getInputManager()->getDispatcher().setDisplayEligibilityForPointerCapture(displayId, + isEligible); +} + static void nativeChangeUniqueIdAssociation(JNIEnv* env, jclass /* clazz */, jlong ptr) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); im->getInputManager()->getReader().requestRefreshConfiguration( @@ -2312,6 +2344,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeGetKeyCodeState", "(JIII)I", (void*)nativeGetKeyCodeState}, {"nativeGetSwitchState", "(JIII)I", (void*)nativeGetSwitchState}, {"nativeHasKeys", "(JII[I[Z)Z", (void*)nativeHasKeys}, + {"nativeGetKeyCodeForKeyLocation", "(JII)I", (void*)nativeGetKeyCodeForKeyLocation}, {"nativeCreateInputChannel", "(JLjava/lang/String;)Landroid/view/InputChannel;", (void*)nativeCreateInputChannel}, {"nativeCreateInputMonitor", "(JIZLjava/lang/String;I)Landroid/view/InputChannel;", @@ -2370,6 +2403,9 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay}, {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged}, {"nativeChangeUniqueIdAssociation", "(J)V", (void*)nativeChangeUniqueIdAssociation}, + {"nativeNotifyPointerDisplayIdChanged", "(J)V", (void*)nativeNotifyPointerDisplayIdChanged}, + {"nativeSetDisplayEligibilityForPointerCapture", "(JIZ)V", + (void*)nativeSetDisplayEligibilityForPointerCapture}, {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled}, {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;", (void*)nativeGetSensorList}, diff --git a/core/jni/android_view_SurfaceControlFpsListener.cpp b/services/core/jni/com_android_server_wm_TaskFpsCallbackController.cpp index 0b15acd77689..0202306fc395 100644 --- a/core/jni/android_view_SurfaceControlFpsListener.cpp +++ b/services/core/jni/com_android_server_wm_TaskFpsCallbackController.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Android Open Source Project + * Copyright 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "SurfaceControlFpsListener" +#define LOG_TAG "TaskFpsCallbackController" #include <android/gui/BnFpsListener.h> #include <android_runtime/AndroidRuntime.h> @@ -35,11 +35,10 @@ namespace { struct { jclass mClass; jmethodID mDispatchOnFpsReported; -} gListenerClassInfo; +} gCallbackClassInfo; -struct SurfaceControlFpsListener : public gui::BnFpsListener { - SurfaceControlFpsListener(JNIEnv* env, jobject listener) - : mListener(env->NewWeakGlobalRef(listener)) {} +struct TaskFpsCallback : public gui::BnFpsListener { + TaskFpsCallback(JNIEnv* env, jobject listener) : mListener(env->NewWeakGlobalRef(listener)) {} binder::Status onFpsReported(float fps) override { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -50,13 +49,13 @@ struct SurfaceControlFpsListener : public gui::BnFpsListener { // Weak reference went out of scope return binder::Status::ok(); } - env->CallStaticVoidMethod(gListenerClassInfo.mClass, - gListenerClassInfo.mDispatchOnFpsReported, listener, + env->CallStaticVoidMethod(gCallbackClassInfo.mClass, + gCallbackClassInfo.mDispatchOnFpsReported, listener, static_cast<jfloat>(fps)); env->DeleteGlobalRef(listener); if (env->ExceptionCheck()) { - ALOGE("SurfaceControlFpsListener.onFpsReported() failed."); + ALOGE("TaskFpsCallback.onFpsReported() failed."); LOGE_EX(env); env->ExceptionClear(); } @@ -64,7 +63,7 @@ struct SurfaceControlFpsListener : public gui::BnFpsListener { } protected: - virtual ~SurfaceControlFpsListener() { + virtual ~TaskFpsCallback() { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mListener); } @@ -73,55 +72,48 @@ private: jweak mListener; }; -jlong nativeCreate(JNIEnv* env, jclass clazz, jobject obj) { - SurfaceControlFpsListener* listener = new SurfaceControlFpsListener(env, obj); - listener->incStrong((void*)nativeCreate); - return reinterpret_cast<jlong>(listener); -} - -void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) { - SurfaceControlFpsListener* listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); - listener->decStrong((void*)nativeCreate); -} +jlong nativeRegister(JNIEnv* env, jclass clazz, jobject obj, jint taskId) { + TaskFpsCallback* callback = new TaskFpsCallback(env, obj); -void nativeRegister(JNIEnv* env, jclass clazz, jlong ptr, jint taskId) { - sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); - if (SurfaceComposerClient::addFpsListener(taskId, listener) != OK) { + if (SurfaceComposerClient::addFpsListener(taskId, callback) != OK) { constexpr auto error_msg = "Couldn't addFpsListener"; ALOGE(error_msg); jniThrowRuntimeException(env, error_msg); } + callback->incStrong((void*)nativeRegister); + + return reinterpret_cast<jlong>(callback); } void nativeUnregister(JNIEnv* env, jclass clazz, jlong ptr) { - sp<SurfaceControlFpsListener> listener = reinterpret_cast<SurfaceControlFpsListener*>(ptr); + sp<TaskFpsCallback> callback = reinterpret_cast<TaskFpsCallback*>(ptr); - if (SurfaceComposerClient::removeFpsListener(listener) != OK) { + if (SurfaceComposerClient::removeFpsListener(callback) != OK) { constexpr auto error_msg = "Couldn't removeFpsListener"; ALOGE(error_msg); jniThrowRuntimeException(env, error_msg); } + + callback->decStrong((void*)nativeRegister); } -const JNINativeMethod gMethods[] = { +static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"nativeCreate", "(Landroid/view/SurfaceControlFpsListener;)J", (void*)nativeCreate}, - {"nativeDestroy", "(J)V", (void*)nativeDestroy}, - {"nativeRegister", "(JI)V", (void*)nativeRegister}, + {"nativeRegister", "(Landroid/window/IOnFpsCallbackListener;I)J", (void*)nativeRegister}, {"nativeUnregister", "(J)V", (void*)nativeUnregister}}; } // namespace -int register_android_view_SurfaceControlFpsListener(JNIEnv* env) { - int res = jniRegisterNativeMethods(env, "android/view/SurfaceControlFpsListener", gMethods, - NELEM(gMethods)); +int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/wm/TaskFpsCallbackController", + gMethods, NELEM(gMethods)); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); - jclass clazz = env->FindClass("android/view/SurfaceControlFpsListener"); - gListenerClassInfo.mClass = MakeGlobalRefOrDie(env, clazz); - gListenerClassInfo.mDispatchOnFpsReported = + jclass clazz = env->FindClass("android/window/TaskFpsCallback"); + gCallbackClassInfo.mClass = MakeGlobalRefOrDie(env, clazz); + gCallbackClassInfo.mDispatchOnFpsReported = env->GetStaticMethodID(clazz, "dispatchOnFpsReported", - "(Landroid/view/SurfaceControlFpsListener;F)V"); + "(Landroid/window/IOnFpsCallbackListener;F)V"); return 0; } diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 80d7055735c3..ba5b3f54efa1 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -65,6 +65,7 @@ int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env); int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env); int register_android_server_companion_virtual_InputController(JNIEnv* env); int register_android_server_app_GameManagerService(JNIEnv* env); +int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); }; using namespace android; @@ -123,5 +124,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_sensor_SensorService(vm, env); register_android_server_companion_virtual_InputController(env); register_android_server_app_GameManagerService(env); + register_com_android_server_wm_TaskFpsCallbackController(env); return JNI_VERSION_1_4; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java index df9ab5003122..f19202aff2f0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java @@ -145,6 +145,10 @@ class ActiveAdmin { private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_ENABLED = "preferential-network-service-enabled"; private static final String TAG_USB_DATA_SIGNALING = "usb-data-signaling"; + private static final String TAG_WIFI_MIN_SECURITY = "wifi-min-security"; + private static final String TAG_SSID_ALLOWLIST = "ssid-allowlist"; + private static final String TAG_SSID_DENYLIST = "ssid-denylist"; + private static final String TAG_SSID = "ssid"; private static final String ATTR_VALUE = "value"; private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; @@ -237,6 +241,14 @@ class ActiveAdmin { // List of package names to keep cached. List<String> keepUninstalledPackages; + // The allowlist of SSIDs the device may connect to. + // By default, the allowlist restriction is deactivated. + List<String> mSsidAllowlist; + + // The denylist of SSIDs the device may not connect to. + // By default, the denylist restriction is deactivated. + List<String> mSsidDenylist; + // TODO: review implementation decisions with frameworks team boolean specifiesGlobalProxy = false; String globalProxySpec = null; @@ -298,6 +310,8 @@ class ActiveAdmin { private static final boolean USB_DATA_SIGNALING_ENABLED_DEFAULT = true; boolean mUsbDataSignalingEnabled = USB_DATA_SIGNALING_ENABLED_DEFAULT; + int mWifiMinimumSecurityLevel = DevicePolicyManager.WIFI_SECURITY_OPEN; + ActiveAdmin(DeviceAdminInfo info, boolean isParent) { this.info = info; this.isParent = isParent; @@ -574,6 +588,15 @@ class ActiveAdmin { if (mUsbDataSignalingEnabled != USB_DATA_SIGNALING_ENABLED_DEFAULT) { writeAttributeValueToXml(out, TAG_USB_DATA_SIGNALING, mUsbDataSignalingEnabled); } + if (mWifiMinimumSecurityLevel != DevicePolicyManager.WIFI_SECURITY_OPEN) { + writeAttributeValueToXml(out, TAG_WIFI_MIN_SECURITY, mWifiMinimumSecurityLevel); + } + if (mSsidAllowlist != null && !mSsidAllowlist.isEmpty()) { + writeAttributeValuesToXml(out, TAG_SSID_ALLOWLIST, TAG_SSID, mSsidAllowlist); + } + if (mSsidDenylist != null && !mSsidDenylist.isEmpty()) { + writeAttributeValuesToXml(out, TAG_SSID_DENYLIST, TAG_SSID, mSsidDenylist); + } } void writeTextToXml(TypedXmlSerializer out, String tag, String text) throws IOException { @@ -826,6 +849,14 @@ class ActiveAdmin { } else if (TAG_USB_DATA_SIGNALING.equals(tag)) { mUsbDataSignalingEnabled = parser.getAttributeBoolean(null, ATTR_VALUE, USB_DATA_SIGNALING_ENABLED_DEFAULT); + } else if (TAG_WIFI_MIN_SECURITY.equals(tag)) { + mWifiMinimumSecurityLevel = parser.getAttributeInt(null, ATTR_VALUE); + } else if (TAG_SSID_ALLOWLIST.equals(tag)) { + mSsidAllowlist = new ArrayList<>(); + readAttributeValues(parser, TAG_SSID, mSsidAllowlist); + } else if (TAG_SSID_DENYLIST.equals(tag)) { + mSsidDenylist = new ArrayList<>(); + readAttributeValues(parser, TAG_SSID, mSsidDenylist); } else { Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag); XmlUtils.skipCurrentTag(parser); @@ -1184,5 +1215,14 @@ class ActiveAdmin { pw.print("mUsbDataSignaling="); pw.println(mUsbDataSignalingEnabled); + + pw.print("mWifiMinimumSecurityLevel="); + pw.println(mWifiMinimumSecurityLevel); + + pw.print("mSsidAllowlist="); + pw.println(mSsidAllowlist); + + pw.print("mSsidDenylist="); + pw.println(mSsidDenylist); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7c0d549b2fe0..40196dbbd8b5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -100,6 +100,18 @@ import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; import static android.app.admin.DevicePolicyManager.WIPE_SILENTLY; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.LOCATION_CHANGED_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NETWORK_LOGGING_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TITLE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE; +import static android.app.admin.DevicePolicyResources.Strings.Core.PRINTING_DISABLED_NAMED_ADMIN; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_GENERIC_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE; +import static android.app.admin.DevicePolicyResources.Strings.Core.WORK_PROFILE_DELETED_TITLE; import static android.app.admin.ProvisioningException.ERROR_ADMIN_PACKAGE_INSTALLATION_FAILED; import static android.app.admin.ProvisioningException.ERROR_PRE_CONDITION_FAILED; import static android.app.admin.ProvisioningException.ERROR_PROFILE_CREATION_FAILED; @@ -113,6 +125,7 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; +import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; @@ -2236,7 +2249,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { * a managed profile. */ @GuardedBy("getLockObject()") - private void applyManagedProfileRestrictionIfDeviceOwnerLocked() { + private void applyProfileRestrictionsIfDeviceOwnerLocked() { final int doUserId = mOwners.getDeviceOwnerUserId(); if (doUserId == UserHandle.USER_NULL) { if (VERBOSE_LOG) Slogf.d(LOG_TAG, "No DO found, skipping application of restriction."); @@ -2244,7 +2257,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final UserHandle doUserHandle = UserHandle.of(doUserId); - // Set the restriction if not set. + + // Based on CDD : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support, + // creation of clone profile is not allowed in case device owner is set. + // Enforcing this restriction on setting up of device owner. + if (!mUserManager.hasUserRestriction( + UserManager.DISALLOW_ADD_CLONE_PROFILE, doUserHandle)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true, + doUserHandle); + } + // Creation of managed profile is restricted in case device owner is set, enforcing this + // restriction by setting user level restriction at time of device owner setup. if (!mUserManager.hasUserRestriction( UserManager.DISALLOW_ADD_MANAGED_PROFILE, doUserHandle)) { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, @@ -3153,7 +3176,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { case SystemService.PHASE_ACTIVITY_MANAGER_READY: synchronized (getLockObject()) { migrateToProfileOnOrganizationOwnedDeviceIfCompLocked(); - applyManagedProfileRestrictionIfDeviceOwnerLocked(); + applyProfileRestrictionsIfDeviceOwnerLocked(); } maybeStartSecurityLogMonitorOnActivityManagerReady(); break; @@ -3778,6 +3801,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false, userHandle); } + // When a device owner is set, the system automatically restricts adding a clone profile. + // Remove this restriction when the device owner is cleared. + if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, userHandle)) { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, false, + userHandle); + } } /** @@ -6929,12 +6958,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_WIPE_DATA); if (TextUtils.isEmpty(wipeReasonForUser)) { - if (calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance) { - wipeReasonForUser = mContext.getString(R.string.device_ownership_relinquished); - } else { - wipeReasonForUser = mContext.getString( - R.string.work_profile_deleted_description_dpm_wipe); - } + wipeReasonForUser = getGenericWipeReason( + calledByProfileOwnerOnOrgOwnedDevice, calledOnParentInstance); } int userId = admin != null ? admin.getUserHandle().getIdentifier() @@ -6985,6 +7010,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { wipeDataNoLock(adminComp, flags, internalReason, wipeReasonForUser, userId); } + private String getGenericWipeReason( + boolean calledByProfileOwnerOnOrgOwnedDevice, boolean calledOnParentInstance) { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return calledByProfileOwnerOnOrgOwnedDevice && !calledOnParentInstance + ? dpm.getString(WORK_PROFILE_DELETED_ORG_OWNED_MESSAGE, + () -> mContext.getString( + R.string.device_ownership_relinquished)) + : dpm.getString(WORK_PROFILE_DELETED_GENERIC_MESSAGE, + () -> mContext.getString( + R.string.work_profile_deleted_description_dpm_wipe)); + } + /** * Clears device wide policies enforced by COPE PO when relinquishing the device. This method * should be invoked once the admin is gone, so that all methods that rely on calculating @@ -7069,7 +7106,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(android.R.drawable.stat_sys_warning) - .setContentTitle(mContext.getString(R.string.work_profile_deleted)) + .setContentTitle(getWorkProfileDeletedTitle()) .setContentText(wipeReasonForUser) .setColor(mContext.getColor(R.color.system_notification_accent_color)) .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser)) @@ -7077,6 +7114,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification); } + private String getWorkProfileDeletedTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(WORK_PROFILE_DELETED_TITLE, + () -> mContext.getString(R.string.work_profile_deleted)); + } + private void clearWipeProfileNotification() { mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PROFILE_WIPED); } @@ -7307,12 +7350,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // able to do so). // IMPORTANT: Call without holding the lock to prevent deadlock. try { - String wipeReasonForUser = mContext.getString( - R.string.work_profile_deleted_reason_maximum_password_failure); wipeDataNoLock(strictestAdmin.info.getComponent(), /*flags=*/ 0, /*reason=*/ "reportFailedPasswordAttempt()", - wipeReasonForUser, + getFailedPasswordAttemptWipeMessage(), userId); } catch (SecurityException e) { Slogf.w(LOG_TAG, "Failed to wipe user " + userId @@ -7326,6 +7367,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private String getFailedPasswordAttemptWipeMessage() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(WORK_PROFILE_DELETED_FAILED_PASSWORD_ATTEMPTS_MESSAGE, + () -> mContext.getString( + R.string.work_profile_deleted_reason_maximum_password_failure)); + } + /** * Returns which user should be wiped if this admin's maximum filed password attempts policy is * violated. @@ -8470,6 +8518,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // on the primary profile). mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true, UserHandle.of(userId)); + // Restrict adding a clone profile when a device owner is set on the device. + // That is to prevent the co-existence of a clone profile and a device owner + // on the same device. + // CDD for reference : https://source.android.com/compatibility/12/android-12-cdd#95_multi-user_support + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, true, + UserHandle.of(userId)); // TODO Send to system too? sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId); }); @@ -8942,7 +8996,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mOwners.writeProfileOwner(userId); deleteTransferOwnershipBundleLocked(userId); toggleBackupServiceActive(userId, true); - applyManagedProfileRestrictionIfDeviceOwnerLocked(); + applyProfileRestrictionsIfDeviceOwnerLocked(); setNetworkLoggingActiveInternal(false); } @@ -12397,8 +12451,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(R.drawable.ic_info_outline) - .setContentTitle(mContext.getString(R.string.location_changed_notification_title)) - .setContentText(mContext.getString(R.string.location_changed_notification_text)) + .setContentTitle(getLocationChangedTitle()) + .setContentText(getLocationChangedText()) .setColor(mContext.getColor(R.color.system_notification_accent_color)) .setShowWhen(true) .setContentIntent(locationSettingsIntent) @@ -12408,6 +12462,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { notification); } + private String getLocationChangedTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(LOCATION_CHANGED_TITLE, + () -> mContext.getString(R.string.location_changed_notification_title)); + } + + private String getLocationChangedText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(LOCATION_CHANGED_MESSAGE, + () -> mContext.getString(R.string.location_changed_notification_text)); + } + @Override public boolean setTime(ComponentName who, long millis) { Objects.requireNonNull(who, "ComponentName is null"); @@ -12998,11 +13064,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { Slogf.e(LOG_TAG, "appLabel is inexplicably null"); return null; } - return ((Context) ActivityThread.currentActivityThread().getSystemUiContext()) - .getResources().getString(R.string.printing_disabled_by, appLabel); + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString( + PRINTING_DISABLED_NAMED_ADMIN, + () -> getDefaultPrintingDisabledMsg(appLabel), + appLabel); } } + private String getDefaultPrintingDisabledMsg(CharSequence appLabel) { + return ((Context) ActivityThread.currentActivityThread().getSystemUiContext()) + .getResources().getString(R.string.printing_disabled_by, appLabel); + } + @Override protected DevicePolicyCache getDevicePolicyCache() { return mPolicyCache; @@ -15534,16 +15608,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // Simple notification clicks are immutable final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT); + + final String title = getNetworkLoggingTitle(); + final String text = getNetworkLoggingText(); Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(R.drawable.ic_info_outline) - .setContentTitle(mContext.getString(R.string.network_logging_notification_title)) - .setContentText(mContext.getString(R.string.network_logging_notification_text)) - .setTicker(mContext.getString(R.string.network_logging_notification_title)) + .setContentTitle(title) + .setContentText(text) + .setTicker(title) .setShowWhen(true) .setContentIntent(pendingIntent) - .setStyle(new Notification.BigTextStyle() - .bigText(mContext.getString(R.string.network_logging_notification_text))) + .setStyle(new Notification.BigTextStyle().bigText(text)) .build(); Slogf.i(LOG_TAG, "Sending network logging notification to user %d", mNetworkLoggingNotificationUserId); @@ -15552,6 +15628,18 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { UserHandle.of(mNetworkLoggingNotificationUserId)); } + private String getNetworkLoggingTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(NETWORK_LOGGING_TITLE, + () -> mContext.getString(R.string.network_logging_notification_title)); + } + + private String getNetworkLoggingText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(NETWORK_LOGGING_MESSAGE, + () -> mContext.getString(R.string.network_logging_notification_text)); + } + private void handleCancelNetworkLoggingNotification() { if (mNetworkLoggingNotificationUserId == UserHandle.USER_NULL) { // Happens when setNetworkLoggingActive(false) is called before called with true @@ -17044,10 +17132,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - final String buttonText = - mContext.getString(R.string.personal_apps_suspended_turn_profile_on); - final Notification.Action turnProfileOnButton = - new Notification.Action.Builder(null /* icon */, buttonText, pendingIntent).build(); + final Notification.Action turnProfileOnButton = new Notification.Action.Builder( + /* icon= */ null, getPersonalAppSuspensionButtonText(), pendingIntent).build(); final String text; final boolean ongoing; @@ -17059,26 +17145,24 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_DATE); final String time = DateUtils.formatDateTime( mContext, profileOwner.mProfileOffDeadline, DateUtils.FORMAT_SHOW_TIME); - text = mContext.getString( - R.string.personal_apps_suspension_soon_text, date, time, maxDays); + text = getPersonalAppSuspensionSoonText(date, time, maxDays); ongoing = false; } else { - text = mContext.getString(R.string.personal_apps_suspension_text); + text = getPersonalAppSuspensionText(); ongoing = true; } final int color = mContext.getColor(R.color.personal_apps_suspension_notification_color); final Bundle extras = new Bundle(); // TODO: Create a separate string for this. - extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, - mContext.getString(R.string.notification_work_profile_content_description)); + extras.putString( + Notification.EXTRA_SUBSTITUTE_APP_NAME, getWorkProfileContentDescription()); final Notification notification = new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN) .setSmallIcon(R.drawable.ic_corp_badge_no_background) .setOngoing(ongoing) .setAutoCancel(false) - .setContentTitle(mContext.getString( - R.string.personal_apps_suspension_title)) + .setContentTitle(getPersonalAppSuspensionTitle()) .setContentText(text) .setStyle(new Notification.BigTextStyle().bigText(text)) .setColor(color) @@ -17089,6 +17173,38 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification); } + private String getPersonalAppSuspensionButtonText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TURN_ON_PROFILE, + () -> mContext.getString(R.string.personal_apps_suspended_turn_profile_on)); + } + + private String getPersonalAppSuspensionTitle() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE, + () -> mContext.getString(R.string.personal_apps_suspension_title)); + } + + private String getPersonalAppSuspensionText() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE, + () -> mContext.getString(R.string.personal_apps_suspension_text)); + } + + private String getPersonalAppSuspensionSoonText(String date, String time, int maxDays) { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(PERSONAL_APP_SUSPENSION_TITLE, + () -> mContext.getString( + R.string.personal_apps_suspension_soon_text, date, time, maxDays), + date, time, maxDays); + } + + private String getWorkProfileContentDescription() { + DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + return dpm.getString(NOTIFICATION_WORK_PROFILE_CONTENT_DESCRIPTION, + () -> mContext.getString(R.string.notification_work_profile_content_description)); + } + @Override public void setManagedProfileMaximumTimeOff(ComponentName who, long timeoutMillis) { Objects.requireNonNull(who, "ComponentName is null"); @@ -17852,7 +17968,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ? PROFILE_NETWORK_PREFERENCE_ENTERPRISE : PROFILE_NETWORK_PREFERENCE_DEFAULT; ProfileNetworkPreference.Builder preferenceBuilder = new ProfileNetworkPreference.Builder(); - preferenceBuilder.setPreference(networkPreference); + if (preferentialNetworkServiceEnabled) { + preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE); + preferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1); + } else { + preferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT); + } List<ProfileNetworkPreference> preferences = new ArrayList<>(); preferences.add(preferenceBuilder.build()); mInjector.binderWithCleanCallingIdentity(() -> @@ -17979,6 +18100,117 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { ); } + private void validateCurrentWifiMeetsAdminRequirements() { + mInjector.binderWithCleanCallingIdentity( + () -> mInjector.getWifiManager().validateCurrentWifiMeetsAdminRequirements()); + } + + @Override + public void setMinimumRequiredWifiSecurityLevel(int level) { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "Wi-Fi minimum security level can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + + boolean valueChanged = false; + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + if (admin.mWifiMinimumSecurityLevel != level) { + admin.mWifiMinimumSecurityLevel = level; + saveSettingsLocked(caller.getUserId()); + valueChanged = true; + } + } + if (valueChanged) validateCurrentWifiMeetsAdminRequirements(); + } + + @Override + public int getMinimumRequiredWifiSecurityLevel() { + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + return (admin == null) ? DevicePolicyManager.WIFI_SECURITY_OPEN + : admin.mWifiMinimumSecurityLevel; + } + } + + @Override + public void setSsidAllowlist(List<String> ssids) { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "SSID allowlist can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + + Collections.sort(ssids); + boolean changed = false; + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + if (!ssids.equals(admin.mSsidAllowlist)) { + admin.mSsidAllowlist = ssids; + admin.mSsidDenylist = null; + changed = true; + } + if (changed) saveSettingsLocked(caller.getUserId()); + } + if (changed) validateCurrentWifiMeetsAdminRequirements(); + } + + @Override + public List<String> getSsidAllowlist() { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isSystemUid(caller), + "SSID allowlist can only be retrieved by a device owner or " + + "a profile owner on an organization-owned device or a system app."); + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + return (admin == null || admin.mSsidAllowlist == null) ? new ArrayList<>() + : admin.mSsidAllowlist; + } + } + + @Override + public void setSsidDenylist(List<String> ssids) { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), + "SSID denylist can only be controlled by a device owner or " + + "a profile owner on an organization-owned device."); + + Collections.sort(ssids); + boolean changed = false; + synchronized (getLockObject()) { + final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller); + if (!ssids.equals(admin.mSsidDenylist)) { + admin.mSsidDenylist = ssids; + admin.mSsidAllowlist = null; + changed = true; + } + if (changed) saveSettingsLocked(caller.getUserId()); + } + if (changed) validateCurrentWifiMeetsAdminRequirements(); + } + + @Override + public List<String> getSsidDenylist() { + final CallerIdentity caller = getCallerIdentity(); + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isSystemUid(caller), + "SSID denylist can only be retrieved by a device owner or " + + "a profile owner on an organization-owned device or a system app."); + synchronized (getLockObject()) { + final ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked( + UserHandle.USER_SYSTEM); + return (admin == null || admin.mSsidDenylist == null) ? new ArrayList<>() + : admin.mSsidDenylist; + } + } + @Override public void setDrawables(@NonNull List<DevicePolicyDrawableResource> drawables) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a7b7d1aafb71..ad8753d4c136 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -54,6 +54,7 @@ import android.hardware.display.DisplayManagerInternal; import android.net.ConnectivityManager; import android.net.ConnectivityModuleConnector; import android.net.NetworkStackClient; +import android.net.TrafficStats; import android.os.BaseBundle; import android.os.Binder; import android.os.Build; @@ -103,6 +104,7 @@ import com.android.internal.util.EmergencyAffordanceManager; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.widget.ILockSettings; import com.android.server.am.ActivityManagerService; +import com.android.server.ambientcontext.AmbientContextManagerService; import com.android.server.appbinding.AppBindingService; import com.android.server.art.ArtManagerLocal; import com.android.server.attention.AttentionManagerService; @@ -1809,6 +1811,7 @@ public final class SystemServer implements Dumpable { startRotationResolverService(context, t); startSystemCaptionsManagerService(context, t); startTextToSpeechManagerService(context, t); + startAmbientContextService(t); // System Speech Recognition Service t.traceBegin("StartSpeechRecognitionManagerService"); @@ -1903,6 +1906,7 @@ public final class SystemServer implements Dumpable { try { networkStats = NetworkStatsService.create(context); ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); + TrafficStats.init(context); } catch (Throwable e) { reportWtf("starting NetworkStats Service", e); } @@ -3160,6 +3164,17 @@ public final class SystemServer implements Dumpable { } + private void startAmbientContextService(@NonNull TimingsTraceAndSlog t) { + if (!AmbientContextManagerService.isDetectionServiceConfigured()) { + Slog.d(TAG, "AmbientContextDetectionService is not configured on this device"); + return; + } + + t.traceBegin("StartAmbientContextService"); + mSystemServiceManager.startService(AmbientContextManagerService.class); + t.traceEnd(); + } + private static void startSystemUi(Context context, WindowManagerService windowManager) { PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); Intent intent = new Intent(); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 6e724792b6e9..d56278629bf2 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -864,7 +864,15 @@ public class MidiService extends IMidiManager.Stub { if (device == null) { throw new IllegalArgumentException("no such device for " + deviceInfo); } - return device.getDeviceStatus(); + int uid = Binder.getCallingUid(); + if (device.isUidAllowed(uid)) { + return device.getDeviceStatus(); + } else { + Log.e(TAG, "getDeviceStatus() invalid UID = " + uid); + EventLog.writeEvent(0x534e4554, "203549963", + uid, "getDeviceStatus: invalid uid"); + return null; + } } @Override diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index a0f3bbf928ab..cc663d955612 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -503,6 +503,11 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag PackageManager.Property::getString ) } + ), + getSetByValue( + AndroidPackage::shouldInheritKeyStoreKeys, + ParsingPackage::setInheritKeyStoreKeys, + true ) ) diff --git a/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java new file mode 100644 index 000000000000..fec9b1249d17 --- /dev/null +++ b/services/tests/mockingservicestests/src/android/service/games/GameSessionTest.java @@ -0,0 +1,391 @@ +/* + * 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 android.service.games; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; + +import android.graphics.Bitmap; +import android.platform.test.annotations.Presubmit; +import android.service.games.GameSession.ScreenshotCallback; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceControlViewHost; + +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import com.android.internal.infra.AndroidFuture; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoSession; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Unit tests for the {@link android.service.games.GameSession}. + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +@Presubmit +@TestableLooper.RunWithLooper(setAsMainLooper = true) +public final class GameSessionTest { + private static final long WAIT_FOR_CALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1); + private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + + @Mock + private IGameSessionController mMockGameSessionController; + @Mock + SurfaceControlViewHost mSurfaceControlViewHost; + private LifecycleTrackingGameSession mGameSession; + private MockitoSession mMockitoSession; + + @Before + public void setUp() { + mMockitoSession = mockitoSession() + .initMocks(this) + .startMocking(); + + mGameSession = new LifecycleTrackingGameSession() {}; + mGameSession.attach(mMockGameSessionController, /* taskId= */ 10, + InstrumentationRegistry.getContext(), + mSurfaceControlViewHost, + /* widthPx= */ 0, /* heightPx= */0); + } + + @After + public void tearDown() { + mMockitoSession.finishMocking(); + } + + @Test + public void takeScreenshot_attachNotCalled_throwsIllegalStateException() throws Exception { + GameSession gameSession = new GameSession() {}; + + try { + gameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + fail(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + fail(); + } + }); + fail(); + } catch (IllegalStateException expected) { + + } + } + + @Test + public void takeScreenshot_gameManagerException_returnsInternalError() throws Exception { + doAnswer(invocation -> { + AndroidFuture result = invocation.getArgument(1); + result.completeExceptionally(new Exception()); + return null; + }).when(mMockGameSessionController).takeScreenshot(anyInt(), any()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + mGameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + assertEquals(ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, + statusCode); + countDownLatch.countDown(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + fail(); + } + }); + + assertTrue(countDownLatch.await( + WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void takeScreenshot_gameManagerError_returnsInternalError() throws Exception { + doAnswer(invocation -> { + AndroidFuture result = invocation.getArgument(1); + result.complete(GameScreenshotResult.createInternalErrorResult()); + return null; + }).when(mMockGameSessionController).takeScreenshot(anyInt(), any()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + mGameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + assertEquals(ScreenshotCallback.ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, + statusCode); + countDownLatch.countDown(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + fail(); + } + }); + + assertTrue(countDownLatch.await( + WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void takeScreenshot_gameManagerSuccess_returnsBitmap() throws Exception { + doAnswer(invocation -> { + AndroidFuture result = invocation.getArgument(1); + result.complete(GameScreenshotResult.createSuccessResult(TEST_BITMAP)); + return null; + }).when(mMockGameSessionController).takeScreenshot(anyInt(), any()); + + CountDownLatch countDownLatch = new CountDownLatch(1); + + mGameSession.takeScreenshot(DIRECT_EXECUTOR, + new ScreenshotCallback() { + @Override + public void onFailure(int statusCode) { + fail(); + } + + @Override + public void onSuccess(Bitmap bitmap) { + assertEquals(TEST_BITMAP, bitmap); + countDownLatch.countDown(); + } + }); + + assertTrue(countDownLatch.await( + WAIT_FOR_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } + + @Test + public void moveState_InitializedToInitialized_noLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.INITIALIZED); + + assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue(); + } + + @Test + public void moveState_FullLifecycle_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_DestroyedWhenInitialized_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + // ON_CREATE is always called before ON_DESTROY. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_DestroyedWhenFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + // The ON_GAME_TASK_UNFOCUSED lifecycle event is implied because the session is destroyed + // while in focus. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_FocusCycled_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // Both cycles from focus and unfocus are captured. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED).inOrder(); + } + + @Test + public void moveState_MultipleFocusAndUnfocusCalls_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // The second TASK_FOCUSED call and the second TASK_UNFOCUSED call are ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED).inOrder(); + } + + @Test + public void moveState_CreatedAfterFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + + // The second CREATED call is ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED).inOrder(); + } + + @Test + public void moveState_UnfocusedWithoutFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // The TASK_UNFOCUSED call without an earlier TASK_FOCUSED call is ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE).inOrder(); + } + + @Test + public void moveState_NeverFocused_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + @Test + public void moveState_MultipleFocusCalls_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + + // The extra TASK_FOCUSED moves are ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_GAME_TASK_FOCUSED).inOrder(); + } + + @Test + public void moveState_MultipleCreateCalls_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + + // The extra CREATE moves are ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE).inOrder(); + } + + @Test + public void moveState_FocusBeforeCreate_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + + // The TASK_FOCUSED move before CREATE is ignored. + assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue(); + } + + @Test + public void moveState_UnfocusBeforeCreate_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.TASK_UNFOCUSED); + + // The TASK_UNFOCUSED move before CREATE is ignored. + assertThat(mGameSession.mLifecycleMethodCalls.isEmpty()).isTrue(); + } + + @Test + public void moveState_FocusWhenDestroyed_ExpectedLifecycleCalls() throws Exception { + mGameSession.moveToState(GameSession.LifecycleState.CREATED); + mGameSession.moveToState(GameSession.LifecycleState.DESTROYED); + mGameSession.moveToState(GameSession.LifecycleState.TASK_FOCUSED); + + // The TASK_FOCUSED move after DESTROYED is ignored. + assertThat(mGameSession.mLifecycleMethodCalls).containsExactly( + LifecycleTrackingGameSession.LifecycleMethodCall.ON_CREATE, + LifecycleTrackingGameSession.LifecycleMethodCall.ON_DESTROY).inOrder(); + } + + private static class LifecycleTrackingGameSession extends GameSession { + private enum LifecycleMethodCall { + ON_CREATE, + ON_DESTROY, + ON_GAME_TASK_FOCUSED, + ON_GAME_TASK_UNFOCUSED + } + + final List<LifecycleMethodCall> mLifecycleMethodCalls = new ArrayList<>(); + + @Override + public void onCreate() { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_CREATE); + } + + @Override + public void onDestroy() { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_DESTROY); + } + + @Override + public void onGameTaskFocusChanged(boolean focused) { + if (focused) { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_FOCUSED); + } else { + mLifecycleMethodCalls.add(LifecycleMethodCall.ON_GAME_TASK_UNFOCUSED); + } + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index d6705a508f5e..0198253e2586 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -20,6 +20,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; @@ -28,6 +29,7 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.app.GameManager; +import android.app.GameModeInfo; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; @@ -515,7 +517,7 @@ public class GameManagerServiceTests { public void testDeviceConfigDefault() { mockDeviceConfigDefault(); mockModifyGameModeGranted(); - checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); } /** @@ -525,7 +527,7 @@ public class GameManagerServiceTests { public void testDeviceConfigNone() { mockDeviceConfigNone(); mockModifyGameModeGranted(); - checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); } /** @@ -566,7 +568,7 @@ public class GameManagerServiceTests { public void testDeviceConfigInvalid() { mockDeviceConfigInvalid(); mockModifyGameModeGranted(); - checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); } /** @@ -576,7 +578,7 @@ public class GameManagerServiceTests { public void testDeviceConfigMalformed() { mockDeviceConfigMalformed(); mockModifyGameModeGranted(); - checkReportedModes(null, GameManager.GAME_MODE_UNSUPPORTED); + checkReportedModes(null); } /** @@ -966,4 +968,84 @@ public class GameManagerServiceTests { static { System.loadLibrary("mockingservicestestjni"); } + @Test + public void testGetGameModeInfoPermissionDenied() { + mockDeviceConfigAll(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + + // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated. + mockModifyGameModeDenied(); + assertThrows(SecurityException.class, + () -> gameManagerService.getGameModeInfo(mPackageName, USER_ID_1)); + } + + @Test + public void testGetGameModeInfoWithAllGameModesDefault() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode()); + assertEquals(3, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithAllGameModes() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode()); + assertEquals(3, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithBatteryMode() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_BATTERY, gameModeInfo.getActiveGameMode()); + assertEquals(2, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithPerformanceMode() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameModeInfo.getActiveGameMode()); + assertEquals(2, gameModeInfo.getAvailableGameModes().length); + } + + @Test + public void testGetGameModeInfoWithUnsupportedGameMode() { + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = + new GameManagerService(mMockContext, mTestLooper.getLooper()); + startUser(gameManagerService, USER_ID_1); + GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1); + + assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameModeInfo.getActiveGameMode()); + assertEquals(0, gameModeInfo.getAvailableGameModes().length); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java index 167090693a10..bdfa3bfedb55 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameServiceProviderInstanceImplTest.java @@ -23,6 +23,7 @@ import static com.android.server.app.GameServiceProviderInstanceImplTest.FakeGam import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -33,21 +34,25 @@ import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; import android.app.IActivityTaskManager; import android.app.ITaskStackListener; import android.content.ComponentName; import android.content.pm.PackageManager; +import android.graphics.Bitmap; import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.service.games.CreateGameSessionRequest; import android.service.games.CreateGameSessionResult; +import android.service.games.GameScreenshotResult; import android.service.games.GameSessionViewHostConfiguration; import android.service.games.GameStartedEvent; import android.service.games.IGameService; import android.service.games.IGameServiceController; import android.service.games.IGameSession; +import android.service.games.IGameSessionController; import android.service.games.IGameSessionService; import android.view.SurfaceControlViewHost.SurfacePackage; @@ -59,6 +64,7 @@ import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.FunctionalUtils.ThrowingConsumer; import com.android.internal.util.Preconditions; import com.android.server.wm.WindowManagerInternal; +import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; @@ -93,11 +99,15 @@ public final class GameServiceProviderInstanceImplTest { private static final ComponentName GAME_A_MAIN_ACTIVITY = new ComponentName(GAME_A_PACKAGE, "com.package.game.a.MainActivity"); + private static final Bitmap TEST_BITMAP = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888); + private MockitoSession mMockingSession; private GameServiceProviderInstance mGameServiceProviderInstance; @Mock private IActivityTaskManager mMockActivityTaskManager; @Mock + private WindowManagerService mMockWindowManagerService; + @Mock private WindowManagerInternal mMockWindowManagerInternal; private FakeGameClassifier mFakeGameClassifier; private FakeGameService mFakeGameService; @@ -142,6 +152,7 @@ public final class GameServiceProviderInstanceImplTest { ConcurrentUtils.DIRECT_EXECUTOR, mFakeGameClassifier, mMockActivityTaskManager, + mMockWindowManagerService, mMockWindowManagerInternal, mFakeGameServiceConnector, mFakeGameSessionServiceConnector); @@ -321,6 +332,7 @@ public final class GameServiceProviderInstanceImplTest { .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); assertThat(gameSession10.mIsDestroyed).isFalse(); + assertThat(gameSession10.mIsFocused).isFalse(); } @Test @@ -355,6 +367,45 @@ public final class GameServiceProviderInstanceImplTest { } @Test + public void gameTaskFocused_propagatedToGameSession() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsFocused).isFalse(); + + dispatchTaskFocused(10, /*focused=*/ true); + assertThat(gameSession10.mIsFocused).isTrue(); + + dispatchTaskFocused(10, /*focused=*/ false); + assertThat(gameSession10.mIsFocused).isFalse(); + } + + @Test + public void gameTaskAlreadyFocusedWhenGameSessionCreated_propagatedToGameSession() + throws Exception { + ActivityTaskManager.RootTaskInfo gameATaskInfo = new ActivityTaskManager.RootTaskInfo(); + gameATaskInfo.taskId = 10; + when(mMockActivityTaskManager.getFocusedRootTaskInfo()).thenReturn(gameATaskInfo); + + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + FakeGameSession gameSession10 = new FakeGameSession(); + SurfacePackage mockSurfacePackage10 = Mockito.mock(SurfacePackage.class); + mFakeGameSessionService.removePendingFutureForTaskId(10) + .complete(new CreateGameSessionResult(gameSession10, mockSurfacePackage10)); + + assertThat(gameSession10.mIsFocused).isTrue(); + } + + @Test public void gameTaskRemoved_whileAwaitingGameSessionAttached_destroysGameSession() throws Exception { mGameServiceProviderInstance.start(); @@ -574,6 +625,41 @@ public final class GameServiceProviderInstanceImplTest { assertThat(mFakeGameSessionServiceConnector.getIsConnected()).isFalse(); } + @Test + public void takeScreenshot_failureNoBitmapCaptured() throws Exception { + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + IGameSessionController gameSessionController = getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController; + AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>(); + gameSessionController.takeScreenshot(10, resultFuture); + + GameScreenshotResult result = resultFuture.get(); + assertEquals(GameScreenshotResult.GAME_SCREENSHOT_ERROR_INTERNAL_ERROR, + result.getStatus()); + verify(mMockWindowManagerService).captureTaskBitmap(10); + } + + @Test + public void takeScreenshot_success() throws Exception { + when(mMockWindowManagerService.captureTaskBitmap(10)).thenReturn(TEST_BITMAP); + + mGameServiceProviderInstance.start(); + startTask(10, GAME_A_MAIN_ACTIVITY); + mFakeGameService.requestCreateGameSession(10); + + IGameSessionController gameSessionController = getOnlyElement( + mFakeGameSessionService.getCapturedCreateInvocations()).mGameSessionController; + AndroidFuture<GameScreenshotResult> resultFuture = new AndroidFuture<>(); + gameSessionController.takeScreenshot(10, resultFuture); + + GameScreenshotResult result = resultFuture.get(); + assertEquals(GameScreenshotResult.GAME_SCREENSHOT_SUCCESS, result.getStatus()); + assertEquals(TEST_BITMAP, result.getBitmap()); + } + private void startTask(int taskId, ComponentName componentName) { RunningTaskInfo runningTaskInfo = new RunningTaskInfo(); runningTaskInfo.taskId = taskId; @@ -602,6 +688,12 @@ public final class GameServiceProviderInstanceImplTest { }); } + private void dispatchTaskFocused(int taskId, boolean focused) { + dispatchTaskChangeEvent(taskStackListener -> { + taskStackListener.onTaskFocusChanged(taskId, focused); + }); + } + private void dispatchTaskChangeEvent( ThrowingConsumer<ITaskStackListener> taskStackListenerConsumer) { for (ITaskStackListener taskStackListener : mTaskStackListeners) { @@ -677,12 +769,15 @@ public final class GameServiceProviderInstanceImplTest { new HashMap<>(); public static final class CapturedCreateInvocation { + private final IGameSessionController mGameSessionController; private final CreateGameSessionRequest mCreateGameSessionRequest; private final GameSessionViewHostConfiguration mGameSessionViewHostConfiguration; CapturedCreateInvocation( + IGameSessionController gameSessionController, CreateGameSessionRequest createGameSessionRequest, GameSessionViewHostConfiguration gameSessionViewHostConfiguration) { + mGameSessionController = gameSessionController; mCreateGameSessionRequest = createGameSessionRequest; mGameSessionViewHostConfiguration = gameSessionViewHostConfiguration; } @@ -698,12 +793,14 @@ public final class GameServiceProviderInstanceImplTest { @Override public void create( + IGameSessionController gameSessionController, CreateGameSessionRequest createGameSessionRequest, GameSessionViewHostConfiguration gameSessionViewHostConfiguration, AndroidFuture createGameSessionResultFuture) { mCapturedCreateInvocations.add( new CapturedCreateInvocation( + gameSessionController, createGameSessionRequest, gameSessionViewHostConfiguration)); @@ -717,10 +814,16 @@ public final class GameServiceProviderInstanceImplTest { private static class FakeGameSession extends IGameSession.Stub { boolean mIsDestroyed = false; + boolean mIsFocused = false; @Override - public void destroy() { + public void onDestroyed() { mIsDestroyed = true; } + + @Override + public void onTaskFocusChanged(boolean focused) { + mIsFocused = focused; + } } -} +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 36c37c4dbf2a..677f0f642e6e 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -541,11 +541,14 @@ public class ActivityManagerServiceTest { | ActivityManager.UID_OBSERVER_CAPABILITY }; final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length]; + doReturn(Process.myUid()).when(sPackageManagerInternal) + .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId()); for (int i = 0; i < observers.length; ++i) { observers[i] = mock(IUidObserver.Stub.class); when(observers[i].asBinder()).thenReturn((IBinder) observers[i]); mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */, - ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */); + ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, + mContext.getOpPackageName()); // When we invoke AMS.registerUidObserver, there are some interactions with observers[i] // mock in RemoteCallbackList class. We don't want to test those interactions and @@ -674,10 +677,12 @@ public class ActivityManagerServiceTest { mockNoteOperation(); final IUidObserver observer = mock(IUidObserver.Stub.class); - when(observer.asBinder()).thenReturn((IBinder) observer); + doReturn(Process.myUid()).when(sPackageManagerInternal) + .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId()); mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */, - ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, null /* callingPackage */); + ActivityManager.PROCESS_STATE_SERVICE /* cutpoint */, + mContext.getOpPackageName()); // When we invoke AMS.registerUidObserver, there are some interactions with observer // mock in RemoteCallbackList class. We don't want to test those interactions and // at the same time, we don't want those to interfere with verifyNoMoreInteractions. @@ -771,7 +776,9 @@ public class ActivityManagerServiceTest { final IUidObserver observer = mock(IUidObserver.Stub.class); when(observer.asBinder()).thenReturn((IBinder) observer); - mAms.registerUidObserver(observer, 0, 0, null); + doReturn(Process.myUid()).when(sPackageManagerInternal) + .getPackageUid(mContext.getOpPackageName(), 0 /* flags */, mContext.getUserId()); + mAms.registerUidObserver(observer, 0, 0, mContext.getOpPackageName()); // Verify that when observers are registered, then validateUids is correctly updated. addPendingUidChanges(pendingItemsForUids); mAms.mUidObserverController.dispatchUidsChanged(); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java new file mode 100644 index 000000000000..53468c81a1e2 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -0,0 +1,84 @@ +/* + * 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.server.companion.virtual; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; + +import android.hardware.input.InputManagerInternal; +import android.os.Binder; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.view.Display; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class InputControllerTest { + + @Mock + private InputManagerInternal mInputManagerInternalMock; + @Mock + private InputController.NativeWrapper mNativeWrapperMock; + + private InputController mInputController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + + mInputController = new InputController(new Object(), mNativeWrapperMock); + } + + @Test + public void unregisterInputDevice_allMiceUnregistered_unsetValues() { + final IBinder deviceToken = new Binder(); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 1); + mInputController.unregisterInputDevice(deviceToken); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId( + eq(Display.INVALID_DISPLAY)); + } + + @Test + public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride() { + final IBinder deviceToken = new Binder(); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 1); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); + mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, + /* displayId= */ 2); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2)); + mInputController.unregisterInputDevice(deviceToken); + verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index a6b4aecf1cb6..72100e44b3eb 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -18,6 +18,7 @@ package com.android.server.companion.virtual; import static com.google.common.truth.Truth.assertWithMessage; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doCallRealMethod; @@ -28,11 +29,13 @@ import static org.testng.Assert.assertThrows; import android.Manifest; import android.app.admin.DevicePolicyManager; +import android.companion.virtual.IVirtualDeviceActivityListener; import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.content.ContextWrapper; import android.graphics.Point; import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -77,6 +80,10 @@ public class VirtualDeviceManagerServiceTest { private VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback; @Mock private DevicePolicyManager mDevicePolicyManagerMock; + @Mock + private InputManagerInternal mInputManagerInternalMock; + @Mock + private IVirtualDeviceActivityListener mActivityListener; @Before public void setUp() { @@ -85,6 +92,10 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); + doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + LocalServices.removeServiceForTest(InputManagerInternal.class); + LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); + mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext())); doNothing().when(mContext).enforceCallingOrSelfPermission( eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString()); @@ -94,7 +105,7 @@ public class VirtualDeviceManagerServiceTest { mInputController = new InputController(new Object(), mNativeWrapperMock); mDeviceImpl = new VirtualDeviceImpl(mContext, /* association info */ null, new Binder(), /* uid */ 0, mInputController, - (int associationId) -> {}, mPendingTrampolineCallback, + (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener, new VirtualDeviceParams.Builder().build()); } @@ -169,7 +180,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualKeyboard(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputKeyboard(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); } @@ -179,7 +190,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualMouse(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputMouse(DEVICE_NAME, VENDOR_ID, PRODUCT_ID); } @@ -189,7 +200,7 @@ public class VirtualDeviceManagerServiceTest { mDeviceImpl.createVirtualTouchscreen(DISPLAY_ID, DEVICE_NAME, VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)); assertWithMessage("Virtual keyboard should register fd when the display matches") - .that(mInputController.mInputDeviceFds).isNotEmpty(); + .that(mInputController.mInputDeviceDescriptors).isNotEmpty(); verify(mNativeWrapperMock).openUinputTouchscreen(DEVICE_NAME, VENDOR_ID, PRODUCT_ID, HEIGHT, WIDTH); } @@ -209,7 +220,9 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int keyCode = KeyEvent.KEYCODE_A; final int action = VirtualKeyEvent.ACTION_UP; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 1, + /* displayId= */ 1)); mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode) .setAction(action).build()); verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action); @@ -232,7 +245,10 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() .setButtonCode(buttonCode) .setAction(action).build()); @@ -240,6 +256,22 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void sendButtonEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK; + final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder() + .setButtonCode(buttonCode) + .setAction(action).build())); + } + + @Test public void sendRelativeEvent_noFd() { assertThrows( IllegalArgumentException.class, @@ -254,13 +286,32 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = -0.2f; final float y = 0.7f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder() .setRelativeX(x).setRelativeY(y).build()); verify(mNativeWrapperMock).writeRelativeEvent(fd, x, y); } @Test + public void sendRelativeEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final float x = -0.2f; + final float y = 0.7f; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendRelativeEvent(BINDER, + new VirtualMouseRelativeEvent.Builder() + .setRelativeX(x).setRelativeY(y).build())); + } + + @Test public void sendScrollEvent_noFd() { assertThrows( IllegalArgumentException.class, @@ -276,7 +327,10 @@ public class VirtualDeviceManagerServiceTest { final int fd = 1; final float x = 0.5f; final float y = 1f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + mInputController.mActivePointerDisplayId = 1; mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() .setXAxisMovement(x) .setYAxisMovement(y).build()); @@ -284,6 +338,22 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void sendScrollEvent_hasFd_wrongDisplay_throwsIllegalStateException() { + final int fd = 1; + final float x = 0.5f; + final float y = 1f; + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 2, + /* displayId= */ 1)); + assertThrows( + IllegalStateException.class, + () -> + mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder() + .setXAxisMovement(x) + .setYAxisMovement(y).build())); + } + + @Test public void sendTouchEvent_noFd() { assertThrows( IllegalArgumentException.class, @@ -305,7 +375,9 @@ public class VirtualDeviceManagerServiceTest { final float x = 100.5f; final float y = 200.5f; final int action = VirtualTouchEvent.ACTION_UP; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, + /* displayId= */ 1)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build()); verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN, @@ -322,7 +394,9 @@ public class VirtualDeviceManagerServiceTest { final int action = VirtualTouchEvent.ACTION_UP; final float pressure = 1.0f; final float majorAxisSize = 10.0f; - mInputController.mInputDeviceFds.put(BINDER, fd); + mInputController.mInputDeviceDescriptors.put(BINDER, + new InputController.InputDeviceDescriptor(fd, () -> {}, /* type= */ 3, + /* displayId= */ 1)); mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x) .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType) .setPressure(pressure).setMajorAxisSize(majorAxisSize).build()); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 1228d625325f..842a4381bc8a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -18,7 +18,6 @@ package com.android.server.devicepolicy; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_DEFAULT; import static android.app.AppOpsManager.OP_ACTIVATE_VPN; -import static android.app.Notification.EXTRA_TEXT; import static android.app.Notification.EXTRA_TITLE; import static android.app.admin.DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE; import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS; @@ -34,12 +33,17 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE; import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_OPEN; +import static android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.PasswordMetrics.computeForPasswordOrPin; import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; import static android.net.InetAddresses.parseNumericAddress; +import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback; @@ -91,6 +95,7 @@ import android.app.admin.DevicePolicyManagerLiteInternal; import android.app.admin.FactoryResetProtectionPolicy; import android.app.admin.PasswordMetrics; import android.app.admin.SystemUpdatePolicy; +import android.app.admin.WifiSsidPolicy; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Intent; @@ -1112,6 +1117,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE), eq(true), eq(UserHandle.SYSTEM)); + verify(getServices().userManager, times(1)).setUserRestriction( + eq(UserManager.DISALLOW_ADD_CLONE_PROFILE), + eq(true), eq(UserHandle.SYSTEM)); + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED), MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); @@ -1393,6 +1402,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(false), MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + verify(getServices().userManager) + .setUserRestriction(eq(UserManager.DISALLOW_ADD_CLONE_PROFILE), eq(false), + MockUtils.checkUserHandle(UserHandle.USER_SYSTEM)); + verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(UserHandle.USER_SYSTEM), MockUtils.checkUserRestrictions(), MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM), eq(true)); @@ -4123,6 +4136,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { ProfileNetworkPreference preferenceDetails2 = new ProfileNetworkPreference.Builder() .setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE) + .setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1) .build(); List<ProfileNetworkPreference> preferences2 = new ArrayList<>(); preferences2.add(preferenceDetails); @@ -7209,8 +7223,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any()); // Now the user should see a warning notification. verify(getServices().notificationManager, times(1)) - .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE, - EXTRA_TEXT, PROFILE_OFF_SUSPENSION_SOON_TEXT))); + .notify(anyInt(), any()); // Apps shouldn't be suspended yet. verifyZeroInteractions(getServices().ipackageManager); clearInvocations(getServices().alarmManager); @@ -7224,8 +7237,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { verifyZeroInteractions(getServices().alarmManager); // Now the user should see a notification about suspended apps. verify(getServices().notificationManager, times(1)) - .notify(anyInt(), argThat(hasExtra(EXTRA_TITLE, PROFILE_OFF_SUSPENSION_TITLE, - EXTRA_TEXT, PROFILE_OFF_SUSPENSION_TEXT))); + .notify(anyInt(), any()); // Verify that the apps are suspended. verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser( any(), eq(true), any(), any(), any(), any(), anyInt()); @@ -7828,6 +7840,128 @@ public class DevicePolicyManagerTest extends DpmTestBase { () -> dpm.getOrganizationNameForUser(UserHandle.USER_SYSTEM)); } + @Test + public void testSetWifiMinimumSecurity_noDeviceOwnerOrPoOfOrgOwnedDevice() { + assertThrows(SecurityException.class, () -> dpm.setMinimumRequiredWifiSecurityLevel( + DevicePolicyManager.WIFI_SECURITY_PERSONAL)); + } + + @Test + public void testSetWifiMinimumSecurity_asDeviceOwner() throws Exception { + setDeviceOwner(); + + final Set<Integer> allowedLevels = Set.of(WIFI_SECURITY_OPEN, WIFI_SECURITY_PERSONAL, + WIFI_SECURITY_ENTERPRISE_EAP, WIFI_SECURITY_ENTERPRISE_192); + for (int level : allowedLevels) { + dpm.setMinimumRequiredWifiSecurityLevel(level); + assertThat(dpm.getMinimumRequiredWifiSecurityLevel()).isEqualTo(level); + } + } + + @Test + public void testSetWifiMinimumSecurity_asPoOfOrgOwnedDevice() throws Exception { + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId); + mContext.binder.callingUid = managedProfileAdminUid; + + final Set<Integer> allowedLevels = Set.of(WIFI_SECURITY_OPEN, WIFI_SECURITY_PERSONAL, + WIFI_SECURITY_ENTERPRISE_EAP, WIFI_SECURITY_ENTERPRISE_192); + for (int level : allowedLevels) { + dpm.setMinimumRequiredWifiSecurityLevel(level); + assertThat(dpm.getMinimumRequiredWifiSecurityLevel()).isEqualTo(level); + } + } + + @Test + public void testSetSsidAllowlist_noDeviceOwnerOrPoOfOrgOwnedDevice() { + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids); + assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy)); + } + + @Test + public void testSetSsidAllowlist_asDeviceOwner() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST); + } + + @Test + public void testSetSsidAllowlist_asPoOfOrgOwnedDevice() throws Exception { + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId); + mContext.binder.callingUid = managedProfileAdminUid; + + final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3")); + WifiSsidPolicy policy = WifiSsidPolicy.createAllowlistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST); + } + + @Test + public void testSetSsidAllowlist_emptyList() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = new ArraySet<>(); + assertThrows(IllegalArgumentException.class, + () -> WifiSsidPolicy.createAllowlistPolicy(ssids)); + } + + @Test + public void testSetSsidDenylist_noDeviceOwnerOrPoOfOrgOwnedDevice() { + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids); + assertThrows(SecurityException.class, () -> dpm.setWifiSsidPolicy(policy)); + } + + @Test + public void testSetSsidDenylist_asDeviceOwner() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = Collections.singleton("ssid1"); + WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST); + } + + @Test + public void testSetSsidDenylist_asPoOfOrgOwnedDevice() throws Exception { + final int managedProfileUserId = 15; + final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 19436); + addManagedProfile(admin1, managedProfileAdminUid, admin1); + configureProfileOwnerOfOrgOwnedDevice(admin1, managedProfileUserId); + mContext.binder.callingUid = managedProfileAdminUid; + + final Set<String> ssids = new ArraySet<>(Arrays.asList("ssid1", "ssid2", "ssid3")); + WifiSsidPolicy policy = WifiSsidPolicy.createDenylistPolicy(ssids); + dpm.setWifiSsidPolicy(policy); + assertThat(dpm.getWifiSsidPolicy().getSsids()).isEqualTo(ssids); + assertThat(dpm.getWifiSsidPolicy().getPolicyType()).isEqualTo( + WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST); + } + + @Test + public void testSetSsidDenylist_emptyList() throws Exception { + setDeviceOwner(); + + final Set<String> ssids = new ArraySet<>(); + assertThrows(IllegalArgumentException.class, + () -> WifiSsidPolicy.createDenylistPolicy(ssids)); + } + private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) { final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage, userVpnUid, List.of(new AppOpsManager.OpEntry( @@ -7863,18 +7997,6 @@ public class DevicePolicyManagerTest extends DpmTestBase { // To allow creation of Notification via Notification.Builder mContext.applicationInfo = mRealTestContext.getApplicationInfo(); - // Setup resources to render notification titles and texts. - when(mServiceContext.resources - .getString(R.string.personal_apps_suspension_title)) - .thenReturn(PROFILE_OFF_SUSPENSION_TITLE); - when(mServiceContext.resources - .getString(R.string.personal_apps_suspension_text)) - .thenReturn(PROFILE_OFF_SUSPENSION_TEXT); - when(mServiceContext.resources - .getString(eq(R.string.personal_apps_suspension_soon_text), - anyString(), anyString(), anyInt())) - .thenReturn(PROFILE_OFF_SUSPENSION_SOON_TEXT); - // Make locale available for date formatting: when(mServiceContext.resources.getConfiguration()) .thenReturn(mRealTestContext.getResources().getConfiguration()); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index d4b1165f6a08..6eb2085d24bb 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -232,6 +232,8 @@ public class DpmMockContext extends MockContext { return mMockSystemServices.crossProfileApps; case Context.VPN_MANAGEMENT_SERVICE: return mMockSystemServices.vpnManager; + case Context.DEVICE_POLICY_SERVICE: + return mMockSystemServices.devicePolicyManager; } throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 8a2919d55216..597a165cbb0f 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -31,6 +31,7 @@ import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IActivityTaskManager; import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; import android.app.backup.IBackupManager; import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; @@ -125,6 +126,7 @@ public class MockSystemServices { public final AppOpsManager appOpsManager; public final UsbManager usbManager; public final VpnManager vpnManager; + public final DevicePolicyManager devicePolicyManager; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; public final BuildMock buildMock = new BuildMock(); @@ -172,6 +174,7 @@ public class MockSystemServices { appOpsManager = mock(AppOpsManager.class); usbManager = mock(UsbManager.class); vpnManager = mock(VpnManager.class); + devicePolicyManager = mock(DevicePolicyManager.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(realContext.getPackageManager()); diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index c544f5c2e245..81c98717d2e7 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -2046,7 +2046,7 @@ public class NetworkPolicyManagerServiceTest { private static NetworkStateSnapshot buildWifi() { WifiInfo mockWifiInfo = mock(WifiInfo.class); when(mockWifiInfo.makeCopy(anyLong())).thenReturn(mockWifiInfo); - when(mockWifiInfo.getCurrentNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY); + when(mockWifiInfo.getNetworkKey()).thenReturn(TEST_WIFI_NETWORK_KEY); final LinkProperties prop = new LinkProperties(); prop.setInterfaceName(TEST_IFACE); final NetworkCapabilities networkCapabilities = new NetworkCapabilities.Builder() 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 429445f80dbb..7e5fe0496a1c 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -664,6 +664,24 @@ public final class UserManagerTest { } } + // Make sure createProfile would fail if we have DISALLOW_ADD_CLONE_PROFILE. + @MediumTest + @Test + public void testCreateUser_disallowAddClonedUserProfile() throws Exception { + final int primaryUserId = ActivityManager.getCurrentUser(); + final UserHandle primaryUserHandle = asHandle(primaryUserId); + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, + true, primaryUserHandle); + try { + UserInfo cloneProfileUserInfo = createProfileForUser("Clone", + UserManager.USER_TYPE_PROFILE_CLONE, primaryUserId); + assertThat(cloneProfileUserInfo).isNull(); + } finally { + mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_CLONE_PROFILE, false, + primaryUserHandle); + } + } + // Make sure createProfile would fail if we have DISALLOW_ADD_MANAGED_PROFILE. @MediumTest @Test 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 0dcf7992e02b..774e5b9c7fe3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE; @@ -571,7 +570,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWith2LevelTask(); final Task task = activity.getTask(); final Task rootTask = activity.getRootTask(); - rootTask.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); final Rect stableRect = new Rect(); rootTask.mDisplayContent.getStableRect(stableRect); @@ -616,7 +615,7 @@ public class ActivityRecordTests extends WindowTestsBase { spyOn(tda); doReturn(true).when(tda).supportsNonResizableMultiWindow(); final Task rootTask = mDisplayContent.getDefaultTaskDisplayArea().createRootTask( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */); rootTask.setBounds(0, 0, 1000, 500); final ActivityRecord activity = new ActivityBuilder(mAtm) .setParentTask(rootTask) diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index a2b04c295944..7c340ecac2c7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -77,6 +77,7 @@ import org.mockito.Mockito; import org.mockito.MockitoSession; import java.util.ArrayList; +import java.util.List; import java.util.function.Consumer; /** @@ -188,6 +189,9 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { @Override public void onFixedRotationFinished(int displayId) {} + + @Override + public void onKeepClearAreasChanged(int displayId, List<Rect> keepClearAreas) {} }; int[] displayIds = mAtm.mWindowManager.registerDisplayWindowListener(listener); for (int i = 0; i < displayIds.length; i++) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 2f78b588f305..8d58ec00df96 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -119,6 +119,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.view.DisplayCutout; import android.view.DisplayInfo; @@ -1101,7 +1102,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "app")); dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode( - WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); } @@ -1152,7 +1153,7 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeInputTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); dc.getImeTarget(IME_TARGET_INPUT).getWindow().setWindowingMode( - WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeLayeringTarget(dc.getImeTarget(IME_TARGET_INPUT).getWindow()); dc.setRemoteInsetsController(createDisplayWindowInsetsController()); assertNotEquals(dc.getImeTarget(IME_TARGET_INPUT).getWindow(), @@ -1982,6 +1983,7 @@ public class DisplayContentTests extends WindowTestsBase { // Test step 1: appWin1 is the current IME target and soft-keyboard is visible. mDisplayContent.computeImeTarget(true); assertEquals(appWin1, mDisplayContent.getImeTarget(IME_TARGET_LAYERING)); + mDisplayContent.setImeInputTarget(appWin1); spyOn(mDisplayContent.mInputMethodWindow); doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible(); mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true); @@ -1998,7 +2000,6 @@ public class DisplayContentTests extends WindowTestsBase { // be shown at this time. final Transaction t = mDisplayContent.getPendingTransaction(); spyOn(t); - mDisplayContent.setImeInputTarget(appWin2); mDisplayContent.computeImeTarget(true); assertEquals(appWin2, mDisplayContent.getImeTarget(IME_TARGET_LAYERING)); assertTrue(mDisplayContent.shouldImeAttachedToApp()); @@ -2436,6 +2437,31 @@ public class DisplayContentTests extends WindowTestsBase { mockSession.finishMocking(); } + @Test + public void testKeepClearAreasMultipleWindows() { + final WindowState w1 = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "w1"); + final Rect rect1 = new Rect(0, 0, 10, 10); + w1.setKeepClearAreas(Arrays.asList(rect1)); + final WindowState w2 = createWindow(null, TYPE_NOTIFICATION_SHADE, mDisplayContent, "w2"); + final Rect rect2 = new Rect(10, 10, 20, 20); + w2.setKeepClearAreas(Arrays.asList(rect2)); + + // No keep clear areas on display, because the windows are not visible + assertEquals(Arrays.asList(), mDisplayContent.getKeepClearAreas()); + + makeWindowVisible(w1); + + // The returned keep-clear areas contain the areas just from the visible window + assertEquals(new ArraySet(Arrays.asList(rect1)), + new ArraySet(mDisplayContent.getKeepClearAreas())); + + makeWindowVisible(w1, w2); + + // The returned keep-clear areas contain the areas from all visible windows + assertEquals(new ArraySet(Arrays.asList(rect1, rect2)), + new ArraySet(mDisplayContent.getKeepClearAreas())); + } + private class TestToken extends Binder { } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index acf6dc58a597..497ae1defb61 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -31,10 +31,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; @@ -44,7 +42,6 @@ import android.graphics.Insets; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.Pair; -import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.InsetsState; import android.view.PrivacyIndicatorBounds; @@ -210,24 +207,6 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { expectThrows(IllegalArgumentException.class, () -> addWindow(win2)); } - @Test - public void layoutHint_appWindow() { - mWindow.mAttrs.setFitInsetsTypes(0); - - final DisplayCutout.ParcelableWrapper outDisplayCutout = - new DisplayCutout.ParcelableWrapper(); - final InsetsState outState = new InsetsState(); - - mDisplayPolicy.getLayoutHint(mWindow.mAttrs, null /* windowToken */, outState, - true /* localClient */); - - assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); - assertThat(outState.getSource(ITYPE_STATUS_BAR).getFrame(), - is(new Rect(0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT))); - assertThat(outState.getSource(ITYPE_NAVIGATION_BAR).getFrame(), - is(new Rect(0, DISPLAY_HEIGHT - NAV_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT))); - } - /** * Verify that {@link DisplayPolicy#simulateLayoutDisplay} outputs the same display frames as * the real one. diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index 407f9cfdbe3e..d64bf121736f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -26,11 +26,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertNotNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import android.app.ActivityThread; import android.content.Context; @@ -43,6 +46,7 @@ import android.view.Display; import android.view.IWindowManager; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.window.WindowTokenClient; import com.android.server.inputmethod.InputMethodManagerService; import com.android.server.inputmethod.InputMethodMenuController; @@ -130,15 +134,31 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { @Test public void testGetSettingsContextOnDualDisplayContent() { final Context context = mController.getSettingsContext(mSecondaryDisplay.getDisplayId()); + final WindowTokenClient tokenClient = (WindowTokenClient) context.getWindowContextToken(); + assertNotNull(tokenClient); + spyOn(tokenClient); final DisplayArea.Tokens imeContainer = mSecondaryDisplay.getImeContainer(); + spyOn(imeContainer); assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay); mSecondaryDisplay.mFirstRoot.placeImeContainer(imeContainer); + + verify(imeContainer, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mFirstRoot.getConfiguration())); + verify(tokenClient, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mFirstRoot.getConfiguration()), + eq(mSecondaryDisplay.mDisplayId)); assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mFirstRoot); assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay); mSecondaryDisplay.mSecondRoot.placeImeContainer(imeContainer); + + verify(imeContainer, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mSecondRoot.getConfiguration())); + verify(tokenClient, atLeastOnce()).onConfigurationChanged( + eq(mSecondaryDisplay.mSecondRoot.getConfiguration()), + eq(mSecondaryDisplay.mDisplayId)); assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mSecondaryDisplay.mSecondRoot); assertImeSwitchContextMetricsValidity(context, mSecondaryDisplay); } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 94bc7f2dc0d1..2eece4c2ca4d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_CLIMATE_BAR; import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_IME; @@ -246,7 +245,7 @@ public class InsetsStateControllerTest extends WindowTestsBase { final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME)); child.mAttrs.flags |= FLAG_NOT_FOCUSABLE; - child.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); mDisplayContent.computeImeTarget(true); mDisplayContent.setLayoutNeeded(); diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java index fc298b0d96a1..0c2de5c6031b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsControllerTests.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.INVALID_DISPLAY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; @@ -334,7 +333,7 @@ public class LaunchParamsControllerTests extends WindowTestsBase { params.mWindowingMode = windowingMode; final InstrumentedPositioner positioner = new InstrumentedPositioner(RESULT_DONE, params); final Task task = new TaskBuilder(mAtm.mTaskSupervisor).setCreateParentTask(true).build(); - task.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY); + task.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); mController.registerModifier(positioner); 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 3cb0bed32493..65b5cf5f13c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java @@ -88,6 +88,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -529,6 +530,7 @@ public class RootTaskTests extends WindowTestsBase { // TODO(b/199236198): check this is unnecessary or need to migrate after remove legacy split. @Test + @Ignore public void testShouldBeVisible_SplitScreen() { // task not supporting split should be fullscreen for this test. final Task notSupportingSplitTask = createTaskForShouldBeVisibleTest( 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 4069f0f41d90..f4abf883c219 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -21,8 +21,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.TYPE_VIRTUAL; @@ -458,7 +458,7 @@ public class RootWindowContainerTests extends WindowTestsBase { final TaskDisplayArea defaultTaskDisplayArea = mRootWindowContainer .getDefaultTaskDisplayArea(); final Task task = defaultTaskDisplayArea.createRootTask( - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, true /* onTop */); + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, true /* onTop */); final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); // Created tasks are focusable by default. diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index c722b0a70c0a..b815c38b7a8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -77,6 +77,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; @@ -2182,6 +2183,29 @@ public class SizeCompatTests extends WindowTestsBase { .computeAspectRatio(sizeCompatAppBounds), delta); } + @Test + public void testClearSizeCompat_resetOverrideConfig() { + final int origDensity = 480; + final int newDensity = 520; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 600, 800) + .setDensityDpi(origDensity) + .build(); + setUpApp(display); + prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + + // Activity should enter size compat with old density after display density change. + display.setForcedDensity(newDensity, UserHandle.USER_CURRENT); + + assertScaled(); + assertEquals(origDensity, mActivity.getConfiguration().densityDpi); + + // Activity should exit size compat with new density. + mActivity.clearSizeCompatMode(); + + assertFitted(); + assertEquals(newDensity, mActivity.getConfiguration().densityDpi); + } + private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( float letterboxHorizontalPositionMultiplier) { // Set up a display in landscape and ignoring orientation request. diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java index cdf6b59d4737..80f6bceb884c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java @@ -25,8 +25,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; @@ -355,14 +353,10 @@ public class TaskDisplayAreaTests extends WindowTestsBase { true /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_UNDEFINED, type, candidateTask, true /* reuseCandidate */); - assertGetOrCreateRootTask(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, type, candidateTask, - true /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_FREEFORM, type, candidateTask, true /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_MULTI_WINDOW, type, candidateTask, true /* reuseCandidate */); - assertGetOrCreateRootTask(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, type, candidateTask, - false /* reuseCandidate */); assertGetOrCreateRootTask(WINDOWING_MODE_PINNED, type, candidateTask, true /* reuseCandidate */); @@ -388,7 +382,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase { final Task primarySplitTask = new TaskBuilder(mSupervisor) .setTaskDisplayArea(defaultTaskDisplayArea) - .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) + .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW) .setActivityType(ACTIVITY_TYPE_STANDARD) .setOnTop(true) .setCreateActivity(true) diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java index f13847559aa0..64959f240887 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -17,8 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -50,11 +49,8 @@ public class WindowContainerTraversalTests extends WindowTestsBase { @Test public void testDockedDividerPosition() { final WindowState splitScreenWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenWindow"); - final WindowState splitScreenSecondaryWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, - TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); mDisplayContent.setImeLayeringTarget(splitScreenWindow); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 75a87ba9e04d..4d5fb6dc5813 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -24,8 +24,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; @@ -520,16 +518,16 @@ public class WindowOrganizerTests extends WindowTestsBase { DisplayContent dc = mWm.mRoot.getDisplayContent(Display.DEFAULT_DISPLAY); Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - dc, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + dc, WINDOWING_MODE_FULLSCREEN, null); RunningTaskInfo info1 = task1.getTaskInfo(); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, + assertEquals(WINDOWING_MODE_FULLSCREEN, info1.configuration.windowConfiguration.getWindowingMode()); assertEquals(ACTIVITY_TYPE_UNDEFINED, info1.topActivityType); Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - dc, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + dc, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info2 = task2.getTaskInfo(); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, + assertEquals(WINDOWING_MODE_MULTI_WINDOW, info2.configuration.windowConfiguration.getWindowingMode()); assertEquals(ACTIVITY_TYPE_UNDEFINED, info2.topActivityType); @@ -539,7 +537,7 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(mWm.mAtmService.mTaskOrganizerController.deleteRootTask(info1.token)); infos = getTasksCreatedByOrganizer(dc); assertEquals(1, infos.size()); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, infos.get(0).getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, infos.get(0).getWindowingMode()); } @Test @@ -577,7 +575,7 @@ public class WindowOrganizerTests extends WindowTestsBase { final StubOrganizer listener = new StubOrganizer(); mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info1 = task.getTaskInfo(); final Task rootTask = createTask( @@ -626,7 +624,7 @@ public class WindowOrganizerTests extends WindowTestsBase { }; mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); Task task = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info1 = task.getTaskInfo(); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); @@ -684,10 +682,10 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mAtmService.mTaskOrganizerController.registerTaskOrganizer(listener); Task task1 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info1 = task1.getTaskInfo(); Task task2 = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); RunningTaskInfo info2 = task2.getTaskInfo(); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); @@ -1056,7 +1054,7 @@ public class WindowOrganizerTests extends WindowTestsBase { public void testReparentToOrganizedTask() { final ITaskOrganizer organizer = registerMockOrganizer(); Task rootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); final Task task1 = createRootTask(); final Task task2 = createTask(rootTask, false /* fakeDraw */); WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -1223,7 +1221,7 @@ public class WindowOrganizerTests extends WindowTestsBase { final Task rootTask = activity.getRootTask(); rootTask.setResizeMode(activity.info.resizeMode); final Task splitPrimaryRootTask = mWm.mAtmService.mTaskOrganizerController.createRootTask( - mDisplayContent, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, null); + mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, null); final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparent(rootTask.mRemoteToken.toWindowContainerToken(), splitPrimaryRootTask.mRemoteToken.toWindowContainerToken(), true /* onTop */); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index ec8ec2b31918..80192f7b5d60 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -83,6 +82,7 @@ import android.graphics.Rect; import android.os.IBinder; import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.util.ArraySet; import android.view.Gravity; import android.view.InputWindowHandle; import android.view.InsetsSource; @@ -265,22 +265,19 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(appWindow.canBeImeTarget()); assertFalse(imeWindow.canBeImeTarget()); - // Simulate the window is in split screen primary root task and the current state is - // minimized and home root task is resizable, so that we should ignore input for the - // root task. + // Simulate the window is in split screen root task. final DockedTaskDividerController controller = mDisplayContent.getDockedDividerController(); final Task rootTask = createTask(mDisplayContent, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD); + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD); spyOn(appWindow); spyOn(controller); spyOn(rootTask); rootTask.setFocusable(false); doReturn(rootTask).when(appWindow).getRootTask(); - // Make sure canBeImeTarget is false due to shouldIgnoreInput is true; + // Make sure canBeImeTarget is false; assertFalse(appWindow.canBeImeTarget()); - assertTrue(rootTask.shouldIgnoreInput()); } @Test @@ -727,8 +724,9 @@ public class WindowStateTests extends WindowTestsBase { @Test public void testCantReceiveTouchWhenNotFocusable() { final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0"); - win0.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - win0.mActivityRecord.getRootTask().setFocusable(false); + final Task rootTask = win0.mActivityRecord.getRootTask(); + spyOn(rootTask); + when(rootTask.shouldIgnoreInput()).thenReturn(true); assertFalse(win0.canReceiveTouchInput()); } @@ -928,8 +926,8 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(mAppWindow); // Simulate entering multi-window mode and verify if the IME control target is remote. - app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, app.getWindowingMode()); + app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, app.getWindowingMode()); assertEquals(mDisplayContent.mRemoteInsetsControlTarget, mDisplayContent.computeImeControlTarget()); @@ -944,14 +942,13 @@ public class WindowStateTests extends WindowTestsBase { @UseTestDisplay(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE }) @Test - public void testNotificationShadeHasImeInsetsWhenSplitscreenActivated() { + public void testNotificationShadeHasImeInsetsWhenMultiWindow() { WindowState app = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken, "app"); - // Simulate entering multi-window mode and verify if the split-screen is activated. - app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); - assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, app.getWindowingMode()); - assertTrue(mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()); + // Simulate entering multi-window mode and windowing mode is multi-window. + app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, app.getWindowingMode()); // Simulate notificationShade is shown and being IME layering target. mNotificationShadeWindow.setHasSurface(true); @@ -965,7 +962,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.getInsetsStateController().getRawInsetsState() .setSourceVisible(ITYPE_IME, true); - // Verify notificationShade can still get IME insets even the split-screen is activated. + // Verify notificationShade can still get IME insets even windowing mode is multi-window. InsetsState state = mDisplayContent.getInsetsStateController().getInsetsForWindow( mNotificationShadeWindow); assertNotNull(state.peekSource(ITYPE_IME)); @@ -986,4 +983,40 @@ public class WindowStateTests extends WindowTestsBase { assertFalse(app.isVisible()); assertTrue(app.isVisibleRequested()); } + + @Test + public void testKeepClearAreas() { + final WindowState window = createWindow(null, TYPE_APPLICATION, "window"); + makeWindowVisible(window); + + final Rect keepClearArea1 = new Rect(0, 0, 10, 10); + final Rect keepClearArea2 = new Rect(5, 10, 15, 20); + final List<Rect> keepClearAreas = Arrays.asList(keepClearArea1, keepClearArea2); + window.setKeepClearAreas(keepClearAreas); + + // Test that the keep-clear rects are stored and returned + assertEquals(new ArraySet(keepClearAreas), new ArraySet(window.getKeepClearAreas())); + + // Test that keep-clear rects are overwritten + window.setKeepClearAreas(Arrays.asList()); + assertEquals(0, window.getKeepClearAreas().size()); + + // Move the window position + final SurfaceControl.Transaction t = spy(StubTransaction.class); + window.mSurfaceControl = mock(SurfaceControl.class); + final Rect frame = window.getFrame(); + frame.set(10, 20, 60, 80); + window.updateSurfacePosition(t); + assertEquals(new Point(frame.left, frame.top), window.mLastSurfacePosition); + + // Test that the returned keep-clear rects are translated to display space + window.setKeepClearAreas(keepClearAreas); + Rect expectedArea1 = new Rect(keepClearArea1); + expectedArea1.offset(frame.left, frame.top); + Rect expectedArea2 = new Rect(keepClearArea2); + expectedArea2.offset(frame.left, frame.top); + + assertEquals(new ArraySet(Arrays.asList(expectedArea1, expectedArea2)), + new ArraySet(window.getKeepClearAreas())); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 4dffe7e8345b..0f223ca037ee 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -22,7 +22,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -363,7 +362,7 @@ public class ZOrderingTests extends WindowTestsBase { ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "pinnedStackWindow"); final WindowState dockedStackWindow = createWindow(null, - WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent, "dockedStackWindow"); final WindowState assistantStackWindow = createWindow(null, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION, diff --git a/services/usb/Android.bp b/services/usb/Android.bp index 01feacd826c5..3b50fa43536c 100644 --- a/services/usb/Android.bp +++ b/services/usb/Android.bp @@ -29,6 +29,7 @@ java_library_static { "android.hardware.usb-V1.1-java", "android.hardware.usb-V1.2-java", "android.hardware.usb-V1.3-java", + "android.hardware.usb-V1-java", "android.hardware.usb.gadget-V1.0-java", "android.hardware.usb.gadget-V1.1-java", "android.hardware.usb.gadget-V1.2-java", diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java index 9d4db003a297..85b1de5478e1 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -41,6 +41,7 @@ public final class UsbAlsaDevice { private final boolean mIsInputHeadset; private final boolean mIsOutputHeadset; + private final boolean mIsDock; private boolean mSelected = false; private int mOutputState; @@ -53,7 +54,7 @@ public final class UsbAlsaDevice { public UsbAlsaDevice(IAudioService audioService, int card, int device, String deviceAddress, boolean hasOutput, boolean hasInput, - boolean isInputHeadset, boolean isOutputHeadset) { + boolean isInputHeadset, boolean isOutputHeadset, boolean isDock) { mAudioService = audioService; mCardNum = card; mDeviceNum = device; @@ -62,31 +63,32 @@ public final class UsbAlsaDevice { mHasInput = hasInput; mIsInputHeadset = isInputHeadset; mIsOutputHeadset = isOutputHeadset; + mIsDock = isDock; } /** - * @returns the ALSA card number associated with this peripheral. + * @return the ALSA card number associated with this peripheral. */ public int getCardNum() { return mCardNum; } /** - * @returns the ALSA device number associated with this peripheral. + * @return the ALSA device number associated with this peripheral. */ public int getDeviceNum() { return mDeviceNum; } /** - * @returns the USB device device address associated with this peripheral. + * @return the USB device device address associated with this peripheral. */ public String getDeviceAddress() { return mDeviceAddress; } /** - * @returns the ALSA card/device address string. + * @return the ALSA card/device address string. */ public String getAlsaCardDeviceString() { if (mCardNum < 0 || mDeviceNum < 0) { @@ -98,35 +100,42 @@ public final class UsbAlsaDevice { } /** - * @returns true if the device supports output. + * @return true if the device supports output. */ public boolean hasOutput() { return mHasOutput; } /** - * @returns true if the device supports input (recording). + * @return true if the device supports input (recording). */ public boolean hasInput() { return mHasInput; } /** - * @returns true if the device is a headset for purposes of input. + * @return true if the device is a headset for purposes of input. */ public boolean isInputHeadset() { return mIsInputHeadset; } /** - * @returns true if the device is a headset for purposes of output. + * @return true if the device is a headset for purposes of output. */ public boolean isOutputHeadset() { return mIsOutputHeadset; } /** - * @returns true if input jack is detected or jack detection is not supported. + * @return true if the device is a USB dock. + */ + public boolean isDock() { + return mIsDock; + } + + /** + * @return true if input jack is detected or jack detection is not supported. */ private synchronized boolean isInputJackConnected() { if (mJackDetector == null) { @@ -136,7 +145,7 @@ public final class UsbAlsaDevice { } /** - * @returns true if input jack is detected or jack detection is not supported. + * @return true if input jack is detected or jack detection is not supported. */ private synchronized boolean isOutputJackConnected() { if (mJackDetector == null) { @@ -190,9 +199,10 @@ public final class UsbAlsaDevice { try { // Output Device if (mHasOutput) { - int device = mIsOutputHeadset - ? AudioSystem.DEVICE_OUT_USB_HEADSET - : AudioSystem.DEVICE_OUT_USB_DEVICE; + int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET + : (mIsOutputHeadset + ? AudioSystem.DEVICE_OUT_USB_HEADSET + : AudioSystem.DEVICE_OUT_USB_DEVICE); if (DEBUG) { Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device) + " addr:" + alsaCardDeviceString @@ -231,7 +241,7 @@ public final class UsbAlsaDevice { /** * @Override - * @returns a string representation of the object. + * @return a string representation of the object. */ public synchronized String toString() { return "UsbAlsaDevice: [card: " + mCardNum @@ -273,7 +283,7 @@ public final class UsbAlsaDevice { /** * @Override - * @returns true if the objects are equivalent. + * @return true if the objects are equivalent. */ public boolean equals(Object obj) { if (!(obj instanceof UsbAlsaDevice)) { @@ -285,12 +295,13 @@ public final class UsbAlsaDevice { && mHasOutput == other.mHasOutput && mHasInput == other.mHasInput && mIsInputHeadset == other.mIsInputHeadset - && mIsOutputHeadset == other.mIsOutputHeadset); + && mIsOutputHeadset == other.mIsOutputHeadset + && mIsDock == other.mIsDock); } /** * @Override - * @returns a hash code generated from the object contents. + * @return a hash code generated from the object contents. */ public int hashCode() { final int prime = 31; @@ -301,6 +312,7 @@ public final class UsbAlsaDevice { result = prime * result + (mHasInput ? 0 : 1); result = prime * result + (mIsInputHeadset ? 0 : 1); result = prime * result + (mIsOutputHeadset ? 0 : 1); + result = prime * result + (mIsDock ? 0 : 1); return result; } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 1c72eb8db708..fd9b9952331a 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -237,6 +237,7 @@ public final class UsbAlsaManager { if (hasInput || hasOutput) { boolean isInputHeadset = parser.isInputHeadset(); boolean isOutputHeadset = parser.isOutputHeadset(); + boolean isDock = parser.isDock(); if (mAudioService == null) { Slog.e(TAG, "no AudioService"); @@ -246,7 +247,7 @@ public final class UsbAlsaManager { UsbAlsaDevice alsaDevice = new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/, deviceAddress, hasOutput, hasInput, - isInputHeadset, isOutputHeadset); + isInputHeadset, isOutputHeadset, isDock); if (alsaDevice != null) { alsaDevice.setDeviceNameAndDescription( cardRec.getCardName(), cardRec.getCardDescription()); diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 9ac270f17fc4..94cc826ffc43 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -165,7 +165,7 @@ public class UsbHostManager { pw.println("manfacturer:0x" + Integer.toHexString(deviceDescriptor.getVendorID()) + " product:" + Integer.toHexString(deviceDescriptor.getProductID())); pw.println("isHeadset[in: " + parser.isInputHeadset() - + " , out: " + parser.isOutputHeadset() + "]"); + + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock()); } else { pw.println(formatTime() + " Disconnect " + mDeviceAddress); } @@ -179,9 +179,8 @@ public class UsbHostManager { UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree(); descriptorTree.parse(parser); descriptorTree.report(new TextReportCanvas(parser, stringBuilder)); - stringBuilder.append("isHeadset[in: " + parser.isInputHeadset() - + " , out: " + parser.isOutputHeadset() + "]"); + + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock()); pw.println(stringBuilder.toString()); } else { pw.println(formatTime() + " Disconnect " + mDeviceAddress); @@ -198,9 +197,8 @@ public class UsbHostManager { descriptor.report(canvas); } pw.println(stringBuilder.toString()); - pw.println("isHeadset[in: " + parser.isInputHeadset() - + " , out: " + parser.isOutputHeadset() + "]"); + + " , out: " + parser.isOutputHeadset() + "], isDock: " + parser.isDock()); } else { pw.println(formatTime() + " Disconnect " + mDeviceAddress); } diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java index ec28040f82d8..98173adfd0eb 100644 --- a/services/usb/java/com/android/server/usb/UsbPortManager.java +++ b/services/usb/java/com/android/server/usb/UsbPortManager.java @@ -16,6 +16,8 @@ package com.android.server.usb; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_PORT_MISMATCH; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; @@ -25,6 +27,12 @@ import static android.hardware.usb.UsbPortStatus.MODE_DUAL; import static android.hardware.usb.UsbPortStatus.MODE_UFP; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_POWER_ROLE_SOURCE; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_POWER_ROLE_SINK; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_DATA_ROLE_HOST; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_DATA_ROLE_DEVICE; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_MODE_DFP; +import static com.android.server.usb.hal.port.UsbPortHal.HAL_MODE_UFP; import static com.android.internal.usb.DumpUtils.writePort; import static com.android.internal.usb.DumpUtils.writePortStatus; @@ -38,6 +46,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; @@ -74,9 +83,13 @@ import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; +import com.android.server.usb.hal.port.RawPortInfo; +import com.android.server.usb.hal.port.UsbPortHal; +import com.android.server.usb.hal.port.UsbPortHalInstance; import java.util.ArrayList; import java.util.NoSuchElementException; +import java.util.Objects; /** * Allows trusted components to control the properties of physical USB ports @@ -109,16 +122,9 @@ public class UsbPortManager { // The system context. private final Context mContext; - // Proxy object for the usb hal daemon. - @GuardedBy("mLock") - private IUsb mProxy = null; - // Callback when the UsbPort status is changed by the kernel. // Mostly due a command sent by the remote Usb device. - private HALCallback mHALCallback = new HALCallback(null, this); - - // Cookie sent for usb hal death notification. - private static final int USB_HAL_DEATH_COOKIE = 1000; + //private HALCallback mHALCallback = new HALCallback(null, this); // Used as the key while sending the bundle to Main thread. private static final String PORT_INFO = "port_info"; @@ -156,36 +162,23 @@ public class UsbPortManager { */ private int mIsPortContaminatedNotificationId; - private boolean mEnableUsbDataSignaling; - protected int mCurrentUsbHalVersion; + private UsbPortHal mUsbPortHal; + + private long mTransactionId; public UsbPortManager(Context context) { mContext = context; - try { - ServiceNotification serviceNotification = new ServiceNotification(); - - boolean ret = IServiceManager.getService() - .registerForNotifications("android.hardware.usb@1.0::IUsb", - "", serviceNotification); - if (!ret) { - logAndPrint(Log.ERROR, null, - "Failed to register service start notification"); - } - } catch (RemoteException e) { - logAndPrintException(null, - "Failed to register service start notification", e); - return; - } - connectToProxy(null); + mUsbPortHal = UsbPortHalInstance.getInstance(this, null); + logAndPrint(Log.DEBUG, null, "getInstance done"); } public void systemReady() { - mSystemReady = true; - if (mProxy != null) { + mSystemReady = true; + if (mUsbPortHal != null) { + mUsbPortHal.systemReady(); try { - mProxy.queryPortStatus(); - mEnableUsbDataSignaling = true; - } catch (RemoteException e) { + mUsbPortHal.queryPortStatus(++mTransactionId); + } catch (Exception e) { logAndPrintException(null, "ServiceStart: Failed to query port status", e); } @@ -233,6 +226,7 @@ public class UsbPortManager { intent.setComponent(ComponentName.unflattenFromString(r.getString( com.android.internal.R.string.config_usbContaminantActivity))); intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort)); + intent.putExtra(UsbManager.EXTRA_PORT_STATUS, currentPortInfo.mUsbPortStatus); // Simple notification clicks are immutable PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, @@ -340,61 +334,133 @@ public class UsbPortManager { } try { - // Oneway call into the hal. Use the castFrom method from HIDL. - android.hardware.usb.V1_2.IUsb proxy = android.hardware.usb.V1_2.IUsb.castFrom(mProxy); - proxy.enableContaminantPresenceDetection(portId, enable); - } catch (RemoteException e) { + mUsbPortHal.enableContaminantPresenceDetection(portId, enable, ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set contaminant detection", e); - } catch (ClassCastException e) { - logAndPrintException(pw, "Method only applicable to V1.2 or above implementation", e); } } /** - * Enable/disable the USB data signaling + * Limits power transfer in/out of USB-C port. * - * @param enable enable or disable USB data signaling + * @param portId port identifier. + * @param limit limit power transfer when true. */ - public boolean enableUsbDataSignal(boolean enable) { + public void enableLimitPowerTransfer(@NonNull String portId, boolean limit, long transactionId, + IUsbOperationInternal callback, IndentingPrintWriter pw) { + Objects.requireNonNull(portId); + final PortInfo portInfo = mPorts.get(portId); + if (portInfo == null) { + logAndPrint(Log.ERROR, pw, "enableLimitPowerTransfer: No such port: " + portId + + " opId:" + transactionId); + try { + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH); + } + } catch (RemoteException e) { + logAndPrintException(pw, + "enableLimitPowerTransfer: Failed to call OperationComplete. opId:" + + transactionId, e); + } + return; + } + try { - mEnableUsbDataSignaling = enable; - // Call into the hal. Use the castFrom method from HIDL. - android.hardware.usb.V1_3.IUsb proxy = android.hardware.usb.V1_3.IUsb.castFrom(mProxy); - return proxy.enableUsbDataSignal(enable); + try { + mUsbPortHal.enableLimitPowerTransfer(portId, limit, transactionId, callback); + } catch (Exception e) { + logAndPrintException(pw, + "enableLimitPowerTransfer: Failed to limit power transfer. opId:" + + transactionId , e); + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + } } catch (RemoteException e) { - logAndPrintException(null, "Failed to set USB data signaling", e); - return false; - } catch (ClassCastException e) { - logAndPrintException(null, "Method only applicable to V1.3 or above implementation", e); - return false; + logAndPrintException(pw, + "enableLimitPowerTransfer:Failed to call onOperationComplete. opId:" + + transactionId, e); } } /** - * Get USB HAL version + * Enable/disable the USB data signaling * - * @param none + * @param enable enable or disable USB data signaling */ - public int getUsbHalVersion() { - return mCurrentUsbHalVersion; + public boolean enableUsbData(@NonNull String portId, boolean enable, int transactionId, + @NonNull IUsbOperationInternal callback, IndentingPrintWriter pw) { + Objects.requireNonNull(callback); + Objects.requireNonNull(portId); + final PortInfo portInfo = mPorts.get(portId); + if (portInfo == null) { + logAndPrint(Log.ERROR, pw, "enableUsbData: No such port: " + portId + + " opId:" + transactionId); + try { + callback.onOperationComplete(USB_OPERATION_ERROR_PORT_MISMATCH); + } catch (RemoteException e) { + logAndPrintException(pw, + "enableUsbData: Failed to call OperationComplete. opId:" + + transactionId, e); + } + return false; + } + + try { + try { + return mUsbPortHal.enableUsbData(portId, enable, transactionId, callback); + } catch (Exception e) { + logAndPrintException(pw, + "enableUsbData: Failed to invoke enableUsbData. opId:" + + transactionId , e); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + } catch (RemoteException e) { + logAndPrintException(pw, + "enableUsbData: Failed to call onOperationComplete. opId:" + + transactionId, e); + } + + return false; } /** - * update USB HAL version + * Get USB HAL version * * @param none + * @return {@link UsbManager#USB_HAL_RETRY} returned when hal version + * is yet to be determined. */ - private void updateUsbHalVersion() { - if (android.hardware.usb.V1_3.IUsb.castFrom(mProxy) != null) { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_3; - } else if (android.hardware.usb.V1_2.IUsb.castFrom(mProxy) != null) { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_2; - } else if (android.hardware.usb.V1_1.IUsb.castFrom(mProxy) != null) { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_1; - } else { - mCurrentUsbHalVersion = UsbManager.USB_HAL_V1_0; + public int getUsbHalVersion() { + if (mUsbPortHal != null) { + try { + return mUsbPortHal.getUsbHalVersion(); + } catch (RemoteException e) { + return UsbManager.USB_HAL_RETRY; + } } - logAndPrint(Log.INFO, null, "USB HAL version: " + mCurrentUsbHalVersion); + return UsbManager.USB_HAL_RETRY; + } + + private int toHalUsbDataRole(int usbDataRole) { + if (usbDataRole == DATA_ROLE_DEVICE) + return HAL_DATA_ROLE_DEVICE; + else + return HAL_DATA_ROLE_HOST; + } + + private int toHalUsbPowerRole(int usbPowerRole) { + if (usbPowerRole == POWER_ROLE_SINK) + return HAL_POWER_ROLE_SINK; + else + return HAL_POWER_ROLE_SOURCE; + } + + private int toHalUsbMode(int usbMode) { + if (usbMode == MODE_UFP) + return HAL_MODE_UFP; + else + return HAL_MODE_DFP; } public void setPortRoles(String portId, int newPowerRole, int newDataRole, @@ -473,7 +539,7 @@ public class UsbPortManager { sim.currentPowerRole = newPowerRole; sim.currentDataRole = newDataRole; updatePortsLocked(pw, null); - } else if (mProxy != null) { + } else if (mUsbPortHal != null) { if (currentMode != newMode) { // Changing the mode will have the side-effect of also changing // the power and data roles but it might take some time to apply @@ -485,44 +551,37 @@ public class UsbPortManager { logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: " + "portId=" + portId + ", newMode=" + UsbPort.modeToString(newMode)); - PortRole newRole = new PortRole(); - newRole.type = PortRoleType.MODE; - newRole.role = newMode; try { - mProxy.switchRole(portId, newRole); - } catch (RemoteException e) { + mUsbPortHal.switchMode(portId, toHalUsbMode(newMode), ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set the USB port mode: " + "portId=" + portId - + ", newMode=" + UsbPort.modeToString(newRole.role), e); + + ", newMode=" + UsbPort.modeToString(newMode), e); } } else { // Change power and data role independently as needed. if (currentPowerRole != newPowerRole) { - PortRole newRole = new PortRole(); - newRole.type = PortRoleType.POWER_ROLE; - newRole.role = newPowerRole; try { - mProxy.switchRole(portId, newRole); - } catch (RemoteException e) { + mUsbPortHal.switchPowerRole(portId, toHalUsbPowerRole(newPowerRole), + ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set the USB port power role: " + "portId=" + portId + ", newPowerRole=" + UsbPort.powerRoleToString - (newRole.role), + (newPowerRole), e); return; } } if (currentDataRole != newDataRole) { - PortRole newRole = new PortRole(); - newRole.type = PortRoleType.DATA_ROLE; - newRole.role = newDataRole; try { - mProxy.switchRole(portId, newRole); - } catch (RemoteException e) { + mUsbPortHal.switchDataRole(portId, toHalUsbDataRole(newDataRole), + ++mTransactionId); + } catch (Exception e) { logAndPrintException(pw, "Failed to set the USB port data role: " + "portId=" + portId - + ", newDataRole=" + UsbPort.dataRoleToString(newRole - .role), + + ", newDataRole=" + UsbPort.dataRoleToString + (newDataRole), e); } } @@ -531,6 +590,15 @@ public class UsbPortManager { } } + public void updatePorts(ArrayList<RawPortInfo> newPortInfo) { + Message message = mHandler.obtainMessage(); + Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(PORT_INFO, newPortInfo); + message.what = MSG_UPDATE_PORTS; + message.setData(bundle); + mHandler.sendMessage(message); + } + public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) { synchronized (mLock) { if (mSimulatedPorts.containsKey(portId)) { @@ -662,191 +730,12 @@ public class UsbPortManager { portInfo.dump(dump, "usb_ports", UsbPortManagerProto.USB_PORTS); } - dump.write("enable_usb_data_signaling", UsbPortManagerProto.ENABLE_USB_DATA_SIGNALING, - mEnableUsbDataSignaling); + dump.write("usb_hal_version", UsbPortManagerProto.HAL_VERSION, getUsbHalVersion()); } dump.end(token); } - private static class HALCallback extends IUsbCallback.Stub { - public IndentingPrintWriter pw; - public UsbPortManager portManager; - - HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) { - this.pw = pw; - this.portManager = portManager; - } - - public void notifyPortStatusChange( - ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) { - if (!portManager.mSystemReady) { - return; - } - - if (retval != Status.SUCCESS) { - logAndPrint(Log.ERROR, pw, "port status enquiry failed"); - return; - } - - ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - - for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) { - RawPortInfo temp = new RawPortInfo(current.portName, - current.supportedModes, CONTAMINANT_PROTECTION_NONE, - current.currentMode, - current.canChangeMode, current.currentPowerRole, - current.canChangePowerRole, - current.currentDataRole, current.canChangeDataRole, - false, CONTAMINANT_PROTECTION_NONE, - false, CONTAMINANT_DETECTION_NOT_SUPPORTED); - newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback V1_0: " + current.portName); - } - - Message message = portManager.mHandler.obtainMessage(); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(PORT_INFO, newPortInfo); - message.what = MSG_UPDATE_PORTS; - message.setData(bundle); - portManager.mHandler.sendMessage(message); - } - - - public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus, - int retval) { - if (!portManager.mSystemReady) { - return; - } - - if (retval != Status.SUCCESS) { - logAndPrint(Log.ERROR, pw, "port status enquiry failed"); - return; - } - - ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - - int numStatus = currentPortStatus.size(); - for (int i = 0; i < numStatus; i++) { - PortStatus_1_1 current = currentPortStatus.get(i); - RawPortInfo temp = new RawPortInfo(current.status.portName, - current.supportedModes, CONTAMINANT_PROTECTION_NONE, - current.currentMode, - current.status.canChangeMode, current.status.currentPowerRole, - current.status.canChangePowerRole, - current.status.currentDataRole, current.status.canChangeDataRole, - false, CONTAMINANT_PROTECTION_NONE, - false, CONTAMINANT_DETECTION_NOT_SUPPORTED); - newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback V1_1: " + current.status.portName); - } - - Message message = portManager.mHandler.obtainMessage(); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(PORT_INFO, newPortInfo); - message.what = MSG_UPDATE_PORTS; - message.setData(bundle); - portManager.mHandler.sendMessage(message); - } - - public void notifyPortStatusChange_1_2( - ArrayList<PortStatus> currentPortStatus, int retval) { - if (!portManager.mSystemReady) { - return; - } - - if (retval != Status.SUCCESS) { - logAndPrint(Log.ERROR, pw, "port status enquiry failed"); - return; - } - - ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); - - int numStatus = currentPortStatus.size(); - for (int i = 0; i < numStatus; i++) { - PortStatus current = currentPortStatus.get(i); - RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName, - current.status_1_1.supportedModes, - current.supportedContaminantProtectionModes, - current.status_1_1.currentMode, - current.status_1_1.status.canChangeMode, - current.status_1_1.status.currentPowerRole, - current.status_1_1.status.canChangePowerRole, - current.status_1_1.status.currentDataRole, - current.status_1_1.status.canChangeDataRole, - current.supportsEnableContaminantPresenceProtection, - current.contaminantProtectionStatus, - current.supportsEnableContaminantPresenceDetection, - current.contaminantDetectionStatus); - newPortInfo.add(temp); - logAndPrint(Log.INFO, pw, "ClientCallback V1_2: " - + current.status_1_1.status.portName); - } - - Message message = portManager.mHandler.obtainMessage(); - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(PORT_INFO, newPortInfo); - message.what = MSG_UPDATE_PORTS; - message.setData(bundle); - portManager.mHandler.sendMessage(message); - } - - public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) { - if (retval == Status.SUCCESS) { - logAndPrint(Log.INFO, pw, portName + " role switch successful"); - } else { - logAndPrint(Log.ERROR, pw, portName + " role switch failed"); - } - } - } - - final class DeathRecipient implements HwBinder.DeathRecipient { - public IndentingPrintWriter pw; - - DeathRecipient(IndentingPrintWriter pw) { - this.pw = pw; - } - - @Override - public void serviceDied(long cookie) { - if (cookie == USB_HAL_DEATH_COOKIE) { - logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie); - synchronized (mLock) { - mProxy = null; - } - } - } - } - - final class ServiceNotification extends IServiceNotification.Stub { - @Override - public void onRegistration(String fqName, String name, boolean preexisting) { - logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name); - connectToProxy(null); - } - } - - private void connectToProxy(IndentingPrintWriter pw) { - synchronized (mLock) { - if (mProxy != null) { - return; - } - - try { - mProxy = IUsb.getService(); - mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE); - mProxy.setCallback(mHALCallback); - mProxy.queryPortStatus(); - updateUsbHalVersion(); - } catch (NoSuchElementException e) { - logAndPrintException(pw, "connectToProxy: usb hal service not found." - + " Did the service fail to start?", e); - } catch (RemoteException e) { - logAndPrintException(pw, "connectToProxy: usb hal service not responding", e); - } - } - } - /** * Simulated ports directly add the new roles to mSimulatedPorts before calling. * USB hal callback populates and sends the newPortInfo. @@ -869,7 +758,9 @@ public class UsbPortManager { portInfo.supportsEnableContaminantPresenceProtection, portInfo.contaminantProtectionStatus, portInfo.supportsEnableContaminantPresenceDetection, - portInfo.contaminantDetectionStatus, pw); + portInfo.contaminantDetectionStatus, + portInfo.usbDataEnabled, + portInfo.powerTransferLimited, pw); } } else { for (RawPortInfo currentPortInfo : newPortInfo) { @@ -881,7 +772,9 @@ public class UsbPortManager { currentPortInfo.supportsEnableContaminantPresenceProtection, currentPortInfo.contaminantProtectionStatus, currentPortInfo.supportsEnableContaminantPresenceDetection, - currentPortInfo.contaminantDetectionStatus, pw); + currentPortInfo.contaminantDetectionStatus, + currentPortInfo.usbDataEnabled, + currentPortInfo.powerTransferLimited, pw); } } @@ -917,6 +810,8 @@ public class UsbPortManager { int contaminantProtectionStatus, boolean supportsEnableContaminantPresenceDetection, int contaminantDetectionStatus, + boolean usbDataEnabled, + boolean powerTransferLimited, IndentingPrintWriter pw) { // Only allow mode switch capability for dual role ports. // Validate that the current mode matches the supported modes we expect. @@ -975,7 +870,8 @@ public class UsbPortManager { currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited); mPorts.put(portId, portInfo); } else { // Validate that ports aren't changing definition out from under us. @@ -1012,7 +908,8 @@ public class UsbPortManager { currentPowerRole, canChangePowerRole, currentDataRole, canChangeDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus)) { + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited)) { portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; } else { portInfo.mDisposition = PortInfo.DISPOSITION_READY; @@ -1034,6 +931,7 @@ public class UsbPortManager { private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) { logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo); enableContaminantDetectionIfNeeded(portInfo, pw); + disableLimitPowerTransferIfNeeded(portInfo, pw); handlePortLocked(portInfo, pw); } @@ -1090,6 +988,19 @@ public class UsbPortManager { } } + private void disableLimitPowerTransferIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) { + if (!mConnected.containsKey(portInfo.mUsbPort.getId())) { + return; + } + + if (mConnected.get(portInfo.mUsbPort.getId()) + && !portInfo.mUsbPortStatus.isConnected() + && portInfo.mUsbPortStatus.isPowerTransferLimited()) { + // Relax enableLimitPowerTransfer upon unplug. + enableLimitPowerTransfer(portInfo.mUsbPort.getId(), false, ++mTransactionId, null, pw); + } + } + private void logToStatsd(PortInfo portInfo, IndentingPrintWriter pw) { // Port is removed if (portInfo.mUsbPortStatus == null) { @@ -1141,14 +1052,14 @@ public class UsbPortManager { } } - private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { + public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { Slog.println(priority, TAG, msg); if (pw != null) { pw.println(msg); } } - private static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) { + public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) { Slog.e(TAG, msg, e); if (pw != null) { pw.println(msg + e); @@ -1179,7 +1090,7 @@ public class UsbPortManager { /** * Describes a USB port. */ - private static final class PortInfo { + public static final class PortInfo { public static final int DISPOSITION_ADDED = 0; public static final int DISPOSITION_CHANGED = 1; public static final int DISPOSITION_READY = 2; @@ -1224,7 +1135,7 @@ public class UsbPortManager { != supportedRoleCombinations) { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE, - UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED); + UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED, true, false); dispositionChanged = true; } @@ -1243,7 +1154,8 @@ public class UsbPortManager { int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, - int contaminantDetectionStatus) { + int contaminantDetectionStatus, boolean usbDataEnabled, + boolean powerTransferLimited) { boolean dispositionChanged = false; mCanChangeMode = canChangeMode; @@ -1258,10 +1170,15 @@ public class UsbPortManager { || mUsbPortStatus.getContaminantProtectionStatus() != contaminantProtectionStatus || mUsbPortStatus.getContaminantDetectionStatus() - != contaminantDetectionStatus) { + != contaminantDetectionStatus + || mUsbPortStatus.getUsbDataStatus() + != usbDataEnabled + || mUsbPortStatus.isPowerTransferLimited() + != powerTransferLimited) { mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, supportedRoleCombinations, contaminantProtectionStatus, - contaminantDetectionStatus); + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited); dispositionChanged = true; } @@ -1290,7 +1207,6 @@ public class UsbPortManager { UsbPortInfoProto.CONNECTED_AT_MILLIS, mConnectedAtMillis); dump.write("last_connect_duration_millis", UsbPortInfoProto.LAST_CONNECT_DURATION_MILLIS, mLastConnectDurationMillis); - dump.end(token); } @@ -1304,115 +1220,4 @@ public class UsbPortManager { + ", lastConnectDurationMillis=" + mLastConnectDurationMillis; } } - - /** - * Used for storing the raw data from the kernel - * Values of the member variables mocked directly incase of emulation. - */ - private static final class RawPortInfo implements Parcelable { - public final String portId; - public final int supportedModes; - public final int supportedContaminantProtectionModes; - public int currentMode; - public boolean canChangeMode; - public int currentPowerRole; - public boolean canChangePowerRole; - public int currentDataRole; - public boolean canChangeDataRole; - public boolean supportsEnableContaminantPresenceProtection; - public int contaminantProtectionStatus; - public boolean supportsEnableContaminantPresenceDetection; - public int contaminantDetectionStatus; - - RawPortInfo(String portId, int supportedModes) { - this.portId = portId; - this.supportedModes = supportedModes; - this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; - this.supportsEnableContaminantPresenceProtection = false; - this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; - this.supportsEnableContaminantPresenceDetection = false; - this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; - } - - RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, - int currentMode, boolean canChangeMode, - int currentPowerRole, boolean canChangePowerRole, - int currentDataRole, boolean canChangeDataRole, - boolean supportsEnableContaminantPresenceProtection, - int contaminantProtectionStatus, - boolean supportsEnableContaminantPresenceDetection, - int contaminantDetectionStatus) { - this.portId = portId; - this.supportedModes = supportedModes; - this.supportedContaminantProtectionModes = supportedContaminantProtectionModes; - this.currentMode = currentMode; - this.canChangeMode = canChangeMode; - this.currentPowerRole = currentPowerRole; - this.canChangePowerRole = canChangePowerRole; - this.currentDataRole = currentDataRole; - this.canChangeDataRole = canChangeDataRole; - this.supportsEnableContaminantPresenceProtection = - supportsEnableContaminantPresenceProtection; - this.contaminantProtectionStatus = contaminantProtectionStatus; - this.supportsEnableContaminantPresenceDetection = - supportsEnableContaminantPresenceDetection; - this.contaminantDetectionStatus = contaminantDetectionStatus; - } - - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(portId); - dest.writeInt(supportedModes); - dest.writeInt(supportedContaminantProtectionModes); - dest.writeInt(currentMode); - dest.writeByte((byte) (canChangeMode ? 1 : 0)); - dest.writeInt(currentPowerRole); - dest.writeByte((byte) (canChangePowerRole ? 1 : 0)); - dest.writeInt(currentDataRole); - dest.writeByte((byte) (canChangeDataRole ? 1 : 0)); - dest.writeBoolean(supportsEnableContaminantPresenceProtection); - dest.writeInt(contaminantProtectionStatus); - dest.writeBoolean(supportsEnableContaminantPresenceDetection); - dest.writeInt(contaminantDetectionStatus); - } - - public static final Parcelable.Creator<RawPortInfo> CREATOR = - new Parcelable.Creator<RawPortInfo>() { - @Override - public RawPortInfo createFromParcel(Parcel in) { - String id = in.readString(); - int supportedModes = in.readInt(); - int supportedContaminantProtectionModes = in.readInt(); - int currentMode = in.readInt(); - boolean canChangeMode = in.readByte() != 0; - int currentPowerRole = in.readInt(); - boolean canChangePowerRole = in.readByte() != 0; - int currentDataRole = in.readInt(); - boolean canChangeDataRole = in.readByte() != 0; - boolean supportsEnableContaminantPresenceProtection = in.readBoolean(); - int contaminantProtectionStatus = in.readInt(); - boolean supportsEnableContaminantPresenceDetection = in.readBoolean(); - int contaminantDetectionStatus = in.readInt(); - return new RawPortInfo(id, supportedModes, - supportedContaminantProtectionModes, currentMode, canChangeMode, - currentPowerRole, canChangePowerRole, - currentDataRole, canChangeDataRole, - supportsEnableContaminantPresenceProtection, - contaminantProtectionStatus, - supportsEnableContaminantPresenceDetection, - contaminantDetectionStatus); - } - - @Override - public RawPortInfo[] newArray(int size) { - return new RawPortInfo[size]; - } - }; - } } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 3d3538d7ae49..51643e7d7d3c 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -16,6 +16,7 @@ package com.android.server.usb; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; import static android.hardware.usb.UsbPortStatus.MODE_DFP; @@ -35,6 +36,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.hardware.usb.IUsbManager; +import android.hardware.usb.IUsbOperationInternal; import android.hardware.usb.ParcelableUsbPort; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbDevice; @@ -44,6 +46,7 @@ import android.hardware.usb.UsbPortStatus; import android.os.Binder; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.service.usb.UsbServiceDumpProto; @@ -731,6 +734,28 @@ public class UsbService extends IUsbManager.Stub { } @Override + public void enableLimitPowerTransfer(String portId, boolean limit, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(portId, "portId must not be null. opID:" + operationId); + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + + final long ident = Binder.clearCallingIdentity(); + try { + if (mPortManager != null) { + mPortManager.enableLimitPowerTransfer(portId, limit, operationId, callback, null); + } else { + try { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException e) { + Slog.e(TAG, "enableLimitPowerTransfer: Failed to call onOperationComplete", e); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void enableContaminantDetection(String portId, boolean enable) { Objects.requireNonNull(portId, "portId must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); @@ -762,19 +787,30 @@ public class UsbService extends IUsbManager.Stub { } @Override - public boolean enableUsbDataSignal(boolean enable) { + public boolean enableUsbData(String portId, boolean enable, int operationId, + IUsbOperationInternal callback) { + Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:" + + operationId); + Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:" + + operationId); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - final long ident = Binder.clearCallingIdentity(); + boolean wait; try { if (mPortManager != null) { - return mPortManager.enableUsbDataSignal(enable); + wait = mPortManager.enableUsbData(portId, enable, operationId, callback, null); } else { - return false; + wait = false; + try { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException e) { + Slog.e(TAG, "enableUsbData: Failed to call onOperationComplete", e); + } } } finally { Binder.restoreCallingIdentity(ident); } + return wait; } @Override diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index 3412a6f80cc7..6e68a9174cb5 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -870,4 +870,35 @@ public final class UsbDescriptorParser { return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; } + /** + * isDock() indicates if the connected USB output peripheral is a docking station with + * audio output. + * A valid audio dock must declare only one audio output control terminal of type + * TERMINAL_EXTERN_DIGITAL. + */ + public boolean isDock() { + if (hasMIDIInterface() || hasHIDInterface()) { + return false; + } + + ArrayList<UsbDescriptor> acDescriptors = + getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, + UsbACInterface.AUDIO_AUDIOCONTROL); + + if (acDescriptors.size() != 1) { + return false; + } + + if (acDescriptors.get(0) instanceof UsbACTerminal) { + UsbACTerminal outDescr = (UsbACTerminal) acDescriptors.get(0); + if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL) { + return true; + } + } else { + Log.w(TAG, "Undefined Audio Output terminal l: " + acDescriptors.get(0).getLength() + + " t:0x" + Integer.toHexString(acDescriptors.get(0).getType())); + } + return false; + } + } diff --git a/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java new file mode 100644 index 000000000000..8dfc85995272 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/RawPortInfo.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2021 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.usb.hal.port; + +import android.hardware.usb.UsbPortStatus; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Used for storing the raw data from the HAL. + * Values of the member variables mocked directly in case of emulation. + */ +public final class RawPortInfo implements Parcelable { + public final String portId; + public final int supportedModes; + public final int supportedContaminantProtectionModes; + public int currentMode; + public boolean canChangeMode; + public int currentPowerRole; + public boolean canChangePowerRole; + public int currentDataRole; + public boolean canChangeDataRole; + public boolean supportsEnableContaminantPresenceProtection; + public int contaminantProtectionStatus; + public boolean supportsEnableContaminantPresenceDetection; + public int contaminantDetectionStatus; + public boolean usbDataEnabled; + public boolean powerTransferLimited; + + public RawPortInfo(String portId, int supportedModes) { + this.portId = portId; + this.supportedModes = supportedModes; + this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + this.supportsEnableContaminantPresenceProtection = false; + this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + this.supportsEnableContaminantPresenceDetection = false; + this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; + this.usbDataEnabled = true; + this.powerTransferLimited = false; + } + + public RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, + int currentMode, boolean canChangeMode, + int currentPowerRole, boolean canChangePowerRole, + int currentDataRole, boolean canChangeDataRole, + boolean supportsEnableContaminantPresenceProtection, + int contaminantProtectionStatus, + boolean supportsEnableContaminantPresenceDetection, + int contaminantDetectionStatus, + boolean usbDataEnabled, + boolean powerTransferLimited) { + this.portId = portId; + this.supportedModes = supportedModes; + this.supportedContaminantProtectionModes = supportedContaminantProtectionModes; + this.currentMode = currentMode; + this.canChangeMode = canChangeMode; + this.currentPowerRole = currentPowerRole; + this.canChangePowerRole = canChangePowerRole; + this.currentDataRole = currentDataRole; + this.canChangeDataRole = canChangeDataRole; + this.supportsEnableContaminantPresenceProtection = + supportsEnableContaminantPresenceProtection; + this.contaminantProtectionStatus = contaminantProtectionStatus; + this.supportsEnableContaminantPresenceDetection = + supportsEnableContaminantPresenceDetection; + this.contaminantDetectionStatus = contaminantDetectionStatus; + this.usbDataEnabled = usbDataEnabled; + this.powerTransferLimited = powerTransferLimited; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(portId); + dest.writeInt(supportedModes); + dest.writeInt(supportedContaminantProtectionModes); + dest.writeInt(currentMode); + dest.writeByte((byte) (canChangeMode ? 1 : 0)); + dest.writeInt(currentPowerRole); + dest.writeByte((byte) (canChangePowerRole ? 1 : 0)); + dest.writeInt(currentDataRole); + dest.writeByte((byte) (canChangeDataRole ? 1 : 0)); + dest.writeBoolean(supportsEnableContaminantPresenceProtection); + dest.writeInt(contaminantProtectionStatus); + dest.writeBoolean(supportsEnableContaminantPresenceDetection); + dest.writeInt(contaminantDetectionStatus); + dest.writeBoolean(usbDataEnabled); + dest.writeBoolean(powerTransferLimited); + } + + public static final Parcelable.Creator<RawPortInfo> CREATOR = + new Parcelable.Creator<RawPortInfo>() { + @Override + public RawPortInfo createFromParcel(Parcel in) { + String id = in.readString(); + int supportedModes = in.readInt(); + int supportedContaminantProtectionModes = in.readInt(); + int currentMode = in.readInt(); + boolean canChangeMode = in.readByte() != 0; + int currentPowerRole = in.readInt(); + boolean canChangePowerRole = in.readByte() != 0; + int currentDataRole = in.readInt(); + boolean canChangeDataRole = in.readByte() != 0; + boolean supportsEnableContaminantPresenceProtection = in.readBoolean(); + int contaminantProtectionStatus = in.readInt(); + boolean supportsEnableContaminantPresenceDetection = in.readBoolean(); + int contaminantDetectionStatus = in.readInt(); + boolean usbDataEnabled = in.readBoolean(); + boolean powerTransferLimited = in.readBoolean(); + return new RawPortInfo(id, supportedModes, + supportedContaminantProtectionModes, currentMode, canChangeMode, + currentPowerRole, canChangePowerRole, + currentDataRole, canChangeDataRole, + supportsEnableContaminantPresenceProtection, + contaminantProtectionStatus, + supportsEnableContaminantPresenceDetection, + contaminantDetectionStatus, usbDataEnabled, + powerTransferLimited); + } + + @Override + public RawPortInfo[] newArray(int size) { + return new RawPortInfo[size]; + } + }; +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java new file mode 100644 index 000000000000..24602426930d --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2021 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.usb.hal.port; + +import static android.hardware.usb.UsbManager.USB_HAL_V2_0; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS; + +import static com.android.server.usb.UsbPortManager.logAndPrint; +import static com.android.server.usb.UsbPortManager.logAndPrintException; + +import android.annotation.Nullable; +import android.hardware.usb.ContaminantProtectionStatus; +import android.hardware.usb.IUsb; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.hardware.usb.UsbPort; +import android.hardware.usb.UsbPortStatus; +import android.hardware.usb.PortMode; +import android.hardware.usb.Status; +import android.hardware.usb.IUsbCallback; +import android.hardware.usb.PortRole; +import android.hardware.usb.PortStatus; +import android.os.ServiceManager; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.UsbPortManager; +import com.android.server.usb.hal.port.RawPortInfo; + +import java.util.ArrayList; +import java.util.concurrent.ThreadLocalRandom; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * Implements the methods to interact with AIDL USB HAL. + */ +public final class UsbPortAidl implements UsbPortHal { + private static final String TAG = UsbPortAidl.class.getSimpleName(); + private static final String USB_AIDL_SERVICE = + "android.hardware.usb.IUsb/default"; + private static final LongSparseArray<IUsbOperationInternal> + sCallbacks = new LongSparseArray<>(); + // Proxy object for the usb hal daemon. + @GuardedBy("mLock") + private IUsb mProxy; + private UsbPortManager mPortManager; + public IndentingPrintWriter mPw; + // Mutex for all mutable shared state. + private final Object mLock = new Object(); + // Callback when the UsbPort status is changed by the kernel. + private HALCallback mHALCallback; + private IBinder mBinder; + private boolean mSystemReady; + private long mTransactionId; + + public @UsbHalVersion int getUsbHalVersion() throws RemoteException { + synchronized (mLock) { + if (mProxy == null) { + throw new RemoteException("IUsb not initialized yet"); + } + } + logAndPrint(Log.INFO, null, "USB HAL AIDL version: USB_HAL_V2_0"); + return USB_HAL_V2_0; + } + + @Override + public void systemReady() { + mSystemReady = true; + } + + public void serviceDied() { + logAndPrint(Log.ERROR, mPw, "Usb AIDL hal service died"); + synchronized (mLock) { + mProxy = null; + } + connectToProxy(null); + } + + private void connectToProxy(IndentingPrintWriter pw) { + synchronized (mLock) { + if (mProxy != null) { + return; + } + + try { + mBinder = ServiceManager.waitForService(USB_AIDL_SERVICE); + mProxy = IUsb.Stub.asInterface(mBinder); + mBinder.linkToDeath(this::serviceDied, 0); + mProxy.setCallback(mHALCallback); + mProxy.queryPortStatus(++mTransactionId); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not found." + + " Did the service fail to start?", e); + } catch (RemoteException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not responding", e); + } + } + } + + static boolean isServicePresent(IndentingPrintWriter pw) { + try { + return ServiceManager.isDeclared(USB_AIDL_SERVICE); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb Aidl hal service not found.", e); + } + + return false; + } + + public UsbPortAidl(UsbPortManager portManager, IndentingPrintWriter pw) { + mPortManager = Objects.requireNonNull(portManager); + mPw = pw; + mHALCallback = new HALCallback(null, mPortManager, this); + connectToProxy(mPw); + } + + @Override + public void enableContaminantPresenceDetection(String portName, boolean enable, + long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID: " + + operationID); + return; + } + + try { + // Oneway call into the hal. Use the castFrom method from HIDL. + mProxy.enableContaminantPresenceDetection(portName, enable, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set contaminant detection. opID:" + + operationID, e); + } + } + } + + @Override + public void queryPortStatus(long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + try { + mProxy.queryPortStatus(operationID); + } catch (RemoteException e) { + logAndPrintException(null, "ServiceStart: Failed to query port status. opID:" + + operationID, e); + } + } + } + + @Override + public void switchMode(String portId, @HalUsbPortMode int newMode, long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + PortRole newRole = new PortRole(); + newRole.setMode((byte)newMode); + try { + mProxy.switchRole(portId, newRole, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB port mode: " + + "portId=" + portId + + ", newMode=" + UsbPort.modeToString(newMode) + + "opID:" + operationID, e); + } + } + } + + @Override + public void switchPowerRole(String portId, @HalUsbPowerRole int newPowerRole, + long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + PortRole newRole = new PortRole(); + newRole.setPowerRole((byte)newPowerRole); + try { + mProxy.switchRole(portId, newRole, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB power role: portId=" + portId + + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) + + "opID:" + operationID, e); + } + } + } + + @Override + public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long operationID) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry ! opID:" + + operationID); + return; + } + + PortRole newRole = new PortRole(); + newRole.setDataRole((byte)newDataRole); + try { + mProxy.switchRole(portId, newRole, operationID); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB data role: portId=" + portId + + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole) + + "opID:" + operationID, e); + } + } + } + + @Override + public boolean enableUsbData(String portName, boolean enable, long operationID, + IUsbOperationInternal callback) { + Objects.requireNonNull(portName); + Objects.requireNonNull(callback); + long key = operationID; + synchronized (mLock) { + try { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, + "enableUsbData: Proxy is null. Retry !opID:" + + operationID); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + return false; + } + while (sCallbacks.get(key) != null) { + key = ThreadLocalRandom.current().nextInt(); + } + if (key != operationID) { + logAndPrint(Log.INFO, mPw, "enableUsbData: operationID exists ! opID:" + + operationID + " key:" + key); + } + try { + sCallbacks.put(key, callback); + mProxy.enableUsbData(portName, enable, key); + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableUsbData: Failed to invoke enableUsbData: portID=" + + portName + "opID:" + operationID, e); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + sCallbacks.remove(key); + return false; + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableUsbData: Failed to call onOperationComplete portID=" + + portName + "opID:" + operationID, e); + sCallbacks.remove(key); + return false; + } + return true; + } + } + + @Override + public void enableLimitPowerTransfer(String portName, boolean limit, long operationID, + IUsbOperationInternal callback) { + Objects.requireNonNull(portName); + long key = operationID; + synchronized (mLock) { + try { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, + "enableLimitPowerTransfer: Proxy is null. Retry !opID:" + + operationID); + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + return; + } + while (sCallbacks.get(key) != null) { + key = ThreadLocalRandom.current().nextInt(); + } + if (key != operationID) { + logAndPrint(Log.INFO, mPw, "enableUsbData: operationID exists ! opID:" + + operationID + " key:" + key); + } + try { + sCallbacks.put(key, callback); + mProxy.limitPowerTransfer(portName, limit, key); + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableLimitPowerTransfer: Failed while invoking AIDL HAL" + + " portID=" + portName + " opID:" + operationID, e); + if (callback != null) { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + sCallbacks.remove(key); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableLimitPowerTransfer: Failed to call onOperationComplete portID=" + + portName + " opID:" + operationID, e); + } + } + } + + private static class HALCallback extends IUsbCallback.Stub { + public IndentingPrintWriter mPw; + public UsbPortManager mPortManager; + public UsbPortAidl mUsbPortAidl; + + HALCallback(IndentingPrintWriter pw, UsbPortManager portManager, UsbPortAidl usbPortAidl) { + this.mPw = pw; + this.mPortManager = portManager; + this.mUsbPortAidl = usbPortAidl; + } + + /** + * Converts from AIDL defined mode constants to UsbPortStatus constants. + * AIDL does not gracefully support bitfield when combined with enums. + */ + private int toPortMode(byte aidlPortMode) { + switch (aidlPortMode) { + case PortMode.NONE: + return UsbPortStatus.MODE_NONE; + case PortMode.UFP: + return UsbPortStatus.MODE_UFP; + case PortMode.DFP: + return UsbPortStatus.MODE_DFP; + case PortMode.DRP: + return UsbPortStatus.MODE_DUAL; + case PortMode.AUDIO_ACCESSORY: + return UsbPortStatus.MODE_AUDIO_ACCESSORY; + case PortMode.DEBUG_ACCESSORY: + return UsbPortStatus.MODE_DEBUG_ACCESSORY; + default: + UsbPortManager.logAndPrint(Log.ERROR, mPw, "Unrecognized aidlPortMode:" + + aidlPortMode); + return UsbPortStatus.MODE_NONE; + } + } + + private int toSupportedModes(byte[] aidlPortModes) { + int supportedModes = UsbPortStatus.MODE_NONE; + + for (byte aidlPortMode : aidlPortModes) { + supportedModes |= toPortMode(aidlPortMode); + } + + return supportedModes; + } + + /** + * Converts from AIDL defined contaminant protection constants to UsbPortStatus constants. + * AIDL does not gracefully support bitfield when combined with enums. + * Common to both ContaminantProtectionMode and ContaminantProtectionStatus. + */ + private int toContaminantProtectionStatus(byte aidlContaminantProtection) { + switch (aidlContaminantProtection) { + case ContaminantProtectionStatus.NONE: + return UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + case ContaminantProtectionStatus.FORCE_SINK: + return UsbPortStatus.CONTAMINANT_PROTECTION_SINK; + case ContaminantProtectionStatus.FORCE_SOURCE: + return UsbPortStatus.CONTAMINANT_PROTECTION_SOURCE; + case ContaminantProtectionStatus.FORCE_DISABLE: + return UsbPortStatus.CONTAMINANT_PROTECTION_FORCE_DISABLE; + case ContaminantProtectionStatus.DISABLED: + return UsbPortStatus.CONTAMINANT_PROTECTION_DISABLED; + default: + UsbPortManager.logAndPrint(Log.ERROR, mPw, + "Unrecognized aidlContaminantProtection:" + + aidlContaminantProtection); + return UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + } + } + + private int toSupportedContaminantProtectionModes(byte[] aidlModes) { + int supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE; + + for (byte aidlMode : aidlModes) { + supportedContaminantProtectionModes |= toContaminantProtectionStatus(aidlMode); + } + + return supportedContaminantProtectionModes; + } + + @Override + public void notifyPortStatusChange( + android.hardware.usb.PortStatus[] currentPortStatus, int retval) { + if (!mUsbPortAidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + int numStatus = currentPortStatus.length; + for (int i = 0; i < numStatus; i++) { + PortStatus current = currentPortStatus[i]; + RawPortInfo temp = new RawPortInfo(current.portName, + toSupportedModes(current.supportedModes), + toSupportedContaminantProtectionModes(current + .supportedContaminantProtectionModes), + toPortMode(current.currentMode), + current.canChangeMode, + current.currentPowerRole, + current.canChangePowerRole, + current.currentDataRole, + current.canChangeDataRole, + current.supportsEnableContaminantPresenceProtection, + toContaminantProtectionStatus(current.contaminantProtectionStatus), + current.supportsEnableContaminantPresenceDetection, + current.contaminantDetectionStatus, + current.usbDataEnabled, + current.powerTransferLimited); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback AIDL V1: " + + current.portName); + } + mPortManager.updatePorts(newPortInfo); + } + + @Override + public void notifyRoleSwitchStatus(String portName, PortRole role, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + + " role switch successful. opID:" + + operationID); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + " role switch failed. err:" + + retval + + "opID:" + operationID); + } + } + + @Override + public void notifyQueryPortStatus(String portName, int retval, long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:" + + operationID + " successful"); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + ": opID:" + + operationID + " failed. err:" + retval); + } + } + + @Override + public void notifyEnableUsbDataStatus(String portName, boolean enable, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, "notifyEnableUsbDataStatus:" + + portName + ": opID:" + + operationID + " enable:" + enable); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + + "notifyEnableUsbDataStatus: opID:" + + operationID + " failed. err:" + retval); + } + try { + sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS + ? USB_OPERATION_SUCCESS + : USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException e) { + logAndPrintException(mPw, + "notifyEnableUsbDataStatus: Failed to call onOperationComplete", + e); + } + } + + @Override + public void notifyContaminantEnabledStatus(String portName, boolean enable, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, "notifyContaminantEnabledStatus:" + + portName + ": opID:" + + operationID + " enable:" + enable); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + + "notifyContaminantEnabledStatus: opID:" + + operationID + " failed. err:" + retval); + } + } + + @Override + public void notifyLimitPowerTransferStatus(String portName, boolean limit, int retval, + long operationID) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + ": opID:" + + operationID + " successful"); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + + "notifyLimitPowerTransferStatus: opID:" + + operationID + " failed. err:" + retval); + } + try { + IUsbOperationInternal callback = sCallbacks.get(operationID); + if (callback != null) { + sCallbacks.get(operationID).onOperationComplete(retval == Status.SUCCESS + ? USB_OPERATION_SUCCESS + : USB_OPERATION_ERROR_INTERNAL); + } + } catch (RemoteException e) { + logAndPrintException(mPw, + "enableLimitPowerTransfer: Failed to call onOperationComplete", + e); + } + } + } +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java new file mode 100644 index 000000000000..90c89090ef16 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHal.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2021 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.usb.hal.port; + +import android.annotation.IntDef; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.os.RemoteException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.String; + +/** + * @hide + */ +public interface UsbPortHal { + /** + * Power role: This USB port can act as a source (provide power). + * @hide + */ + public static final int HAL_POWER_ROLE_SOURCE = 1; + + /** + * Power role: This USB port can act as a sink (receive power). + * @hide + */ + public static final int HAL_POWER_ROLE_SINK = 2; + + @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = { + HAL_POWER_ROLE_SOURCE, + HAL_POWER_ROLE_SINK + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbPowerRole{} + + /** + * Data role: This USB port can act as a host (access data services). + * @hide + */ + public static final int HAL_DATA_ROLE_HOST = 1; + + /** + * Data role: This USB port can act as a device (offer data services). + * @hide + */ + public static final int HAL_DATA_ROLE_DEVICE = 2; + + @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = { + HAL_DATA_ROLE_HOST, + HAL_DATA_ROLE_DEVICE + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbDataRole{} + + /** + * This USB port can act as a downstream facing port (host). + * + * @hide + */ + public static final int HAL_MODE_DFP = 1; + + /** + * This USB port can act as an upstream facing port (device). + * + * @hide + */ + public static final int HAL_MODE_UFP = 2; + @IntDef(prefix = { "HAL_MODE_" }, value = { + HAL_MODE_DFP, + HAL_MODE_UFP, + }) + @Retention(RetentionPolicy.SOURCE) + @interface HalUsbPortMode{} + + /** + * UsbPortManager would call this when the system is done booting. + */ + public void systemReady(); + + /** + * Invoked to enable/disable contaminant presence detection on the USB port. + * + * @param portName Port Identifier. + * @param enable Enable contaminant presence detection when true. + * Disable when false. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void enableContaminantPresenceDetection(String portName, boolean enable, + long transactionId); + + /** + * Invoked to query port status of all the ports. + * + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void queryPortStatus(long transactionId); + + /** + * Invoked to switch USB port mode. + * + * @param portName Port Identifier. + * @param mode New mode that the port is switching into. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void switchMode(String portName, @HalUsbPortMode int mode, long transactionId); + + /** + * Invoked to switch USB port power role. + * + * @param portName Port Identifier. + * @param powerRole New power role that the port is switching into. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void switchPowerRole(String portName, @HalUsbPowerRole int powerRole, + long transactionId); + + /** + * Invoked to switch USB port data role. + * + * @param portName Port Identifier. + * @param dataRole New data role that the port is switching into. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + */ + public void switchDataRole(String portName, @HalUsbDataRole int dataRole, long transactionId); + + /** + * Invoked to query the version of current hal implementation. + */ + public @UsbHalVersion int getUsbHalVersion() throws RemoteException; + + /** + * Invoked to enable/disable UsbData on the specified port. + * + * @param portName Port Identifier. + * @param enable Enable USB data when true. + * Disable when false. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + * @param callback callback object to be invoked to invoke the status of the operation upon + * completion. + * @param callback callback object to be invoked when the operation is complete. + * @return True when the operation is asynchronous. The caller of + * {@link UsbOperationCallbackInternal} must therefore call + * {@link UsbOperationCallbackInternal#waitForOperationComplete} for processing + * the result. + * False when the operation is synchronous. Caller can proceed reading the result + * through {@link UsbOperationCallbackInternal#getStatus} + */ + public boolean enableUsbData(String portName, boolean enable, long transactionId, + IUsbOperationInternal callback); + + /** + * Invoked to enableLimitPowerTransfer on the specified port. + * + * @param portName Port Identifier. + * @param limit limit power transfer when true. Port wouldn't charge or power USB accessoried + * when set. + * Lift power transfer restrictions when false. + * @param transactionId Used for tracking the current request and is passed down to the HAL + * implementation as needed. + * @param callback callback object to be invoked to invoke the status of the operation upon + * completion. + */ + public void enableLimitPowerTransfer(String portName, boolean limit, long transactionId, + IUsbOperationInternal callback); +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java new file mode 100644 index 000000000000..41f9faef99df --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHalInstance.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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.usb.hal.port; + +import static com.android.server.usb.UsbPortManager.logAndPrint; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.hal.port.UsbPortHidl; +import com.android.server.usb.hal.port.UsbPortAidl; +import com.android.server.usb.UsbPortManager; + +import android.util.Log; +/** + * Helper class that queries the underlying hal layer to populate UsbPortHal instance. + */ +public final class UsbPortHalInstance { + + public static UsbPortHal getInstance(UsbPortManager portManager, IndentingPrintWriter pw) { + + logAndPrint(Log.DEBUG, null, "Querying USB HAL version"); + if (UsbPortHidl.isServicePresent(null)) { + logAndPrint(Log.INFO, null, "USB HAL HIDL present"); + return new UsbPortHidl(portManager, pw); + } + if (UsbPortAidl.isServicePresent(null)) { + logAndPrint(Log.INFO, null, "USB HAL AIDL present"); + return new UsbPortAidl(portManager, pw); + } + + return null; + } +} diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java new file mode 100644 index 000000000000..8a0370ad4909 --- /dev/null +++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortHidl.java @@ -0,0 +1,485 @@ +/* + * Copyright (C) 2021 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.usb.hal.port; + +import static android.hardware.usb.UsbManager.USB_HAL_NOT_SUPPORTED; +import static android.hardware.usb.UsbManager.USB_HAL_V1_0; +import static android.hardware.usb.UsbManager.USB_HAL_V1_1; +import static android.hardware.usb.UsbManager.USB_HAL_V1_2; +import static android.hardware.usb.UsbManager.USB_HAL_V1_3; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_NOT_SUPPORTED; +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_SUCCESS; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED; +import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE; +import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST; +import static android.hardware.usb.UsbPortStatus.MODE_DFP; +import static android.hardware.usb.UsbPortStatus.MODE_DUAL; +import static android.hardware.usb.UsbPortStatus.MODE_UFP; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK; +import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE; + +import static com.android.server.usb.UsbPortManager.logAndPrint; +import static com.android.server.usb.UsbPortManager.logAndPrintException; + +import android.annotation.Nullable; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.UsbManager.UsbHalVersion; +import android.hardware.usb.UsbPort; +import android.hardware.usb.V1_0.IUsb; +import android.hardware.usb.V1_0.PortRoleType; +import android.hardware.usb.V1_0.Status; +import android.hardware.usb.V1_1.PortStatus_1_1; +import android.hardware.usb.V1_2.IUsbCallback; +import android.hardware.usb.V1_0.PortRole; +import android.hardware.usb.V1_2.PortStatus; +import android.hidl.manager.V1_0.IServiceManager; +import android.hidl.manager.V1_0.IServiceNotification; +import android.os.IHwBinder; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.UsbPortManager; +import com.android.server.usb.hal.port.RawPortInfo; + +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.Objects; +/** + * + */ +public final class UsbPortHidl implements UsbPortHal { + private static final String TAG = UsbPortHidl.class.getSimpleName(); + // Cookie sent for usb hal death notification. + private static final int USB_HAL_DEATH_COOKIE = 1000; + // Proxy object for the usb hal daemon. + @GuardedBy("mLock") + private IUsb mProxy; + private UsbPortManager mPortManager; + public IndentingPrintWriter mPw; + // Mutex for all mutable shared state. + private final Object mLock = new Object(); + // Callback when the UsbPort status is changed by the kernel. + private HALCallback mHALCallback; + private boolean mSystemReady; + // Workaround since HIDL HAL versions report UsbDataEnabled status in UsbPortStatus; + private static boolean sUsbDataEnabled = true; + + public @UsbHalVersion int getUsbHalVersion() throws RemoteException { + int version; + synchronized(mLock) { + if (mProxy == null) { + throw new RemoteException("IUsb not initialized yet"); + } + if (android.hardware.usb.V1_3.IUsb.castFrom(mProxy) != null) { + version = USB_HAL_V1_3; + } else if (android.hardware.usb.V1_2.IUsb.castFrom(mProxy) != null) { + version = USB_HAL_V1_2; + } else if (android.hardware.usb.V1_1.IUsb.castFrom(mProxy) != null) { + version = USB_HAL_V1_1; + } else { + version = USB_HAL_V1_0; + } + logAndPrint(Log.INFO, null, "USB HAL HIDL version: " + version); + return version; + } + } + + final class DeathRecipient implements IHwBinder.DeathRecipient { + public IndentingPrintWriter pw; + + DeathRecipient(IndentingPrintWriter pw) { + this.pw = pw; + } + + @Override + public void serviceDied(long cookie) { + if (cookie == USB_HAL_DEATH_COOKIE) { + logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie); + synchronized (mLock) { + mProxy = null; + } + } + } + } + + final class ServiceNotification extends IServiceNotification.Stub { + @Override + public void onRegistration(String fqName, String name, boolean preexisting) { + logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name); + connectToProxy(null); + } + } + + private void connectToProxy(IndentingPrintWriter pw) { + synchronized (mLock) { + if (mProxy != null) { + return; + } + + try { + mProxy = IUsb.getService(); + mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE); + mProxy.setCallback(mHALCallback); + mProxy.queryPortStatus(); + //updateUsbHalVersion(); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not found." + + " Did the service fail to start?", e); + } catch (RemoteException e) { + logAndPrintException(pw, "connectToProxy: usb hal service not responding", e); + } + } + } + + @Override + public void systemReady() { + mSystemReady = true; + } + + static boolean isServicePresent(IndentingPrintWriter pw) { + try { + IUsb.getService(true); + } catch (NoSuchElementException e) { + logAndPrintException(pw, "connectToProxy: usb hidl hal service not found.", e); + return false; + } catch (RemoteException e) { + logAndPrintException(pw, "IUSB hal service present but failed to get service", e); + } + + return true; + } + + public UsbPortHidl(UsbPortManager portManager, IndentingPrintWriter pw) { + mPortManager = Objects.requireNonNull(portManager); + mPw = pw; + mHALCallback = new HALCallback(null, mPortManager, this); + try { + ServiceNotification serviceNotification = new ServiceNotification(); + + boolean ret = IServiceManager.getService() + .registerForNotifications("android.hardware.usb@1.0::IUsb", + "", serviceNotification); + if (!ret) { + logAndPrint(Log.ERROR, null, + "Failed to register service start notification"); + } + } catch (RemoteException e) { + logAndPrintException(null, + "Failed to register service start notification", e); + return; + } + connectToProxy(mPw); + } + + @Override + public void enableContaminantPresenceDetection(String portName, boolean enable, + long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + try { + // Oneway call into the hal. Use the castFrom method from HIDL. + android.hardware.usb.V1_2.IUsb proxy = + android.hardware.usb.V1_2.IUsb.castFrom(mProxy); + proxy.enableContaminantPresenceDetection(portName, enable); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set contaminant detection", e); + } catch (ClassCastException e) { + logAndPrintException(mPw, "Method only applicable to V1.2 or above implementation", + e); + } + } + } + + @Override + public void queryPortStatus(long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + try { + mProxy.queryPortStatus(); + } catch (RemoteException e) { + logAndPrintException(null, "ServiceStart: Failed to query port status", e); + } + } + } + + @Override + public void switchMode(String portId, @HalUsbPortMode int newMode, long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.MODE; + newRole.role = newMode; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB port mode: " + + "portId=" + portId + + ", newMode=" + UsbPort.modeToString(newRole.role), e); + } + } + } + + @Override + public void switchPowerRole(String portId, @HalUsbPowerRole int newPowerRole, + long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.POWER_ROLE; + newRole.role = newPowerRole; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB power role: portId=" + portId + + ", newPowerRole=" + UsbPort.powerRoleToString(newRole.role), e); + } + } + } + + @Override + public void enableLimitPowerTransfer(String portName, boolean limit, long transactionId, + IUsbOperationInternal callback) { + /* Not supported in HIDL hals*/ + try { + callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to call onOperationComplete", e); + } + } + + @Override + public void switchDataRole(String portId, @HalUsbDataRole int newDataRole, long transactionId) { + synchronized (mLock) { + if (mProxy == null) { + logAndPrint(Log.ERROR, mPw, "Proxy is null. Retry !"); + return; + } + + PortRole newRole = new PortRole(); + newRole.type = PortRoleType.DATA_ROLE; + newRole.role = newDataRole; + try { + mProxy.switchRole(portId, newRole); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to set the USB data role: portId=" + portId + + ", newDataRole=" + UsbPort.dataRoleToString(newRole.role), e); + } + } + } + + @Override + public boolean enableUsbData(String portName, boolean enable, long transactionId, + IUsbOperationInternal callback) { + int halVersion; + + try { + halVersion = getUsbHalVersion(); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to query USB HAL version. opID:" + + transactionId + + " portId:" + portName, e); + return false; + } + + if (halVersion != USB_HAL_V1_3) { + try { + callback.onOperationComplete(USB_OPERATION_ERROR_NOT_SUPPORTED); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed to call onOperationComplete. opID:" + + transactionId + + " portId:" + portName, e); + } + return false; + } + + boolean success; + synchronized(mLock) { + try { + android.hardware.usb.V1_3.IUsb proxy + = android.hardware.usb.V1_3.IUsb.castFrom(mProxy); + success = proxy.enableUsbDataSignal(enable); + } catch (RemoteException e) { + logAndPrintException(mPw, "Failed enableUsbData: opId:" + transactionId + + " portId=" + portName , e); + try { + callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + logAndPrintException(mPw, "Failed to call onOperationComplete. opID:" + + transactionId + + " portId:" + portName, r); + } + return false; + } + } + if (success) { + sUsbDataEnabled = enable; + } + + try { + callback.onOperationComplete(success + ? USB_OPERATION_SUCCESS + : USB_OPERATION_ERROR_INTERNAL); + } catch (RemoteException r) { + logAndPrintException(mPw, "Failed to call onOperationComplete. opID:" + + transactionId + + " portId:" + portName, r); + } + return false; + } + + private static class HALCallback extends IUsbCallback.Stub { + public IndentingPrintWriter mPw; + public UsbPortManager mPortManager; + public UsbPortHidl mUsbPortHidl; + + HALCallback(IndentingPrintWriter pw, UsbPortManager portManager, UsbPortHidl usbPortHidl) { + this.mPw = pw; + this.mPortManager = portManager; + this.mUsbPortHidl = usbPortHidl; + } + + public void notifyPortStatusChange( + ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) { + if (!mUsbPortHidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) { + RawPortInfo temp = new RawPortInfo(current.portName, + current.supportedModes, CONTAMINANT_PROTECTION_NONE, + current.currentMode, + current.canChangeMode, current.currentPowerRole, + current.canChangePowerRole, + current.currentDataRole, current.canChangeDataRole, + false, CONTAMINANT_PROTECTION_NONE, + false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataEnabled, + false); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_0: " + + current.portName); + } + + mPortManager.updatePorts(newPortInfo); + } + + + public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus, + int retval) { + if (!mUsbPortHidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + int numStatus = currentPortStatus.size(); + for (int i = 0; i < numStatus; i++) { + PortStatus_1_1 current = currentPortStatus.get(i); + RawPortInfo temp = new RawPortInfo(current.status.portName, + current.supportedModes, CONTAMINANT_PROTECTION_NONE, + current.currentMode, + current.status.canChangeMode, current.status.currentPowerRole, + current.status.canChangePowerRole, + current.status.currentDataRole, current.status.canChangeDataRole, + false, CONTAMINANT_PROTECTION_NONE, + false, CONTAMINANT_DETECTION_NOT_SUPPORTED, sUsbDataEnabled, + false); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_1: " + + current.status.portName); + } + mPortManager.updatePorts(newPortInfo); + } + + public void notifyPortStatusChange_1_2( + ArrayList<PortStatus> currentPortStatus, int retval) { + if (!mUsbPortHidl.mSystemReady) { + return; + } + + if (retval != Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.ERROR, mPw, "port status enquiry failed"); + return; + } + + ArrayList<RawPortInfo> newPortInfo = new ArrayList<>(); + + int numStatus = currentPortStatus.size(); + for (int i = 0; i < numStatus; i++) { + PortStatus current = currentPortStatus.get(i); + RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName, + current.status_1_1.supportedModes, + current.supportedContaminantProtectionModes, + current.status_1_1.currentMode, + current.status_1_1.status.canChangeMode, + current.status_1_1.status.currentPowerRole, + current.status_1_1.status.canChangePowerRole, + current.status_1_1.status.currentDataRole, + current.status_1_1.status.canChangeDataRole, + current.supportsEnableContaminantPresenceProtection, + current.contaminantProtectionStatus, + current.supportsEnableContaminantPresenceDetection, + current.contaminantDetectionStatus, + sUsbDataEnabled, + false); + newPortInfo.add(temp); + UsbPortManager.logAndPrint(Log.INFO, mPw, "ClientCallback V1_2: " + + current.status_1_1.status.portName); + } + mPortManager.updatePorts(newPortInfo); + } + + public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) { + if (retval == Status.SUCCESS) { + UsbPortManager.logAndPrint(Log.INFO, mPw, portName + " role switch successful"); + } else { + UsbPortManager.logAndPrint(Log.ERROR, mPw, portName + " role switch failed"); + } + } + } +} diff --git a/services/wallpapereffectsgeneration/OWNERS b/services/wallpapereffectsgeneration/OWNERS new file mode 100644 index 000000000000..d2d3e2c0a7b6 --- /dev/null +++ b/services/wallpapereffectsgeneration/OWNERS @@ -0,0 +1,4 @@ +susharon@google.com +shanh@google.com +huiwu@google.com +srazdan@google.com diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index e1be973752fb..d5c846d5ed81 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -175,7 +175,10 @@ public class CarrierConfigManager { /** * This flag specifies whether VoLTE availability is based on provisioning. By default this is * false. + * Used for UCE to determine if EAB provisioning checks should be based on provisioning. + * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead. */ + @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; @@ -879,7 +882,12 @@ public class CarrierConfigManager { /** * Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi * Calling. + + * Combines VoLTE, VT, VoWiFI calling provisioning into one parameter. + * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead for + * finer-grained control. */ + @Deprecated public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool"; @@ -893,7 +901,11 @@ public class CarrierConfigManager { * and enable the UT over IMS capability for the subscription when the subscription is loaded. * * The default value for this key is {@code false}. + * + * @deprecated Use {@link Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE} instead for + * determining if UT requires provisioning. */ + @Deprecated public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; @@ -5173,6 +5185,95 @@ public class CarrierConfigManager { /** E911 RTP inactivity occurred when call is connected. */ public static final int E911_RTP_INACTIVITY_ON_CONNECTED = 4; + /** + * A bundle which specifies the MMTEL capability and registration technology + * that requires provisioning. If a tuple is not present, the + * framework will not require that the tuple requires provisioning before + * enabling the capability. + * <p> Possible keys in this bundle are + * <ul> + * <li>{@link #KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_UT_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_SMS_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY}</li> + * </ul> + * <p> The values are defined in + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech} + */ + public static final String KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE = + KEY_PREFIX + "mmtel_requires_provisioning_bundle"; + + /** + * This MmTelFeature supports Voice calling (IR.92) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE + */ + public static final String KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY = + KEY_PREFIX + "key_capability_type_voice_int_array"; + + /** + * This MmTelFeature supports Video (IR.94) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO + */ + public static final String KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY = + KEY_PREFIX + "key_capability_type_video_int_array"; + + /** + * This MmTelFeature supports XCAP over Ut for supplementary services. (IR.92) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT + */ + public static final String KEY_CAPABILITY_TYPE_UT_INT_ARRAY = + KEY_PREFIX + "key_capability_type_ut_int_array"; + + /** + * This MmTelFeature supports SMS (IR.92) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS + */ + public static final String KEY_CAPABILITY_TYPE_SMS_INT_ARRAY = + KEY_PREFIX + "key_capability_type_sms_int_array"; + + /** + * This MmTelFeature supports Call Composer (section 2.4 of RCC.20) + * @see MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_CALL_COMPOSER + */ + public static final String KEY_CAPABILITY_CALL_COMPOSER_INT_ARRAY = + KEY_PREFIX + "key_capability_type_call_composer_int_array"; + + /** + * A bundle which specifies the RCS capability and registration technology + * that requires provisioning. If a tuple is not present, the + * framework will not require that the tuple requires provisioning before + * enabling the capability. + * <p> Possible keys in this bundle are + * <ul> + * <li>{@link #KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY}</li> + * <li>{@link #KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY}</li> + * </ul> + * <p> The values are defined in + * {@link android.telephony.ims.stub.ImsRegistrationImplBase.ImsRegistrationTech} + */ + public static final String KEY_RCS_REQUIRES_PROVISIONING_BUNDLE = + KEY_PREFIX + "rcs_requires_provisioning_bundle"; + + /** + * This carrier supports User Capability Exchange using SIP OPTIONS as defined by the + * framework. If set, the RcsFeature should support capability exchange using SIP OPTIONS. + * If not set, this RcsFeature should not service capability requests. + * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_OPTIONS_UCE + */ + public static final String KEY_CAPABILITY_TYPE_OPTIONS_UCE_INT_ARRAY = + KEY_PREFIX + "key_capability_type_options_uce_int_array"; + + /** + * This carrier supports User Capability Exchange using a presence server as defined by the + * framework. If set, the RcsFeature should support capability exchange using a presence + * server. If not set, this RcsFeature should not publish capabilities or service capability + * requests using presence. + * @see RcsFeature.RcsImsCapabilities#CAPABILITY_TYPE_PRESENCE_UCE + */ + public static final String KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY = + KEY_PREFIX + "key_capability_type_presence_uce_int_array"; + private Ims() {} private static PersistableBundle getDefaults() { @@ -5209,6 +5310,20 @@ public class CarrierConfigManager { "+g.gsma.rcs.botversion=\"#=1,#=2\"", "+g.gsma.rcs.cpimext"}); + /** + * @see #KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE + */ + PersistableBundle mmtel_requires_provisioning_int_array = new PersistableBundle(); + defaults.putPersistableBundle( + KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE, mmtel_requires_provisioning_int_array); + + /** + * @see #KEY_RCS_REQUIRES_PROVISIONING_BUNDLE + */ + PersistableBundle rcs_requires_provisioning_int_array = new PersistableBundle(); + defaults.putPersistableBundle( + KEY_RCS_REQUIRES_PROVISIONING_BUNDLE, rcs_requires_provisioning_int_array); + defaults.putBoolean(KEY_GRUU_ENABLED_BOOL, true); defaults.putBoolean(KEY_SIP_OVER_IPSEC_ENABLED_BOOL, true); defaults.putBoolean(KEY_KEEP_PDN_UP_IN_NO_VOPS_BOOL, false); @@ -7572,6 +7687,10 @@ public class CarrierConfigManager { /** Specifies the PCO id for IPv4 Epdg server address */ public static final String KEY_EPDG_PCO_ID_IPV4_INT = KEY_PREFIX + "epdg_pco_id_ipv4_int"; + /** Controls if the IKE tunnel setup supports EAP-AKA fast reauth */ + public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL = + KEY_PREFIX + "supports_eap_aka_fast_reauth_bool"; + /** @hide */ @IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT}) public @interface AuthenticationMethodType {} @@ -7718,6 +7837,7 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_ADD_KE_TO_CHILD_SESSION_REKEY_BOOL, false); defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0); defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0); + defaults.putBoolean(KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL, false); return defaults; } diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java index 2758e1273cec..b0ff9499eac4 100644 --- a/telephony/java/android/telephony/ImsManager.java +++ b/telephony/java/android/telephony/ImsManager.java @@ -163,6 +163,26 @@ public class ImsManager { return new SipDelegateManager(mContext, subscriptionId, sRcsCache, sTelephonyCache); } + + /** + * Create an instance of {@link ProvisioningManager} for the subscription id specified. + * <p> + * Provides a ProvisioningManager instance to carrier apps to update carrier provisioning + * information, as well as provides a callback so that apps can listen for changes + * in MMTEL/RCS provisioning + * @param subscriptionId The ID of the subscription that this ProvisioningManager will use. + * @throws IllegalArgumentException if the subscription is invalid. + * @return a ProvisioningManager instance with the specific subscription ID. + */ + @NonNull + public ProvisioningManager getProvisioningManager(int subscriptionId) { + if (!SubscriptionManager.isValidSubscriptionId(subscriptionId)) { + throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId); + } + + return new ProvisioningManager(subscriptionId); + } + private static IImsRcsController getIImsRcsControllerInterface() { return IImsRcsController.Stub.asInterface( TelephonyFrameworkInitializer diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 574a356c43f2..250e55cf5014 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3892,6 +3892,11 @@ public class SubscriptionManager { * {@link #PHONE_NUMBER_SOURCE_UICC UICC} and decide if the previously set phone number * of source {@link #PHONE_NUMBER_SOURCE_CARRIER carrier} should be updated. * + * <p>The API provides no guarantees of what format the number is in: the format can vary + * depending on the {@code source} and the network etc. Programmatic parsing should be done + * cautiously, for example, after formatting the number to a consistent format with + * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}. + * * <p>Note the assumption is that one subscription (which usually means one SIM) has * only one phone number. The multiple sources backup each other so hopefully at least one * is availavle. For example, for a carrier that doesn't typically set phone numbers @@ -3950,6 +3955,11 @@ public class SubscriptionManager { * from available sources in the following order: {@link #PHONE_NUMBER_SOURCE_CARRIER} * > {@link #PHONE_NUMBER_SOURCE_UICC} > {@link #PHONE_NUMBER_SOURCE_IMS}. * + * <p>The API provides no guarantees of what format the number is in: the format can vary + * depending on the underlying source and the network etc. Programmatic parsing should be done + * cautiously, for example, after formatting the number to a consistent format with + * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}. + * * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} * for the default one. * @return the phone number, or an empty string if not available. @@ -3988,6 +3998,9 @@ public class SubscriptionManager { * <p>The API is suitable for carrier apps to provide a phone number, for example when * it's not possible to update {@link #PHONE_NUMBER_SOURCE_UICC UICC} directly. * + * <p>It's recommended that the phone number is formatted to well-known formats, + * for example, by {@link PhoneNumberUtils} {@code formatNumber*} methods. + * * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} * for the default one. * @param number the phone number, or an empty string to remove the previously set number. diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index dbf4c99939de..677c1a9a7d76 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -34,6 +34,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; +import android.telephony.ims.aidl.IFeatureProvisioningCallback; import android.telephony.ims.aidl.IImsConfigCallback; import android.telephony.ims.aidl.IRcsConfigCallback; import android.telephony.ims.feature.MmTelFeature; @@ -54,18 +55,12 @@ import java.util.concurrent.Executor; * IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning * applications and may vary. It is up to the carrier and OEM applications to ensure that the * correct provisioning keys are being used when integrating with a vendor's ImsService. - * - * Note: For compatibility purposes, the integer values [0 - 99] used in - * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys - * previously defined in the Android framework. Please do not redefine new provisioning keys in this - * range or it may generate collisions with existing keys. Some common constants have also been - * defined in this class to make integrating with other system apps easier. - * @hide */ -@SystemApi @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS) public class ProvisioningManager { + private static final String TAG = "ProvisioningManager"; + /**@hide*/ @StringDef(prefix = "STRING_QUERY_RESULT_ERROR_", value = { STRING_QUERY_RESULT_ERROR_GENERIC, @@ -76,14 +71,18 @@ public class ProvisioningManager { /** * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error. + * @hide */ + @SystemApi public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC"; /** * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the * ImsService implementation was not ready for provisioning queries. + * @hide */ + @SystemApi public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY"; @@ -95,12 +94,16 @@ public class ProvisioningManager { /** * The integer result of provisioning for the queried key is disabled. + * @hide */ + @SystemApi public static final int PROVISIONING_VALUE_DISABLED = 0; /** * The integer result of provisioning for the queried key is enabled. + * @hide */ + @SystemApi public static final int PROVISIONING_VALUE_ENABLED = 1; @@ -445,27 +448,31 @@ public class ProvisioningManager { /** * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in - * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning - * the subscription for WiFi Calling. + * {@link android.telephony.SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, + * for the purposes of provisioning the subscription for WiFi Calling. * - * @see #getProvisioningIntValue(int) * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + * @hide */ + @SystemApi public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; /** * Override the user-defined WiFi mode for this subscription, defined in - * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning - * this subscription for WiFi Calling. + * {@link android.telephony.SubscriptionManager#WFC_MODE_CONTENT_URI}, + * for the purposes of provisioning this subscription for WiFi Calling. * * Valid values for this key are: * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY}, * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}. * - * @see #getProvisioningIntValue(int) * @see #setProvisioningIntValue(int, int) + * @see #getProvisioningIntValue(int) + * @hide */ + @SystemApi public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; /** @@ -864,7 +871,9 @@ public class ProvisioningManager { * <p>Value is in String format. * @see #setProvisioningStringValue(int, String) * @see #getProvisioningStringValue(int) + * @hide */ + @SystemApi public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; /** @@ -886,7 +895,9 @@ public class ProvisioningManager { /** * Callback for IMS provisioning changes. + * @hide */ + @SystemApi public static class Callback { private static class CallbackBinder extends IImsConfigCallback.Stub { @@ -956,11 +967,105 @@ public class ProvisioningManager { } } + /** + * Callback for IMS provisioning feature changes. + */ + public static class FeatureProvisioningCallback { + + private static class CallbackBinder extends IFeatureProvisioningCallback.Stub { + + private final FeatureProvisioningCallback mFeatureProvisioningCallback; + private Executor mExecutor; + + private CallbackBinder(FeatureProvisioningCallback featureProvisioningCallback) { + mFeatureProvisioningCallback = featureProvisioningCallback; + } + + @Override + public final void onFeatureProvisioningChanged( + int capability, int tech, boolean isProvisioned) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mFeatureProvisioningCallback.onFeatureProvisioningChanged( + capability, tech, isProvisioned)); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + + @Override + public final void onRcsFeatureProvisioningChanged( + int capability, int tech, boolean isProvisioned) { + final long callingIdentity = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mFeatureProvisioningCallback.onRcsFeatureProvisioningChanged( + capability, tech, isProvisioned)); + } finally { + restoreCallingIdentity(callingIdentity); + } + } + + private void setExecutor(Executor executor) { + mExecutor = executor; + } + } + + private final CallbackBinder mBinder = new CallbackBinder(this); + + /** + * The IMS MMTEL provisioning has changed for a specific capability and IMS + * registration technology. + * @param capability The MMTEL capability that provisioning has changed for. + * @param tech The IMS registration technology associated with the MMTEL capability that + * provisioning has changed for. + * @param isProvisioned {@code true} if the capability is provisioned for the technology + * specified, or {@code false} if the capability is not provisioned for the technology + * specified. + */ + public void onFeatureProvisioningChanged( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, + boolean isProvisioned) { + // Base Implementation + } + + /** + * The IMS RCS provisioning has changed for a specific capability and IMS + * registration technology. + * @param capability The RCS capability that provisioning has changed for. + * @param tech The IMS registration technology associated with the RCS capability that + * provisioning has changed for. + * @param isProvisioned {@code true} if the capability is provisioned for the technology + * specified, or {@code false} if the capability is not provisioned for the technology + * specified. + */ + public void onRcsFeatureProvisioningChanged( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, + boolean isProvisioned) { + // Base Implementation + } + + /**@hide*/ + public final IFeatureProvisioningCallback getBinder() { + return mBinder; + } + + /**@hide*/ + public void setExecutor(Executor executor) { + mBinder.setExecutor(executor); + } + } + private int mSubId; /** * The callback for RCS provisioning changes. + * @hide */ + @SystemApi public static class RcsProvisioningCallback { private static class CallbackBinder extends IRcsConfigCallback.Stub { @@ -1098,7 +1203,9 @@ public class ProvisioningManager { * @param subId The ID of the subscription that this ProvisioningManager will use. * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() * @throws IllegalArgumentException if the subscription is invalid. + * @hide */ + @SystemApi public static @NonNull ProvisioningManager createForSubscriptionId(int subId) { if (!SubscriptionManager.isValidSubscriptionId(subId)) { throw new IllegalArgumentException("Invalid subscription ID"); @@ -1107,7 +1214,9 @@ public class ProvisioningManager { return new ProvisioningManager(subId); } - private ProvisioningManager(int subId) { + /**@hide*/ + //@SystemApi + public ProvisioningManager(int subId) { mSubId = subId; } @@ -1116,6 +1225,12 @@ public class ProvisioningManager { * * When the subscription associated with this callback is removed (SIM removed, ESIM swap, * etc...), this callback will automatically be removed. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> + * </ul> + * * @param executor The {@link Executor} to call the callback methods on * @param callback The provisioning callbackto be registered. * @see #unregisterProvisioningChangedCallback(Callback) @@ -1126,7 +1241,9 @@ public class ProvisioningManager { * the {@link ImsService} associated with the subscription is not available. This can happen if * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed * reason. + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) throws ImsException { @@ -1144,12 +1261,20 @@ public class ProvisioningManager { * Unregister an existing {@link Callback}. When the subscription associated with this * callback is removed (SIM removed, ESIM swap, etc...), this callback will automatically be * removed. If this method is called for an inactive subscription, it will result in a no-op. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> + * </ul> + * * @param callback The existing {@link Callback} to be removed. * @see #registerProvisioningChangedCallback(Executor, Callback) * * @throws IllegalArgumentException if the subscription associated with this callback is * invalid. + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull Callback callback) { try { @@ -1160,6 +1285,62 @@ public class ProvisioningManager { } /** + * Register a new {@link FeatureProvisioningCallback}, which is used to listen for + * IMS feature provisioning updates. + * <p> + * When the subscription associated with this callback is removed (SIM removed, + * ESIM swap,etc...), this callback will automatically be removed. + * + * <p> Requires Permission: + * <ul> + * <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @param executor The executor that the callback methods will be called on. + * @param callback The callback instance being registered. + * @throws ImsException if the subscription associated with this callback is + * valid, but the {@link ImsService the service crashed, for example. See + * {@link ImsException#getCode()} for a more detailed reason. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public void registerFeatureProvisioningChangedCallback( + @NonNull @CallbackExecutor Executor executor, + @NonNull FeatureProvisioningCallback callback) throws ImsException { + callback.setExecutor(executor); + try { + getITelephony().registerFeatureProvisioningChangedCallback(mSubId, + callback.getBinder()); + } catch (ServiceSpecificException e) { + throw new ImsException(e.getMessage(), e.errorCode); + } catch (RemoteException | IllegalStateException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** + * Unregisters a previously registered {@link FeatureProvisioningCallback} + * instance. When the subscription associated with this + * callback is removed (SIM removed, ESIM swap, etc...), this callback will + * automatically be removed. If this method is called for an inactive + * subscription, it will result in a no-op. + * + * @param callback The existing {@link FeatureProvisioningCallback} to be removed. + * @see #registerFeatureProvisioningChangedCallback(Executor, FeatureProvisioningCallback) + */ + public void unregisterFeatureProvisioningChangedCallback( + @NonNull FeatureProvisioningCallback callback) { + try { + getITelephony().unregisterFeatureProvisioningChangedCallback(mSubId, + callback.getBinder()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Query for the integer value associated with the provided key. * * This operation is blocking and should not be performed on the UI thread. @@ -1168,7 +1349,9 @@ public class ProvisioningManager { * @return an integer value for the provided key, or * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist. * @throws IllegalArgumentException if the key provided was invalid. + * @hide */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getProvisioningIntValue(int key) { @@ -1188,7 +1371,9 @@ public class ProvisioningManager { * @return a String value for the provided key, {@code null} if the key doesn't exist, or * {@link StringResultError} if there was an error getting the value for the provided key. * @throws IllegalArgumentException if the key provided was invalid. + * @hide */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public @Nullable @StringResultError String getProvisioningStringValue(int key) { @@ -1209,7 +1394,15 @@ public class ProvisioningManager { * @param key An integer that represents the provisioning key, which is defined by the OEM. * @param value a integer value for the provided key. * @return the result of setting the configuration value. + * @hide + * + * Note: For compatibility purposes, the integer values [0 - 99] used in + * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys + * previously defined in the Android framework. Please do not redefine new provisioning keys + * in this range or it may generate collisions with existing keys. Some common constants have + * also been defined in this class to make integrating with other system apps easier. */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) { @@ -1229,7 +1422,9 @@ public class ProvisioningManager { * should be appropriately namespaced to avoid collision. * @param value a String value for the provided key. * @return the result of setting the configuration value. + * @hide */ + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key, @@ -1249,8 +1444,14 @@ public class ProvisioningManager { * does not support the capability/technology combination specified, this operation will be a * no-op. * - * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL - * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * <p>Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE},</li> + * <li>or that the calling app has carrier privileges (see</li> + * <li>{@link TelephonyManager#hasCarrierPrivileges}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise. */ @WorkerThread @@ -1258,9 +1459,10 @@ public class ProvisioningManager { public void setProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) { + try { getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech, - isProvisioned); + isProvisioned); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -1274,14 +1476,21 @@ public class ProvisioningManager { * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will * always return {@code true}. * - * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL - * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL + * <p> Requires Permission: + * <ul> + * <li>android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE * @return true if the device is provisioned for the capability or does not require * provisioning, false if the capability does require provisioning and has not been * provisioned yet. */ @WorkerThread - @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) public boolean getProvisioningStatusForCapability( @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech) { @@ -1299,17 +1508,55 @@ public class ProvisioningManager { * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return * {@code true}. * - * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL + * @see CarrierConfigManager.Ims#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL * @return true if the device is provisioned for the capability or does not require * provisioning, false if the capability does require provisioning and has not been * provisioned yet. + * @deprecated Use {@link #getRcsProvisioningStatusForCapability(int, int)} instead, + * as this only retrieves provisioning information for + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * @hide */ + @Deprecated + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getRcsProvisioningStatusForCapability( @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) { try { - return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability); + return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability, + ImsRegistrationImplBase.REGISTRATION_TECH_LTE); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Get the provisioning status for the IMS RCS capability specified. + * + * If provisioning is not required for the queried + * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method + * will always return {@code true}. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE + * @return true if the device is provisioned for the capability or does not require + * provisioning, false if the capability does require provisioning and has not been + * provisioned yet. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public boolean getRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability, tech); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -1318,6 +1565,13 @@ public class ProvisioningManager { /** * Set the provisioning status for the IMS RCS capability using the specified subscription. * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE}</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * Provisioning may or may not be required, depending on the carrier configuration. If * provisioning is not required for the carrier associated with this subscription or the device * does not support the capability/technology combination specified, this operation will be a @@ -1326,7 +1580,13 @@ public class ProvisioningManager { * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL * @param isProvisioned true if the device is provisioned for the RCS capability specified, * false otherwise. + * @deprecated Use {@link #setRcsProvisioningStatusForCapability(int, int, boolean)} instead, + * as this method only sets provisioning information for + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} + * @hide */ + @Deprecated + @SystemApi @WorkerThread @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public void setRcsProvisioningStatusForCapability( @@ -1334,28 +1594,121 @@ public class ProvisioningManager { boolean isProvisioned) { try { getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability, - isProvisioned); + ImsRegistrationImplBase.REGISTRATION_TECH_LTE, isProvisioned); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** + * Set the provisioning status for the IMS RCS capability using the specified subscription. + * + * Provisioning may or may not be required, depending on the carrier configuration. If + * provisioning is not required for the carrier associated with this subscription or the device + * does not support the capability/technology combination specified, this operation will be a + * no-op. + * + * <p> Requires Permission: + * <ul> + * <li>{@link android.Manifest.permission#MODIFY_PHONE_STATE},</li> + * <li>or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE + * @param isProvisioned true if the device is provisioned for the RCS capability specified, + * false otherwise. + */ + @WorkerThread + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setRcsProvisioningStatusForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned) { + try { + getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability, + tech, isProvisioned); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Indicates whether provisioning for the MMTEL capability and IMS registration technology + * specified is required or not + * + * <p> Requires Permission: + * <ul> + * <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li> or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @return true if provisioning is required for the MMTEL capability and IMS + * registration technology specified, false if it is not required. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public boolean isProvisioningRequiredForCapability( + @MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().isProvisioningRequiredForCapability(mSubId, capability, tech); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + + /** + * Indicates whether provisioning for the RCS capability and IMS registration technology + * specified is required or not + * + * <p> Requires Permission: + * <ul> + * <li> android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE,</li> + * <li>{@link android.Manifest.permission#READ_PRECISE_PHONE_STATE},</li> + * <li> or that the caller has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * + * @return true if provisioning is required for the RCS capability and IMS + * registration technology specified, false if it is not required. + */ + @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE) + public boolean isRcsProvisioningRequiredForCapability( + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int tech) { + try { + return getITelephony().isRcsProvisioningRequiredForCapability(mSubId, capability, tech); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + + /** * Notify the framework that an RCS autoconfiguration XML file has been received for * provisioning. - * <p> - * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has - * carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * <p>Requires Permission: + * <ul> + * <li>{@link Manifest.permission#MODIFY_PHONE_STATE},</li> + * <li>or that the calling app has carrier privileges (see + * {@link TelephonyManager#hasCarrierPrivileges()}).</li> + * </ul> + * * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed. * @param isCompressed The XML file is compressed in gzip format and must be decompressed * before being read. - * + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) { if (config == null) { throw new IllegalArgumentException("Must include a non-null config XML file."); } + try { getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed); } catch (RemoteException e) { @@ -1374,7 +1727,9 @@ public class ProvisioningManager { * <p>Contains {@link #EXTRA_SUBSCRIPTION_ID} to specify the subscription index for which * the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration * status. + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE = @@ -1382,7 +1737,9 @@ public class ProvisioningManager { /** * Integer extra to specify subscription index. + * @hide */ + @SystemApi public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.ims.extra.SUBSCRIPTION_ID"; @@ -1392,22 +1749,30 @@ public class ProvisioningManager { * <p>The value can be {@link #STATUS_CAPABLE}, {@link #STATUS_DEVICE_NOT_CAPABLE}, * {@link #STATUS_CARRIER_NOT_CAPABLE}, or bitwise OR of * {@link #STATUS_DEVICE_NOT_CAPABLE} and {@link #STATUS_CARRIER_NOT_CAPABLE}. + * @hide */ + @SystemApi public static final String EXTRA_STATUS = "android.telephony.ims.extra.STATUS"; /** * RCS VoLTE single registration is supported by the device and carrier. + * @hide */ + @SystemApi public static final int STATUS_CAPABLE = 0; /** * RCS VoLTE single registration is not supported by the device. + * @hide */ + @SystemApi public static final int STATUS_DEVICE_NOT_CAPABLE = 0x01; /** * RCS VoLTE single registration is not supported by the carrier + * @hide */ + @SystemApi public static final int STATUS_CARRIER_NOT_CAPABLE = 0x01 << 1; /** @@ -1417,11 +1782,14 @@ public class ProvisioningManager { * provisioning is done using autoconfiguration, then these parameters shall be * sent in the HTTP get request to fetch the RCS provisioning. RCS client * configuration must be provided by the application before registering for the - * provisioning status events {@link #registerRcsProvisioningCallback()} + * provisioning status events + * {@link #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback)} * When the IMS/RCS service receives the RCS client configuration, it will detect * the change in the configuration, and trigger the auto-configuration as needed. * @param rcc RCS client configuration {@link RcsClientConfiguration} + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void setRcsClientConfiguration( @NonNull RcsClientConfiguration rcc) throws ImsException { @@ -1442,18 +1810,21 @@ public class ProvisioningManager { * <ul> * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> * <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li> - * <li>or that the caller has carrier privileges (see + * <li>or that the calling app has carrier privileges (see * {@link TelephonyManager#hasCarrierPrivileges()}).</li> * </ul> + * * @return true if IMS single registration is capable at this time, or false otherwise - * @throws ImsException If the remote ImsService is not available for - * any reason or the subscription associated with this instance is no - * longer active. See {@link ImsException#getCode()} for more - * information. + * @throws ImsException If the remote ImsService is not available for any reason or + * the subscription associated with this instance is no longer active. + * See {@link ImsException#getCode()} for more information. * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION for whether or not this * device supports IMS single registration. + * @hide */ - @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public boolean isRcsVolteSingleRegistrationCapable() throws ImsException { try { @@ -1480,8 +1851,6 @@ public class ProvisioningManager { * <ul> * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> * <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li> - * <li>or that the caller has carrier privileges (see - * {@link TelephonyManager#hasCarrierPrivileges()}).</li> * </ul> * * @param executor The {@link Executor} to call the callback methods on @@ -1499,8 +1868,11 @@ public class ProvisioningManager { * params (See {@link #setRcsClientConfiguration}) and re register the * callback. * See {@link ImsException#getCode()} for a more detailed reason. + * @hide */ - @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void registerRcsProvisioningCallback( @NonNull @CallbackExecutor Executor executor, @@ -1527,8 +1899,6 @@ public class ProvisioningManager { * <ul> * <li>{@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE},</li> * <li>{@link android.Manifest.permission#PERFORM_IMS_SINGLE_REGISTRATION},</li> - * <li>or that the caller has carrier privileges (see - * {@link TelephonyManager#hasCarrierPrivileges()}).</li> * </ul> * * @param callback The existing {@link RcsProvisioningCallback} to be @@ -1536,8 +1906,11 @@ public class ProvisioningManager { * @see #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback) * @throws IllegalArgumentException if the subscription associated with * this callback is invalid. + * @hide */ - @RequiresPermission(anyOf = {Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + @SystemApi + @RequiresPermission(anyOf = { + Manifest.permission.READ_PRIVILEGED_PHONE_STATE, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION}) public void unregisterRcsProvisioningCallback( @NonNull RcsProvisioningCallback callback) { @@ -1558,9 +1931,10 @@ public class ProvisioningManager { * {@link RcsProvisioningCallback} may expect to receive * {@link RcsProvisioningCallback#onConfigurationReset}, then * {@link RcsProvisioningCallback#onConfigurationChanged} when the new - * RCS configuration is received and notified by - * {@link #notifyRcsAutoConfigurationReceived} + * RCS configuration is received and notified by {@link #notifyRcsAutoConfigurationReceived} + * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION) public void triggerRcsReconfiguration() { try { diff --git a/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl b/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl new file mode 100644 index 000000000000..63ec4aaf1f48 --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/IFeatureProvisioningCallback.aidl @@ -0,0 +1,28 @@ +/* + * 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 android.telephony.ims.aidl; + +/** + * Provides callback interface for FeatureProvisioning when a value has changed. + * + * {@hide} + */ +oneway interface IFeatureProvisioningCallback { + void onFeatureProvisioningChanged(int capability, int tech, boolean isProvisioned); + void onRcsFeatureProvisioningChanged(int capability, int tech, boolean isProvisioned); +} diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 7fdf21b3e5ff..ad2e9e133a11 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -394,6 +394,13 @@ public class MmTelFeature extends ImsFeature { public @interface MmTelCapability {} /** + * Undefined capability type for initialization + * This is used to check the upper range of MmTel capability + * {@hide} + */ + public static final int CAPABILITY_TYPE_NONE = 0; + + /** * This MmTelFeature supports Voice calling (IR.92) */ public static final int CAPABILITY_TYPE_VOICE = 1 << 0; @@ -419,6 +426,12 @@ public class MmTelFeature extends ImsFeature { public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4; /** + * This is used to check the upper range of MmTel capability + * {@hide} + */ + public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_CALL_COMPOSER + 1; + + /** * @hide */ @Override diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index 11cf0e3f7855..af7373b376d5 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -59,9 +59,7 @@ import java.util.function.Supplier; /** * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend * this class and provide implementations of the RcsFeature methods that they support. - * @hide */ -@SystemApi public class RcsFeature extends ImsFeature { private static final String LOG_TAG = "RcsFeature"; @@ -186,14 +184,14 @@ public class RcsFeature extends ImsFeature { * Contains the capabilities defined and supported by a {@link RcsFeature} in the * form of a bitmask. The capabilities that are used in the RcsFeature are * defined as: - * {@link RcsUceAdatper.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE} - * {@link RceUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE} + * {RcsUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_OPTIONS_UCE} + * {RcsUceAdapter.RcsImsCapabilityFlag#CAPABILITY_TYPE_PRESENCE_UCE} * * The enabled capabilities of this RcsFeature will be set by the framework - * using {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}. + * using {#changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}. * After the capabilities have been set, the RcsFeature may then perform the necessary bring up * of the capability and notify the capability status as true using - * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the + * {#notifyCapabilitiesStatusChanged(RcsImsCapabilities)}. This will signal to the * framework that the capability is available for usage. */ public static class RcsImsCapabilities extends Capabilities { @@ -227,10 +225,18 @@ public class RcsFeature extends ImsFeature { public static final int CAPABILITY_TYPE_PRESENCE_UCE = 1 << 1; /** + * This is used to check the upper range of RCS capability + * {@hide} + */ + public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_PRESENCE_UCE + 1; + + /** * Create a new {@link RcsImsCapabilities} instance with the provided capabilities. * @param capabilities The capabilities that are supported for RCS in the form of a * bitfield. + * @hide */ + @SystemApi public RcsImsCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { super(capabilities); } @@ -243,17 +249,29 @@ public class RcsFeature extends ImsFeature { super(capabilities.getMask()); } + /** + * @hide + */ @Override + @SystemApi public void addCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { super.addCapabilities(capabilities); } + /** + * @hide + */ @Override + @SystemApi public void removeCapabilities(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { super.removeCapabilities(capabilities); } + /** + * @hide + */ @Override + @SystemApi public boolean isCapable(@RcsUceAdapter.RcsImsCapabilityFlag int capabilities) { return super.isCapable(capabilities); } @@ -270,7 +288,9 @@ public class RcsFeature extends ImsFeature { * Method stubs called from the framework will be called asynchronously. To specify the * {@link Executor} that the methods stubs will be called, use * {@link RcsFeature#RcsFeature(Executor)} instead. + * @hide */ + @SystemApi public RcsFeature() { super(); // Run on the Binder threads that call them. @@ -282,7 +302,9 @@ public class RcsFeature extends ImsFeature { * framework. * @param executor The executor for the framework to use when executing the methods overridden * by the implementation of RcsFeature. + * @hide */ + @SystemApi public RcsFeature(@NonNull Executor executor) { super(); if (executor == null) { @@ -301,7 +323,7 @@ public class RcsFeature extends ImsFeature { * @hide */ @Override - public void initialize(Context context, int slotId) { + public void initialize(@NonNull Context context, @NonNull int slotId) { super.initialize(context, slotId); // Notify that the RcsFeature is ready. mExecutor.execute(() -> onFeatureReady()); @@ -313,8 +335,10 @@ public class RcsFeature extends ImsFeature { * requests. To change the status of the capabilities * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)} should be called. * @return A copy of the current RcsFeature capability status. + * @hide */ @Override + @SystemApi public @NonNull final RcsImsCapabilities queryCapabilityStatus() { return new RcsImsCapabilities(super.queryCapabilityStatus()); } @@ -324,7 +348,9 @@ public class RcsFeature extends ImsFeature { * this signals to the framework that the capability has been initialized and is ready. * Call {@link #queryCapabilityStatus()} to return the current capability status. * @param capabilities The current capability status of the RcsFeature. + * @hide */ + @SystemApi public final void notifyCapabilitiesStatusChanged(@NonNull RcsImsCapabilities capabilities) { if (capabilities == null) { throw new IllegalArgumentException("RcsImsCapabilities must be non-null!"); @@ -341,7 +367,9 @@ public class RcsFeature extends ImsFeature { * @param capability The capability that we are querying the configuration for. * @param radioTech The radio technology type that we are querying. * @return true if the capability is enabled, false otherwise. + * @hide */ + @SystemApi public boolean queryCapabilityConfiguration( @RcsUceAdapter.RcsImsCapabilityFlag int capability, @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { @@ -364,8 +392,10 @@ public class RcsFeature extends ImsFeature { * be called for each capability change that resulted in an error. * @param request The request to change the capability. * @param callback To notify the framework that the result of the capability changes. + * @hide */ @Override + @SystemApi public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, @NonNull CapabilityCallbackProxy callback) { // Base Implementation - Override to provide functionality @@ -385,7 +415,9 @@ public class RcsFeature extends ImsFeature { * event to the framework. * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability * exchange if it is supported by the device. + * @hide */ + @SystemApi public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl( @NonNull CapabilityExchangeEventListener listener) { // Base Implementation, override to implement functionality @@ -395,20 +427,28 @@ public class RcsFeature extends ImsFeature { /** * Remove the given CapabilityExchangeImplBase instance. * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be destroyed. + * @hide */ + @SystemApi public void destroyCapabilityExchangeImpl( @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) { // Override to implement the process of destroying RcsCapabilityExchangeImplBase instance. } - /**{@inheritDoc}*/ + /**{@inheritDoc} + * @hide + */ @Override + @SystemApi public void onFeatureRemoved() { } - /**{@inheritDoc}*/ + /**{@inheritDoc} + * @hide + */ @Override + @SystemApi public void onFeatureReady() { } @@ -448,7 +488,9 @@ public class RcsFeature extends ImsFeature { * has already been created in the framework. * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange * event to the framework. + * @hide */ + @SystemApi private void initRcsCapabilityExchangeImplBase( @NonNull CapabilityExchangeEventListener listener) { synchronized (mLock) { diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 3b151a422b57..ac5565bea810 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -34,7 +34,6 @@ import com.android.internal.telephony.util.RemoteCallbackListExt; import com.android.internal.telephony.util.TelephonyUtils; import com.android.internal.util.ArrayUtils; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.concurrent.CancellationException; @@ -51,9 +50,7 @@ import java.util.function.Supplier; * <p> * Note: There is no guarantee on the thread that the calls from the framework will be called on. It * is the implementors responsibility to handle moving the calls to a working thread if required. - * @hide */ -@SystemApi public class ImsRegistrationImplBase { private static final String LOG_TAG = "ImsRegistrationImplBase"; @@ -94,6 +91,12 @@ public class ImsRegistrationImplBase { */ public static final int REGISTRATION_TECH_NR = 3; + /** + * This is used to check the upper range of registration tech + * {@hide} + */ + public static final int REGISTRATION_TECH_MAX = REGISTRATION_TECH_NR + 1; + // Registration states, used to notify new ImsRegistrationImplBase#Callbacks of the current // state. // The unknown state is set as the initialization state. This is so that we do not call back @@ -109,7 +112,9 @@ public class ImsRegistrationImplBase { * Method stubs called from the framework will be called asynchronously. To specify the * {@link Executor} that the methods stubs will be called, use * {@link ImsRegistrationImplBase#ImsRegistrationImplBase(Executor)} instead. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public ImsRegistrationImplBase() { super(); } @@ -119,7 +124,9 @@ public class ImsRegistrationImplBase { * framework. * @param executor The executor for the framework to use when executing the methods overridden * by the implementation of ImsRegistration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public ImsRegistrationImplBase(@NonNull Executor executor) { super(); mExecutor = executor; @@ -250,7 +257,9 @@ public class ImsRegistrationImplBase { * If the SIP delegate feature tag configuration has changed, then this method will be * called in order to let the ImsService know that it can pick up these changes in the IMS * registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public void updateSipDelegateRegistration() { // Stub implementation, ImsService should implement this } @@ -266,7 +275,9 @@ public class ImsRegistrationImplBase { * <p> * This should not affect the registration of features managed by the ImsService itself, such as * feature tags related to MMTEL registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public void triggerSipDelegateDeregistration() { // Stub implementation, ImsService should implement this } @@ -284,7 +295,9 @@ public class ImsRegistrationImplBase { * be carrier specific. * @param sipReason The reason associated with the SIP error code. {@code null} if there was no * reason associated with the error. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public void triggerFullNetworkRegistration(@IntRange(from = 100, to = 699) int sipCode, @Nullable String sipReason) { // Stub implementation, ImsService should implement this @@ -295,7 +308,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is connected to the IMS network. * * @param imsRadioTech the radio access technology. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistered(@ImsRegistrationTech int imsRadioTech) { onRegistered(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); } @@ -304,7 +319,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is connected to the IMS network. * * @param attributes The attributes associated with the IMS registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistered(@NonNull ImsRegistrationAttributes attributes) { updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERED); mCallbacks.broadcastAction((c) -> { @@ -320,7 +337,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is trying to connect the IMS network. * * @param imsRadioTech the radio access technology. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistering(@ImsRegistrationTech int imsRadioTech) { onRegistering(new ImsRegistrationAttributes.Builder(imsRadioTech).build()); } @@ -329,7 +348,9 @@ public class ImsRegistrationImplBase { * Notify the framework that the device is trying to connect the IMS network. * * @param attributes The attributes associated with the IMS registration. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onRegistering(@NonNull ImsRegistrationAttributes attributes) { updateToState(attributes, RegistrationManager.REGISTRATION_STATE_REGISTERING); mCallbacks.broadcastAction((c) -> { @@ -356,7 +377,9 @@ public class ImsRegistrationImplBase { * result. * * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onDeregistered(ImsReasonInfo info) { updateToDisconnectedState(info); // ImsReasonInfo should never be null. @@ -377,7 +400,9 @@ public class ImsRegistrationImplBase { * {@link #REGISTRATION_TECH_LTE}, {@link #REGISTRATION_TECH_IWLAN} and * {@link #REGISTRATION_TECH_CROSS_SIM}. * @param info The {@link ImsReasonInfo} for the failure to change technology. + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { final ImsReasonInfo reasonInfo = (info != null) ? info : new ImsReasonInfo(); @@ -396,7 +421,9 @@ public class ImsRegistrationImplBase { * * The {@link Uri}s are not guaranteed to be different between subsequent calls. * @param uris changed uris + * @hide This API is not part of the Android public SDK API */ + @SystemApi public final void onSubscriberAssociatedUriChanged(Uri[] uris) { synchronized (mLock) { mUris = ArrayUtils.cloneOrNull(uris); diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index be54cecbe3ba..bce7a246463d 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -55,6 +55,7 @@ import android.telephony.VisualVoicemailSmsFilterSettings; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.RcsClientConfiguration; import android.telephony.ims.RcsContactUceCapability; +import android.telephony.ims.aidl.IFeatureProvisioningCallback; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsConfigCallback; @@ -1992,6 +1993,18 @@ interface ITelephony { void unregisterImsProvisioningChangedCallback(int subId, IImsConfigCallback callback); /** + * Register an IMS provisioning change callback with Telephony. + */ + void registerFeatureProvisioningChangedCallback(int subId, + IFeatureProvisioningCallback callback); + + /** + * unregister an existing IMS provisioning change callback. + */ + void unregisterFeatureProvisioningChangedCallback(int subId, + IFeatureProvisioningCallback callback); + + /** * Set the provisioning status for the IMS MmTel capability using the specified subscription. */ void setImsProvisioningStatusForCapability(int subId, int capability, int tech, @@ -2005,19 +2018,12 @@ interface ITelephony { /** * Get the provisioning status for the IMS Rcs capability specified. */ - boolean getRcsProvisioningStatusForCapability(int subId, int capability); + boolean getRcsProvisioningStatusForCapability(int subId, int capability, int tech); /** * Set the provisioning status for the IMS Rcs capability using the specified subscription. */ - void setRcsProvisioningStatusForCapability(int subId, int capability, - boolean isProvisioned); - - /** Is the capability and tech flagged as provisioned in the cache */ - boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech); - - /** Set the provisioning for the capability and tech in the cache */ - void cacheMmTelCapabilityProvisioning(int subId, int capability, int tech, + void setRcsProvisioningStatusForCapability(int subId, int capability, int tech, boolean isProvisioned); /** @@ -2523,4 +2529,18 @@ interface ITelephony { * @return the service name of the modem service which bind to. */ String getModemService(); + + /** + * Is Provisioning required for capability + * @return true if provisioning is required for the MMTEL capability and IMS + * registration technology specified, false if it is not required. + */ + boolean isProvisioningRequiredForCapability(int subId, int capability, int tech); + + /** + * Is RCS Provisioning required for capability + * @return true if provisioning is required for the RCS capability and IMS + * registration technology specified, false if it is not required. + */ + boolean isRcsProvisioningRequiredForCapability(int subId, int capability, int tech); } diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index 21c3f760771c..f518d53fa517 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.graphics.Color; import android.os.Build; import android.telephony.UiccPortInfo; +import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.GsmAlphabet; @@ -934,6 +935,13 @@ public class IccUtils { } /** + * Strip all the trailing 'F' characters of a string if exists and compare. + */ + public static boolean compareIgnoreTrailingFs(String a, String b) { + return TextUtils.equals(a, b) || TextUtils.equals(stripTrailingFs(a), stripTrailingFs(b)); + } + + /** * Converts a character of [0-9a-fA-F] to its hex value in a byte. If the character is not a * hex number, 0 will be returned. */ diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index 22acc03af6b6..d960e94bf09d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -108,6 +108,15 @@ class CloseAppHomeButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio super.entireScreenCovered() } + /** {@inheritDoc} */ + @Presubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + // This test doesn't work in shell transitions because of b/215885246 + assumeFalse(isShellTransitionsEnabled) + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt index 0b1748a6bda4..535612a2bd8b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt @@ -17,7 +17,9 @@ package com.android.server.wm.flicker.helpers import android.app.Instrumentation +import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.Until import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.traces.common.FlickerComponentName import com.android.server.wm.traces.parser.toFlickerComponent @@ -47,4 +49,28 @@ class ImeAppAutoFocusHelper @JvmOverloads constructor( } launcherStrategy.launch(appName, expectedPackage) } + + fun startDialogThemedActivity(wmHelper: WindowManagerStateHelper) { + val button = uiDevice.wait(Until.findObject(By.res(getPackage(), + "start_dialog_themed_activity_btn")), FIND_TIMEOUT) + + require(button != null) { + "Button not found, this usually happens when the device " + + "was left in an unknown state (e.g. Screen turned off)" + } + button.click() + wmHelper.waitForAppTransitionIdle() + wmHelper.waitForFullScreenApp( + ActivityOptions.DIALOG_THEMED_ACTIVITY_COMPONENT_NAME.toFlickerComponent()) + } + fun dismissDialog(wmHelper: WindowManagerStateHelper) { + val dialog = uiDevice.wait( + Until.findObject(By.text("Dialog for test")), FIND_TIMEOUT) + + // Pressing back key to dismiss the dialog + if (dialog != null) { + uiDevice.pressBack() + wmHelper.waitForAppTransitionIdle() + } + } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt index f2696d8a71ff..815ea778555b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToHomeTest.kt @@ -178,7 +178,7 @@ class CloseImeAutoOpenWindowToHomeTest(private val testSpec: FlickerTestParamete @JvmStatic fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() - .getConfigNonRotationTests(repetitions = 1, + .getConfigNonRotationTests(repetitions = 5, // b/190352379 (IME doesn't show on app launch in 90 degrees) supportedRotations = listOf(Surface.ROTATION_0), supportedNavigationModes = listOf( diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt new file mode 100644 index 000000000000..429541c28d19 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeAndDialogThemeAppTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2021 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.wm.flicker.ime + +import android.app.Instrumentation +import android.platform.test.annotations.Presubmit +import android.view.Surface +import android.view.WindowManagerPolicyConstants +import androidx.test.filters.FlakyTest +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerBuilderProvider +import com.android.server.wm.flicker.FlickerParametersRunnerFactory +import com.android.server.wm.flicker.FlickerTestParameter +import com.android.server.wm.flicker.FlickerTestParameterFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.traces.common.FlickerComponentName +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. + * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class LaunchAppShowImeAndDialogThemeAppTest(private val testSpec: FlickerTestParameter) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + + @FlickerBuilderProvider + fun buildFlicker(): FlickerBuilder { + return FlickerBuilder(instrumentation).apply { + setup { + eachRun { + testApp.launchViaIntent(wmHelper) + wmHelper.waitImeShown() + testApp.startDialogThemedActivity(wmHelper) + } + } + teardown { + eachRun { + testApp.exit() + } + } + transitions { + testApp.dismissDialog(wmHelper) + } + } + } + + /** + * Checks that [FlickerComponentName.IME] layer becomes visible during the transition + */ + @FlakyTest(bugId = 215884488) + @Test + fun imeWindowIsAlwaysVisible() = testSpec.imeWindowIsAlwaysVisible() + + /** + * Checks that [FlickerComponentName.IME] layer is visible at the end of the transition + */ + @Presubmit + @Test + fun imeLayerExistsEnd() { + testSpec.assertLayersEnd { + this.isVisible(FlickerComponentName.IME) + } + } + + /** + * Checks that [FlickerComponentName.IME_SNAPSHOT] layer is invisible always. + */ + @Presubmit + @Test + fun imeSnapshotNotVisible() { + testSpec.assertLayers { + this.isInvisible(FlickerComponentName.IME_SNAPSHOT) + } + } + + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestParameterFactory.getConfigNonRotationTests] for configuring + * repetitions, screen orientation and navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTestParameter> { + return FlickerTestParameterFactory.getInstance() + .getConfigNonRotationTests( + repetitions = 3, + supportedRotations = listOf(Surface.ROTATION_0), + supportedNavigationModes = listOf( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY, + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY + ) + ) + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt index 0ad0a033a4fe..5e06f11fb6b8 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/ReOpenImeWindowTest.kt @@ -18,8 +18,8 @@ package com.android.server.wm.flicker.ime import android.app.Instrumentation import android.platform.test.annotations.Presubmit +import android.view.Display import android.view.Surface -import android.view.WindowManagerPolicyConstants import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry @@ -41,7 +41,9 @@ import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled import com.android.server.wm.flicker.statusBarLayerIsVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsVisible +import com.android.server.wm.traces.common.ConditionList import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.common.WindowManagerConditionsFactory import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue import org.junit.FixMethodOrder @@ -63,6 +65,12 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + private val waitConditionSetup = ConditionList(listOf( + WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY), + WindowManagerConditionsFactory.hasLayersAnimating().negate(), + WindowManagerConditionsFactory.isHomeActivityVisible() + )) + @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { @@ -73,8 +81,7 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { } eachRun { device.pressRecentApps() - wmHelper.waitImeGone() - wmHelper.waitForAppTransitionIdle() + wmHelper.waitFor(waitConditionSetup) this.setRotation(testSpec.startRotation) } } @@ -231,11 +238,8 @@ class ReOpenImeWindowTest(private val testSpec: FlickerTestParameter) { fun getParams(): Collection<FlickerTestParameter> { return FlickerTestParameterFactory.getInstance() .getConfigNonRotationTests( - repetitions = 1, - supportedRotations = listOf(Surface.ROTATION_0), - supportedNavigationModes = listOf( - WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY - ) + repetitions = 5, + supportedRotations = listOf(Surface.ROTATION_0) ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index e07a8f94d651..5450610722fa 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -122,6 +122,11 @@ class OpenAppColdTest(testSpec: FlickerTestParameter) : OpenAppTransition(testSp @Test override fun navBarWindowIsVisible() = super.navBarWindowIsVisible() + /** {@inheritDoc} */ + @FlakyTest(bugId = 213852103) + @Test + override fun entireScreenCovered() = super.entireScreenCovered() + companion object { /** * Creates the test configurations. diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 6e5c600eb141..a85dcc57ddb2 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -127,7 +127,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * The `isAppWindowInvisible` step is optional because we log once per frame, upon logging, * the window may be visible or not depending on what was processed until that moment. */ - @Presubmit + @FlakyTest(bugId = 203538234) @Test fun appWindowBecomesVisible() { testSpec.assertWm { @@ -240,7 +240,7 @@ class OpenAppNonResizeableTest(testSpec: FlickerTestParameter) : OpenAppTransiti * it cannot use the regular assertion (check over time), because on lock screen neither * the app not the launcher are visible, and there is no top visible window. */ - @Presubmit + @FlakyTest(bugId = 203538234) @Test override fun appWindowReplacesLauncherAsTopWindow() { testSpec.assertWm { diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 0a88f6bb5908..9e371e5e381e 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -97,5 +97,16 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".DialogThemedActivity" + android:taskAffinity="com.android.server.wm.flicker.testapp.DialogThemedActivity" + android:configChanges="orientation|screenSize" + android:theme="@style/DialogTheme" + android:label="DialogThemedActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml index 2620ff407efc..baaf7073b3a6 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_ime.xml @@ -31,4 +31,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Finish activity" /> + <Button + android:id="@+id/start_dialog_themed_activity_btn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="Start dialog themed activity" /> </LinearLayout> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 87a61a88c094..746b0f4ca7e1 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -27,4 +27,15 @@ <style name="CutoutNever"> <item name="android:windowLayoutInDisplayCutoutMode">never</item> </style> -</resources>
\ No newline at end of file + + <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowAnimationStyle">@null</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowBackground">@null</item> + <item name="android:windowContentOverlay">@null</item> + <item name="android:windowNoTitle">true</item> + <item name="android:windowIsFloating">true</item> + <item name="android:backgroundDimEnabled">false</item> + <item name="android:windowSoftInputMode">stateUnchanged</item> + </style> +</resources> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index baf36ab0e132..13adb681c30c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -56,4 +56,8 @@ public class ActivityOptions { public static final ComponentName LAUNCH_NEW_TASK_ACTIVITY_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".LaunchNewTaskActivity"); + public static final String DIALOG_THEMED_ACTIVITY = "DialogThemedActivity"; + public static final ComponentName DIALOG_THEMED_ACTIVITY_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".DialogThemedActivity"); } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java new file mode 100644 index 000000000000..27606d81f9d3 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/DialogThemedActivity.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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.wm.flicker.testapp; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; +import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; + +import android.app.Activity; +import android.app.AlertDialog; +import android.graphics.Color; +import android.os.Bundle; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class DialogThemedActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_simple); + getWindow().getDecorView().setBackgroundColor(Color.TRANSPARENT); + TextView textView = new TextView(this); + textView.setText("This is a test dialog"); + textView.setTextColor(Color.BLACK); + LinearLayout layout = new LinearLayout(this); + layout.setBackgroundColor(Color.GREEN); + layout.addView(textView); + + // Create a dialog with dialog-themed activity + AlertDialog dialog = new AlertDialog.Builder(this) + .setView(layout) + .setTitle("Dialog for test") + .create(); + final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(MATCH_PARENT, + MATCH_PARENT); + attrs.flags = FLAG_DIM_BEHIND | FLAG_ALT_FOCUSABLE_IM; + dialog.getWindow().getDecorView().setLayoutParams(attrs); + dialog.setCanceledOnTouchOutside(true); + dialog.show(); + dialog.setOnDismissListener((d) -> finish()); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java index 05da717620aa..bb200f125507 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeActivityAutoFocus.java @@ -16,6 +16,8 @@ package com.android.server.wm.flicker.testapp; +import android.content.Intent; +import android.widget.Button; import android.widget.EditText; public class ImeActivityAutoFocus extends ImeActivity { @@ -26,5 +28,9 @@ public class ImeActivityAutoFocus extends ImeActivity { EditText editTextField = findViewById(R.id.plain_text_input); editTextField.requestFocus(); + + Button startThemedActivityButton = findViewById(R.id.start_dialog_themed_activity_btn); + startThemedActivityButton.setOnClickListener( + button -> startActivity(new Intent(this, DialogThemedActivity.class))); } } diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index 3131f5687c6a..99f77fee4698 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -54,6 +54,7 @@ LANG_TO_SCRIPT = { 'or': 'Orya', 'pa': 'Guru', 'pt': 'Latn', + 'ru': 'Latn', 'sk': 'Latn', 'sl': 'Latn', 'sq': 'Latn', diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 900c2145975a..a6fd9bba6192 100644 --- a/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -31,7 +31,9 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { CallingIdentityTokenDetector.ISSUE_RESTORE_IDENTITY_CALL_NOT_IN_FINALLY_BLOCK, CallingIdentityTokenDetector.ISSUE_USE_OF_CALLER_AWARE_METHODS_WITH_CLEARED_IDENTITY, CallingIdentityTokenDetector.ISSUE_CLEAR_IDENTITY_CALL_NOT_FOLLOWED_BY_TRY_FINALLY, - CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED + CallingSettingsNonUserGetterMethodsDetector.ISSUE_NON_USER_GETTER_CALLED, + EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION ) override val api: Int diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt new file mode 100644 index 000000000000..8011b36c9a8f --- /dev/null +++ b/tools/lint/checks/src/main/java/com/google/android/lint/EnforcePermissionDetector.kt @@ -0,0 +1,169 @@ +/* + * 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.google.android.lint + +import com.android.tools.lint.detector.api.AnnotationInfo +import com.android.tools.lint.detector.api.AnnotationOrigin +import com.android.tools.lint.detector.api.AnnotationUsageInfo +import com.android.tools.lint.detector.api.AnnotationUsageType +import com.android.tools.lint.detector.api.ConstantEvaluator +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import com.intellij.psi.PsiAnnotation +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.UElement + +/** + * Lint Detector that ensures that any method overriding a method annotated + * with @EnforcePermission is also annotated with the exact same annotation. + * The intent is to surface the effective permission checks to the service + * implementations. + */ +class EnforcePermissionDetector : Detector(), SourceCodeScanner { + + val ENFORCE_PERMISSION = "android.annotation.EnforcePermission" + + override fun applicableAnnotations(): List<String> { + return listOf(ENFORCE_PERMISSION) + } + + private fun areAnnotationsEquivalent( + context: JavaContext, + anno1: PsiAnnotation, + anno2: PsiAnnotation + ): Boolean { + if (anno1.qualifiedName != anno2.qualifiedName) { + return false + } + val attr1 = anno1.parameterList.attributes + val attr2 = anno2.parameterList.attributes + if (attr1.size != attr2.size) { + return false + } + for (i in attr1.indices) { + if (attr1[i].name != attr2[i].name) { + return false + } + val v1 = ConstantEvaluator.evaluate(context, attr1[i].value) + val v2 = ConstantEvaluator.evaluate(context, attr2[i].value) + if (v1 != v2) { + return false + } + } + return true + } + + override fun visitAnnotationUsage( + context: JavaContext, + element: UElement, + annotationInfo: AnnotationInfo, + usageInfo: AnnotationUsageInfo + ) { + if (usageInfo.type == AnnotationUsageType.EXTENDS) { + val newClass = element.sourcePsi?.parent?.parent as PsiClass + val extendedClass: PsiClass = usageInfo.referenced as PsiClass + val newAnnotation = newClass.getAnnotation(ENFORCE_PERMISSION) + val extendedAnnotation = extendedClass.getAnnotation(ENFORCE_PERMISSION)!! + + val location = context.getLocation(element) + val newClassName = newClass.qualifiedName + val extendedClassName = extendedClass.qualifiedName + if (newAnnotation == null) { + val msg = "The class $newClassName extends the class $extendedClassName which " + + "is annotated with @EnforcePermission. The same annotation must be used " + + "on $newClassName." + context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) + } else if (!areAnnotationsEquivalent(context, newAnnotation, extendedAnnotation)) { + val msg = "The class $newClassName is annotated with ${newAnnotation.text} " + + "which differs from the parent class $extendedClassName: " + + "${extendedAnnotation.text}. The same annotation must be used for " + + "both classes." + context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) + } + } else if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE && + annotationInfo.origin == AnnotationOrigin.METHOD) { + val overridingMethod = element.sourcePsi as PsiMethod + val overriddenMethod = usageInfo.referenced as PsiMethod + val overridingAnnotation = overridingMethod.getAnnotation(ENFORCE_PERMISSION) + val overriddenAnnotation = overriddenMethod.getAnnotation(ENFORCE_PERMISSION)!! + + val location = context.getLocation(element) + val overridingClass = overridingMethod.parent as PsiClass + val overriddenClass = overriddenMethod.parent as PsiClass + val overridingName = "${overridingClass.name}.${overridingMethod.name}" + val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}" + if (overridingAnnotation == null) { + val msg = "The method $overridingName overrides the method $overriddenName which " + + "is annotated with @EnforcePermission. The same annotation must be used " + + "on $overridingName" + context.report(ISSUE_MISSING_ENFORCE_PERMISSION, element, location, msg) + } else if (!areAnnotationsEquivalent( + context, overridingAnnotation, overriddenAnnotation)) { + val msg = "The method $overridingName is annotated with " + + "${overridingAnnotation.text} which differs from the overridden " + + "method $overriddenName: ${overriddenAnnotation.text}. The same " + + "annotation must be used for both methods." + context.report(ISSUE_MISMATCHING_ENFORCE_PERMISSION, element, location, msg) + } + } + } + + companion object { + val EXPLANATION = """ + The @EnforcePermission annotation is used to indicate that the underlying binder code + has already verified the caller's permissions before calling the appropriate method. The + verification code is usually generated by the AIDL compiler, which also takes care of + annotating the generated Java code. + + In order to surface that information to platform developers, the same annotation must be + used on the implementation class or methods. + """ + + val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MissingEnforcePermissionAnnotation", + briefDescription = "Missing @EnforcePermission annotation on Binder method", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + val ISSUE_MISMATCHING_ENFORCE_PERMISSION: Issue = Issue.create( + id = "MismatchingEnforcePermissionAnnotation", + briefDescription = "Incorrect @EnforcePermission annotation on Binder method", + explanation = EXPLANATION, + category = Category.SECURITY, + priority = 6, + severity = Severity.ERROR, + implementation = Implementation( + EnforcePermissionDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + } +} diff --git a/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt new file mode 100644 index 000000000000..f5f4ebee24e0 --- /dev/null +++ b/tools/lint/checks/src/test/java/com/google/android/lint/EnforcePermissionDetectorTest.kt @@ -0,0 +1,202 @@ +/* + * 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.google.android.lint + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class EnforcePermissionDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = EnforcePermissionDetector() + + override fun getIssues(): List<Issue> = listOf( + EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION, + EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + fun testDoesNotDetectIssuesCorrectAnnotationOnClass() { + lint().files(java( + """ + package test.pkg; + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public class TestClass1 extends IFoo.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDoesNotDetectIssuesCorrectAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + import android.annotation.EnforcePermission; + public class TestClass2 extends IFooMethod.Stub { + @Override + @EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testDetectIssuesMismatchingAnnotationOnClass() { + lint().files(java( + """ + package test.pkg; + @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) + public class TestClass3 extends IFoo.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass3.java:3: Error: The class test.pkg.TestClass3 is \ +annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \ +which differs from the parent class IFoo.Stub: \ +@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The \ +same annotation must be used for both classes. [MismatchingEnforcePermissionAnnotation] +public class TestClass3 extends IFoo.Stub { + ~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + fun testDetectIssuesMismatchingAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass4 extends IFooMethod.Stub { + @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass4.java:4: Error: The method TestClass4.testMethod is \ +annotated with @android.annotation.EnforcePermission(android.Manifest.permission.INTERNET) \ +which differs from the overridden method Stub.testMethod: \ +@android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE). The same \ +annotation must be used for both methods. [MismatchingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + fun testDetectIssuesMissingAnnotationOnClass() { + lint().files(java( + """ + package test.pkg; + public class TestClass5 extends IFoo.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass5.java:2: Error: The class test.pkg.TestClass5 extends \ +the class IFoo.Stub which is annotated with @EnforcePermission. The same annotation must be \ +used on test.pkg.TestClass5. [MissingEnforcePermissionAnnotation] +public class TestClass5 extends IFoo.Stub { + ~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + fun testDetectIssuesMissingAnnotationOnMethod() { + lint().files(java( + """ + package test.pkg; + public class TestClass6 extends IFooMethod.Stub { + public void testMethod() {} + } + """).indented(), + *stubs + ) + .run() + .expect("""src/test/pkg/TestClass6.java:3: Error: The method TestClass6.testMethod \ +overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same \ +annotation must be used on TestClass6.testMethod [MissingEnforcePermissionAnnotation] + public void testMethod() {} + ~~~~~~~~~~ +1 errors, 0 warnings""".addLineContinuation()) + } + + /* Stubs */ + + private val interfaceIFooStub: TestFile = java( + """ + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public interface IFoo { + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public static abstract class Stub implements IFoo { + @Override + public void testMethod() {} + } + public void testMethod(); + } + """ + ).indented() + + private val interfaceIFooMethodStub: TestFile = java( + """ + public interface IFooMethod { + public static abstract class Stub implements IFooMethod { + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() {} + } + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + } + """ + ).indented() + + private val manifestPermissionStub: TestFile = java( + """ + package android.Manifest; + class permission { + public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE"; + public static final String INTERNET = "android.permission.INTERNET"; + } + """ + ).indented() + + private val enforcePermissionAnnotationStub: TestFile = java( + """ + package android.annotation; + public @interface EnforcePermission {} + """ + ).indented() + + private val stubs = arrayOf(interfaceIFooStub, interfaceIFooMethodStub, + manifestPermissionStub, enforcePermissionAnnotationStub) + + // Substitutes "backslash + new line" with an empty string to imitate line continuation + private fun String.addLineContinuation(): String = this.trimIndent().replace("\\\n", "") +} diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index 3b7566051fae..459696e81ee4 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -1262,6 +1262,26 @@ public class WifiNl80211Manager { } /** + * Notifies the wificond daemon that the WiFi framework has successfully updated the Country + * Code of the driver. The wificond daemon needs this notification if the device does not + * support the NL80211_CMD_REG_CHANGED (otherwise it will find out on its own). The wificond + * updates in internal state in response to this Country Code update. + * + * @return true on success, false otherwise. + */ + public boolean notifyCountryCodeChanged() { + try { + if (mWificond != null) { + mWificond.notifyCountryCodeChanged(); + return true; + } + } catch (RemoteException e1) { + Log.e(TAG, "Failed to notify country code changed due to remote exception"); + } + return false; + } + + /** * Register the provided callback handler for SoftAp events. The interface must first be created * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index 3fb23013bdec..4032a7b0c75c 100644 --- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java +++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java @@ -1137,6 +1137,26 @@ public class WifiNl80211ManagerTest { assertEquals(capaExpected, capaActual); } + /** + * Tests notifyCountryCodeChanged + */ + @Test + public void testNotifyCountryCodeChanged() throws Exception { + doNothing().when(mWificond).notifyCountryCodeChanged(); + assertTrue(mWificondControl.notifyCountryCodeChanged()); + verify(mWificond).notifyCountryCodeChanged(); + } + + /** + * Tests notifyCountryCodeChanged with RemoteException + */ + @Test + public void testNotifyCountryCodeChangedRemoteException() throws Exception { + doThrow(new RemoteException()).when(mWificond).notifyCountryCodeChanged(); + assertFalse(mWificondControl.notifyCountryCodeChanged()); + verify(mWificond).notifyCountryCodeChanged(); + } + // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it // matches the provided frequency set and ssid set. private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> { |