summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/OWNERS7
-rw-r--r--config/preloaded-classes1
-rw-r--r--core/api/current.txt9
-rw-r--r--core/api/system-current.txt53
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java6
-rw-r--r--core/java/android/content/pm/LimitedLengthInputStream.java2
-rw-r--r--core/java/android/content/pm/PackageManager.java17
-rw-r--r--core/java/android/content/res/ResourcesImpl.java19
-rw-r--r--core/java/android/net/ConnectivityManager.java4
-rw-r--r--core/java/android/os/FileBridge.java3
-rw-r--r--core/java/android/os/IRecoverySystem.aidl7
-rw-r--r--core/java/android/os/Parcel.java3
-rw-r--r--core/java/android/os/RecoverySystem.java124
-rw-r--r--core/java/android/os/incremental/OWNERS5
-rw-r--r--core/java/android/provider/OWNERS26
-rw-r--r--core/java/android/provider/Telephony.java16
-rw-r--r--core/java/android/security/OWNERS2
-rw-r--r--core/java/android/service/attestation/OWNERS2
-rw-r--r--core/java/android/service/storage/OWNERS1
-rw-r--r--core/java/android/uwb/RangingManager.java178
-rw-r--r--core/java/android/uwb/RangingSession.java115
-rw-r--r--core/java/android/uwb/UwbManager.java6
-rw-r--r--core/java/android/view/OWNERS2
-rw-r--r--core/java/com/android/internal/util/ArrayUtils.java19
-rw-r--r--core/res/AndroidManifest.xml10
-rw-r--r--core/res/OWNERS1
-rw-r--r--core/res/res/values-am/strings.xml10
-rw-r--r--core/res/res/values-es/strings.xml2
-rw-r--r--core/res/res/values-fa/strings.xml2
-rw-r--r--core/res/res/values-fr-rCA/strings.xml34
-rw-r--r--core/res/res/values-fr/strings.xml34
-rw-r--r--core/res/res/values-it/strings.xml2
-rw-r--r--core/res/res/values-ne/strings.xml2
-rw-r--r--core/res/res/values-nl/strings.xml2
-rw-r--r--core/res/res/values-sk/strings.xml8
-rw-r--r--core/res/res/values-uz/strings.xml2
-rw-r--r--core/tests/coretests/src/android/os/storage/OWNERS1
-rw-r--r--core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java85
-rw-r--r--core/tests/uwbtests/src/android/uwb/RangingManagerTest.java260
-rw-r--r--core/tests/uwbtests/src/android/uwb/RangingSessionTest.java194
-rw-r--r--core/tests/uwbtests/src/android/uwb/UwbTestUtils.java10
-rw-r--r--data/etc/car/Android.bp7
-rw-r--r--data/etc/car/com.google.android.car.networking.preferenceupdater.xml27
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--data/keyboards/keyboards.mk12
-rw-r--r--drm/java/android/drm/DrmOutputStream.java3
-rw-r--r--graphics/java/android/graphics/OWNERS2
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java23
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java5
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java4
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java111
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java25
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java4
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java31
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java26
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java48
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java81
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java13
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java27
-rw-r--r--keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java2
-rw-r--r--media/java/android/media/MediaCodecInfo.java33
-rw-r--r--mime/OWNERS1
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml1
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml5
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml8
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-tl/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml2
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java183
-rw-r--r--services/core/java/com/android/server/VcnManagementService.java73
-rw-r--r--services/core/java/com/android/server/connectivity/NetworkNotificationManager.java8
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java28
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java25
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java12
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java160
-rw-r--r--services/core/java/com/android/server/policy/OWNERS1
-rw-r--r--services/core/java/com/android/server/recoverysystem/RecoverySystemService.java218
-rw-r--r--services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java21
-rw-r--r--services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java314
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java164
-rw-r--r--telephony/common/com/android/internal/telephony/TelephonyPermissions.java19
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java7
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthLte.java7
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthNr.java9
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java7
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java17
-rw-r--r--telephony/java/android/telephony/ims/ProvisioningManager.java279
-rw-r--r--telephony/java/android/telephony/ims/RcsClientConfiguration.aidl19
-rw-r--r--telephony/java/android/telephony/ims/RcsClientConfiguration.java162
-rw-r--r--telephony/java/android/telephony/ims/RcsConfig.aidl19
-rw-r--r--telephony/java/android/telephony/ims/RcsConfig.java306
-rw-r--r--telephony/java/android/telephony/ims/SipDelegateManager.java35
-rw-r--r--telephony/java/android/telephony/ims/aidl/IImsConfig.aidl8
-rw-r--r--telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl2
-rw-r--r--telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl5
-rw-r--r--telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl29
-rw-r--r--telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java123
-rw-r--r--telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java23
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl49
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java2
-rw-r--r--test-mock/src/android/test/mock/OWNERS2
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java346
-rw-r--r--tests/net/java/com/android/server/connectivity/VpnTest.java6
-rw-r--r--tests/vcn/java/com/android/server/VcnManagementServiceTest.java135
-rw-r--r--tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java333
114 files changed, 4466 insertions, 516 deletions
diff --git a/apex/jobscheduler/OWNERS b/apex/jobscheduler/OWNERS
index d004eed2a0db..c77ea336990d 100644
--- a/apex/jobscheduler/OWNERS
+++ b/apex/jobscheduler/OWNERS
@@ -1,6 +1,7 @@
-yamasani@google.com
-omakoto@google.com
ctate@android.com
ctate@google.com
+dplotnikov@google.com
kwekua@google.com
-suprabh@google.com \ No newline at end of file
+omakoto@google.com
+suprabh@google.com
+yamasani@google.com
diff --git a/config/preloaded-classes b/config/preloaded-classes
index d56fc7720c89..29f6055ec6a5 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -11759,7 +11759,6 @@ libcore.util.XmlObjectFactory
libcore.util.ZoneInfo$CheckedArithmeticException
libcore.util.ZoneInfo$WallTime
libcore.util.ZoneInfo
-org.apache.harmony.dalvik.NativeTestTarget
org.apache.harmony.dalvik.ddmc.Chunk
org.apache.harmony.dalvik.ddmc.ChunkHandler
org.apache.harmony.dalvik.ddmc.DdmServer
diff --git a/core/api/current.txt b/core/api/current.txt
index 87fc6361983a..3f173bd38fb1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12142,6 +12142,7 @@ package android.content.pm {
field public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
field public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
field public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC = "android.hardware.nfc.uicc";
+ field public static final String FEATURE_OPENGLES_DEQP_LEVEL = "android.software.opengles.deqp.level";
field public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
field public static final String FEATURE_PC = "android.hardware.type.pc";
field public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
@@ -40253,6 +40254,7 @@ package android.telephony {
field public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = "treat_downgraded_video_calls_as_video_calls_bool";
field public static final String KEY_TTY_SUPPORTED_BOOL = "tty_supported_bool";
field public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array";
+ field public static final String KEY_USE_ACS_FOR_RCS_BOOL = "use_acs_for_rcs_bool";
field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool";
field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool";
field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
@@ -40506,7 +40508,8 @@ package android.telephony {
public final class CellSignalStrengthLte extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
method public int getAsuLevel();
- method public int getCqi();
+ method @IntRange(from=0, to=15) public int getCqi();
+ method @IntRange(from=1, to=6) public int getCqiTableIndex();
method public int getDbm();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
method public int getRsrp();
@@ -40521,6 +40524,8 @@ package android.telephony {
public final class CellSignalStrengthNr extends android.telephony.CellSignalStrength implements android.os.Parcelable {
method public int describeContents();
method public int getAsuLevel();
+ method @IntRange(from=0, to=15) @NonNull public java.util.List<java.lang.Integer> getCsiCqiReport();
+ method @IntRange(from=1, to=3) public int getCsiCqiTableIndex();
method public int getCsiRsrp();
method public int getCsiRsrq();
method public int getCsiSinr();
@@ -41757,7 +41762,7 @@ package android.telephony {
method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
method public boolean isConcurrentVoiceAndDataSupported();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
- method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled();
+ method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
method public boolean isEmergencyNumber(@NonNull String);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3b3170f6f278..cbd65d174fb0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -237,7 +237,9 @@ package android {
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
+ field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_SET_DEVICE_MOBILITY_STATE = "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE";
+ field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
@@ -1874,6 +1876,9 @@ package android.content.pm {
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
method public boolean isEncryptionAware();
method public boolean isInstantApp();
+ method public boolean isOem();
+ method public boolean isProduct();
+ method public boolean isVendor();
field public String credentialProtectedDataDir;
field public int targetSandboxVersion;
}
@@ -7343,12 +7348,14 @@ package android.os {
public class RecoverySystem {
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void clearPrepareForUnattendedUpdate(@NonNull android.content.Context) throws java.io.IOException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void clearPrepareForUnattendedUpdate(@NonNull android.content.Context) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void prepareForUnattendedUpdate(@NonNull android.content.Context, @NonNull String, @Nullable android.content.IntentSender) throws java.io.IOException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static boolean isPreparedForUnattendedUpdate(@NonNull android.content.Context) throws java.io.IOException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void prepareForUnattendedUpdate(@NonNull android.content.Context, @NonNull String, @Nullable android.content.IntentSender) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void rebootAndApply(@NonNull android.content.Context, @NonNull String, @NonNull String) throws java.io.IOException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootAndApply(@NonNull android.content.Context, @NonNull String, @NonNull String) throws java.io.IOException;
+ method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootAndApply(@NonNull android.content.Context, @NonNull String, boolean) throws java.io.IOException;
method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
@@ -11441,18 +11448,29 @@ package android.telephony.ims {
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 @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) 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(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerRcsProvisioningChangedCallback(@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.MODIFY_PHONE_STATE) 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 @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void triggerRcsReconfiguration();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterRcsProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.RcsProvisioningCallback);
+ field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE = "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE";
+ field public static final String EXTRA_STATUS = "android.telephony.ims.extra.STATUS";
+ field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.ims.extra.SUBSCRIPTION_ID";
field public static final int KEY_VOICE_OVER_WIFI_ENTITLEMENT_ID = 67; // 0x43
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
+ field public static final int STATUS_CAPABLE = 0; // 0x0
+ field public static final int STATUS_CARRIER_NOT_CAPABLE = 2; // 0x2
+ field public static final int STATUS_DEVICE_NOT_CAPABLE = 1; // 0x1
field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
field public static final String STRING_QUERY_RESULT_ERROR_NOT_READY = "STRING_QUERY_RESULT_ERROR_NOT_READY";
}
@@ -11463,6 +11481,27 @@ package android.telephony.ims {
method public void onProvisioningStringChanged(int, @NonNull String);
}
+ public static class ProvisioningManager.RcsProvisioningCallback {
+ ctor public ProvisioningManager.RcsProvisioningCallback();
+ method public void onAutoConfigurationErrorReceived(int, @NonNull String);
+ method public void onConfigurationChanged(@NonNull byte[]);
+ method public void onConfigurationReset();
+ method public void onRemoved();
+ }
+
+ public final class RcsClientConfiguration implements android.os.Parcelable {
+ ctor public RcsClientConfiguration(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
+ method public int describeContents();
+ method @NonNull public String getClientVendor();
+ method @NonNull public String getClientVersion();
+ method @NonNull public String getRcsProfile();
+ method @NonNull public String getRcsVersion();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsClientConfiguration> CREATOR;
+ field public static final String RCS_PROFILE_1_0 = "UP_1.0";
+ field public static final String RCS_PROFILE_2_3 = "UP_2.3";
+ }
+
public class RcsUceAdapter {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setUceSettingEnabled(boolean) throws android.telephony.ims.ImsException;
}
@@ -11555,6 +11594,7 @@ package android.telephony.ims {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void createSipDelegate(@NonNull android.telephony.ims.DelegateRequest, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.stub.DelegateConnectionStateCallback, @NonNull android.telephony.ims.stub.DelegateConnectionMessageCallback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void destroySipDelegate(@NonNull android.telephony.ims.SipDelegateConnection, int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSupported() throws android.telephony.ims.ImsException;
+ method public void triggerFullNetworkRegistration(@NonNull android.telephony.ims.SipDelegateConnection, @IntRange(from=100, to=699) int, @Nullable String);
field public static final int DENIED_REASON_INVALID = 4; // 0x4
field public static final int DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE = 1; // 0x1
field public static final int DENIED_REASON_NOT_ALLOWED = 2; // 0x2
@@ -11750,11 +11790,15 @@ package android.telephony.ims.stub {
ctor public ImsConfigImplBase();
method public int getConfigInt(int);
method public String getConfigString(int);
+ method public final void notifyAutoConfigurationErrorReceived(int, @NonNull String);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
+ method public void notifyRcsAutoConfigurationRemoved();
method public int setConfig(int, int);
method public int setConfig(int, String);
+ method public void setRcsClientConfiguration(@NonNull android.telephony.ims.RcsClientConfiguration);
+ method public void triggerAutoConfiguration();
field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
field public static final int CONFIG_RESULT_SUCCESS = 0; // 0x0
field public static final int CONFIG_RESULT_UNKNOWN = -1; // 0xffffffff
@@ -11799,6 +11843,9 @@ package android.telephony.ims.stub {
method public final void onRegistering(int);
method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]);
method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo);
+ 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_IWLAN = 1; // 0x1
field public static final int REGISTRATION_TECH_LTE = 0; // 0x0
field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 81d9b11bc644..e32068fe4b39 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -2101,7 +2101,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
/** @hide */
- @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ @SystemApi
public boolean isOem() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
}
@@ -2149,13 +2149,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
/** @hide */
- @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ @SystemApi
public boolean isVendor() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0;
}
/** @hide */
- @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+ @SystemApi
public boolean isProduct() {
return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0;
}
diff --git a/core/java/android/content/pm/LimitedLengthInputStream.java b/core/java/android/content/pm/LimitedLengthInputStream.java
index 19b681e4373a..05089f6e0165 100644
--- a/core/java/android/content/pm/LimitedLengthInputStream.java
+++ b/core/java/android/content/pm/LimitedLengthInputStream.java
@@ -1,6 +1,6 @@
package android.content.pm;
-import libcore.util.ArrayUtils;
+import com.android.internal.util.ArrayUtils;
import java.io.FilterInputStream;
import java.io.IOException;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index ae1067d8d82d..00f5fb95768f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2367,6 +2367,23 @@ public abstract class PackageManager {
/**
* Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature(String, int)}: If this feature is supported, the feature version
+ * specifies a date such that the device is known to pass the OpenGLES dEQP test suite
+ * associated with that date. The date is encoded as follows:
+ * <ul>
+ * <li>Year in bits 31-16</li>
+ * <li>Month in bits 15-8</li>
+ * <li>Day in bits 7-0</li>
+ * </ul>
+ * <p>
+ * Example: 2021-03-01 is encoded as 0x07E50301, and would indicate that the device passes the
+ * OpenGL ES dEQP test suite version that was current on 2021-03-01.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_OPENGLES_DEQP_LEVEL = "android.software.opengles.deqp.level";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device includes broadcast radio tuner.
* @hide
*/
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c16006a6a619..477ffeff7e75 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -548,7 +548,24 @@ public class ResourcesImpl {
remainder = languageTag.substring(separator);
}
- return Locale.adjustLanguageCode(language) + remainder;
+ // No need to convert to lower cases because the language in the return value of
+ // Locale.toLanguageTag has been lower-cased.
+ final String adjustedLanguage;
+ switch(language) {
+ case "id":
+ adjustedLanguage = "in";
+ break;
+ case "yi":
+ adjustedLanguage = "ji";
+ break;
+ case "he":
+ adjustedLanguage = "iw";
+ break;
+ default:
+ adjustedLanguage = language;
+ break;
+ }
+ return adjustedLanguage + remainder;
}
/**
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 1e968721e637..540ea5c159cc 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3152,9 +3152,9 @@ public class ConnectivityManager {
}
/**
- * Set sign in error notification to visible or in visible
+ * Set sign in error notification to visible or invisible
*
- * {@hide}
+ * @hide
* @deprecated Doesn't properly deal with multiple connected networks of the same type.
*/
@Deprecated
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
index ab5637cbb878..7b84575a8955 100644
--- a/core/java/android/os/FileBridge.java
+++ b/core/java/android/os/FileBridge.java
@@ -22,11 +22,12 @@ import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
import libcore.io.IoBridge;
import libcore.io.IoUtils;
import libcore.io.Memory;
import libcore.io.Streams;
-import libcore.util.ArrayUtils;
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index 2561e1ea69c6..5f8b93283269 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -27,7 +27,8 @@ interface IRecoverySystem {
boolean setupBcb(in String command);
boolean clearBcb();
void rebootRecoveryWithCommand(in String command);
- boolean requestLskf(in String updateToken, in IntentSender sender);
- boolean clearLskf();
- boolean rebootWithLskf(in String updateToken, in String reason);
+ boolean requestLskf(in String packageName, in IntentSender sender);
+ boolean clearLskf(in String packageName);
+ boolean isLskfCaptured(in String packageName);
+ boolean rebootWithLskf(in String packageName, in String reason, in boolean slotSwitch);
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index cf90174924f1..6acdcc4722d2 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -33,11 +33,12 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.util.ArrayUtils;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
import dalvik.system.VMRuntime;
-import libcore.util.ArrayUtils;
import libcore.util.SneakyThrow;
import java.io.ByteArrayInputStream;
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 38e170402ae9..189e62baba00 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -631,31 +631,35 @@ public class RecoverySystem {
/**
* Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge
* Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup
- * and ready to apply the OTA.
- * <p>
- * When the system is already prepared for update and this API is called again with the same
- * {@code updateToken}, it will not call the intent sender nor request the user enter their Lock
- * Screen Knowledge Factor.
- * <p>
- * When this API is called again with a different {@code updateToken}, the prepared-for-update
- * status is reset and process repeats as though it's the initial call to this method as
- * described in the first paragraph.
+ * and ready to apply the OTA. This API is expected to handle requests from multiple clients
+ * simultaneously, e.g. from ota and mainline.
+ *
+ * <p> The behavior of multi-client Resume on Reboot works as follows
+ * <li> Each client should call this function to prepare for Resume on Reboot before calling
+ * {@link #rebootAndApply(Context, String, boolean)} </li>
+ * <li> One client cannot clear the Resume on Reboot preparation of another client. </li>
+ * <li> If multiple clients have prepared for Resume on Reboot, the subsequent reboot will be
+ * first come, first served. </li>
*
* @param context the Context to use.
- * @param updateToken token used to indicate which update was prepared
+ * @param updateToken this parameter is deprecated and won't be used. Callers can supply with
+ * an empty string. See details in
+ * <a href="http://go/multi-client-ror">http://go/multi-client-ror</a>
+ * TODO(xunchang) update the link of document with the public doc.
* @param intentSender the intent to call when the update is prepared; may be {@code null}
* @throws IOException if there were any errors setting up unattended update
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.RECOVERY)
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
public static void prepareForUnattendedUpdate(@NonNull Context context,
@NonNull String updateToken, @Nullable IntentSender intentSender) throws IOException {
if (updateToken == null) {
throw new NullPointerException("updateToken == null");
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
- if (!rs.requestLskf(updateToken, intentSender)) {
+ if (!rs.requestLskf(context.getPackageName(), intentSender)) {
throw new IOException("preparation for update failed");
}
}
@@ -664,27 +668,32 @@ public class RecoverySystem {
* Request that any previously requested Lock Screen Knowledge Factor (LSKF) is cleared and
* the preparation for unattended update is reset.
*
+ * <p> Note that the API won't clear the underlying Resume on Reboot preparation state if
+ * another client has requested. So the reboot call from the other client can still succeed.
+ *
* @param context the Context to use.
* @throws IOException if there were any errors clearing the unattended update state
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.RECOVERY)
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
public static void clearPrepareForUnattendedUpdate(@NonNull Context context)
throws IOException {
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
- if (!rs.clearLskf()) {
+ if (!rs.clearLskf(context.getPackageName())) {
throw new IOException("could not reset unattended update state");
}
}
/**
- * Request that the device reboot and apply the update that has been prepared. The
- * {@code updateToken} must match what was given for {@link #prepareForUnattendedUpdate} or
- * this will return {@code false}.
+ * Request that the device reboot and apply the update that has been prepared. Callers are
+ * recommended to use {@link #rebootAndApply(Context, String, boolean)} instead.
*
* @param context the Context to use.
- * @param updateToken the token used to call {@link #prepareForUnattendedUpdate} before
+ * @param updateToken this parameter is deprecated and won't be used. See details in
+ * <a href="http://go/multi-client-ror">http://go/multi-client-ror</a>
+ * TODO(xunchang) update the link of document with the public doc.
* @param reason the reboot reason to give to the {@link PowerManager}
* @throws IOException if the reboot couldn't proceed because the device wasn't ready for an
* unattended reboot or if the {@code updateToken} did not match the previously
@@ -692,14 +701,55 @@ public class RecoverySystem {
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.RECOVERY)
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
public static void rebootAndApply(@NonNull Context context, @NonNull String updateToken,
@NonNull String reason) throws IOException {
if (updateToken == null) {
throw new NullPointerException("updateToken == null");
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
- if (!rs.rebootWithLskf(updateToken, reason)) {
+ // OTA is the sole user before S, and a slot switch is required for ota update.
+ if (!rs.rebootWithLskf(context.getPackageName(), reason, true)) {
+ throw new IOException("system not prepared to apply update");
+ }
+ }
+
+ /**
+ * Query if Resume on Reboot has been prepared for a given caller.
+ *
+ * @param context the Context to use.
+ * @throws IOException if there were any errors connecting to the service or querying the state.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
+ public static boolean isPreparedForUnattendedUpdate(@NonNull Context context)
+ throws IOException {
+ RecoverySystem rs = context.getSystemService(RecoverySystem.class);
+ return rs.isLskfCaptured(context.getPackageName());
+ }
+
+ /**
+ * Request that the device reboot and apply the update that has been prepared.
+ * {@link #prepareForUnattendedUpdate} must be called before for the given client,
+ * otherwise the function call will fail.
+ *
+ * @param context the Context to use.
+ * @param reason the reboot reason to give to the {@link PowerManager}
+ * @param slotSwitch true if the caller intends to switch the slot on an A/B device.
+ * @throws IOException if the reboot couldn't proceed because the device wasn't ready for an
+ * unattended reboot.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.RECOVERY,
+ android.Manifest.permission.REBOOT})
+ public static void rebootAndApply(@NonNull Context context,
+ @NonNull String reason, boolean slotSwitch) throws IOException {
+ RecoverySystem rs = context.getSystemService(RecoverySystem.class);
+ if (!rs.rebootWithLskf(context.getPackageName(), reason, slotSwitch)) {
throw new IOException("system not prepared to apply update");
}
}
@@ -1283,16 +1333,15 @@ public class RecoverySystem {
/**
* Begins the process of asking the user for the Lock Screen Knowledge Factor.
*
- * @param updateToken token that will be used in calls to {@link #rebootAndApply} to ensure
- * that the preparation was for the correct update
+ * @param packageName the package name of the caller who requests Resume on Reboot
* @return true if the request was correct
* @throws IOException if the recovery system service could not be contacted
*/
- private boolean requestLskf(String updateToken, IntentSender sender) throws IOException {
+ private boolean requestLskf(String packageName, IntentSender sender) throws IOException {
try {
- return mService.requestLskf(updateToken, sender);
+ return mService.requestLskf(packageName, sender);
} catch (RemoteException e) {
- throw new IOException("could request update");
+ throw new IOException("could request LSKF capture");
}
}
@@ -1302,22 +1351,37 @@ public class RecoverySystem {
* @return true if the setup for OTA was cleared
* @throws IOException if the recovery system service could not be contacted
*/
- private boolean clearLskf() throws IOException {
+ private boolean clearLskf(String packageName) throws IOException {
try {
- return mService.clearLskf();
+ return mService.clearLskf(packageName);
} catch (RemoteException e) {
throw new IOException("could not clear LSKF");
}
}
/**
+ * Queries if the Resume on Reboot has been prepared for a given caller.
+ *
+ * @param packageName the identifier of the caller who requests Resume on Reboot
+ * @return true if Resume on Reboot is prepared.
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean isLskfCaptured(String packageName) throws IOException {
+ try {
+ return mService.isLskfCaptured(packageName);
+ } catch (RemoteException e) {
+ throw new IOException("could not get LSKF capture state");
+ }
+ }
+
+ /**
* Calls the recovery system service to reboot and apply update.
*
- * @param updateToken the update token for which the update was prepared
*/
- private boolean rebootWithLskf(String updateToken, String reason) throws IOException {
+ private boolean rebootWithLskf(String packageName, String reason, boolean slotSwitch)
+ throws IOException {
try {
- return mService.rebootWithLskf(updateToken, reason);
+ return mService.rebootWithLskf(packageName, reason, slotSwitch);
} catch (RemoteException e) {
throw new IOException("could not reboot for update");
}
diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS
new file mode 100644
index 000000000000..3795493b861f
--- /dev/null
+++ b/core/java/android/os/incremental/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 554432
+alexbuy@google.com
+schfan@google.com
+toddke@google.com
+zyy@google.com
diff --git a/core/java/android/provider/OWNERS b/core/java/android/provider/OWNERS
index 792ff20f6d28..b02102b5e9a7 100644
--- a/core/java/android/provider/OWNERS
+++ b/core/java/android/provider/OWNERS
@@ -1,9 +1,17 @@
-per-file DeviceConfig.java = svetoslavganov@google.com
-per-file DeviceConfig.java = hackbod@google.com
-per-file DeviceConfig.java = schfan@google.com
-
-per-file CallLog.java = file:/telephony/OWNERS
-per-file DocumentsContract.java = file:/core/java/android/os/storage/OWNERS
-per-file DocumentsProvider.java = file:/core/java/android/os/storage/OWNERS
-per-file MediaStore.java = file:/core/java/android/os/storage/OWNERS
-per-file Telephony.java = file:/telephony/OWNERS
+per-file *BlockedNumber* = file:/telephony/OWNERS
+per-file *Telephony* = file:/telephony/OWNERS
+
+per-file *CallLog* = file:platform/packages/providers/ContactsProvider:/OWNERS
+per-file *Contacts* = file:platform/packages/providers/ContactsProvider:/OWNERS
+per-file *Voicemail* = file:platform/packages/providers/ContactsProvider:/OWNERS
+
+per-file *Calendar* = file:platform/packages/providers/CalendarProvider:/OWNERS
+
+per-file *Downloads* = file:platform/packages/providers/DownloadProvider:/OWNERS
+
+per-file *DeviceConfig* = file:/packages/SettingsProvider/OWNERS
+
+per-file *Documents* = file:/core/java/android/os/storage/OWNERS
+per-file *Documents* = file:platform/packages/apps/DocumentsUI:/OWNERS
+
+per-file *Slices* = file:/core/java/android/app/slice/OWNERS
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 6054de8daf42..727769cb5ab8 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -5167,6 +5167,14 @@ public final class Telephony {
public static final String COLUMN_IMS_RCS_UCE_ENABLED = "ims_rcs_uce_enabled";
/**
+ * TelephonyProvider column name for determining if the user has enabled cross SIM calling
+ * for this subscription.
+ *
+ * @hide
+ */
+ public static final String COLUMN_CROSS_SIM_CALLING_ENABLED = "cross_sim_calling_enabled";
+
+ /**
* TelephonyProvider column name for whether a subscription is opportunistic, that is,
* whether the network it connects to is limited in functionality or coverage.
* For example, CBRS.
@@ -5270,5 +5278,13 @@ public final class Telephony {
* @hide
*/
public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types";
+
+ /**
+ * TelephonyProvider column name for RCS configuration.
+ * <p>TYPE: BLOB
+ *
+ * @hide
+ */
+ public static final String COLUMN_RCS_CONFIG = "rcs_config";
}
}
diff --git a/core/java/android/security/OWNERS b/core/java/android/security/OWNERS
index 3f8d75e6118d..7140ff12be64 100644
--- a/core/java/android/security/OWNERS
+++ b/core/java/android/security/OWNERS
@@ -7,3 +7,5 @@ per-file NetworkSecurityPolicy.java = cbrubaker@google.com
per-file NetworkSecurityPolicy.java = klyubin@google.com
per-file FrameworkNetworkSecurityPolicy.java = cbrubaker@google.com
per-file FrameworkNetworkSecurityPolicy.java = klyubin@google.com
+per-file Confirmation*.java = jdanis@google.com
+per-file Confirmation*.java = swillden@google.com
diff --git a/core/java/android/service/attestation/OWNERS b/core/java/android/service/attestation/OWNERS
new file mode 100644
index 000000000000..b9e7b996e5d8
--- /dev/null
+++ b/core/java/android/service/attestation/OWNERS
@@ -0,0 +1,2 @@
+chaviw@google.com
+ogunwale@google.com
diff --git a/core/java/android/service/storage/OWNERS b/core/java/android/service/storage/OWNERS
new file mode 100644
index 000000000000..6f9dbea36b06
--- /dev/null
+++ b/core/java/android/service/storage/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/os/storage/OWNERS
diff --git a/core/java/android/uwb/RangingManager.java b/core/java/android/uwb/RangingManager.java
new file mode 100644
index 000000000000..a9bf4abe566a
--- /dev/null
+++ b/core/java/android/uwb/RangingManager.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2020 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.uwb;
+
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Hashtable;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class RangingManager extends android.uwb.IUwbRangingCallbacks.Stub {
+ private static final String TAG = "Uwb.RangingManager";
+
+ private final IUwbAdapter mAdapter;
+ private final Hashtable<SessionHandle, RangingSession> mRangingSessionTable = new Hashtable<>();
+
+ public RangingManager(IUwbAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Open a new ranging session
+ *
+ * @param params the parameters that define the ranging session
+ * @param executor {@link Executor} to run callbacks
+ * @param callbacks {@link RangingSession.Callback} to associate with the {@link RangingSession}
+ * that is being opened.
+ * @return a new {@link RangingSession}
+ */
+ public RangingSession openSession(@NonNull PersistableBundle params, @NonNull Executor executor,
+ @NonNull RangingSession.Callback callbacks) {
+ SessionHandle sessionHandle;
+ try {
+ sessionHandle = mAdapter.startRanging(this, params);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ synchronized (this) {
+ if (hasSession(sessionHandle)) {
+ Log.w(TAG, "Newly created session unexpectedly reuses an active SessionHandle");
+ executor.execute(() -> callbacks.onClosed(
+ RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR,
+ new PersistableBundle()));
+ }
+
+ RangingSession session =
+ new RangingSession(executor, callbacks, mAdapter, sessionHandle);
+ mRangingSessionTable.put(sessionHandle, session);
+ return session;
+ }
+ }
+
+ private boolean hasSession(SessionHandle sessionHandle) {
+ return mRangingSessionTable.containsKey(sessionHandle);
+ }
+
+ @Override
+ public void onRangingStarted(SessionHandle sessionHandle, PersistableBundle parameters) {
+ synchronized (this) {
+ if (!hasSession(sessionHandle)) {
+ Log.w(TAG,
+ "onRangingStarted - received unexpected SessionHandle: " + sessionHandle);
+ return;
+ }
+
+ RangingSession session = mRangingSessionTable.get(sessionHandle);
+ session.onRangingStarted(parameters);
+ }
+ }
+
+ @Override
+ public void onRangingStartFailed(SessionHandle sessionHandle, int reason,
+ PersistableBundle params) {
+ synchronized (this) {
+ if (!hasSession(sessionHandle)) {
+ Log.w(TAG, "onRangingStartFailed - received unexpected SessionHandle: "
+ + sessionHandle);
+ return;
+ }
+
+ RangingSession session = mRangingSessionTable.get(sessionHandle);
+ session.onRangingClosed(convertStartFailureToCloseReason(reason), params);
+ mRangingSessionTable.remove(sessionHandle);
+ }
+ }
+
+ @Override
+ public void onRangingClosed(SessionHandle sessionHandle, int reason, PersistableBundle params) {
+ synchronized (this) {
+ if (!hasSession(sessionHandle)) {
+ Log.w(TAG, "onRangingClosed - received unexpected SessionHandle: " + sessionHandle);
+ return;
+ }
+
+ RangingSession session = mRangingSessionTable.get(sessionHandle);
+ session.onRangingClosed(convertToCloseReason(reason), params);
+ mRangingSessionTable.remove(sessionHandle);
+ }
+ }
+
+ @Override
+ public void onRangingResult(SessionHandle sessionHandle, RangingReport result) {
+ synchronized (this) {
+ if (!hasSession(sessionHandle)) {
+ Log.w(TAG, "onRangingResult - received unexpected SessionHandle: " + sessionHandle);
+ return;
+ }
+
+ RangingSession session = mRangingSessionTable.get(sessionHandle);
+ session.onRangingResult(result);
+ }
+ }
+
+ @RangingSession.Callback.CloseReason
+ private static int convertToCloseReason(@CloseReason int reason) {
+ switch (reason) {
+ case CloseReason.LOCAL_API:
+ return RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API;
+
+ case CloseReason.MAX_SESSIONS_REACHED:
+ return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED;
+
+ case CloseReason.SYSTEM_POLICY:
+ return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY;
+
+ case CloseReason.REMOTE_REQUEST:
+ return RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST;
+
+ case CloseReason.PROTOCOL_SPECIFIC:
+ return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC;
+
+ case CloseReason.UNKNOWN:
+ default:
+ return RangingSession.Callback.CLOSE_REASON_UNKNOWN;
+ }
+ }
+
+ @RangingSession.Callback.CloseReason
+ private static int convertStartFailureToCloseReason(@StartFailureReason int reason) {
+ switch (reason) {
+ case StartFailureReason.BAD_PARAMETERS:
+ return RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS;
+
+ case StartFailureReason.MAX_SESSIONS_REACHED:
+ return RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED;
+
+ case StartFailureReason.SYSTEM_POLICY:
+ return RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY;
+
+ case StartFailureReason.PROTOCOL_SPECIFIC:
+ return RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC;
+
+ case StartFailureReason.UNKNOWN:
+ default:
+ return RangingSession.Callback.CLOSE_REASON_UNKNOWN;
+ }
+ }
+}
diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java
index e1af9b1d7ce3..b0dbd85c0812 100644
--- a/core/java/android/uwb/RangingSession.java
+++ b/core/java/android/uwb/RangingSession.java
@@ -18,7 +18,10 @@ package android.uwb;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.os.Binder;
import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -34,12 +37,26 @@ import java.util.concurrent.Executor;
* {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} to request to open a
* session. Once the session is opened, a {@link RangingSession} object is provided through
* {@link RangingSession.Callback#onOpenSuccess(RangingSession, PersistableBundle)}. If opening a
- * session fails, the failure is reported through {@link RangingSession.Callback#onClosed(int)} with
- * the failure reason.
+ * session fails, the failure is reported through
+ * {@link RangingSession.Callback#onClosed(int, PersistableBundle)} with the failure reason.
*
* @hide
*/
public final class RangingSession implements AutoCloseable {
+ private static final String TAG = "Uwb.RangingSession";
+ private final SessionHandle mSessionHandle;
+ private final IUwbAdapter mAdapter;
+ private final Executor mExecutor;
+ private final Callback mCallback;
+
+ private enum State {
+ INIT,
+ OPEN,
+ CLOSED,
+ }
+
+ private State mState;
+
/**
* Interface for receiving {@link RangingSession} events
*/
@@ -116,13 +133,20 @@ public final class RangingSession implements AutoCloseable {
int CLOSE_REASON_REMOTE_REQUEST = 7;
/**
+ * Indicates that the session was closed for a protocol specific reason. The associated
+ * {@link PersistableBundle} should be consulted for additional information.
+ */
+ int CLOSE_REASON_PROTOCOL_SPECIFIC = 8;
+
+ /**
* Invoked when session is either closed spontaneously, or per user request via
* {@link RangingSession#close()} or {@link AutoCloseable#close()}, or when session failed
* to open.
*
* @param reason reason for the session closure
+ * @param parameters protocol specific parameters related to the close reason
*/
- void onClosed(@CloseReason int reason);
+ void onClosed(@CloseReason int reason, @NonNull PersistableBundle parameters);
/**
* Called once per ranging interval even when a ranging measurement fails
@@ -133,20 +157,97 @@ public final class RangingSession implements AutoCloseable {
}
/**
+ * @hide
+ */
+ public RangingSession(Executor executor, Callback callback, IUwbAdapter adapter,
+ SessionHandle sessionHandle) {
+ mState = State.INIT;
+ mExecutor = executor;
+ mCallback = callback;
+ mAdapter = adapter;
+ mSessionHandle = sessionHandle;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isOpen() {
+ return mState == State.OPEN;
+ }
+
+ /**
* Close the ranging session
* <p>If this session is currently open, it will close and stop the session.
* <p>If the session is in the process of being opened, it will attempt to stop the session from
* being opened.
- * <p>If the session is already closed, the registered {@link Callback#onClosed(int)} callback
- * will still be invoked.
+ * <p>If the session is already closed, the registered
+ * {@link Callback#onClosed(int, PersistableBundle)} callback will still be invoked.
*
- * <p>{@link Callback#onClosed(int)} will be invoked using the same callback
+ * <p>{@link Callback#onClosed(int, PersistableBundle)} will be invoked using the same callback
* object given to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)}
* when the {@link RangingSession} was opened. The callback will be invoked after each call to
* {@link #close()}, even if the {@link RangingSession} is already closed.
*/
@Override
public void close() {
- throw new UnsupportedOperationException();
+ if (mState == State.CLOSED) {
+ mExecutor.execute(() -> mCallback.onClosed(
+ Callback.CLOSE_REASON_LOCAL_CLOSE_API, new PersistableBundle()));
+ return;
+ }
+
+ try {
+ mAdapter.closeRanging(mSessionHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void onRangingStarted(@NonNull PersistableBundle parameters) {
+ if (mState == State.CLOSED) {
+ Log.w(TAG, "onRangingStarted invoked for a closed session");
+ return;
+ }
+
+ mState = State.OPEN;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onOpenSuccess(this, parameters));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void onRangingClosed(@Callback.CloseReason int reason, PersistableBundle parameters) {
+ mState = State.CLOSED;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onClosed(reason, parameters));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void onRangingResult(@NonNull RangingReport report) {
+ if (!isOpen()) {
+ Log.w(TAG, "onRangingResult invoked for non-open session");
+ return;
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onReportReceived(report));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java
index bb2be0242342..f4d801868e18 100644
--- a/core/java/android/uwb/UwbManager.java
+++ b/core/java/android/uwb/UwbManager.java
@@ -49,7 +49,8 @@ public final class UwbManager {
private IUwbAdapter mUwbAdapter;
private static final String SERVICE_NAME = "uwb";
- private AdapterStateListener mAdapterStateListener;
+ private final AdapterStateListener mAdapterStateListener;
+ private final RangingManager mRangingManager;
/**
* Interface for receiving UWB adapter state changes
@@ -119,6 +120,7 @@ public final class UwbManager {
private UwbManager(IUwbAdapter adapter) {
mUwbAdapter = adapter;
mAdapterStateListener = new AdapterStateListener(adapter);
+ mRangingManager = new RangingManager(adapter);
}
/**
@@ -395,6 +397,6 @@ public final class UwbManager {
public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters,
@NonNull @CallbackExecutor Executor executor,
@NonNull RangingSession.Callback callbacks) {
- throw new UnsupportedOperationException();
+ return mRangingManager.openSession(parameters, executor, callbacks);
}
}
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 72fa4c39f1bc..bae6ee85c064 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -47,11 +47,13 @@ per-file View.java = file:/core/java/android/service/autofill/OWNERS
per-file View.java = file:/graphics/java/android/graphics/OWNERS
per-file View.java = file:/services/core/java/com/android/server/input/OWNERS
per-file View.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file View.java = file:/core/java/android/view/inputmethod/OWNERS
per-file ViewRootImpl.java = file:/services/accessibility/OWNERS
per-file ViewRootImpl.java = file:/core/java/android/service/autofill/OWNERS
per-file ViewRootImpl.java = file:/graphics/java/android/graphics/OWNERS
per-file ViewRootImpl.java = file:/services/core/java/com/android/server/input/OWNERS
per-file ViewRootImpl.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ViewRootImpl.java = file:/core/java/android/view/inputmethod/OWNERS
# WindowManager
per-file DisplayCutout.aidl = file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index 9ad15021f651..931ec64df755 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -734,6 +734,25 @@ public class ArrayUtils {
}
/**
+ * Throws {@link ArrayIndexOutOfBoundsException} if the range is out of bounds.
+ * @param len length of the array. Must be non-negative
+ * @param offset start index of the range. Must be non-negative
+ * @param count length of the range. Must be non-negative
+ * @throws ArrayIndexOutOfBoundsException if the range from {@code offset} with length
+ * {@code count} is out of bounds of the array
+ */
+ public static void throwsIfOutOfBounds(int len, int offset, int count) {
+ if (len < 0) {
+ throw new ArrayIndexOutOfBoundsException("Negative length: " + len);
+ }
+
+ if ((offset | count) < 0 || offset > len - count) {
+ throw new ArrayIndexOutOfBoundsException(
+ "length=" + len + "; regionStart=" + offset + "; regionLength=" + count);
+ }
+ }
+
+ /**
* Returns an array with values from {@code val} minus {@code null} values
*
* @param arrayConstructor typically {@code T[]::new} e.g. {@code String[]::new}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 96a57c116b73..a9fe5d58af05 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1769,6 +1769,16 @@
<permission android:name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows system APK to update Wifi coex channels to avoid.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature" />
+
+ <!-- @SystemApi @hide Allows applications to access Wifi coex channels being avoided.
+ <p>Not for use by third-party applications. -->
+ <permission android:name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS"
+ android:protectionLevel="signature|privileged" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 263d638353d6..02cf0b71ff69 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -6,6 +6,7 @@ hackbod@android.com
hackbod@google.com
jsharkey@android.com
jsharkey@google.com
+juliacr@google.com
michaelwr@google.com
nandana@google.com
narayan@google.com
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 57f2d6aea741..d41868e8c478 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -20,12 +20,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="byteShort" msgid="202579285008794431">"B"</string>
+ <string name="byteShort" msgid="202579285008794431">"ባ"</string>
<string name="kilobyteShort" msgid="2214285521564195803">"ኪባ"</string>
- <string name="megabyteShort" msgid="6649361267635823443">"MB"</string>
- <string name="gigabyteShort" msgid="7515809460261287991">"GB"</string>
- <string name="terabyteShort" msgid="1822367128583886496">"TB"</string>
- <string name="petabyteShort" msgid="5651571254228534832">"PB"</string>
+ <string name="megabyteShort" msgid="6649361267635823443">"ሜባ"</string>
+ <string name="gigabyteShort" msgid="7515809460261287991">"ጊባ"</string>
+ <string name="terabyteShort" msgid="1822367128583886496">"ቴባ"</string>
+ <string name="petabyteShort" msgid="5651571254228534832">"ፔባ"</string>
<string name="fileSizeSuffix" msgid="4233671691980131257">"<xliff:g id="NUMBER">%1$s</xliff:g> <xliff:g id="UNIT">%2$s</xliff:g>"</string>
<string name="untitled" msgid="3381766946944136678">"&lt;ርዕስ አልባ&gt;"</string>
<string name="emptyPhoneNumber" msgid="5812172618020360048">"(ምንም ስልክ ቁጥር የለም)"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 7b6dea33ef60..13f1b5613169 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -823,7 +823,7 @@
<string name="keyguard_password_enter_pin_password_code" msgid="7792964196473964340">"Introduce el código PIN para desbloquear."</string>
<string name="keyguard_password_wrong_pin_code" msgid="8583732939138432793">"Código PIN incorrecto"</string>
<string name="keyguard_label_text" msgid="3841953694564168384">"Para desbloquear el teléfono, pulsa la tecla de menú y, a continuación, pulsa 0."</string>
- <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Llamada de emergencia"</string>
+ <string name="emergency_call_dialog_number_for_display" msgid="2978165477085612673">"Número de emergencia"</string>
<string name="lockscreen_carrier_default" msgid="6192313772955399160">"Sin servicio"</string>
<string name="lockscreen_screen_locked" msgid="7364905540516041817">"Pantalla bloqueada"</string>
<string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pulsa la tecla de menú para desbloquear el teléfono o realizar una llamada de emergencia."</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index fe283abb7699..03a1456d00c5 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1878,7 +1878,7 @@
<string name="language_selection_title" msgid="52674936078683285">"افزودن زبان"</string>
<string name="country_selection_title" msgid="5221495687299014379">"اولویت‌های منطقه"</string>
<string name="search_language_hint" msgid="7004225294308793583">"نام زبان را تایپ کنید"</string>
- <string name="language_picker_section_suggested" msgid="6556199184638990447">"پیشنهادشده"</string>
+ <string name="language_picker_section_suggested" msgid="6556199184638990447">"پیشنهادی"</string>
<string name="language_picker_section_all" msgid="1985809075777564284">"همه زبان‌ها"</string>
<string name="region_picker_section_all" msgid="756441309928774155">"همه منطقه‌ها"</string>
<string name="locale_search_menu" msgid="6258090710176422934">"جستجو"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e8d64088667d..29fe1364f1f4 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -52,7 +52,6 @@
<string name="enablePin" msgid="2543771964137091212">"Opération infructueuse. Activez le verrouillage SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="one">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentative avant que votre carte SIM soit verrouillée.</item>
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
<item quantity="other">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentatives avant que votre carte SIM soit verrouillée.</item>
</plurals>
<string name="imei" msgid="2157082351232630390">"Code IIEM"</string>
@@ -180,7 +179,6 @@
<string name="low_memory" product="default" msgid="2539532364144025569">"La mémoire du téléphone est pleine. Veuillez supprimer des fichiers pour libérer de l\'espace."</string>
<plurals name="ssl_ca_cert_warning" formatted="false" msgid="2288194355006173029">
<item quantity="one">Autorité de certification installée</item>
- <item quantity="many">Certificate authorities installed</item>
<item quantity="other">Autorités de certification installées</item>
</plurals>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Par un tiers inconnu"</string>
@@ -253,7 +251,6 @@
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Utilisez cette option pour qu\'il y ait le moins d\'interférences système possible lorsque votre appareil ne répond pas ou qu\'il est trop lent, ou lorsque vous avez besoin de toutes les sections du rapport de bogue. Aucune capture d\'écran supplémentaire ne peut être capturée, et vous ne pouvez entrer aucune autre information."</string>
<plurals name="bugreport_countdown" formatted="false" msgid="3906120379260059206">
<item quantity="one">Saisie d\'écran pour le rapport de bogue dans <xliff:g id="NUMBER_1">%d</xliff:g> seconde.</item>
- <item quantity="many">Taking screenshot for bug report in <xliff:g id="NUMBER_1">%d</xliff:g> seconds.</item>
<item quantity="other">Saisie d\'écran pour le rapport de bogue dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes.</item>
</plurals>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Capture d\'écran prise avec le rapport de bogue"</string>
@@ -997,7 +994,6 @@
<string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Il y a plus d\'un mois"</string>
<plurals name="last_num_days" formatted="false" msgid="687443109145393632">
<item quantity="one">Le dernier <xliff:g id="COUNT_1">%d</xliff:g> jour</item>
- <item quantity="many">Last <xliff:g id="COUNT_1">%d</xliff:g> days</item>
<item quantity="other">Le dernier <xliff:g id="COUNT_1">%d</xliff:g> jours</item>
</plurals>
<string name="last_month" msgid="1528906781083518683">"Le mois dernier"</string>
@@ -1020,82 +1016,66 @@
<string name="now_string_shortest" msgid="3684914126941650330">"maintenant"</string>
<plurals name="duration_minutes_shortest" formatted="false" msgid="7519574894537185135">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> m</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>m</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> m</item>
</plurals>
<plurals name="duration_hours_shortest" formatted="false" msgid="2838655994500499651">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> h</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>h</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> h</item>
</plurals>
<plurals name="duration_days_shortest" formatted="false" msgid="3686058472983158496">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> j</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>d</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> j</item>
</plurals>
<plurals name="duration_years_shortest" formatted="false" msgid="8299112348723640338">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> a</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>y</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> a</item>
</plurals>
<plurals name="duration_minutes_shortest_future" formatted="false" msgid="849196137176399440">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> m</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>m</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> m</item>
</plurals>
<plurals name="duration_hours_shortest_future" formatted="false" msgid="5386373597343170388">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> h</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>h</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> h</item>
</plurals>
<plurals name="duration_days_shortest_future" formatted="false" msgid="814754627092787227">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> j</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>d</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> j</item>
</plurals>
<plurals name="duration_years_shortest_future" formatted="false" msgid="7683731800140202145">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> a</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>y</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> a</item>
</plurals>
<plurals name="duration_minutes_relative" formatted="false" msgid="6569851308583028344">
<item quantity="one">il y a <xliff:g id="COUNT_1">%d</xliff:g> minute</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> minutes ago</item>
<item quantity="other">il y a <xliff:g id="COUNT_1">%d</xliff:g> minutes</item>
</plurals>
<plurals name="duration_hours_relative" formatted="false" msgid="420434788589102019">
<item quantity="one">il y a<xliff:g id="COUNT_1">%d</xliff:g> heure</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> hours ago</item>
<item quantity="other">il y a<xliff:g id="COUNT_1">%d</xliff:g> heures</item>
</plurals>
<plurals name="duration_days_relative" formatted="false" msgid="6056425878237482431">
<item quantity="one">il y a <xliff:g id="COUNT_1">%d</xliff:g> jour</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> days ago</item>
<item quantity="other">il y a <xliff:g id="COUNT_1">%d</xliff:g> jours</item>
</plurals>
<plurals name="duration_years_relative" formatted="false" msgid="2179998228861172159">
<item quantity="one">il y a <xliff:g id="COUNT_1">%d</xliff:g> an</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> years ago</item>
<item quantity="other">il y a <xliff:g id="COUNT_1">%d</xliff:g> ans</item>
</plurals>
<plurals name="duration_minutes_relative_future" formatted="false" msgid="5759885720917567723">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> minute</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> minutes</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> minutes</item>
</plurals>
<plurals name="duration_hours_relative_future" formatted="false" msgid="8963511608507707959">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> heure</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> hours</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> heures</item>
</plurals>
<plurals name="duration_days_relative_future" formatted="false" msgid="1964709470979250702">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> jour</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> days</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> jours</item>
</plurals>
<plurals name="duration_years_relative_future" formatted="false" msgid="3985129025134896371">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> ans</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> years</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> ans</item>
</plurals>
<string name="VideoView_error_title" msgid="5750686717225068016">"Problème vidéo"</string>
@@ -1466,7 +1446,6 @@
<string name="find_on_page" msgid="5400537367077438198">"Rechercher sur la page"</string>
<plurals name="matches_found" formatted="false" msgid="1101758718194295554">
<item quantity="one"><xliff:g id="INDEX">%d</xliff:g> sur <xliff:g id="TOTAL">%d</xliff:g></item>
- <item quantity="many"><xliff:g id="INDEX">%d</xliff:g> of <xliff:g id="TOTAL">%d</xliff:g></item>
<item quantity="other"><xliff:g id="INDEX">%d</xliff:g> sur <xliff:g id="TOTAL">%d</xliff:g></item>
</plurals>
<string name="action_mode_done" msgid="2536182504764803222">"Terminé"</string>
@@ -1603,7 +1582,6 @@
<string name="kg_wrong_pin" msgid="3680925703673166482">"NIP incorrect."</string>
<plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="236717428673283568">
<item quantity="one">Réessayer dans <xliff:g id="NUMBER">%d</xliff:g> seconde.</item>
- <item quantity="many">Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds.</item>
<item quantity="other">Réessayer dans <xliff:g id="NUMBER">%d</xliff:g> secondes.</item>
</plurals>
<string name="kg_pattern_instructions" msgid="8366024510502517748">"Dessinez votre schéma."</string>
@@ -1790,7 +1768,6 @@
<string name="restr_pin_error_too_short" msgid="1547007808237941065">"Le NIP est trop court. Il doit comporter au moins 4 chiffres."</string>
<plurals name="restr_pin_countdown" formatted="false" msgid="4427486903285216153">
<item quantity="one">Réessayer dans <xliff:g id="COUNT">%d</xliff:g> seconde</item>
- <item quantity="many">Try again in <xliff:g id="COUNT">%d</xliff:g> seconds</item>
<item quantity="other">Réessayer dans <xliff:g id="COUNT">%d</xliff:g> secondes</item>
</plurals>
<string name="restr_pin_try_later" msgid="5897719962541636727">"Réessayez plus tard"</string>
@@ -1822,42 +1799,34 @@
<string name="data_saver_enable_button" msgid="4399405762586419726">"Activer"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="2877101784123058273">
<item quantity="one">Pendant %1$d minute (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d minutes (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d minutes (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_minutes_summary_short" formatted="false" msgid="4230730310318858312">
<item quantity="one">Pendant %1$d min (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d min (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d min (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_hours_summary" formatted="false" msgid="7725354244196466758">
<item quantity="one">Pendant %1$d heure (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d hours (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d heures (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_hours_summary_short" formatted="false" msgid="588719069121765642">
<item quantity="one">Pendant %1$d h (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d hr (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d h (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_minutes" formatted="false" msgid="1148568456958944998">
<item quantity="one">Pendant %d minute</item>
- <item quantity="many">For %d minutes</item>
<item quantity="other">Pendant %d minutes</item>
</plurals>
<plurals name="zen_mode_duration_minutes_short" formatted="false" msgid="2742377799995454859">
<item quantity="one">Pendant %d min</item>
- <item quantity="many">For %d min</item>
<item quantity="other">Pendant %d min</item>
</plurals>
<plurals name="zen_mode_duration_hours" formatted="false" msgid="525401855645490022">
<item quantity="one">Pendant %d heure</item>
- <item quantity="many">For %d hours</item>
<item quantity="other">Pendant %d heures</item>
</plurals>
<plurals name="zen_mode_duration_hours_short" formatted="false" msgid="7644653189680911640">
<item quantity="one">Pendant %d h</item>
- <item quantity="many">For %d hr</item>
<item quantity="other">Pendant %d h</item>
</plurals>
<string name="zen_mode_until" msgid="2250286190237669079">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
@@ -1898,7 +1867,6 @@
<string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g> : <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
<plurals name="selected_count" formatted="false" msgid="3946212171128200491">
<item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> élément sélectionné</item>
- <item quantity="many"><xliff:g id="COUNT_1">%1$d</xliff:g> selected</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> éléments sélectionnés</item>
</plurals>
<string name="default_notification_channel_label" msgid="3697928973567217330">"Sans catégorie"</string>
@@ -1966,7 +1934,6 @@
<string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Aucune suggestion de remplissage automatique"</string>
<plurals name="autofill_picker_some_suggestions" formatted="false" msgid="6651883186966959978">
<item quantity="one"><xliff:g id="COUNT">%1$s</xliff:g> suggestion de remplissage automatique</item>
- <item quantity="many"><xliff:g id="COUNT">%1$s</xliff:g> autofill suggestions</item>
<item quantity="other"><xliff:g id="COUNT">%1$s</xliff:g> suggestions de remplissage automatique</item>
</plurals>
<string name="autofill_save_title" msgid="7719802414283739775">"Enregistrer sous "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
@@ -2060,7 +2027,6 @@
<string name="car_loading_profile" msgid="8219978381196748070">"Chargement en cours…"</string>
<plurals name="file_count" formatted="false" msgid="7063513834724389247">
<item quantity="one"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> fichier</item>
- <item quantity="many"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> files</item>
<item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> fichiers</item>
</plurals>
<string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Aucune recommandation de personnes avec lesquelles effectuer un partage"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index fc91c47b700b..a5b1626aa404 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -52,7 +52,6 @@
<string name="enablePin" msgid="2543771964137091212">"Échec de l\'opération. Veuillez activer le verrouillage de la carte SIM/RUIM."</string>
<plurals name="pinpuk_attempts" formatted="false" msgid="1619867269012213584">
<item quantity="one">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentative avant que votre carte SIM ne soit verrouillée.</item>
- <item quantity="many">You have <xliff:g id="NUMBER_1">%d</xliff:g> remaining attempts before SIM is locked.</item>
<item quantity="other">Il vous reste <xliff:g id="NUMBER_1">%d</xliff:g> tentatives avant que votre carte SIM ne soit verrouillée.</item>
</plurals>
<string name="imei" msgid="2157082351232630390">"Code IMEI"</string>
@@ -180,7 +179,6 @@
<string name="low_memory" product="default" msgid="2539532364144025569">"La mémoire du téléphone est pleine. Veuillez supprimer des fichiers pour libérer de l\'espace."</string>
<plurals name="ssl_ca_cert_warning" formatted="false" msgid="2288194355006173029">
<item quantity="one">Autorité de certification installée</item>
- <item quantity="many">Certificate authorities installed</item>
<item quantity="other">Autorités de certification installées</item>
</plurals>
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"Par un tiers inconnu"</string>
@@ -253,7 +251,6 @@
<string name="bugreport_option_full_summary" msgid="1975130009258435885">"Utilisez cette option pour qu\'il y ait le moins d\'interférences système possible lorsque votre appareil ne répond pas ou qu\'il est trop lent, ou lorsque vous avez besoin de toutes les sections du rapport de bug. Aucune capture d\'écran supplémentaire ne peut être prise, et vous ne pouvez saisir aucune autre information."</string>
<plurals name="bugreport_countdown" formatted="false" msgid="3906120379260059206">
<item quantity="one">Capture d\'écran pour le rapport de bug dans <xliff:g id="NUMBER_1">%d</xliff:g> seconde</item>
- <item quantity="many">Taking screenshot for bug report in <xliff:g id="NUMBER_1">%d</xliff:g> seconds.</item>
<item quantity="other">Capture d\'écran pour le rapport de bug dans <xliff:g id="NUMBER_1">%d</xliff:g> secondes</item>
</plurals>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Capture d\'écran avec rapport de bug effectuée"</string>
@@ -997,7 +994,6 @@
<string name="beforeOneMonthDurationPast" msgid="8315149541372065392">"Il y a plus d\'un mois"</string>
<plurals name="last_num_days" formatted="false" msgid="687443109145393632">
<item quantity="one">Le dernier jour (<xliff:g id="COUNT_1">%d</xliff:g>)</item>
- <item quantity="many">Last <xliff:g id="COUNT_1">%d</xliff:g> days</item>
<item quantity="other">Les <xliff:g id="COUNT_1">%d</xliff:g> derniers jours</item>
</plurals>
<string name="last_month" msgid="1528906781083518683">"Le mois dernier"</string>
@@ -1020,82 +1016,66 @@
<string name="now_string_shortest" msgid="3684914126941650330">"mainten."</string>
<plurals name="duration_minutes_shortest" formatted="false" msgid="7519574894537185135">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> m</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>m</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> m</item>
</plurals>
<plurals name="duration_hours_shortest" formatted="false" msgid="2838655994500499651">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> h</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>h</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> h</item>
</plurals>
<plurals name="duration_days_shortest" formatted="false" msgid="3686058472983158496">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> j</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>d</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> j</item>
</plurals>
<plurals name="duration_years_shortest" formatted="false" msgid="8299112348723640338">
<item quantity="one"><xliff:g id="COUNT_1">%d</xliff:g> a</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g>y</item>
<item quantity="other"><xliff:g id="COUNT_1">%d</xliff:g> a</item>
</plurals>
<plurals name="duration_minutes_shortest_future" formatted="false" msgid="849196137176399440">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> m</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>m</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> m</item>
</plurals>
<plurals name="duration_hours_shortest_future" formatted="false" msgid="5386373597343170388">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> h</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>h</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> h</item>
</plurals>
<plurals name="duration_days_shortest_future" formatted="false" msgid="814754627092787227">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> j</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>d</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> j</item>
</plurals>
<plurals name="duration_years_shortest_future" formatted="false" msgid="7683731800140202145">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> a</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g>y</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> a</item>
</plurals>
<plurals name="duration_minutes_relative" formatted="false" msgid="6569851308583028344">
<item quantity="one">il y a <xliff:g id="COUNT_1">%d</xliff:g> minute</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> minutes ago</item>
<item quantity="other">il y a <xliff:g id="COUNT_1">%d</xliff:g> minutes</item>
</plurals>
<plurals name="duration_hours_relative" formatted="false" msgid="420434788589102019">
<item quantity="one">il y a <xliff:g id="COUNT_1">%d</xliff:g> heure</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> hours ago</item>
<item quantity="other">il y a <xliff:g id="COUNT_1">%d</xliff:g> heures</item>
</plurals>
<plurals name="duration_days_relative" formatted="false" msgid="6056425878237482431">
<item quantity="one">il y a <xliff:g id="COUNT_1">%d</xliff:g> jour</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> days ago</item>
<item quantity="other">il y a <xliff:g id="COUNT_1">%d</xliff:g> jours</item>
</plurals>
<plurals name="duration_years_relative" formatted="false" msgid="2179998228861172159">
<item quantity="one">il y a <xliff:g id="COUNT_1">%d</xliff:g> an</item>
- <item quantity="many"><xliff:g id="COUNT_1">%d</xliff:g> years ago</item>
<item quantity="other">il y a <xliff:g id="COUNT_1">%d</xliff:g> ans</item>
</plurals>
<plurals name="duration_minutes_relative_future" formatted="false" msgid="5759885720917567723">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> minute</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> minutes</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> minutes</item>
</plurals>
<plurals name="duration_hours_relative_future" formatted="false" msgid="8963511608507707959">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> heure</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> hours</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> heures</item>
</plurals>
<plurals name="duration_days_relative_future" formatted="false" msgid="1964709470979250702">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> jour</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> days</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> jours</item>
</plurals>
<plurals name="duration_years_relative_future" formatted="false" msgid="3985129025134896371">
<item quantity="one">dans <xliff:g id="COUNT_1">%d</xliff:g> an</item>
- <item quantity="many">in <xliff:g id="COUNT_1">%d</xliff:g> years</item>
<item quantity="other">dans <xliff:g id="COUNT_1">%d</xliff:g> ans</item>
</plurals>
<string name="VideoView_error_title" msgid="5750686717225068016">"Problème vidéo"</string>
@@ -1466,7 +1446,6 @@
<string name="find_on_page" msgid="5400537367077438198">"Rechercher sur la page"</string>
<plurals name="matches_found" formatted="false" msgid="1101758718194295554">
<item quantity="one"><xliff:g id="INDEX">%d</xliff:g> sur <xliff:g id="TOTAL">%d</xliff:g></item>
- <item quantity="many"><xliff:g id="INDEX">%d</xliff:g> of <xliff:g id="TOTAL">%d</xliff:g></item>
<item quantity="other"><xliff:g id="INDEX">%d</xliff:g> sur <xliff:g id="TOTAL">%d</xliff:g></item>
</plurals>
<string name="action_mode_done" msgid="2536182504764803222">"OK"</string>
@@ -1603,7 +1582,6 @@
<string name="kg_wrong_pin" msgid="3680925703673166482">"Code PIN incorrect."</string>
<plurals name="kg_too_many_failed_attempts_countdown" formatted="false" msgid="236717428673283568">
<item quantity="one">Réessayez dans <xliff:g id="NUMBER">%d</xliff:g> seconde.</item>
- <item quantity="many">Try again in <xliff:g id="NUMBER">%d</xliff:g> seconds.</item>
<item quantity="other">Réessayez dans <xliff:g id="NUMBER">%d</xliff:g> secondes.</item>
</plurals>
<string name="kg_pattern_instructions" msgid="8366024510502517748">"Dessinez votre schéma."</string>
@@ -1790,7 +1768,6 @@
<string name="restr_pin_error_too_short" msgid="1547007808237941065">"Le code PIN est trop court. Il doit comporter au moins 4 chiffres."</string>
<plurals name="restr_pin_countdown" formatted="false" msgid="4427486903285216153">
<item quantity="one">Réessayer dans <xliff:g id="COUNT">%d</xliff:g> seconde</item>
- <item quantity="many">Try again in <xliff:g id="COUNT">%d</xliff:g> seconds</item>
<item quantity="other">Réessayer dans <xliff:g id="COUNT">%d</xliff:g> secondes</item>
</plurals>
<string name="restr_pin_try_later" msgid="5897719962541636727">"Veuillez réessayer ultérieurement."</string>
@@ -1822,42 +1799,34 @@
<string name="data_saver_enable_button" msgid="4399405762586419726">"Activer"</string>
<plurals name="zen_mode_duration_minutes_summary" formatted="false" msgid="2877101784123058273">
<item quantity="one">Pendant %1$d minute (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d minutes (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d minutes (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_minutes_summary_short" formatted="false" msgid="4230730310318858312">
<item quantity="one">Pendant %1$d min (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d min (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d min (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_hours_summary" formatted="false" msgid="7725354244196466758">
<item quantity="one">Pendant %1$d heure (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d hours (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d heures (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_hours_summary_short" formatted="false" msgid="588719069121765642">
<item quantity="one">Pendant %1$d h (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
- <item quantity="many">For %1$d hr (until <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
<item quantity="other">Pendant %1$d h (jusqu\'à <xliff:g id="FORMATTEDTIME_1">%2$s</xliff:g>)</item>
</plurals>
<plurals name="zen_mode_duration_minutes" formatted="false" msgid="1148568456958944998">
<item quantity="one">Pendant %d minute</item>
- <item quantity="many">For %d minutes</item>
<item quantity="other">Pendant %d minutes</item>
</plurals>
<plurals name="zen_mode_duration_minutes_short" formatted="false" msgid="2742377799995454859">
<item quantity="one">Pendant %d min</item>
- <item quantity="many">For %d min</item>
<item quantity="other">Pendant %d min</item>
</plurals>
<plurals name="zen_mode_duration_hours" formatted="false" msgid="525401855645490022">
<item quantity="one">Pendant %d heure</item>
- <item quantity="many">For %d hours</item>
<item quantity="other">Pendant %d heures</item>
</plurals>
<plurals name="zen_mode_duration_hours_short" formatted="false" msgid="7644653189680911640">
<item quantity="one">Pendant %d h</item>
- <item quantity="many">For %d hr</item>
<item quantity="other">Pendant %d h</item>
</plurals>
<string name="zen_mode_until" msgid="2250286190237669079">"Jusqu\'à <xliff:g id="FORMATTEDTIME">%1$s</xliff:g>"</string>
@@ -1898,7 +1867,6 @@
<string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g> : <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
<plurals name="selected_count" formatted="false" msgid="3946212171128200491">
<item quantity="one"><xliff:g id="COUNT_1">%1$d</xliff:g> élément sélectionné</item>
- <item quantity="many"><xliff:g id="COUNT_1">%1$d</xliff:g> selected</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$d</xliff:g> éléments sélectionnés</item>
</plurals>
<string name="default_notification_channel_label" msgid="3697928973567217330">"Sans catégorie"</string>
@@ -1966,7 +1934,6 @@
<string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Aucune suggestion de saisie automatique"</string>
<plurals name="autofill_picker_some_suggestions" formatted="false" msgid="6651883186966959978">
<item quantity="one"><xliff:g id="COUNT">%1$s</xliff:g> suggestion de saisie automatique</item>
- <item quantity="many"><xliff:g id="COUNT">%1$s</xliff:g> autofill suggestions</item>
<item quantity="other"><xliff:g id="COUNT">%1$s</xliff:g> suggestions de saisie automatique</item>
</plurals>
<string name="autofill_save_title" msgid="7719802414283739775">"Enregistrer dans "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>" ?"</string>
@@ -2060,7 +2027,6 @@
<string name="car_loading_profile" msgid="8219978381196748070">"Chargement…"</string>
<plurals name="file_count" formatted="false" msgid="7063513834724389247">
<item quantity="one"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> fichier</item>
- <item quantity="many"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> files</item>
<item quantity="other"><xliff:g id="FILE_NAME_2">%s</xliff:g> + <xliff:g id="COUNT_3">%d</xliff:g> fichiers</item>
</plurals>
<string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Aucune recommandation de personnes avec lesquelles effectuer un partage"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 95e1a48e1c68..054344ebabe5 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1441,7 +1441,7 @@
<string name="car_mode_disable_notification_message" msgid="8954550232288567515">"Tocca per uscire dall\'app di guida."</string>
<string name="back_button_label" msgid="4078224038025043387">"Indietro"</string>
<string name="next_button_label" msgid="6040209156399907780">"Avanti"</string>
- <string name="skip_button_label" msgid="3566599811326688389">"Ignora"</string>
+ <string name="skip_button_label" msgid="3566599811326688389">"Salta"</string>
<string name="no_matches" msgid="6472699895759164599">"Nessuna corrispondenza"</string>
<string name="find_on_page" msgid="5400537367077438198">"Trova nella pagina"</string>
<plurals name="matches_found" formatted="false" msgid="1101758718194295554">
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 6723e6694049..e2cf7f96035d 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1956,7 +1956,7 @@
<string name="autofill_save_type_debit_card" msgid="3169397504133097468">"डेबिट कार्ड"</string>
<string name="autofill_save_type_payment_card" msgid="6555012156728690856">"भुक्तानी कार्ड"</string>
<string name="autofill_save_type_generic_card" msgid="1019367283921448608">"कार्ड"</string>
- <string name="autofill_save_type_username" msgid="1018816929884640882">"प्रयोगकर्ताको नाम"</string>
+ <string name="autofill_save_type_username" msgid="1018816929884640882">"युजरनेम"</string>
<string name="autofill_save_type_email_address" msgid="1303262336895591924">"इमेल ठेगाना"</string>
<string name="etws_primary_default_message_earthquake" msgid="8401079517718280669">"शान्त रहनुहोस् र नजिकै आश्रयस्थल खोज्नुहोस्।"</string>
<string name="etws_primary_default_message_tsunami" msgid="5828171463387976279">"तटीय क्षेत्र र नदीछेउका ठाउँहरू छाडी उच्च सतहमा अवस्थित कुनै अझ सुरक्षित ठाउँमा जानुहोस्।"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index bae44fd801ba..594ea618e2a7 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -954,7 +954,7 @@
<string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"Hiermee kan de app de geschiedenis lezen van alle URL\'s die in de systeemeigen browser zijn bezocht, en alle bookmarks in de systeemeigen browser. Let op: deze rechten kunnen niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
<string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"webbookmarks en -geschiedenis schrijven"</string>
<string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bookmarks die zijn opgeslagen op je tablet. Deze rechten kunnen niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
- <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Hiermee kan de app de browsergeschiedenis of opgeslagen bookmarks bewerken op je Android TV-apparaat. Hierdoor kan de app mogelijk browsergegevens wissen of aanpassen. Opmerking: Dit recht kan niet worden afgedwongen door andere browsers of andere apps met internetmogelijkheden."</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"Hiermee kan de app de browsergeschiedenis of opgeslagen bookmarks bewerken op je Android TV-apparaat. Hierdoor kan de app mogelijk browsegegevens wissen of aanpassen. Opmerking: Dit recht kan niet worden afgedwongen door andere browsers of andere apps met internetmogelijkheden."</string>
<string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"Hiermee kan de app de webgeschiedenis wijzigen in de systeemeigen browser en de bookmarks die zijn opgeslagen op je telefoon. Deze rechten kunnen niet worden geforceerd door andere browsers of andere apps met internetmogelijkheden."</string>
<string name="permlab_setAlarm" msgid="1158001610254173567">"een wekker instellen"</string>
<string name="permdesc_setAlarm" msgid="2185033720060109640">"Hiermee kan de app een wekker instellen in een geïnstalleerde wekker-app. Deze functie wordt door sommige wekker-apps niet geïmplementeerd."</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index df5cdacfca2a..624d55492603 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -162,7 +162,7 @@
<string name="httpErrorAuth" msgid="469553140922938968">"Nepodarilo sa overiť totožnosť."</string>
<string name="httpErrorProxyAuth" msgid="7229662162030113406">"Overenie pomocou servera proxy bolo neúspešné."</string>
<string name="httpErrorConnect" msgid="3295081579893205617">"K serveru sa nepodarilo pripojiť."</string>
- <string name="httpErrorIO" msgid="3860318696166314490">"Nepodarilo sa nadviazať komunikáciu so serverom. Skúste to znova neskôr."</string>
+ <string name="httpErrorIO" msgid="3860318696166314490">"Nepodarilo sa nadviazať komunikáciu so serverom. Skúste to neskôr."</string>
<string name="httpErrorTimeout" msgid="7446272815190334204">"Časový limit pripojenia na server vypršal."</string>
<string name="httpErrorRedirectLoop" msgid="8455757777509512098">"Stránka obsahuje príliš veľa presmerovaní servera."</string>
<string name="httpErrorUnsupportedScheme" msgid="2664108769858966374">"Protokol nie je podporovaný."</string>
@@ -563,7 +563,7 @@
<string name="fingerprint_error_timeout" msgid="2946635815726054226">"Časový limit rozpoznania odtlačku prsta vypršal. Skúste to znova."</string>
<string name="fingerprint_error_canceled" msgid="540026881380070750">"Operácia týkajúca sa odtlačku prsta bola zrušená"</string>
<string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Overenie odtlačku prsta zrušil používateľ."</string>
- <string name="fingerprint_error_lockout" msgid="7853461265604738671">"Príliš veľa pokusov. Skúste to znova neskôr."</string>
+ <string name="fingerprint_error_lockout" msgid="7853461265604738671">"Príliš veľa pokusov. Skúste to neskôr."</string>
<string name="fingerprint_error_lockout_permanent" msgid="3895478283943513746">"Príliš veľa pokusov. Senzor odtlačkov prstov bol deaktivovaný."</string>
<string name="fingerprint_error_unable_to_process" msgid="1148553603490048742">"Skúste to znova"</string>
<string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Neregistrovali ste žiadne odtlačky prstov."</string>
@@ -607,7 +607,7 @@
<string name="face_error_no_space" msgid="5649264057026021723">"Nové údaje o tvári sa nedajú uložiť. Najprv odstráňte jeden zo starých záznamov."</string>
<string name="face_error_canceled" msgid="2164434737103802131">"Operácia týkajúca sa tváre bola zrušená"</string>
<string name="face_error_user_canceled" msgid="8553045452825849843">"Odomknutie tvárou zrušil používateľ."</string>
- <string name="face_error_lockout" msgid="7864408714994529437">"Príliš veľa pokusov. Skúste to znova neskôr."</string>
+ <string name="face_error_lockout" msgid="7864408714994529437">"Príliš veľa pokusov. Skúste to neskôr."</string>
<string name="face_error_lockout_permanent" msgid="8277853602168960343">"Príliš veľa pokusov. Odomknutie tvárou bolo zakázané."</string>
<string name="face_error_unable_to_process" msgid="5723292697366130070">"Nedá sa overiť tvár. Skúste to znova."</string>
<string name="face_error_not_enrolled" msgid="7369928733504691611">"Nenastavili ste odomknutie tvárou."</string>
@@ -1816,7 +1816,7 @@
<item quantity="other">Skúste to znova o <xliff:g id="COUNT">%d</xliff:g> sekúnd</item>
<item quantity="one">Skúste to znova o 1 sekundu</item>
</plurals>
- <string name="restr_pin_try_later" msgid="5897719962541636727">"Skúste to znova neskôr"</string>
+ <string name="restr_pin_try_later" msgid="5897719962541636727">"Skúste to neskôr"</string>
<string name="immersive_cling_title" msgid="2307034298721541791">"Zobrazenie na celú obrazovku"</string>
<string name="immersive_cling_description" msgid="7092737175345204832">"Ukončíte potiahnutím zhora nadol."</string>
<string name="immersive_cling_positive" msgid="7047498036346489883">"Dobre"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index e8449c31639e..2b83c8214bc7 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -1765,7 +1765,7 @@
<string name="restr_pin_confirm_pin" msgid="7143161971614944989">"Yangi PIN kodni tasdiqlash"</string>
<string name="restr_pin_create_pin" msgid="917067613896366033">"Cheklovlarni o‘zgartirish uchun PIN-kod yaratish"</string>
<string name="restr_pin_error_doesnt_match" msgid="7063392698489280556">"PIN-kod mos kelmadi. Qayta urinib ko‘ring."</string>
- <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN kod kamida 4 ta raqamdan iborat bo‘lishi shart."</string>
+ <string name="restr_pin_error_too_short" msgid="1547007808237941065">"PIN kod juda qisqa, kamida 4 ta raqam kiriting."</string>
<plurals name="restr_pin_countdown" formatted="false" msgid="4427486903285216153">
<item quantity="other"><xliff:g id="COUNT">%d</xliff:g> soniyadan so‘ng qayta urinib ko‘ring</item>
<item quantity="one">1 soniyadan so‘ng qayta urinib ko‘ring</item>
diff --git a/core/tests/coretests/src/android/os/storage/OWNERS b/core/tests/coretests/src/android/os/storage/OWNERS
new file mode 100644
index 000000000000..6f9dbea36b06
--- /dev/null
+++ b/core/tests/coretests/src/android/os/storage/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/os/storage/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
index d0267355c92e..e8793a9f6097 100644
--- a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
@@ -118,4 +118,89 @@ public class ArrayUtilsTest extends TestCase {
assertEquals(3, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(0, collection.size());
}
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenRangeInsideArray() {
+ ArrayUtils.throwsIfOutOfBounds(10, 2, 6);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenRangeIsWholeArray() {
+ ArrayUtils.throwsIfOutOfBounds(10, 0, 10);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenEmptyRangeAtStart() {
+ ArrayUtils.throwsIfOutOfBounds(10, 0, 0);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenEmptyRangeAtEnd() {
+ ArrayUtils.throwsIfOutOfBounds(10, 10, 0);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_passesWhenEmptyArray() {
+ ArrayUtils.throwsIfOutOfBounds(0, 0, 0);
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenRangeStartNegative() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, -1, 5);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenCountNegative() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, 5, -1);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenRangeStartTooHigh() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, 11, 0);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenRangeEndTooHigh() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(10, 5, 6);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenLengthNegative() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(-1, 0, 0);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
+
+ @SmallTest
+ public void testThrowsIfOutOfBounds_failsWhenOverflowRangeEndTooHigh() {
+ try {
+ ArrayUtils.throwsIfOutOfBounds(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ fail();
+ } catch (ArrayIndexOutOfBoundsException expected) {
+ // expected
+ }
+ }
}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java
new file mode 100644
index 000000000000..6df1c3ed220f
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/RangingManagerTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2020 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.uwb;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link AdapterStateListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingManagerTest {
+
+ private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class);
+ private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
+ private static final PersistableBundle PARAMS = new PersistableBundle();
+ private static final @CloseReason int CLOSE_REASON = CloseReason.UNKNOWN;
+
+ @Test
+ public void testOpenSession_StartRangingInvoked() throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback);
+ verify(ADAPTER, times(1)).startRanging(eq(rangingManager), eq(PARAMS));
+ }
+
+ @Test
+ public void testOpenSession_ErrorIfSameSessionHandleReturned() throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ SessionHandle handle = new SessionHandle(1);
+ when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+
+ rangingManager.openSession(PARAMS, EXECUTOR, callback);
+
+ // Calling openSession will cause the same session handle to be returned. The onClosed
+ // callback should be invoked
+ RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback2);
+ verify(callback, times(0)).onClosed(anyInt(), any());
+ verify(callback2, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingStarted_ValidSessionHandle() throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ SessionHandle handle = new SessionHandle(1);
+ when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+
+ rangingManager.openSession(PARAMS, EXECUTOR, callback);
+ rangingManager.onRangingStarted(handle, PARAMS);
+ verify(callback, times(1)).onOpenSuccess(any(), any());
+ }
+
+ @Test
+ public void testOnRangingStarted_InvalidSessionHandle() throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+
+ rangingManager.onRangingStarted(new SessionHandle(2), PARAMS);
+ verify(callback, times(0)).onOpenSuccess(any(), any());
+ }
+
+ @Test
+ public void testOnRangingStarted_MultipleSessionsRegistered() throws RemoteException {
+ SessionHandle sessionHandle1 = new SessionHandle(1);
+ SessionHandle sessionHandle2 = new SessionHandle(2);
+ RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
+ RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
+
+ when(ADAPTER.startRanging(any(), any()))
+ .thenReturn(sessionHandle1)
+ .thenReturn(sessionHandle2);
+
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback1);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback2);
+
+ rangingManager.onRangingStarted(sessionHandle1, PARAMS);
+ verify(callback1, times(1)).onOpenSuccess(any(), any());
+ verify(callback2, times(0)).onOpenSuccess(any(), any());
+
+ rangingManager.onRangingStarted(sessionHandle2, PARAMS);
+ verify(callback1, times(1)).onOpenSuccess(any(), any());
+ verify(callback2, times(1)).onOpenSuccess(any(), any());
+ }
+
+ @Test
+ public void testOnRangingClosed_OnRangingClosedCalled() throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ SessionHandle handle = new SessionHandle(1);
+ when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback);
+
+ rangingManager.onRangingClosed(handle, CLOSE_REASON, PARAMS);
+ verify(callback, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingClosed_MultipleSessionsRegistered() throws RemoteException {
+ // Verify that if multiple sessions are registered, only the session that is
+ // requested to close receives the associated callbacks
+ SessionHandle sessionHandle1 = new SessionHandle(1);
+ SessionHandle sessionHandle2 = new SessionHandle(2);
+ RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
+ RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
+
+ when(ADAPTER.startRanging(any(), any()))
+ .thenReturn(sessionHandle1)
+ .thenReturn(sessionHandle2);
+
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback1);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback2);
+
+ rangingManager.onRangingClosed(sessionHandle1, CLOSE_REASON, PARAMS);
+ verify(callback1, times(1)).onClosed(anyInt(), any());
+ verify(callback2, times(0)).onClosed(anyInt(), any());
+
+ rangingManager.onRangingClosed(sessionHandle2, CLOSE_REASON, PARAMS);
+ verify(callback1, times(1)).onClosed(anyInt(), any());
+ verify(callback2, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingReport_OnReportReceived() throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ SessionHandle handle = new SessionHandle(1);
+ when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback);
+ rangingManager.onRangingStarted(handle, PARAMS);
+
+ RangingReport report = UwbTestUtils.getRangingReports(1);
+ rangingManager.onRangingResult(handle, report);
+ verify(callback, times(1)).onReportReceived(eq(report));
+ }
+
+ @Test
+ public void testOnRangingReport_MultipleSessionsRegistered() throws RemoteException {
+ SessionHandle sessionHandle1 = new SessionHandle(1);
+ SessionHandle sessionHandle2 = new SessionHandle(2);
+ RangingSession.Callback callback1 = mock(RangingSession.Callback.class);
+ RangingSession.Callback callback2 = mock(RangingSession.Callback.class);
+
+ when(ADAPTER.startRanging(any(), any()))
+ .thenReturn(sessionHandle1)
+ .thenReturn(sessionHandle2);
+
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback1);
+ rangingManager.onRangingStarted(sessionHandle1, PARAMS);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback2);
+ rangingManager.onRangingStarted(sessionHandle2, PARAMS);
+
+ rangingManager.onRangingResult(sessionHandle1, UwbTestUtils.getRangingReports(1));
+ verify(callback1, times(1)).onReportReceived(any());
+ verify(callback2, times(0)).onReportReceived(any());
+
+ rangingManager.onRangingResult(sessionHandle2, UwbTestUtils.getRangingReports(1));
+ verify(callback1, times(1)).onReportReceived(any());
+ verify(callback2, times(1)).onReportReceived(any());
+ }
+
+ @Test
+ public void testOnClose_Reasons() throws RemoteException {
+ runOnClose_Reason(CloseReason.LOCAL_API,
+ RangingSession.Callback.CLOSE_REASON_LOCAL_CLOSE_API);
+
+ runOnClose_Reason(CloseReason.MAX_SESSIONS_REACHED,
+ RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED);
+
+ runOnClose_Reason(CloseReason.PROTOCOL_SPECIFIC,
+ RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC);
+
+ runOnClose_Reason(CloseReason.REMOTE_REQUEST,
+ RangingSession.Callback.CLOSE_REASON_REMOTE_REQUEST);
+
+ runOnClose_Reason(CloseReason.SYSTEM_POLICY,
+ RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY);
+
+ runOnClose_Reason(CloseReason.UNKNOWN,
+ RangingSession.Callback.CLOSE_REASON_UNKNOWN);
+ }
+
+ private void runOnClose_Reason(@CloseReason int reasonIn,
+ @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ SessionHandle handle = new SessionHandle(1);
+ when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback);
+
+ rangingManager.onRangingClosed(handle, reasonIn, PARAMS);
+ verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS));
+ }
+
+ @Test
+ public void testStartFailureReasons() throws RemoteException {
+ runOnRangingStartFailed_Reason(StartFailureReason.BAD_PARAMETERS,
+ RangingSession.Callback.CLOSE_REASON_LOCAL_BAD_PARAMETERS);
+
+ runOnRangingStartFailed_Reason(StartFailureReason.MAX_SESSIONS_REACHED,
+ RangingSession.Callback.CLOSE_REASON_LOCAL_MAX_SESSIONS_REACHED);
+
+ runOnRangingStartFailed_Reason(StartFailureReason.PROTOCOL_SPECIFIC,
+ RangingSession.Callback.CLOSE_REASON_PROTOCOL_SPECIFIC);
+
+ runOnRangingStartFailed_Reason(StartFailureReason.SYSTEM_POLICY,
+ RangingSession.Callback.CLOSE_REASON_LOCAL_SYSTEM_POLICY);
+
+ runOnRangingStartFailed_Reason(StartFailureReason.UNKNOWN,
+ RangingSession.Callback.CLOSE_REASON_UNKNOWN);
+ }
+
+ private void runOnRangingStartFailed_Reason(@StartFailureReason int reasonIn,
+ @RangingSession.Callback.CloseReason int reasonOut) throws RemoteException {
+ RangingManager rangingManager = new RangingManager(ADAPTER);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ SessionHandle handle = new SessionHandle(1);
+ when(ADAPTER.startRanging(any(), any())).thenReturn(handle);
+ rangingManager.openSession(PARAMS, EXECUTOR, callback);
+
+ rangingManager.onRangingStartFailed(handle, reasonIn, PARAMS);
+ verify(callback, times(1)).onClosed(eq(reasonOut), eq(PARAMS));
+ }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java
new file mode 100644
index 000000000000..702c68ebc9de
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/RangingSessionTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2020 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.uwb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link RangingSession}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingSessionTest {
+ private static final IUwbAdapter ADAPTER = mock(IUwbAdapter.class);
+ private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
+ private static final PersistableBundle PARAMS = new PersistableBundle();
+ private static final @RangingSession.Callback.CloseReason int CLOSE_REASON =
+ RangingSession.Callback.CLOSE_REASON_LOCAL_GENERIC_ERROR;
+
+ @Test
+ public void testOnRangingStarted_OnOpenSuccessCalled() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+ verifyOpenState(session, false);
+
+ session.onRangingStarted(PARAMS);
+ verifyOpenState(session, true);
+
+ // Verify that the onOpenSuccess callback was invoked
+ verify(callback, times(1)).onOpenSuccess(eq(session), any());
+ verify(callback, times(0)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingStarted_CannotOpenClosedSession() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+
+ session.onRangingStarted(PARAMS);
+ verifyOpenState(session, true);
+ verify(callback, times(1)).onOpenSuccess(eq(session), any());
+ verify(callback, times(0)).onClosed(anyInt(), any());
+
+ session.onRangingClosed(CLOSE_REASON, PARAMS);
+ verifyOpenState(session, false);
+ verify(callback, times(1)).onOpenSuccess(eq(session), any());
+ verify(callback, times(1)).onClosed(anyInt(), any());
+
+ // Now invoke the ranging started callback and ensure the session remains closed
+ session.onRangingStarted(PARAMS);
+ verifyOpenState(session, false);
+ verify(callback, times(1)).onOpenSuccess(eq(session), any());
+ verify(callback, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+ verifyOpenState(session, false);
+
+ session.onRangingClosed(CLOSE_REASON, PARAMS);
+ verifyOpenState(session, false);
+
+ // Verify that the onOpenSuccess callback was invoked
+ verify(callback, times(0)).onOpenSuccess(eq(session), any());
+ verify(callback, times(1)).onClosed(anyInt(), any());
+ }
+
+ @Test public void testOnRangingClosed_OnClosedCalled() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+ session.onRangingStarted(PARAMS);
+ session.onRangingClosed(CLOSE_REASON, PARAMS);
+ verify(callback, times(1)).onClosed(anyInt(), any());
+
+ verifyOpenState(session, false);
+ session.onRangingClosed(CLOSE_REASON, PARAMS);
+ verify(callback, times(2)).onClosed(anyInt(), any());
+ }
+
+ @Test
+ public void testOnRangingResult_OnReportReceivedCalled() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+ verifyOpenState(session, false);
+
+ session.onRangingStarted(PARAMS);
+ verifyOpenState(session, true);
+
+ RangingReport report = UwbTestUtils.getRangingReports(1);
+ session.onRangingResult(report);
+ verify(callback, times(1)).onReportReceived(eq(report));
+ }
+
+ @Test
+ public void testClose() throws RemoteException {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+ session.onRangingStarted(PARAMS);
+
+ // Calling close multiple times should invoke closeRanging until the session receives
+ // the onClosed callback.
+ int totalCallsBeforeOnRangingClosed = 3;
+ for (int i = 1; i <= totalCallsBeforeOnRangingClosed; i++) {
+ session.close();
+ verifyOpenState(session, true);
+ verify(ADAPTER, times(i)).closeRanging(handle);
+ verify(callback, times(0)).onClosed(anyInt(), any());
+ }
+
+ // After onClosed is invoked, then the adapter should no longer be called for each call to
+ // the session's close.
+ final int totalCallsAfterOnRangingClosed = 2;
+ for (int i = 1; i <= totalCallsAfterOnRangingClosed; i++) {
+ session.onRangingClosed(CLOSE_REASON, PARAMS);
+ verifyOpenState(session, false);
+ verify(ADAPTER, times(totalCallsBeforeOnRangingClosed)).closeRanging(handle);
+ verify(callback, times(i)).onClosed(anyInt(), any());
+ }
+ }
+
+ @Test
+ public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+
+ assertFalse(session.isOpen());
+ session.onRangingStarted(PARAMS);
+ assertTrue(session.isOpen());
+
+ // Verify that the onReportReceived callback was invoked
+ RangingReport report = UwbTestUtils.getRangingReports(1);
+ session.onRangingResult(report);
+ verify(callback, times(1)).onReportReceived(report);
+ }
+
+ @Test
+ public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
+ SessionHandle handle = new SessionHandle(123);
+ RangingSession.Callback callback = mock(RangingSession.Callback.class);
+ RangingSession session = new RangingSession(EXECUTOR, callback, ADAPTER, handle);
+
+ assertFalse(session.isOpen());
+
+ // Verify that the onReportReceived callback was invoked
+ RangingReport report = UwbTestUtils.getRangingReports(1);
+ session.onRangingResult(report);
+ verify(callback, times(0)).onReportReceived(report);
+ }
+
+ private void verifyOpenState(RangingSession session, boolean expected) {
+ assertEquals(expected, session.isOpen());
+ }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java
index fb7509248b38..b4b2e303443e 100644
--- a/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java
+++ b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java
@@ -22,6 +22,7 @@ import android.os.SystemClock;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
public class UwbTestUtils {
private UwbTestUtils() {}
@@ -96,4 +97,13 @@ public class UwbTestUtils {
}
return UwbAddress.fromBytes(addressBytes);
}
+
+ public static Executor getExecutor() {
+ return new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ };
+ }
}
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index e5492714b29f..12bd0f51fa5f 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -158,6 +158,13 @@ prebuilt_etc {
}
prebuilt_etc {
+ name: "privapp_allowlist_com.google.android.car.networking.preferenceupdater",
+ sub_dir: "permissions",
+ src: "com.google.android.car.networking.preferenceupdater.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
name: "privapp_whitelist_com.android.car.ui.paintbooth",
sub_dir: "permissions",
src: "com.android.car.ui.paintbooth.xml",
diff --git a/data/etc/car/com.google.android.car.networking.preferenceupdater.xml b/data/etc/car/com.google.android.car.networking.preferenceupdater.xml
new file mode 100644
index 000000000000..489ce1b47ffa
--- /dev/null
+++ b/data/etc/car/com.google.android.car.networking.preferenceupdater.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+<permissions>
+ <privapp-permissions package="com.google.android.car.networking.preferenceupdater">
+ <permission name="android.permission.ACCESS_NETWORK_STATE"/>
+ <permission name="android.permission.ACCESS_WIFI_STATE"/>
+ <permission name="android.permission.ACTIVITY_EMBEDDING"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+ <permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
+ <permission name="android.permission.LOCATION_HARDWARE"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 6555fe93c81f..057c0120b685 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -436,6 +436,8 @@ applications that come with the platform
<permission name="android.permission.MANAGE_DEBUGGING" />
<!-- Permissions required for CTS test - TimeManagerTest -->
<permission name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
+ <!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
+ <permission name="android.permission.HDMI_CEC"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/keyboards/keyboards.mk b/data/keyboards/keyboards.mk
index 68cbd29859bb..c7ce8cd6693a 100644
--- a/data/keyboards/keyboards.mk
+++ b/data/keyboards/keyboards.mk
@@ -14,13 +14,9 @@
# Warning: this is actually a product definition, to be inherited from
-include $(LOCAL_PATH)/common.mk
+PRODUCT_COPY_FILES := \
+ $(call find-copy-subdir-files,*.kl,$(LOCAL_PATH),system/usr/keylayout) \
+ $(call find-copy-subdir-files,*.kcm,$(LOCAL_PATH),system/usr/keychars) \
+ $(call find-copy-subdir-files,*.idc,$(LOCAL_PATH),system/usr/idc)
-PRODUCT_COPY_FILES := $(foreach file,$(framework_keylayouts),\
- $(file):system/usr/keylayout/$(notdir $(file)))
-PRODUCT_COPY_FILES += $(foreach file,$(framework_keycharmaps),\
- $(file):system/usr/keychars/$(notdir $(file)))
-
-PRODUCT_COPY_FILES += $(foreach file,$(framework_keyconfigs),\
- $(file):system/usr/idc/$(notdir $(file)))
diff --git a/drm/java/android/drm/DrmOutputStream.java b/drm/java/android/drm/DrmOutputStream.java
index 73e7f23b19c6..2a3f530e04c4 100644
--- a/drm/java/android/drm/DrmOutputStream.java
+++ b/drm/java/android/drm/DrmOutputStream.java
@@ -25,9 +25,10 @@ import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
import libcore.io.IoBridge;
import libcore.io.Streams;
-import libcore.util.ArrayUtils;
import java.io.FileDescriptor;
import java.io.FilterOutputStream;
diff --git a/graphics/java/android/graphics/OWNERS b/graphics/java/android/graphics/OWNERS
index a8a3631f4a7d..9fa8f1b284bb 100644
--- a/graphics/java/android/graphics/OWNERS
+++ b/graphics/java/android/graphics/OWNERS
@@ -4,6 +4,8 @@ romainguy@google.com
jreck@google.com
njawad@google.com
sumir@google.com
+djsollen@google.com
+scroggo@google.com
per-file BLASTBufferQueue.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file FontFamily.java = file:fonts/OWNERS
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java
index 0775a1a99886..83ff84d372f3 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java
@@ -41,7 +41,7 @@ import javax.crypto.spec.IvParameterSpec;
*
* @hide
*/
-public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {
+public abstract class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {
private static final int BLOCK_SIZE_BYTES = 8;
@@ -73,12 +73,22 @@ public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
+
+ @Override
+ protected final String getTransform() {
+ return "DESede/ECB/NoPadding";
+ }
}
public static class PKCS7Padding extends ECB {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
+
+ @Override
+ protected final String getTransform() {
+ return "DESede/ECB/PKCS7Padding";
+ }
}
}
@@ -91,12 +101,23 @@ public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase {
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
+
+ @Override
+ protected final String getTransform() {
+ return "DESede/CBC/NoPadding";
+ }
+
}
public static class PKCS7Padding extends CBC {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
+
+ @Override
+ protected final String getTransform() {
+ return "DESede/CBC/PKCS7Padding";
+ }
}
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java
index bc56f015f3bd..aab84e390c73 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java
@@ -64,6 +64,11 @@ abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreC
}
@Override
+ protected final String getTransform() {
+ return "AES/GCM/NoPadding";
+ }
+
+ @Override
protected final void resetAll() {
mTagLengthBits = DEFAULT_TAG_LENGTH_BITS;
super.resetAll();
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java
index dd943d422e62..9ad6f3adbd33 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java
@@ -254,13 +254,13 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider {
private void putAsymmetricCipherImpl(String transformation, String implClass) {
put("Cipher." + transformation, implClass);
put("Cipher." + transformation + " SupportedKeyClasses",
- KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
+ KEYSTORE_PRIVATE_KEY_CLASS_NAME);
}
private void putSignatureImpl(String algorithm, String implClass) {
put("Signature." + algorithm, implClass);
put("Signature." + algorithm + " SupportedKeyClasses",
- KEYSTORE_PRIVATE_KEY_CLASS_NAME + "|" + KEYSTORE_PUBLIC_KEY_CLASS_NAME);
+ KEYSTORE_PRIVATE_KEY_CLASS_NAME);
}
public static String[] getSupportedEcdsaSignatureDigests() {
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
index a3b04abfba3f..2ee952cbc5fb 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java
@@ -43,6 +43,7 @@ import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
+import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
@@ -57,6 +58,8 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
+import javax.crypto.spec.OAEPParameterSpec;
+import javax.crypto.spec.PSource;
import javax.crypto.spec.SecretKeySpec;
/**
@@ -99,6 +102,8 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
*/
private Exception mCachedException;
+ private Cipher mCipher;
+
AndroidKeyStoreCipherSpiBase() {
mOperation = null;
mEncrypting = false;
@@ -110,6 +115,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
+ mCipher = null;
}
@Override
@@ -117,6 +123,45 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
throws InvalidKeyException {
resetAll();
+ if (!(key instanceof AndroidKeyStorePrivateKey
+ || key instanceof AndroidKeyStoreSecretKey)) {
+ try {
+ mCipher = Cipher.getInstance(getTransform());
+ String transform = getTransform();
+
+ if ("RSA/ECB/OAEPWithSHA-224AndMGF1Padding".equals(transform)) {
+ OAEPParameterSpec spec =
+ new OAEPParameterSpec("SHA-224", "MGF1",
+ new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+ mCipher.init(opmode, key, spec, random);
+ } else if ("RSA/ECB/OAEPWithSHA-256AndMGF1Padding".equals(transform)) {
+ OAEPParameterSpec spec =
+ new OAEPParameterSpec("SHA-256", "MGF1",
+ new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+ mCipher.init(opmode, key, spec, random);
+
+ } else if ("RSA/ECB/OAEPWithSHA-384AndMGF1Padding".equals(transform)) {
+ OAEPParameterSpec spec =
+ new OAEPParameterSpec("SHA-384", "MGF1",
+ new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+ mCipher.init(opmode, key, spec, random);
+
+ } else if ("RSA/ECB/OAEPWithSHA-512AndMGF1Padding".equals(transform)) {
+ OAEPParameterSpec spec =
+ new OAEPParameterSpec("SHA-512", "MGF1",
+ new MGF1ParameterSpec("SHA1"), PSource.PSpecified.DEFAULT);
+ mCipher.init(opmode, key, spec, random);
+ } else {
+ mCipher.init(opmode, key, random);
+ }
+ return;
+ } catch (NoSuchAlgorithmException
+ | NoSuchPaddingException
+ | InvalidAlgorithmParameterException e) {
+ throw new InvalidKeyException(e);
+ }
+ }
+
boolean success = false;
try {
init(opmode, key, random);
@@ -139,6 +184,17 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
+ if (!(key instanceof AndroidKeyStorePrivateKey
+ || key instanceof AndroidKeyStoreSecretKey)) {
+ try {
+ mCipher = Cipher.getInstance(getTransform());
+ mCipher.init(opmode, key, params, random);
+ return;
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new InvalidKeyException(e);
+ }
+ }
+
boolean success = false;
try {
init(opmode, key, random);
@@ -157,6 +213,17 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException {
resetAll();
+ if (!(key instanceof AndroidKeyStorePrivateKey
+ || key instanceof AndroidKeyStoreSecretKey)) {
+ try {
+ mCipher = Cipher.getInstance(getTransform());
+ mCipher.init(opmode, key, params, random);
+ return;
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ throw new InvalidKeyException(e);
+ }
+ }
+
boolean success = false;
try {
init(opmode, key, random);
@@ -214,6 +281,7 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
mAdditionalAuthenticationDataStreamer = null;
mAdditionalAuthenticationDataStreamerClosed = false;
mCachedException = null;
+ mCipher = null;
}
/**
@@ -320,6 +388,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) {
+ if (mCipher != null) {
+ return mCipher.update(input, inputOffset, inputLen);
+ }
+
if (mCachedException != null) {
return null;
}
@@ -371,6 +443,9 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException {
+ if (mCipher != null) {
+ return mCipher.update(input, inputOffset, inputLen, output);
+ }
byte[] outputCopy = engineUpdate(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
@@ -387,6 +462,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final int engineUpdate(ByteBuffer input, ByteBuffer output)
throws ShortBufferException {
+ if (mCipher != null) {
+ return mCipher.update(input, output);
+ }
+
if (input == null) {
throw new NullPointerException("input == null");
}
@@ -423,6 +502,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) {
+ if (mCipher != null) {
+ mCipher.updateAAD(input, inputOffset, inputLen);
+ return;
+ }
+
if (mCachedException != null) {
return;
}
@@ -459,6 +543,11 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final void engineUpdateAAD(ByteBuffer src) {
+ if (mCipher != null) {
+ mCipher.updateAAD(src);
+ return;
+ }
+
if (src == null) {
throw new IllegalArgumentException("src == null");
}
@@ -486,6 +575,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
+ if (mCipher != null) {
+ return mCipher.doFinal(input, inputOffset, inputLen);
+ }
+
if (mCachedException != null) {
throw (IllegalBlockSizeException)
new IllegalBlockSizeException().initCause(mCachedException);
@@ -522,6 +615,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
+ if (mCipher != null) {
+ return mCipher.doFinal(input, inputOffset, inputLen, output);
+ }
+
byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen);
if (outputCopy == null) {
return 0;
@@ -538,6 +635,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException, BadPaddingException {
+ if (mCipher != null) {
+ return mCipher.doFinal(input, output);
+ }
+
if (input == null) {
throw new NullPointerException("input == null");
}
@@ -575,6 +676,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final byte[] engineWrap(Key key)
throws IllegalBlockSizeException, InvalidKeyException {
+ if (mCipher != null) {
+ return mCipher.wrap(key);
+ }
+
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
@@ -656,6 +761,10 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
@Override
protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm,
int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException {
+ if (mCipher != null) {
+ return mCipher.unwrap(wrappedKey, wrappedKeyAlgorithm, wrappedKeyType);
+ }
+
if (mKey == null) {
throw new IllegalStateException("Not initilized");
}
@@ -902,4 +1011,6 @@ abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStor
*/
protected abstract void loadAlgorithmSpecificParametersFromBeginResult(
KeyParameter[] parameters);
+
+ protected abstract String getTransform();
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
index d1ef1df817e6..8289671de212 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java
@@ -44,6 +44,11 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
}
@Override
+ protected String getAlgorithm() {
+ return "NONEwithECDSA";
+ }
+
+ @Override
protected KeyStoreCryptoOperationStreamer createMainDataStreamer(
KeyStoreOperation operation) {
return new TruncateToFieldSizeMessageStreamer(
@@ -113,30 +118,50 @@ abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignature
public SHA1() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA1withECDSA";
+ }
}
public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi {
public SHA224() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA224withECDSA";
+ }
}
public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi {
public SHA256() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA256withECDSA";
+ }
}
public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi {
public SHA384() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA384withECDSA";
+ }
}
public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi {
public SHA512() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA512withECDSA";
+ }
}
private final int mKeymasterDigest;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
index afb10547411b..0c6744f9822c 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java
@@ -22,11 +22,11 @@ import java.security.KeyStore.ProtectionParameter;
/**
* @hide
*/
-class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter {
+public class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter {
private final int mNamespace;
- AndroidKeyStoreLoadStoreParameter(int namespace) {
+ public AndroidKeyStoreLoadStoreParameter(int namespace) {
mNamespace = namespace;
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index b2e32a3175e3..176507c74d96 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -31,9 +31,7 @@ import android.system.keystore2.KeyEntryResponse;
import android.system.keystore2.KeyMetadata;
import android.system.keystore2.ResponseCode;
-import java.security.KeyFactory;
import java.security.KeyPair;
-import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.ProviderException;
import java.security.PublicKey;
@@ -42,8 +40,6 @@ import java.security.Signature;
import java.security.UnrecoverableKeyException;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.Mac;
@@ -237,28 +233,11 @@ public class AndroidKeyStoreProvider extends Provider {
throw new UnrecoverableKeyException("Failed to obtain X.509 form of public key."
+ " Keystore has no public certificate stored.");
}
- final byte[] x509EncodedPublicKey = metadata.certificate;
+ final byte[] x509PublicCert = metadata.certificate;
- String jcaKeyAlgorithm;
- try {
- jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm(
- algorithm);
- } catch (IllegalArgumentException e) {
- throw (UnrecoverableKeyException)
- new UnrecoverableKeyException("Failed to load private key")
- .initCause(e);
- }
+ PublicKey publicKey = AndroidKeyStoreSpi.toCertificate(x509PublicCert).getPublicKey();
- PublicKey publicKey;
- try {
- KeyFactory keyFactory = KeyFactory.getInstance(jcaKeyAlgorithm);
- publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(x509EncodedPublicKey));
- } catch (NoSuchAlgorithmException e) {
- throw new ProviderException(
- "Failed to obtain " + jcaKeyAlgorithm + " KeyFactory", e);
- } catch (InvalidKeySpecException e) {
- throw new ProviderException("Invalid X.509 encoding of public key", e);
- }
+ String jcaKeyAlgorithm = publicKey.getAlgorithm();
KeyStoreSecurityLevel securityLevel = iSecurityLevel;
if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) {
@@ -358,7 +337,7 @@ public class AndroidKeyStoreProvider extends Provider {
KeyDescriptor descriptor = new KeyDescriptor();
if (namespace == KeyProperties.NAMESPACE_APPLICATION) {
- descriptor.nspace = 0; // ignored;
+ descriptor.nspace = KeyProperties.NAMESPACE_APPLICATION; // ignored;
descriptor.domain = Domain.APP;
} else {
descriptor.nspace = namespace;
@@ -407,7 +386,7 @@ public class AndroidKeyStoreProvider extends Provider {
keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) {
return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata,
new KeyStoreSecurityLevel(response.iSecurityLevel),
- keymasterAlgorithm);
+ keymasterAlgorithm).getPrivateKey();
} else {
throw new UnrecoverableKeyException("Key algorithm unknown");
}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
index 951f91887894..6ff9432905ed 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java
@@ -158,7 +158,7 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
}
/**
- * RSA cipher with OAEP encryption padding. Only SHA-1 based MGF1 is supported as MGF.
+ * RSA cipher with OAEP encryption padding.
*/
abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi {
@@ -316,6 +316,25 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
protected final int getAdditionalEntropyAmountForFinish() {
return (isEncrypting()) ? mDigestOutputSizeBytes : 0;
}
+
+ @Override
+ protected final String getTransform() {
+ switch (mKeymasterDigest) {
+ case KeymasterDefs.KM_DIGEST_SHA1:
+ return "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
+ case KeymasterDefs.KM_DIGEST_SHA_2_224:
+ return "RSA/ECB/OAEPWithSHA-224AndMGF1Padding";
+ case KeymasterDefs.KM_DIGEST_SHA_2_256:
+ return "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";
+ case KeymasterDefs.KM_DIGEST_SHA_2_384:
+ return "RSA/ECB/OAEPWithSHA-384AndMGF1Padding";
+ case KeymasterDefs.KM_DIGEST_SHA_2_512:
+ return "RSA/ECB/OAEPWithSHA-512AndMGF1Padding";
+ default:
+ return "RSA/ECB/OAEPPadding";
+ }
+ }
+
}
public static class OAEPWithSHA1AndMGF1Padding extends OAEPWithMGF1Padding {
@@ -358,6 +377,11 @@ abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase
}
@Override
+ protected String getTransform() {
+ return "RSA/ECB/" + KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding);
+ }
+
+ @Override
protected final void initKey(int opmode, Key key) throws InvalidKeyException {
if (key == null) {
throw new InvalidKeyException("Unsupported key: null");
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
index ab7559116a41..931c2f864eba 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java
@@ -48,42 +48,70 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp
public NONEWithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_NONE);
}
+ @Override
+ protected String getAlgorithm() {
+ return "NONEwithRSA";
+ }
}
public static final class MD5WithPKCS1Padding extends PKCS1Padding {
public MD5WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_MD5);
}
+ @Override
+ protected String getAlgorithm() {
+ return "MD5withRSA";
+ }
}
public static final class SHA1WithPKCS1Padding extends PKCS1Padding {
public SHA1WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA1withRSA";
+ }
}
public static final class SHA224WithPKCS1Padding extends PKCS1Padding {
public SHA224WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA224withRSA";
+ }
}
public static final class SHA256WithPKCS1Padding extends PKCS1Padding {
public SHA256WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA256withRSA";
+ }
}
public static final class SHA384WithPKCS1Padding extends PKCS1Padding {
public SHA384WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA384withRSA";
+ }
}
public static final class SHA512WithPKCS1Padding extends PKCS1Padding {
public SHA512WithPKCS1Padding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA512withRSA";
+ }
}
abstract static class PSSPadding extends AndroidKeyStoreRSASignatureSpi {
@@ -103,30 +131,50 @@ abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSp
public SHA1WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA1);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA1withRSA/PSS";
+ }
}
public static final class SHA224WithPSSPadding extends PSSPadding {
public SHA224WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_224);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA224withRSA/PSS";
+ }
}
public static final class SHA256WithPSSPadding extends PSSPadding {
public SHA256WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_256);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA256withRSA/PSS";
+ }
}
public static final class SHA384WithPSSPadding extends PSSPadding {
public SHA384WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_384);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA384withRSA/PSS";
+ }
}
public static final class SHA512WithPSSPadding extends PSSPadding {
public SHA512WithPSSPadding() {
super(KeymasterDefs.KM_DIGEST_SHA_2_512);
}
+ @Override
+ protected String getAlgorithm() {
+ return "SHA512withRSA/PSS";
+ }
}
private final int mKeymasterDigest;
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java
index 9b4f01e744f7..96da1e00ade8 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java
@@ -30,10 +30,12 @@ import libcore.util.EmptyArray;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
+import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.ProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
+import java.security.Signature;
import java.security.SignatureException;
import java.security.SignatureSpi;
import java.util.ArrayList;
@@ -76,6 +78,13 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
*/
private Exception mCachedException;
+ /**
+ * This signature object is used for public key operations, i.e, signatrue verification.
+ * The Android Keystore backend does not perform public key operations and defers to the
+ * Highest priority provider.
+ */
+ private Signature mSignature;
+
AndroidKeyStoreSignatureSpiBase() {
mOperation = null;
mOperationChallenge = 0;
@@ -84,6 +93,7 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
appRandom = null;
mMessageStreamer = null;
mCachedException = null;
+ mSignature = null;
}
@Override
@@ -123,27 +133,13 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
protected final void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
resetAll();
- boolean success = false;
try {
- if (publicKey == null) {
- throw new InvalidKeyException("Unsupported key: null");
- }
- AndroidKeyStoreKey keystoreKey;
- if (publicKey instanceof AndroidKeyStorePublicKey) {
- keystoreKey = (AndroidKeyStorePublicKey) publicKey;
- } else {
- throw new InvalidKeyException("Unsupported public key type: " + publicKey);
- }
- mSigning = false;
- initKey(keystoreKey);
- appRandom = null;
- ensureKeystoreOperationInitialized();
- success = true;
- } finally {
- if (!success) {
- resetAll();
- }
+ mSignature = Signature.getInstance(getAlgorithm());
+ } catch (NoSuchAlgorithmException e) {
+ throw new InvalidKeyException(e);
}
+
+ mSignature.initVerify(publicKey);
}
/**
@@ -251,6 +247,11 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
@Override
protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException {
+ if (mSignature != null) {
+ mSignature.update(b, off, len);
+ return;
+ }
+
if (mCachedException != null) {
throw new SignatureException(mCachedException);
}
@@ -337,39 +338,10 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
@Override
protected final boolean engineVerify(byte[] signature) throws SignatureException {
- if (mCachedException != null) {
- throw new SignatureException(mCachedException);
- }
-
- try {
- ensureKeystoreOperationInitialized();
- } catch (InvalidKeyException e) {
- throw new SignatureException(e);
- }
-
- boolean verified;
- try {
- byte[] output = mMessageStreamer.doFinal(
- EmptyArray.BYTE, 0, 0,
- signature);
- if (output.length != 0) {
- throw new ProviderException(
- "Signature verification unexpected produced output: " + output.length
- + " bytes");
- }
- verified = true;
- } catch (KeyStoreException e) {
- switch (e.getErrorCode()) {
- case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED:
- verified = false;
- break;
- default:
- throw new SignatureException(e);
- }
+ if (mSignature != null) {
+ return mSignature.verify(signature);
}
-
- resetWhilePreservingInitState();
- return verified;
+ throw new IllegalStateException("Not initialised.");
}
@Override
@@ -392,6 +364,13 @@ abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi
}
/**
+ * Implementations need to report the algorithm they implement so that we can delegate to the
+ * highest priority provider.
+ * @return Algorithm string.
+ */
+ protected abstract String getAlgorithm();
+
+ /**
* Returns {@code true} if this signature is initialized for signing, {@code false} if this
* signature is initialized for verification.
*/
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index aca531458382..5e7f6482ebed 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -219,7 +219,7 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
return null;
}
- private static X509Certificate toCertificate(byte[] bytes) {
+ static X509Certificate toCertificate(byte[] bytes) {
try {
final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
return (X509Certificate) certFactory.generateCertificate(
@@ -250,13 +250,10 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
return null;
}
-
- // TODO add modification time to key metadata.
- return null;
- // if (response.metadata.modificationTime == -1) {
- // return null;
- // }
- // return new Date(response.metadata.modificationTime);
+ if (response.metadata.modificationTimeMs == -1) {
+ return null;
+ }
+ return new Date(response.metadata.modificationTimeMs);
}
@Override
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
index 4d4b0d8f183b..fd3d28976b2e 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java
@@ -42,7 +42,7 @@ import javax.crypto.spec.IvParameterSpec;
*
* @hide
*/
-class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
+abstract class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase {
abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi {
protected ECB(int keymasterPadding) {
@@ -53,12 +53,22 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
+
+ @Override
+ protected final String getTransform() {
+ return "AES/ECB/NoPadding";
+ }
}
public static class PKCS7Padding extends ECB {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
+
+ @Override
+ protected final String getTransform() {
+ return "AES/ECB/PKCS7Padding";
+ }
}
}
@@ -71,12 +81,22 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
+
+ @Override
+ protected final String getTransform() {
+ return "AES/CBC/NoPadding";
+ }
}
public static class PKCS7Padding extends CBC {
public PKCS7Padding() {
super(KeymasterDefs.KM_PAD_PKCS7);
}
+
+ @Override
+ protected final String getTransform() {
+ return "AES/CBC/PKCS7Padding";
+ }
}
}
@@ -89,6 +109,11 @@ class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSp
public NoPadding() {
super(KeymasterDefs.KM_PAD_NONE);
}
+
+ @Override
+ protected final String getTransform() {
+ return "AES/CTR/NoPadding";
+ }
}
}
diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
index 18c786aa3093..ae2e47503284 100644
--- a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
+++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java
@@ -177,7 +177,7 @@ public abstract class KeyStore2ParameterUtils {
static void forEachSetFlag(int flags, Consumer<Integer> consumer) {
int offset = 0;
while (flags != 0) {
- if ((flags & 1) == 0) {
+ if ((flags & 1) == 1) {
consumer.accept(1 << offset);
}
offset += 1;
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index e7e83ebb001f..9657b25e7c18 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -1865,10 +1865,6 @@ public final class MediaCodecInfo {
&& aligned.mMaxMacroBlockRate >= otherAligned.mMaxMacroBlockRate);
}
- /* package private */ boolean isEqualDimension(@NonNull PerformancePoint other) {
- return mWidth == other.mWidth && mHeight == other.mHeight;
- }
-
private @NonNull Size getCommonBlockSize(@NonNull PerformancePoint other) {
return new Size(
Math.max(mBlockSize.getWidth(), other.mBlockSize.getWidth()) * 16,
@@ -2010,8 +2006,11 @@ public final class MediaCodecInfo {
* codecs are active, should use that highest pixel count, and add the frame rates of
* each individual codec.
* <p class=note>
- * Supported resolution could be further restricted for 32-bit processes due to
- * the limited virtual memory space.
+ * 32-bit processes will not support resolutions larger than 4096x4096 due to
+ * the limited address space, but performance points will be presented as is.
+ * In other words, even though a component publishes a performance point for
+ * a resolution higher than 4096x4096, it does not mean that the resolution is supported
+ * for 32-bit processes.
*/
@Nullable
public List<PerformancePoint> getSupportedPerformancePoints() {
@@ -2215,28 +2214,6 @@ public final class MediaCodecInfo {
(a.getMaxFrameRate() != b.getMaxFrameRate()) ?
(a.getMaxFrameRate() < b.getMaxFrameRate() ? -1 : 1) : 0));
- // remove redundant points
- for (int i = 1; i < ret.size(); ++i) {
- PerformancePoint a = ret.get(i);
- for (int j = 0; j < i; ++j) {
- PerformancePoint b = ret.get(j);
- if (b.isEqualDimension(a) && b.covers(a)) {
- ret.set(i, null);
- break;
- }
- }
- }
- int newSize = 0;
- for (int i = 0; i < ret.size(); ++i) {
- PerformancePoint a = ret.get(i);
- if (a == null) {
- continue;
- }
- ret.set(newSize, a);
- ++newSize;
- }
- ret.setSize(newSize);
-
return Collections.unmodifiableList(ret);
}
diff --git a/mime/OWNERS b/mime/OWNERS
index 6f9dbea36b06..235797998f24 100644
--- a/mime/OWNERS
+++ b/mime/OWNERS
@@ -1 +1,2 @@
+include platform/libcore:/OWNERS
include /core/java/android/os/storage/OWNERS
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 671461bd0b50..e4ba93aa0a2a 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"Greška u konfiguraciji IP-a"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"Niste povezani zbog slabog kvaliteta mreže"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Greška pri povezivanju na WiFi"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problem pri autentifikaciji."</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problem pri autentifikaciji"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"Nije se moguće povezati"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"Nije se moguće povezati na aplikaciju \'<xliff:g id="AP_NAME">%1$s</xliff:g>\'"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Provjerite lozinku i pokušajte ponovo"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 121da7b175f7..19ba5d07e948 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -34,7 +34,7 @@
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"گذرواژه را بررسی و دوباره امتحان کنید"</string>
<string name="wifi_not_in_range" msgid="1541760821805777772">"در محدوده نیست"</string>
<string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"اتصال به‌صورت خودکار انجام نمی‌شود"</string>
- <string name="wifi_no_internet" msgid="1774198889176926299">"بدون دسترسی به اینترنت"</string>
+ <string name="wifi_no_internet" msgid="1774198889176926299">"دسترسی به اینترنت ندارد"</string>
<string name="saved_network" msgid="7143698034077223645">"ذخیره‌شده توسط <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_network_scorer" msgid="7665725527352893558">"‏اتصال خودکار ازطریق %1$s"</string>
<string name="connected_via_network_scorer_default" msgid="7973529709744526285">"اتصال خودکار ازطریق ارائه‌دهنده رتبه‌بندی شبکه"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 6bb1a3a3279c..bdbfbc641067 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -488,7 +488,6 @@
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"Les adresses MAC sont randomisées"</string>
<plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
<item quantity="one">%1$d appareil connecté</item>
- <item quantity="many">%1$d devices connected</item>
<item quantity="other">%1$d appareils connectés</item>
</plurals>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Plus longtemps."</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 8690dd8be5ff..4509b09d4a5e 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"Échec de configuration de l\'adresse IP"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"Non connecté en raison de la faible qualité du réseau"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Échec de la connexion Wi-Fi"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problème d\'authentification."</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problème d\'authentification"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"Connexion impossible"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"Impossible de se connecter au réseau \"<xliff:g id="AP_NAME">%1$s</xliff:g>\""</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Vérifiez le mot de passe et réessayez"</string>
@@ -251,7 +251,7 @@
<string name="wifi_display_certification" msgid="1805579519992520381">"Certification affichage sans fil"</string>
<string name="wifi_verbose_logging" msgid="1785910450009679371">"Autoriser l\'enregistrement d\'infos Wi-Fi détaillées"</string>
<string name="wifi_scan_throttling" msgid="2985624788509913617">"Limiter la recherche Wi‑Fi"</string>
- <string name="wifi_enhanced_mac_randomization" msgid="5437378364995776979">"Chgt aléatoire d\'adresse MAC en Wi-Fi"</string>
+ <string name="wifi_enhanced_mac_randomization" msgid="5437378364995776979">"Changement aléatoire d\'adresse MAC en Wi-Fi"</string>
<string name="mobile_data_always_on" msgid="8275958101875563572">"Données mobiles toujours actives"</string>
<string name="tethering_hardware_offload" msgid="4116053719006939161">"Accélération matérielle pour le partage de connexion"</string>
<string name="bluetooth_show_devices_without_names" msgid="923584526471885819">"Afficher les appareils Bluetooth sans nom"</string>
@@ -488,7 +488,6 @@
<string name="wifi_status_mac_randomized" msgid="466382542497832189">"La sélection des adresses MAC est aléatoire"</string>
<plurals name="wifi_tether_connected_summary" formatted="false" msgid="6317236306047306139">
<item quantity="one">%1$d appareil connecté</item>
- <item quantity="many">%1$d devices connected</item>
<item quantity="other">%1$d appareils connectés</item>
</plurals>
<string name="accessibility_manual_zen_more_time" msgid="5141801092071134235">"Plus longtemps."</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 6e734bc873b8..4ba2961f0b3c 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"IP कॉन्‍फ़िगरेशन की विफलता"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"खराब नेटवर्क होने के कारण कनेक्ट नहीं हुआ"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"वाईफ़ाई कनेक्‍शन विफलता"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"प्रमाणीकरण समस्या"</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"पुष्टि नहीं हो सकी"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"कनेक्ट नहीं हो पा रहा है"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"\'<xliff:g id="AP_NAME">%1$s</xliff:g>\' से कनेक्ट नहीं हो पा रहा है"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"पासवर्ड जाँचें और दोबारा कोशिश करें"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index ec2429be34e2..3e06a3ae4460 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -207,7 +207,7 @@
<string name="enable_adb_summary" msgid="3711526030096574316">"Mode debug ketika USB tersambung"</string>
<string name="clear_adb_keys" msgid="3010148733140369917">"Cabut otorisasi debug USB"</string>
<string name="enable_adb_wireless" msgid="6973226350963971018">"Proses debug nirkabel"</string>
- <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Mode debug saat Wi-Fi tersambung"</string>
+ <string name="enable_adb_wireless_summary" msgid="7344391423657093011">"Mode debug saat Wi-Fi terhubung"</string>
<string name="adb_wireless_error" msgid="721958772149779856">"Error"</string>
<string name="adb_wireless_settings" msgid="2295017847215680229">"Proses debug nirkabel"</string>
<string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Untuk melihat dan menggunakan perangkat yang tersedia, aktifkan proses debug nirkabel"</string>
@@ -225,13 +225,13 @@
<string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Sambungkan dengan perangkat"</string>
<string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Kode penyambungan Wi-Fi"</string>
<string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Penyambungan perangkat gagal"</string>
- <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Pastikan perangkat tersambung ke jaringan yang sama."</string>
- <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Menyambungkan perangkat melalui Wi‑Fi dengan memindai Kode QR"</string>
+ <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Pastikan perangkat terhubung ke jaringan yang sama."</string>
+ <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Sambungkan perangkat melalui Wi‑Fi dengan memindai Kode QR"</string>
<string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Menyambungkan perangkat…"</string>
<string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Gagal menyambungkan perangkat. Kode QR salah, atau perangkat tidak tersambung ke jaringan yang sama."</string>
<string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"Alamat IP &amp; Port"</string>
<string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Memindai kode QR"</string>
- <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Menyambungkan perangkat melalui Wi‑Fi dengan memindai Kode QR"</string>
+ <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Sambungkan perangkat melalui Wi‑Fi dengan memindai Kode QR"</string>
<string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Harap sambungkan ke jaringan Wi-Fi"</string>
<string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, debug, dev"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Pintasan laporan bug"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 3729a8352abd..d62d944caf30 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"IP ပြုပြင်ခြင်း မအောင်မြင်ပါ"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"ကွန်ရက်ချိတ်ဆက်မှု အားနည်းသည့်အတွက် ချိတ်ဆက်ထားခြင်း မရှိပါ"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"WiFi ချိတ်ဆက်မှု မအောင်မြင်ပါ"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"စစ်မှန်ကြောင်းအတည်ပြုရန်၌ ပြသနာရှိခြင်း"</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"အထောက်အထားစိစစ်မှု ပြဿနာ"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"ချိတ်ဆက်၍ မရပါ"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"\'<xliff:g id="AP_NAME">%1$s</xliff:g>\' နှင့် ချိတ်ဆက်၍ မရပါ"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"စကားဝှက်ကို စစ်ဆေးပြီး ထပ်လုပ်ကြည့်ပါ"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 0e0e7617b06d..d567ee240771 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -34,7 +34,7 @@
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Sjekk passordet og prøv igjen"</string>
<string name="wifi_not_in_range" msgid="1541760821805777772">"Utenfor område"</string>
<string name="wifi_no_internet_no_reconnect" msgid="821591791066497347">"Kobler ikke til automatisk"</string>
- <string name="wifi_no_internet" msgid="1774198889176926299">"Ingen Internett-tilgang"</string>
+ <string name="wifi_no_internet" msgid="1774198889176926299">"Ingen internettilgang"</string>
<string name="saved_network" msgid="7143698034077223645">"Lagret av <xliff:g id="NAME">%1$s</xliff:g>"</string>
<string name="connected_via_network_scorer" msgid="7665725527352893558">"Automatisk tilkoblet via %1$s"</string>
<string name="connected_via_network_scorer_default" msgid="7973529709744526285">"Automatisk tilkoblet via leverandør av nettverksvurdering"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index d200f502879e..c9bbe71340d8 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"IP କନଫିଗରେଶନ ବିଫଳ ହୋଇଛି"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"ନିମ୍ନ ମାନର ନେଟ୍‌ୱର୍କ କାରଣରୁ ସଂଯୁକ୍ତ ହୋଇନାହିଁ"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"ୱାଇଫାଇ ସଂଯୋଗ ବିଫଳ ହୋଇଛି"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"ସତ୍ୟାପନରେ ସମସ୍ୟା"</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"ପ୍ରମାଣୀକରଣରେ ସମସ୍ୟା"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"ସଂଯୋଗ କରିପାରିବ ନାହିଁ"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"\'<xliff:g id="AP_NAME">%1$s</xliff:g>\' ସହିତ ସଂଯୁକ୍ତ ହୋଇପାରୁନାହିଁ"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"ପାସ୍‌ୱର୍ଡ ଯାଞ୍ଚ କରନ୍ତୁ ଏବଂ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index cc1626c841b6..737c000f8e9d 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"Zlyhanie konfigurácie adresy IP"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"Nepripojené z dôvodu siete nízkej kvality"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Zlyhanie pripojenia Wi‑Fi"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problém s overením totožnosti"</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problém s overením"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"Nedá sa pripojiť"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"K sieti <xliff:g id="AP_NAME">%1$s</xliff:g> sa nedá pripojiť"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Skontrolujte heslo a skúste to znova"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 5d4e9752fd93..1a2d5e2d1fa9 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"Pagkabigo ng Configuration ng IP"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"Hindi nakakonekta dahil mababa ang kalidad ng network"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Pagkabigo ng Koneksyon sa WiFi"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problema sa pagpapatotoo"</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Problema sa pag-authenticate"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"Hindi makakonekta"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"Hindi makakonekta sa \'<xliff:g id="AP_NAME">%1$s</xliff:g>\'"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Suriin ang password at subukang muli"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 2ae55fe9f263..2325efd619a7 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -28,7 +28,7 @@
<string name="wifi_disabled_network_failure" msgid="2660396183242399585">"IP manzilini sozlab bo‘lmadi"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="1302938248432705534">"Sifatsiz tarmoq sababli ulanib bo‘lmadi"</string>
<string name="wifi_disabled_wifi_failure" msgid="8819554899148331100">"Wi-Fi ulanishini o‘rnatib bo‘lmadi"</string>
- <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Tasdiqdan o‘tishda muammo"</string>
+ <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Tekshiruvda muammo"</string>
<string name="wifi_cant_connect" msgid="5718417542623056783">"Tarmoqqa ulanilmadi"</string>
<string name="wifi_cant_connect_to_ap" msgid="3099667989279700135">"“<xliff:g id="AP_NAME">%1$s</xliff:g>” nomli tarmoqqa ulanilmadi"</string>
<string name="wifi_check_password_try_again" msgid="8817789642851605628">"Parolni tekshirib, qaytadan urining"</string>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 5d6c4c35560c..0fdb282edc23 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -334,6 +334,9 @@
<!-- Permission needed for CTS test - TimeManagerTest -->
<uses-permission android:name="android.permission.MANAGE_TIME_AND_ZONE_DETECTION" />
+ <!-- Permission needed for CTS test - CtsHdmiCecHostTestCases -->
+ <uses-permission android:name="android.permission.HDMI_CEC" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 3135056383c1..414d75b4d7dc 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -876,6 +876,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@VisibleForTesting
public static class Dependencies {
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
/**
* Get system properties to use in ConnectivityService.
*/
@@ -1376,8 +1380,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
return;
}
final String action = blocked ? "BLOCKED" : "UNBLOCKED";
+ final NetworkRequest satisfiedRequest = nri.getSatisfiedRequest();
+ final int requestId = satisfiedRequest != null
+ ? satisfiedRequest.requestId : nri.mRequests.get(0).requestId;
mNetworkInfoBlockingLogs.log(String.format(
- "%s %d(%d) on netId %d", action, nri.mUid, nri.request.requestId, net.getNetId()));
+ "%s %d(%d) on netId %d", action, nri.mUid, requestId, net.getNetId()));
}
/**
@@ -1408,7 +1415,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public NetworkInfo getActiveNetworkInfo() {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
final NetworkState state = getUnfilteredActiveNetworkState(uid);
filterNetworkStateForUid(state, uid, false);
maybeLogBlockedNetworkInfo(state.networkInfo, uid);
@@ -1418,7 +1425,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public Network getActiveNetwork() {
enforceAccessPermission();
- return getActiveNetworkForUidInternal(Binder.getCallingUid(), false);
+ return getActiveNetworkForUidInternal(mDeps.getCallingUid(), false);
}
@Override
@@ -1458,7 +1465,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// Public because it's used by mLockdownTracker.
public NetworkInfo getActiveNetworkInfoUnfiltered() {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
NetworkState state = getUnfilteredActiveNetworkState(uid);
return state.networkInfo;
}
@@ -1474,7 +1481,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public NetworkInfo getNetworkInfo(int networkType) {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
if (getVpnUnderlyingNetworks(uid) != null) {
// A VPN is active, so we may need to return one of its underlying networks. This
// information is not available in LegacyTypeTracker, so we have to get it from
@@ -1519,7 +1526,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public Network getNetworkForType(int networkType) {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
NetworkState state = getFilteredNetworkState(networkType, uid);
if (!isNetworkWithLinkPropertiesBlocked(state.linkProperties, uid, false)) {
return state.network;
@@ -1566,7 +1573,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
result.put(
nai.network,
maybeSanitizeLocationInfoForCaller(
- nc, Binder.getCallingUid(), callingPackageName));
+ nc, mDeps.getCallingUid(), callingPackageName));
}
synchronized (mVpns) {
@@ -1581,7 +1588,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
result.put(
network,
maybeSanitizeLocationInfoForCaller(
- nc, Binder.getCallingUid(), callingPackageName));
+ nc, mDeps.getCallingUid(), callingPackageName));
}
}
}
@@ -1611,7 +1618,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public LinkProperties getActiveLinkProperties() {
enforceAccessPermission();
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
NetworkState state = getUnfilteredActiveNetworkState(uid);
if (state.linkProperties == null) return null;
return linkPropertiesRestrictedForCallerPermissions(state.linkProperties,
@@ -1625,7 +1632,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
final LinkProperties lp = getLinkProperties(nai);
if (lp == null) return null;
return linkPropertiesRestrictedForCallerPermissions(
- lp, Binder.getCallingPid(), Binder.getCallingUid());
+ lp, Binder.getCallingPid(), mDeps.getCallingUid());
}
// TODO - this should be ALL networks
@@ -1635,7 +1642,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
final LinkProperties lp = getLinkProperties(getNetworkAgentInfoForNetwork(network));
if (lp == null) return null;
return linkPropertiesRestrictedForCallerPermissions(
- lp, Binder.getCallingPid(), Binder.getCallingUid());
+ lp, Binder.getCallingPid(), mDeps.getCallingUid());
}
@Nullable
@@ -1657,17 +1664,17 @@ public class ConnectivityService extends IConnectivityManager.Stub
synchronized (nai) {
if (nai.networkCapabilities == null) return null;
return networkCapabilitiesRestrictedForCallerPermissions(
- nai.networkCapabilities, Binder.getCallingPid(), Binder.getCallingUid());
+ nai.networkCapabilities, Binder.getCallingPid(), mDeps.getCallingUid());
}
}
@Override
public NetworkCapabilities getNetworkCapabilities(Network network, String callingPackageName) {
- mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackageName);
+ mAppOpsManager.checkPackage(mDeps.getCallingUid(), callingPackageName);
enforceAccessPermission();
return maybeSanitizeLocationInfoForCaller(
getNetworkCapabilitiesInternal(network),
- Binder.getCallingUid(), callingPackageName);
+ mDeps.getCallingUid(), callingPackageName);
}
@VisibleForTesting
@@ -1756,7 +1763,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) {
- if (!mPermissionMonitor.hasUseBackgroundNetworksPermission(Binder.getCallingUid())) {
+ if (!mPermissionMonitor.hasUseBackgroundNetworksPermission(mDeps.getCallingUid())) {
nc.addCapability(NET_CAPABILITY_FOREGROUND);
}
}
@@ -1809,7 +1816,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
// requestRouteToHost. In Q, GnssLocationProvider is changed to not call requestRouteToHost
// for devices launched with Q and above. However, existing devices upgrading to Q and
// above must continued to be supported for few more releases.
- if (isSystem(Binder.getCallingUid()) && SystemProperties.getInt(
+ if (isSystem(mDeps.getCallingUid()) && SystemProperties.getInt(
"ro.product.first_api_level", 0) > Build.VERSION_CODES.P) {
log("This method exists only for app backwards compatibility"
+ " and must not be called by system services.");
@@ -1875,7 +1882,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
return false;
}
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
LinkProperties lp;
@@ -2295,7 +2302,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@Override
public void systemReady() {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ if (mDeps.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Calling Uid is not system uid.");
}
systemReadyInternal();
@@ -2521,7 +2528,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump " + tag + " from from pid="
- + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + Binder.getCallingPid() + ", uid=" + mDeps.getCallingUid()
+ " due to missing android.permission.DUMP permission");
return false;
} else {
@@ -2702,7 +2709,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
* Return an array of all current NetworkRequest sorted by request id.
*/
@VisibleForTesting
- protected NetworkRequestInfo[] requestsSortedById() {
+ NetworkRequestInfo[] requestsSortedById() {
NetworkRequestInfo[] requests = new NetworkRequestInfo[0];
requests = mNetworkRequests.values().toArray(requests);
// Sort the array based off the NRI containing the min requestId in its requests.
@@ -3552,30 +3559,58 @@ public class ConnectivityService extends IConnectivityManager.Stub
return false;
}
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- if (reason == UnneededFor.LINGER && nri.request.isBackgroundRequest()) {
+ if (reason == UnneededFor.LINGER
+ && !nri.isMultilayerRequest()
+ && nri.mRequests.get(0).isBackgroundRequest()) {
// Background requests don't affect lingering.
continue;
}
- // If this Network is already the highest scoring Network for a request, or if
- // there is hope for it to become one if it validated, then it is needed.
- if (nri.request.isRequest() && nai.satisfies(nri.request) &&
- (nai.isSatisfyingRequest(nri.request.requestId) ||
- // Note that this catches two important cases:
- // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
- // is currently satisfying the request. This is desirable when
- // cellular ends up validating but WiFi does not.
- // 2. Unvalidated WiFi will not be reaped when validated cellular
- // is currently satisfying the request. This is desirable when
- // WiFi ends up validating and out scoring cellular.
- nri.mSatisfier.getCurrentScore()
- < nai.getCurrentScoreAsValidated())) {
+ if (isNetworkPotentialSatisfier(nai, nri)) {
return false;
}
}
return true;
}
+ private boolean isNetworkPotentialSatisfier(
+ @NonNull final NetworkAgentInfo candidate, @NonNull final NetworkRequestInfo nri) {
+ // listen requests won't keep up a network satisfying it. If this is not a multilayer
+ // request, we can return immediately. For multilayer requests, we have to check to see if
+ // any of the multilayer requests may have a potential satisfier.
+ if (!nri.isMultilayerRequest() && nri.mRequests.get(0).isListen()) {
+ return false;
+ }
+ for (final NetworkRequest req : nri.mRequests) {
+ // As non-multilayer listen requests have already returned, the below would only happen
+ // for a multilayer request therefore continue to the next request if available.
+ if (req.isListen()) {
+ continue;
+ }
+ // If this Network is already the highest scoring Network for a request, or if
+ // there is hope for it to become one if it validated, then it is needed.
+ if (candidate.satisfies(req)) {
+ // As soon as a network is found that satisfies a request, return. Specifically for
+ // multilayer requests, returning as soon as a NetworkAgentInfo satisfies a request
+ // is important so as to not evaluate lower priority requests further in
+ // nri.mRequests.
+ final boolean isNetworkNeeded = candidate.isSatisfyingRequest(req.requestId)
+ // Note that this catches two important cases:
+ // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
+ // is currently satisfying the request. This is desirable when
+ // cellular ends up validating but WiFi does not.
+ // 2. Unvalidated WiFi will not be reaped when validated cellular
+ // is currently satisfying the request. This is desirable when
+ // WiFi ends up validating and out scoring cellular.
+ || nri.mSatisfier.getCurrentScore()
+ < candidate.getCurrentScoreAsValidated();
+ return isNetworkNeeded;
+ }
+ }
+
+ return false;
+ }
+
private NetworkRequestInfo getNriForAppRequest(
NetworkRequest request, int callingUid, String requestedOperation) {
final NetworkRequestInfo nri = mNetworkRequests.get(request);
@@ -3905,7 +3940,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
checkNetworkStackPermission();
- nm.forceReevaluation(Binder.getCallingUid());
+ nm.forceReevaluation(mDeps.getCallingUid());
}
}
@@ -4372,7 +4407,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
enforceAccessPermission();
enforceInternetPermission();
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
final int connectivityInfo = encodeBool(hasConnectivity);
// Handle ConnectivityDiagnostics event before attempting to revalidate the network. This
@@ -4442,13 +4477,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (globalProxy != null) return globalProxy;
if (network == null) {
// Get the network associated with the calling UID.
- final Network activeNetwork = getActiveNetworkForUidInternal(Binder.getCallingUid(),
+ final Network activeNetwork = getActiveNetworkForUidInternal(mDeps.getCallingUid(),
true);
if (activeNetwork == null) {
return null;
}
return getLinkPropertiesProxyInfo(activeNetwork);
- } else if (mDeps.queryUserAccess(Binder.getCallingUid(), network.getNetId())) {
+ } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network.getNetId())) {
// Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
// caller may not have.
return getLinkPropertiesProxyInfo(network);
@@ -4617,7 +4652,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@Override
public ParcelFileDescriptor establishVpn(VpnConfig config) {
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
synchronized (mVpns) {
throwIfLockdownEnabled();
return mVpns.get(user).establish(config);
@@ -4638,7 +4673,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@Override
public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) {
- final int user = UserHandle.getUserId(Binder.getCallingUid());
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
synchronized (mVpns) {
return mVpns.get(user).provisionVpnProfile(packageName, profile, mKeyStore);
}
@@ -4656,7 +4691,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@Override
public void deleteVpnProfile(@NonNull String packageName) {
- final int user = UserHandle.getUserId(Binder.getCallingUid());
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
synchronized (mVpns) {
mVpns.get(user).deleteVpnProfile(packageName, mKeyStore);
}
@@ -4673,7 +4708,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@Override
public void startVpnProfile(@NonNull String packageName) {
- final int user = UserHandle.getUserId(Binder.getCallingUid());
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
synchronized (mVpns) {
throwIfLockdownEnabled();
mVpns.get(user).startVpnProfile(packageName, mKeyStore);
@@ -4690,7 +4725,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@Override
public void stopVpnProfile(@NonNull String packageName) {
- final int user = UserHandle.getUserId(Binder.getCallingUid());
+ final int user = UserHandle.getUserId(mDeps.getCallingUid());
synchronized (mVpns) {
mVpns.get(user).stopVpnProfile(packageName);
}
@@ -4702,7 +4737,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
@Override
public void startLegacyVpn(VpnProfile profile) {
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
final LinkProperties egress = getActiveLinkProperties();
if (egress == null) {
throw new IllegalStateException("Missing active network connection");
@@ -4851,7 +4886,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public boolean updateLockdownVpn() {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ if (mDeps.getCallingUid() != Process.SYSTEM_UID) {
logw("Lockdown VPN only available to AID_SYSTEM");
return false;
}
@@ -4873,7 +4908,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
setLockdownTracker(null);
return true;
}
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
Vpn vpn = mVpns.get(user);
if (vpn == null) {
logw("VPN for user " + user + " not ready yet. Skipping lockdown");
@@ -5438,7 +5473,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
messenger = null;
mBinder = null;
mPid = getCallingPid();
- mUid = getCallingUid();
+ mUid = mDeps.getCallingUid();
enforceRequestCountLimit();
}
@@ -5450,7 +5485,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
ensureAllNetworkRequestsHaveType(mRequests);
mBinder = binder;
mPid = getCallingPid();
- mUid = getCallingUid();
+ mUid = mDeps.getCallingUid();
mPendingIntent = null;
enforceRequestCountLimit();
@@ -5465,6 +5500,10 @@ public class ConnectivityService extends IConnectivityManager.Stub
this(r, null);
}
+ boolean isMultilayerRequest() {
+ return mRequests.size() > 1;
+ }
+
private List<NetworkRequest> initializeRequests(NetworkRequest r) {
final ArrayList<NetworkRequest> tempRequests = new ArrayList<>();
tempRequests.add(new NetworkRequest(r));
@@ -5506,7 +5545,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
public void binderDied() {
log("ConnectivityService NetworkRequestInfo binderDied(" +
mRequests + ", " + mBinder + ")");
- releaseNetworkRequest(mRequests);
+ releaseNetworkRequests(mRequests);
}
@Override
@@ -5539,13 +5578,15 @@ public class ConnectivityService extends IConnectivityManager.Stub
mAppOpsManager.checkPackage(callerUid, callerPackageName);
}
- private ArrayList<Integer> getSignalStrengthThresholds(NetworkAgentInfo nai) {
+ private ArrayList<Integer> getSignalStrengthThresholds(@NonNull final NetworkAgentInfo nai) {
final SortedSet<Integer> thresholds = new TreeSet<>();
synchronized (nai) {
- for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- if (nri.request.networkCapabilities.hasSignalStrength() &&
- nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
- thresholds.add(nri.request.networkCapabilities.getSignalStrength());
+ for (final NetworkRequestInfo nri : mNetworkRequests.values()) {
+ for (final NetworkRequest req : nri.mRequests) {
+ if (req.networkCapabilities.hasSignalStrength()
+ && nai.satisfiesImmutableCapabilitiesOf(req)) {
+ thresholds.add(req.networkCapabilities.getSignalStrength());
+ }
}
}
}
@@ -5593,7 +5634,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private boolean checkUnsupportedStartingFrom(int version, String callingPackageName) {
- final UserHandle user = UserHandle.getUserHandleForUid(Binder.getCallingUid());
+ final UserHandle user = UserHandle.getUserHandleForUid(mDeps.getCallingUid());
final PackageManager pm =
mContext.createContextAsUser(user, 0 /* flags */).getPackageManager();
try {
@@ -5613,7 +5654,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
throw new SecurityException("Insufficient permissions to specify legacy type");
}
}
- final int callingUid = Binder.getCallingUid();
+ final int callingUid = mDeps.getCallingUid();
final NetworkRequest.Type type = (networkCapabilities == null)
? NetworkRequest.Type.TRACK_DEFAULT
: NetworkRequest.Type.REQUEST;
@@ -5683,7 +5724,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
if (nai != null) {
nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE);
synchronized (mBandwidthRequests) {
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
Integer uidReqs = mBandwidthRequests.get(uid);
if (uidReqs == null) {
uidReqs = 0;
@@ -5700,7 +5741,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
if (isSystem(uid)) {
// Exemption for system uid.
return;
@@ -5720,7 +5761,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
PendingIntent operation, @NonNull String callingPackageName,
@Nullable String callingAttributionTag) {
Objects.requireNonNull(operation, "PendingIntent cannot be null.");
- final int callingUid = Binder.getCallingUid();
+ final int callingUid = mDeps.getCallingUid();
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities, callingPackageName,
callingAttributionTag);
@@ -5779,7 +5820,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder, @NonNull String callingPackageName) {
- final int callingUid = Binder.getCallingUid();
+ final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
@@ -5809,7 +5850,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
PendingIntent operation, @NonNull String callingPackageName) {
Objects.requireNonNull(operation, "PendingIntent cannot be null.");
- final int callingUid = Binder.getCallingUid();
+ final int callingUid = mDeps.getCallingUid();
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
@@ -5832,7 +5873,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
return mNextNetworkProviderId.getAndIncrement();
}
- private void releaseNetworkRequest(List<NetworkRequest> networkRequests) {
+ private void releaseNetworkRequests(List<NetworkRequest> networkRequests) {
for (int i = 0; i < networkRequests.size(); i++) {
releaseNetworkRequest(networkRequests.get(i));
}
@@ -5910,7 +5951,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
} else {
enforceNetworkFactoryPermission();
}
- mHandler.post(() -> handleReleaseNetworkRequest(request, Binder.getCallingUid(), true));
+ mHandler.post(() -> handleReleaseNetworkRequest(request, mDeps.getCallingUid(), true));
}
// NOTE: Accessed on multiple threads, must be synchronized on itself.
@@ -6004,7 +6045,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
enforceNetworkFactoryPermission();
}
- final int uid = Binder.getCallingUid();
+ final int uid = mDeps.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
return registerNetworkAgentInternal(messenger, networkInfo, linkProperties,
@@ -7658,7 +7699,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public boolean addVpnAddress(String address, int prefixLength) {
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
synchronized (mVpns) {
throwIfLockdownEnabled();
return mVpns.get(user).addAddress(address, prefixLength);
@@ -7667,7 +7708,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public boolean removeVpnAddress(String address, int prefixLength) {
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
synchronized (mVpns) {
throwIfLockdownEnabled();
return mVpns.get(user).removeAddress(address, prefixLength);
@@ -7676,7 +7717,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@Override
public boolean setUnderlyingNetworksForVpn(Network[] networks) {
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ int user = UserHandle.getUserId(mDeps.getCallingUid());
final boolean success;
synchronized (mVpns) {
throwIfLockdownEnabled();
@@ -7906,7 +7947,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
@GuardedBy("mVpns")
private Vpn getVpnIfOwner() {
- return getVpnIfOwner(Binder.getCallingUid());
+ return getVpnIfOwner(mDeps.getCallingUid());
}
@GuardedBy("mVpns")
@@ -8384,7 +8425,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated."
+ " Please use NetworkCapabilities instead.");
}
- final int callingUid = Binder.getCallingUid();
+ final int callingUid = mDeps.getCallingUid();
mAppOpsManager.checkPackage(callingUid, callingPackageName);
// This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid
@@ -8419,7 +8460,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
mConnectivityDiagnosticsHandler.obtainMessage(
ConnectivityDiagnosticsHandler
.EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK,
- Binder.getCallingUid(),
+ mDeps.getCallingUid(),
0,
callback));
}
@@ -8435,7 +8476,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
}
final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- if (nai == null || nai.creatorUid != Binder.getCallingUid()) {
+ if (nai == null || nai.creatorUid != mDeps.getCallingUid()) {
throw new SecurityException("Data Stall simulation is only possible for network "
+ "creators");
}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 165b6a1b08f1..e9f17fff5a61 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -25,13 +25,22 @@ import android.net.NetworkProvider;
import android.net.NetworkRequest;
import android.net.vcn.IVcnManagementService;
import android.net.vcn.VcnConfig;
+import android.os.Binder;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.ParcelUuid;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* VcnManagementService manages Virtual Carrier Network profiles and lifecycles.
*
@@ -130,6 +139,18 @@ public class VcnManagementService extends IVcnManagementService.Stub {
}
return mHandlerThread.getLooper();
}
+
+ /**
+ * Retrieves the caller's UID
+ *
+ * <p>This call MUST be made before calling {@link Binder#clearCallingIdentity}, otherwise
+ * this will not work properly.
+ *
+ * @return
+ */
+ public int getBinderCallingUid() {
+ return Binder.getCallingUid();
+ }
}
/** Notifies the VcnManagementService that external dependencies can be set up. */
@@ -140,6 +161,50 @@ public class VcnManagementService extends IVcnManagementService.Stub {
.registerNetworkProvider(mNetworkProvider);
}
+ private void enforcePrimaryUser() {
+ final int uid = mDeps.getBinderCallingUid();
+ if (uid == Process.SYSTEM_UID) {
+ throw new IllegalStateException(
+ "Calling identity was System Server. Was Binder calling identity cleared?");
+ }
+
+ if (!UserHandle.getUserHandleForUid(uid).isSystem()) {
+ throw new SecurityException(
+ "VcnManagementService can only be used by callers running as the primary user");
+ }
+ }
+
+ private void enforceCallingUserAndCarrierPrivilege(ParcelUuid subscriptionGroup) {
+ // Only apps running in the primary (system) user are allowed to configure the VCN. This is
+ // in line with Telephony's behavior with regards to binding to a Carrier App provided
+ // CarrierConfigService.
+ enforcePrimaryUser();
+
+ // TODO (b/172619301): Check based on events propagated from CarrierPrivilegesTracker
+ final SubscriptionManager subMgr = mContext.getSystemService(SubscriptionManager.class);
+ final List<SubscriptionInfo> subscriptionInfos = new ArrayList<>();
+ Binder.withCleanCallingIdentity(
+ () -> {
+ subscriptionInfos.addAll(subMgr.getSubscriptionsInGroup(subscriptionGroup));
+ });
+
+ final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);
+ for (SubscriptionInfo info : subscriptionInfos) {
+ // Check subscription is active first; much cheaper/faster check, and an app (currently)
+ // cannot be carrier privileged for inactive subscriptions.
+ if (subMgr.isValidSlotIndex(info.getSimSlotIndex())
+ && telMgr.hasCarrierPrivileges(info.getSubscriptionId())) {
+ // TODO (b/173717728): Allow configuration for inactive, but manageable
+ // subscriptions.
+ // TODO (b/173718661): Check for whole subscription groups at a time.
+ return;
+ }
+ }
+
+ throw new SecurityException(
+ "Carrier privilege required for subscription group to set VCN Config");
+ }
+
/**
* Sets a VCN config for a given subscription group.
*
@@ -150,6 +215,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
requireNonNull(config, "config was null");
+ enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+
+ // TODO: Clear Binder calling identity
+
// TODO: Store VCN configuration, trigger startup as necessary
}
@@ -162,6 +231,10 @@ public class VcnManagementService extends IVcnManagementService.Stub {
public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+ enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+
+ // TODO: Clear Binder calling identity
+
// TODO: Clear VCN configuration, trigger teardown as necessary
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 7795ed38a74f..3d71b0a269c9 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -334,7 +334,13 @@ public class NetworkNotificationManager {
*/
public void setProvNotificationVisible(boolean visible, int id, String action) {
if (visible) {
- Intent intent = new Intent(action);
+ // For legacy purposes, action is sent as the action + the phone ID from DcTracker.
+ // Split the string here and send the phone ID as an extra instead.
+ String[] splitAction = action.split(":");
+ Intent intent = new Intent(splitAction[0]);
+ try {
+ intent.putExtra("provision.phone.id", Integer.parseInt(splitAction[1]));
+ } catch (NumberFormatException ignored) { }
PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_IMMUTABLE);
showNotification(id, NotificationType.SIGN_IN, null, null, pendingIntent, false);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index cabfbc02491c..228ad588525f 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -246,7 +246,12 @@ public class Vpn {
void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException;
}
- static class Dependencies {
+ @VisibleForTesting
+ public static class Dependencies {
+ public boolean isCallerSystem() {
+ return Binder.getCallingUid() == Process.SYSTEM_UID;
+ }
+
public void startService(final String serviceName) {
SystemService.start(serviceName);
}
@@ -267,6 +272,10 @@ public class Vpn {
return new File("/data/misc/vpn/state");
}
+ public DeviceIdleInternal getDeviceIdleInternal() {
+ return LocalServices.getService(DeviceIdleInternal.class);
+ }
+
public void sendArgumentsToDaemon(
final String daemon, final LocalSocket socket, final String[] arguments,
final RetryScheduler retryScheduler) throws IOException, InterruptedException {
@@ -373,6 +382,14 @@ public class Vpn {
}
@VisibleForTesting
+ public Vpn(Looper looper, Context context, Dependencies deps,
+ INetworkManagementService netService, INetd netd, @UserIdInt int userId,
+ @NonNull KeyStore keyStore) {
+ this(looper, context, deps, netService, netd, userId, keyStore,
+ new SystemServices(context), new Ikev2SessionCreator());
+ }
+
+ @VisibleForTesting
protected Vpn(Looper looper, Context context, Dependencies deps,
INetworkManagementService netService, INetd netd,
int userId, @NonNull KeyStore keyStore, SystemServices systemServices,
@@ -772,8 +789,7 @@ public class Vpn {
// Tell the OS that background services in this app need to be allowed for
// a short time, so we can bootstrap the VPN service.
- DeviceIdleInternal idleController =
- LocalServices.getService(DeviceIdleInternal.class);
+ DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal();
idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
VPN_LAUNCH_IDLE_ALLOWLIST_DURATION_MS, mUserId, false, "vpn");
@@ -1959,10 +1975,6 @@ public class Vpn {
return mContext.createContextAsUser(
UserHandle.of(userId), 0 /* flags */).getContentResolver();
}
-
- public boolean isCallerSystem() {
- return Binder.getCallingUid() == Process.SYSTEM_UID;
- }
}
private native int jniCreate(int mtu);
@@ -3112,7 +3124,7 @@ public class Vpn {
@VisibleForTesting
@Nullable
VpnProfile getVpnProfilePrivileged(@NonNull String packageName, @NonNull KeyStore keyStore) {
- if (!mSystemServices.isCallerSystem()) {
+ if (!mDeps.isCallerSystem()) {
Log.wtf(TAG, "getVpnProfilePrivileged called as non-System UID ");
return null;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 535284083d11..0fdb8798af6f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -26,6 +26,8 @@ import android.hardware.tv.cec.V1_0.IHdmiCec.getPhysicalAddressCallback;
import android.hardware.tv.cec.V1_0.IHdmiCecCallback;
import android.hardware.tv.cec.V1_0.Result;
import android.hardware.tv.cec.V1_0.SendMessageResult;
+import android.icu.util.IllformedLocaleException;
+import android.icu.util.ULocale;
import android.os.Handler;
import android.os.IHwBinder;
import android.os.Looper;
@@ -33,6 +35,7 @@ import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.hdmi.HdmiAnnotations.IoThreadOnly;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -48,8 +51,6 @@ import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.function.Predicate;
-import sun.util.locale.LanguageTag;
-
/**
* Manages HDMI-CEC command and behaviors. It converts user's command into CEC command
* and pass it to CEC HAL so that it sends message to other device. For incoming
@@ -369,13 +370,31 @@ final class HdmiCecController {
@ServiceThreadOnly
void setLanguage(String language) {
assertRunOnServiceThread();
- if (!LanguageTag.isLanguage(language)) {
+ if (!isLanguage(language)) {
return;
}
mNativeWrapperImpl.nativeSetLanguage(language);
}
/**
+ * Returns true if the language code is well-formed.
+ */
+ @VisibleForTesting static boolean isLanguage(String language) {
+ // Handle null and empty string because because ULocale.Builder#setLanguage accepts them.
+ if (language == null || language.isEmpty()) {
+ return false;
+ }
+
+ ULocale.Builder builder = new ULocale.Builder();
+ try {
+ builder.setLanguage(language);
+ return true;
+ } catch (IllformedLocaleException e) {
+ return false;
+ }
+ }
+
+ /**
* Configure ARC circuit in the hardware logic to start or stop the feature.
*
* @param port ID of HDMI port to which AVR is connected
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e2145f077a90..8e50bb4885d8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -66,6 +66,8 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -2291,6 +2293,16 @@ public class HdmiControlService extends SystemService {
}
@Override
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err, String[] args,
+ @Nullable ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ enforceAccessPermission();
+ new HdmiControlShellCommand(this)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
new file mode 100644
index 000000000000..ee3427f0a383
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiControlShellCommand.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 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.hdmi;
+
+
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiControlService;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class HdmiControlShellCommand extends ShellCommand {
+
+ private static final String TAG = "HdmiShellCommand";
+
+ private final IHdmiControlService.Stub mBinderService;
+
+
+ HdmiControlShellCommand(IHdmiControlService.Stub binderService) {
+ mBinderService = binderService;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(cmd);
+ }
+
+ try {
+ return handleShellCommand(cmd);
+ } catch (Exception e) {
+ getErrPrintWriter().println(
+ "Caught error for command '" + cmd + "': " + e.getMessage());
+ Slog.e(TAG, "Error handling hdmi_control shell command: " + cmd, e);
+ return 1;
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+
+ pw.println("HdmiControlManager (hdmi_control) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" onetouchplay, otp");
+ pw.println(" Send the \"One Touch Play\" feature from a source to the TV");
+ pw.println(" vendorcommand --device_type <originating device type>");
+ pw.println(" --destination <destination device>");
+ pw.println(" --args <vendor specific arguments>");
+ pw.println(" [--id <true if vendor command should be sent with vendor id>]");
+ pw.println(" Send a Vendor Command to the given target device");
+ }
+
+ private int handleShellCommand(String cmd) throws RemoteException {
+ PrintWriter pw = getOutPrintWriter();
+
+ switch (cmd) {
+ case "otp":
+ case "onetouchplay":
+ return oneTouchPlay(pw);
+ case "vendorcommand":
+ return vendorCommand(pw);
+ }
+
+ getErrPrintWriter().println("Unhandled command: " + cmd);
+ return 1;
+ }
+
+ private int oneTouchPlay(PrintWriter pw) throws RemoteException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ AtomicInteger cecResult = new AtomicInteger();
+ pw.print("Sending One Touch Play...");
+ mBinderService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ pw.println(" done (" + result + ")");
+ latch.countDown();
+ cecResult.set(result);
+ }
+ });
+
+ try {
+ if (!latch.await(HdmiConfig.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ getErrPrintWriter().println("One Touch Play timed out.");
+ return 1;
+ }
+ } catch (InterruptedException e) {
+ getErrPrintWriter().println("Caught InterruptedException");
+ Thread.currentThread().interrupt();
+ }
+ return cecResult.get() == HdmiControlManager.RESULT_SUCCESS ? 0 : 1;
+ }
+
+ private int vendorCommand(PrintWriter pw) throws RemoteException {
+ if (6 > getRemainingArgsCount()) {
+ throw new IllegalArgumentException("Expected 3 arguments.");
+ }
+
+ int deviceType = -1;
+ int destination = -1;
+ String parameters = "";
+ boolean hasVendorId = false;
+
+ String arg = getNextOption();
+ while (arg != null) {
+ switch (arg) {
+ case "-t":
+ case "--device_type":
+ deviceType = Integer.parseInt(getNextArgRequired());
+ break;
+ case "-d":
+ case "--destination":
+ destination = Integer.parseInt(getNextArgRequired());
+ break;
+ case "-a":
+ case "--args":
+ parameters = getNextArgRequired();
+ break;
+ case "-i":
+ case "--id":
+ hasVendorId = Boolean.parseBoolean(getNextArgRequired());
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown argument: " + arg);
+ }
+ arg = getNextArg();
+ }
+
+ String[] parts = parameters.split(":");
+ byte[] params = new byte[parts.length];
+ for (int i = 0; i < params.length; i++) {
+ params[i] = (byte) Integer.parseInt(parts[i], 16);
+ }
+
+ pw.println("Sending <Vendor Command>");
+ mBinderService.sendVendorCommand(deviceType, destination, params, hasVendorId);
+ return 0;
+ }
+}
diff --git a/services/core/java/com/android/server/policy/OWNERS b/services/core/java/com/android/server/policy/OWNERS
index d25ec4a560a3..8887e401211a 100644
--- a/services/core/java/com/android/server/policy/OWNERS
+++ b/services/core/java/com/android/server/policy/OWNERS
@@ -1,2 +1,3 @@
include /services/core/java/com/android/server/wm/OWNERS
include /services/core/java/com/android/server/input/OWNERS
+include /services/core/java/com/android/server/pm/permission/OWNERS
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index e0701e867cad..990055ebda9a 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -16,8 +16,10 @@
package com.android.server.recoverysystem;
+import android.annotation.IntDef;
import android.content.Context;
import android.content.IntentSender;
+import android.content.pm.PackageManager;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.os.Binder;
@@ -32,6 +34,7 @@ import android.os.ShellCallback;
import android.os.SystemProperties;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.RebootEscrowListener;
@@ -46,6 +49,10 @@ import java.io.FileDescriptor;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
/**
* The recovery system service is responsible for coordinating recovery related
@@ -76,9 +83,53 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
private final Injector mInjector;
private final Context mContext;
- private boolean mPreparedForReboot;
- private String mUnattendedRebootToken;
- private IntentSender mPreparedForRebootIntentSender;
+ @GuardedBy("this")
+ private final Map<String, IntentSender> mCallerPendingRequest = new HashMap<>();
+ @GuardedBy("this")
+ private final Set<String> mCallerPreparedForReboot = new HashSet<>();
+
+ /**
+ * Need to prepare for resume on reboot.
+ */
+ private static final int ROR_NEED_PREPARATION = 0;
+ /**
+ * Resume on reboot has been prepared, notify the caller.
+ */
+ private static final int ROR_SKIP_PREPARATION_AND_NOTIFY = 1;
+ /**
+ * Resume on reboot has been requested. Caller won't be notified until the preparation is done.
+ */
+ private static final int ROR_SKIP_PREPARATION_NOT_NOTIFY = 2;
+
+ /**
+ * The caller never requests for resume on reboot, no need for clear.
+ */
+ private static final int ROR_NOT_REQUESTED = 0;
+ /**
+ * Clear the resume on reboot preparation state.
+ */
+ private static final int ROR_REQUESTED_NEED_CLEAR = 1;
+ /**
+ * The caller has requested for resume on reboot. No need for clear since other callers may
+ * exist.
+ */
+ private static final int ROR_REQUESTED_SKIP_CLEAR = 2;
+
+ /**
+ * The action to perform upon new resume on reboot prepare request for a given client.
+ */
+ @IntDef({ ROR_NEED_PREPARATION,
+ ROR_SKIP_PREPARATION_AND_NOTIFY,
+ ROR_SKIP_PREPARATION_NOT_NOTIFY })
+ @interface ResumeOnRebootActionsOnRequest {}
+
+ /**
+ * The action to perform upon resume on reboot clear request for a given client.
+ */
+ @IntDef({ROR_NOT_REQUESTED,
+ ROR_REQUESTED_NEED_CLEAR,
+ ROR_REQUESTED_SKIP_CLEAR})
+ @interface ResumeOnRebootActionsOnClear{}
static class Injector {
protected final Context mContext;
@@ -286,47 +337,92 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
}
}
+ private void enforcePermissionForResumeOnReboot() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.RECOVERY)
+ != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(android.Manifest.permission.REBOOT)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller or self must have "
+ + android.Manifest.permission.RECOVERY + " or "
+ + android.Manifest.permission.REBOOT + " for resume on reboot.");
+ }
+ }
+
@Override // Binder call
- public boolean requestLskf(String updateToken, IntentSender intentSender) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+ public boolean requestLskf(String packageName, IntentSender intentSender) {
+ enforcePermissionForResumeOnReboot();
- if (updateToken == null) {
+ if (packageName == null) {
+ Slog.w(TAG, "Missing packageName when requesting lskf.");
return false;
}
- // No need to prepare again for the same token.
- if (mPreparedForReboot && updateToken.equals(mUnattendedRebootToken)) {
- return true;
+ @ResumeOnRebootActionsOnRequest int action = updateRoRPreparationStateOnNewRequest(
+ packageName, intentSender);
+ switch (action) {
+ case ROR_SKIP_PREPARATION_AND_NOTIFY:
+ // We consider the preparation done if someone else has prepared.
+ sendPreparedForRebootIntentIfNeeded(intentSender);
+ return true;
+ case ROR_SKIP_PREPARATION_NOT_NOTIFY:
+ return true;
+ case ROR_NEED_PREPARATION:
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ mInjector.getLockSettingsService().prepareRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ return true;
+ default:
+ throw new IllegalStateException("Unsupported action type on new request " + action);
}
+ }
- mPreparedForReboot = false;
- mUnattendedRebootToken = updateToken;
- mPreparedForRebootIntentSender = intentSender;
+ // Checks and updates the resume on reboot preparation state.
+ private synchronized @ResumeOnRebootActionsOnRequest int updateRoRPreparationStateOnNewRequest(
+ String packageName, IntentSender intentSender) {
+ if (!mCallerPreparedForReboot.isEmpty()) {
+ if (mCallerPreparedForReboot.contains(packageName)) {
+ Slog.i(TAG, "RoR already has prepared for " + packageName);
+ }
- final long origId = Binder.clearCallingIdentity();
- try {
- mInjector.getLockSettingsService().prepareRebootEscrow();
- } finally {
- Binder.restoreCallingIdentity(origId);
+ // Someone else has prepared. Consider the preparation done, and send back the intent.
+ mCallerPreparedForReboot.add(packageName);
+ return ROR_SKIP_PREPARATION_AND_NOTIFY;
}
- return true;
+ boolean needPreparation = mCallerPendingRequest.isEmpty();
+ if (mCallerPendingRequest.containsKey(packageName)) {
+ Slog.i(TAG, "Duplicate RoR preparation request for " + packageName);
+ }
+ // Update the request with the new intentSender.
+ mCallerPendingRequest.put(packageName, intentSender);
+ return needPreparation ? ROR_NEED_PREPARATION : ROR_SKIP_PREPARATION_NOT_NOTIFY;
}
@Override
public void onPreparedForReboot(boolean ready) {
- if (mUnattendedRebootToken == null) {
- Slog.w(TAG, "onPreparedForReboot called when mUnattendedRebootToken is null");
+ if (!ready) {
+ return;
+ }
+ updateRoRPreparationStateOnPreparedForReboot();
+ }
+
+ private synchronized void updateRoRPreparationStateOnPreparedForReboot() {
+ if (!mCallerPreparedForReboot.isEmpty()) {
+ Slog.w(TAG, "onPreparedForReboot called when some clients have prepared.");
}
- mPreparedForReboot = ready;
- if (ready) {
- sendPreparedForRebootIntentIfNeeded();
+ // Send intents to notify callers
+ for (Map.Entry<String, IntentSender> entry : mCallerPendingRequest.entrySet()) {
+ sendPreparedForRebootIntentIfNeeded(entry.getValue());
+ mCallerPreparedForReboot.add(entry.getKey());
}
+ mCallerPendingRequest.clear();
}
- private void sendPreparedForRebootIntentIfNeeded() {
- final IntentSender intentSender = mPreparedForRebootIntentSender;
+ private void sendPreparedForRebootIntentIfNeeded(IntentSender intentSender) {
if (intentSender != null) {
try {
intentSender.sendIntent(null, 0, null, null, null);
@@ -337,37 +433,61 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
}
@Override // Binder call
- public boolean clearLskf() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+ public boolean clearLskf(String packageName) {
+ enforcePermissionForResumeOnReboot();
+ if (packageName == null) {
+ Slog.w(TAG, "Missing packageName when clearing lskf.");
+ return false;
+ }
- mPreparedForReboot = false;
- mUnattendedRebootToken = null;
- mPreparedForRebootIntentSender = null;
+ @ResumeOnRebootActionsOnClear int action = updateRoRPreparationStateOnClear(packageName);
+ switch (action) {
+ case ROR_NOT_REQUESTED:
+ Slog.w(TAG, "RoR clear called before preparation for caller " + packageName);
+ return true;
+ case ROR_REQUESTED_SKIP_CLEAR:
+ return true;
+ case ROR_REQUESTED_NEED_CLEAR:
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ mInjector.getLockSettingsService().clearRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ return true;
+ default:
+ throw new IllegalStateException("Unsupported action type on clear " + action);
+ }
+ }
- final long origId = Binder.clearCallingIdentity();
- try {
- mInjector.getLockSettingsService().clearRebootEscrow();
- } finally {
- Binder.restoreCallingIdentity(origId);
+ private synchronized @ResumeOnRebootActionsOnClear int updateRoRPreparationStateOnClear(
+ String packageName) {
+ if (!mCallerPreparedForReboot.contains(packageName) && !mCallerPendingRequest.containsKey(
+ packageName)) {
+ Slog.w(TAG, packageName + " hasn't prepared for resume on reboot");
+ return ROR_NOT_REQUESTED;
}
+ mCallerPendingRequest.remove(packageName);
+ mCallerPreparedForReboot.remove(packageName);
- return true;
+ // Check if others have prepared ROR.
+ boolean needClear = mCallerPendingRequest.isEmpty() && mCallerPreparedForReboot.isEmpty();
+ return needClear ? ROR_REQUESTED_NEED_CLEAR : ROR_REQUESTED_SKIP_CLEAR;
}
@Override // Binder call
- public boolean rebootWithLskf(String updateToken, String reason) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
-
- if (!mPreparedForReboot) {
- Slog.i(TAG, "Reboot requested before prepare completed");
+ public boolean rebootWithLskf(String packageName, String reason, boolean slotSwitch) {
+ enforcePermissionForResumeOnReboot();
+ if (packageName == null) {
+ Slog.w(TAG, "Missing packageName when rebooting with lskf.");
return false;
}
-
- if (updateToken != null && !updateToken.equals(mUnattendedRebootToken)) {
- Slog.i(TAG, "Reboot requested after preparation, but with mismatching token");
+ if (!isLskfCaptured(packageName)) {
return false;
}
+ // TODO(xunchang) check the slot to boot into, and fail the reboot upon slot mismatch.
+ // TODO(xunchang) write the vbmeta digest along with the escrowKey before reboot.
if (!mInjector.getLockSettingsService().armRebootEscrow()) {
Slog.w(TAG, "Failure to escrow key for reboot");
return false;
@@ -378,6 +498,16 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
return true;
}
+ @Override // Binder call
+ public synchronized boolean isLskfCaptured(String packageName) {
+ enforcePermissionForResumeOnReboot();
+ if (!mCallerPreparedForReboot.contains(packageName)) {
+ Slog.i(TAG, "Reboot requested before prepare completed for caller " + packageName);
+ return false;
+ }
+ return true;
+ }
+
/**
* Check if any of the init services is still running. If so, we cannot
* start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
index c6905b5c7dd2..f20d80d57476 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemShellCommand.java
@@ -56,26 +56,31 @@ public class RecoverySystemShellCommand extends ShellCommand {
}
private int requestLskf() throws RemoteException {
- String updateToken = getNextArgRequired();
- boolean success = mService.requestLskf(updateToken, null);
+ String packageName = getNextArgRequired();
+ boolean success = mService.requestLskf(packageName, null);
PrintWriter pw = getOutPrintWriter();
- pw.println("Request LSKF status: " + (success ? "success" : "failure"));
+ pw.printf("Request LSKF for packageName: %s, status: %s\n", packageName,
+ success ? "success" : "failure");
return 0;
}
private int clearLskf() throws RemoteException {
- boolean success = mService.clearLskf();
+ String packageName = getNextArgRequired();
+ boolean success = mService.clearLskf(packageName);
PrintWriter pw = getOutPrintWriter();
- pw.println("Clear LSKF: " + (success ? "success" : "failure"));
+ pw.printf("Clear LSKF for packageName: %s, status: %s\n", packageName,
+ success ? "success" : "failure");
return 0;
}
private int rebootAndApply() throws RemoteException {
- String updateToken = getNextArgRequired();
+ String packageName = getNextArgRequired();
String rebootReason = getNextArgRequired();
- boolean success = mService.rebootWithLskf(updateToken, rebootReason);
+ boolean success = mService.rebootWithLskf(packageName, rebootReason, true);
PrintWriter pw = getOutPrintWriter();
- pw.println("Reboot and apply status: " + (success ? "success" : "failure"));
+ // Keep the old message for cts test.
+ pw.printf("%s Reboot and apply status: %s\n", packageName,
+ success ? "success" : "failure");
return 0;
}
diff --git a/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
new file mode 100644
index 000000000000..c0608072df9d
--- /dev/null
+++ b/services/core/java/com/android/server/vcn/TelephonySubscriptionTracker.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
+import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.ParcelUuid;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * TelephonySubscriptionTracker provides a caching layer for tracking active subscription groups.
+ *
+ * <p>This class performs two roles:
+ *
+ * <ol>
+ * <li>De-noises subscription changes by ensuring that only changes in active and ready
+ * subscription groups are acted upon
+ * <li>Caches mapping between subIds and subscription groups
+ * </ol>
+ *
+ * <p>An subscription group is active and ready if any of its contained subIds has had BOTH the
+ * {@link CarrierConfigManager#isConfigForIdentifiedCarrier()} return true, AND the subscription is
+ * listed as active per SubscriptionManager#getAllSubscriptionInfoList().
+ *
+ * <p>Note that due to the asynchronous nature of callbacks and broadcasts, the output of this class
+ * is (only) eventually consistent.
+ *
+ * @hide
+ */
+public class TelephonySubscriptionTracker extends BroadcastReceiver {
+ @NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName();
+ private static final boolean LOG_DBG = false; // STOPSHIP if true
+
+ @NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final TelephonySubscriptionTrackerCallback mCallback;
+ @NonNull private final Dependencies mDeps;
+
+ @NonNull private final SubscriptionManager mSubscriptionManager;
+ @NonNull private final CarrierConfigManager mCarrierConfigManager;
+
+ // TODO (Android T+): Add ability to handle multiple subIds per slot.
+ @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>();
+ @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener;
+
+ @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot;
+
+ public TelephonySubscriptionTracker(
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull TelephonySubscriptionTrackerCallback callback) {
+ this(context, handler, callback, new Dependencies());
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ TelephonySubscriptionTracker(
+ @NonNull Context context,
+ @NonNull Handler handler,
+ @NonNull TelephonySubscriptionTrackerCallback callback,
+ @NonNull Dependencies deps) {
+ mContext = Objects.requireNonNull(context, "Missing context");
+ mHandler = Objects.requireNonNull(handler, "Missing handler");
+ mCallback = Objects.requireNonNull(callback, "Missing callback");
+ mDeps = Objects.requireNonNull(deps, "Missing deps");
+
+ mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
+ mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
+
+ mSubscriptionChangedListener =
+ new OnSubscriptionsChangedListener() {
+ @Override
+ public void onSubscriptionsChanged() {
+ handleSubscriptionsChanged();
+ }
+ };
+ }
+
+ /** Registers the receivers, and starts tracking subscriptions. */
+ public void register() {
+ mContext.registerReceiver(
+ this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED), null, mHandler);
+ mSubscriptionManager.addOnSubscriptionsChangedListener(
+ new HandlerExecutor(mHandler), mSubscriptionChangedListener);
+ }
+
+ /** Unregisters the receivers, and stops tracking subscriptions. */
+ public void unregister() {
+ mContext.unregisterReceiver(this);
+ mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+ }
+
+ /**
+ * Handles subscription changes, correlating available subscriptions and loaded carrier configs
+ *
+ * <p>The subscription change listener is registered with a HandlerExecutor backed by mHandler,
+ * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
+ */
+ public void handleSubscriptionsChanged() {
+ final Set<ParcelUuid> activeSubGroups = new ArraySet<>();
+ final Map<Integer, ParcelUuid> newSubIdToGroupMap = new HashMap<>();
+
+ final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
+ if (allSubs == null) {
+ return; // Telephony crashed; no way to verify subscriptions.
+ }
+
+ // If allSubs is empty, no subscriptions exist. Cache will be cleared by virtue of no active
+ // subscriptions
+ for (SubscriptionInfo subInfo : allSubs) {
+ if (subInfo.getGroupUuid() == null) {
+ continue;
+ }
+
+ // Build subId -> subGrp cache
+ newSubIdToGroupMap.put(subInfo.getSubscriptionId(), subInfo.getGroupUuid());
+
+ // Update subscription groups that are both ready, and active. For a group to be
+ // considered active, both of the following must be true:
+ //
+ // 1. A final CARRIER_CONFIG_CHANGED (where config is for an identified carrier)
+ // broadcast must have been received for the subId
+ // 2. A active subscription (is loaded into a SIM slot) must be part of the subscription
+ // group.
+ if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
+ && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
+ activeSubGroups.add(subInfo.getGroupUuid());
+ }
+ }
+
+ final TelephonySubscriptionSnapshot newSnapshot =
+ new TelephonySubscriptionSnapshot(newSubIdToGroupMap, activeSubGroups);
+
+ // If snapshot was meaningfully updated, fire the callback
+ if (!newSnapshot.equals(mCurrentSnapshot)) {
+ mCurrentSnapshot = newSnapshot;
+ mHandler.post(
+ () -> {
+ mCallback.onNewSnapshot(newSnapshot);
+ });
+ }
+ }
+
+ /**
+ * Broadcast receiver for ACTION_CARRIER_CONFIG_CHANGED
+ *
+ * <p>The broadcast receiver is registered with mHandler, so callbacks & broadcasts are all
+ * serialized on mHandler, avoiding the need for locking.
+ */
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Accept sticky broadcasts; if CARRIER_CONFIG_CHANGED was previously broadcast and it
+ // already was for an identified carrier, we can stop waiting for initial load to complete
+ if (!ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ return;
+ }
+
+ final int subId = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID);
+ final int slotId = intent.getIntExtra(EXTRA_SLOT_INDEX, INVALID_SIM_SLOT_INDEX);
+
+ if (slotId == INVALID_SIM_SLOT_INDEX) {
+ return;
+ }
+
+ if (SubscriptionManager.isValidSubscriptionId(subId)) {
+ final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
+ if (mDeps.isConfigForIdentifiedCarrier(carrierConfigs)) {
+ Slog.v(TAG, String.format("SubId %s ready for SlotId %s", subId, slotId));
+ mReadySubIdsBySlotId.put(slotId, subId);
+ handleSubscriptionsChanged();
+ }
+ } else {
+ Slog.v(TAG, "Slot unloaded: " + slotId);
+ mReadySubIdsBySlotId.remove(slotId);
+ handleSubscriptionsChanged();
+ }
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) {
+ mReadySubIdsBySlotId.putAll(readySubIdsBySlotId);
+ }
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ Map<Integer, Integer> getReadySubIdsBySlotId() {
+ return Collections.unmodifiableMap(mReadySubIdsBySlotId);
+ }
+
+ /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
+ public static class TelephonySubscriptionSnapshot {
+ private final Map<Integer, ParcelUuid> mSubIdToGroupMap;
+ private final Set<ParcelUuid> mActiveGroups;
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ TelephonySubscriptionSnapshot(
+ @NonNull Map<Integer, ParcelUuid> subIdToGroupMap,
+ @NonNull Set<ParcelUuid> activeGroups) {
+ mSubIdToGroupMap = Collections.unmodifiableMap(
+ Objects.requireNonNull(subIdToGroupMap, "subIdToGroupMap was null"));
+ mActiveGroups = Collections.unmodifiableSet(
+ Objects.requireNonNull(activeGroups, "activeGroups was null"));
+ }
+
+ /** Returns the active subscription groups */
+ @NonNull
+ public Set<ParcelUuid> getActiveSubscriptionGroups() {
+ return mActiveGroups;
+ }
+
+ /** Returns the Subscription Group for a given subId. */
+ @Nullable
+ public ParcelUuid getGroupForSubId(int subId) {
+ return mSubIdToGroupMap.get(subId);
+ }
+
+ /**
+ * Returns all the subIds in a given group, including available, but inactive subscriptions.
+ */
+ @NonNull
+ public Set<Integer> getAllSubIdsInGroup(ParcelUuid subGrp) {
+ final Set<Integer> subIds = new ArraySet<>();
+
+ for (Entry<Integer, ParcelUuid> entry : mSubIdToGroupMap.entrySet()) {
+ if (subGrp.equals(entry.getValue())) {
+ subIds.add(entry.getKey());
+ }
+ }
+
+ return subIds;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSubIdToGroupMap, mActiveGroups);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TelephonySubscriptionSnapshot)) {
+ return false;
+ }
+
+ final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
+
+ return mSubIdToGroupMap.equals(other.mSubIdToGroupMap)
+ && mActiveGroups.equals(other.mActiveGroups);
+ }
+ }
+
+ /**
+ * Interface for listening to changes in subscriptions
+ *
+ * @see TelephonySubscriptionTracker
+ */
+ public interface TelephonySubscriptionTrackerCallback {
+ /**
+ * Called when subscription information changes, and a new subscription snapshot was taken
+ *
+ * @param snapshot the snapshot of subscription information.
+ */
+ void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot);
+ }
+
+ /** External static dependencies for test injection */
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class Dependencies {
+ /** Checks if the given bundle is for an identified carrier */
+ public boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) {
+ return CarrierConfigManager.isConfigForIdentifiedCarrier(bundle);
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/OWNERS b/services/tests/mockingservicestests/src/com/android/server/location/OWNERS
new file mode 100644
index 000000000000..696a0c22c605
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/OWNERS
@@ -0,0 +1 @@
+file:/location/java/android/location/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 5d8131f35eb7..d905b69ce37f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -28,6 +28,8 @@ import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
import android.content.Context;
import android.hardware.tv.cec.V1_0.SendMessageResult;
@@ -189,4 +191,19 @@ public class HdmiCecControllerTest {
mTestLooper.dispatchAll();
assertEquals(ADDR_UNREGISTERED, mLogicalAddress);
}
+
+ @Test
+ public void testIsLanguage() {
+ assertTrue(HdmiCecController.isLanguage("en"));
+ assertTrue(HdmiCecController.isLanguage("eng"));
+ assertTrue(HdmiCecController.isLanguage("ger"));
+ assertTrue(HdmiCecController.isLanguage("zh"));
+ assertTrue(HdmiCecController.isLanguage("zhi"));
+ assertTrue(HdmiCecController.isLanguage("zho"));
+
+ assertFalse(HdmiCecController.isLanguage(null));
+ assertFalse(HdmiCecController.isLanguage(""));
+ assertFalse(HdmiCecController.isLanguage("e"));
+ assertFalse(HdmiCecController.isLanguage("一")); // language code must be ASCII
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
index 035a2f11112c..b07b8fa059d1 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
@@ -33,6 +34,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.IntentSender;
+import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IRecoverySystemProgressListener;
@@ -67,6 +69,9 @@ public class RecoverySystemServiceTest {
private FileWriter mUncryptUpdateFileWriter;
private LockSettingsInternal mLockSettingsInternal;
+ private static final String FAKE_OTA_PACKAGE_NAME = "fake.ota.package";
+ private static final String FAKE_OTHER_PACKAGE_NAME = "fake.other.package";
+
@Before
public void setup() {
mContext = mock(Context.class);
@@ -209,65 +214,99 @@ public class RecoverySystemServiceTest {
@Test(expected = SecurityException.class)
public void requestLskf_protected() {
- doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
- eq(android.Manifest.permission.RECOVERY), any());
- mRecoverySystemService.requestLskf("test", null);
- }
-
-
- @Test
- public void requestLskf_nullToken_failure() {
- assertThat(mRecoverySystemService.requestLskf(null, null), is(false));
+ when(mContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null);
}
@Test
public void requestLskf_success() throws Exception {
IntentSender intentSender = mock(IntentSender.class);
- assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
mRecoverySystemService.onPreparedForReboot(true);
verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
}
@Test
- public void requestLskf_subsequentRequestClearsPrepared() throws Exception {
+ public void requestLskf_subsequentRequestNotClearPrepared() throws Exception {
IntentSender intentSender = mock(IntentSender.class);
- assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
mRecoverySystemService.onPreparedForReboot(true);
verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
- assertThat(mRecoverySystemService.requestLskf("test2", null), is(true));
- assertThat(mRecoverySystemService.rebootWithLskf("test", null), is(false));
- assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(false));
-
- mRecoverySystemService.onPreparedForReboot(true);
- assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(true));
- verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
+ assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "foobar", true),
+ is(true));
verify(mIPowerManager).reboot(anyBoolean(), eq("foobar"), anyBoolean());
}
-
@Test
public void requestLskf_requestedButNotPrepared() throws Exception {
IntentSender intentSender = mock(IntentSender.class);
- assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
verify(intentSender, never()).sendIntent(any(), anyInt(), any(), any(), any());
}
+ @Test
+ public void isLskfCaptured_requestedButNotPrepared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
+ assertThat(mRecoverySystemService.isLskfCaptured(FAKE_OTA_PACKAGE_NAME), is(false));
+ }
+
+ @Test
+ public void isLskfCaptured_Prepared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+ assertThat(mRecoverySystemService.isLskfCaptured(FAKE_OTA_PACKAGE_NAME), is(true));
+ }
+
@Test(expected = SecurityException.class)
public void clearLskf_protected() {
- doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
- eq(android.Manifest.permission.RECOVERY), any());
- mRecoverySystemService.clearLskf();
+ when(mContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME);
}
@Test
public void clearLskf_requestedThenCleared() throws Exception {
IntentSender intentSender = mock(IntentSender.class);
- assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
mRecoverySystemService.onPreparedForReboot(true);
verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
- assertThat(mRecoverySystemService.clearLskf(), is(true));
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true));
+ verify(mLockSettingsInternal).clearRebootEscrow();
+ }
+
+ @Test
+ public void clearLskf_callerNotRequested_Success() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTHER_PACKAGE_NAME), is(true));
+ verify(mLockSettingsInternal, never()).clearRebootEscrow();
+ }
+
+ @Test
+ public void clearLskf_multiClient_BothClientsClear() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, intentSender),
+ is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, intentSender),
+ is(true));
+
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true));
+ verify(mLockSettingsInternal, never()).clearRebootEscrow();
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTHER_PACKAGE_NAME), is(true));
verify(mLockSettingsInternal).clearRebootEscrow();
}
@@ -279,27 +318,84 @@ public class RecoverySystemServiceTest {
@Test(expected = SecurityException.class)
public void rebootWithLskf_protected() {
- doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
- eq(android.Manifest.permission.RECOVERY), any());
- mRecoverySystemService.rebootWithLskf("test1", null);
+ when(mContext.checkCallingOrSelfPermission(anyString())).thenReturn(
+ PackageManager.PERMISSION_DENIED);
+ mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true);
}
@Test
public void rebootWithLskf_Success() throws Exception {
- assertThat(mRecoverySystemService.requestLskf("test", null), is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
mRecoverySystemService.onPreparedForReboot(true);
- assertThat(mRecoverySystemService.rebootWithLskf("test", "ab-update"), is(true));
+ assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true),
+ is(true));
verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
}
@Test
public void rebootWithLskf_withoutPrepare_Failure() throws Exception {
- assertThat(mRecoverySystemService.rebootWithLskf("test1", null), is(false));
+ assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true),
+ is(false));
}
@Test
- public void rebootWithLskf_withNullUpdateToken_Failure() throws Exception {
- assertThat(mRecoverySystemService.rebootWithLskf(null, null), is(false));
+ public void rebootWithLskf_withNullCallerId_Failure() throws Exception {
+ assertThat(mRecoverySystemService.rebootWithLskf(null, null, true), is(false));
verifyNoMoreInteractions(mIPowerManager);
}
+
+ @Test
+ public void rebootWithLskf_multiClient_ClientASuccess() throws Exception {
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+
+ // Client B's clear won't affect client A's preparation.
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTHER_PACKAGE_NAME), is(true));
+ assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, "ab-update", true),
+ is(true));
+ verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
+ }
+
+
+ @Test
+ public void rebootWithLskf_multiClient_ClientBSuccess() throws Exception {
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
+
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true));
+ assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true),
+ is(false));
+ verifyNoMoreInteractions(mIPowerManager);
+
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
+ assertThat(
+ mRecoverySystemService.rebootWithLskf(FAKE_OTHER_PACKAGE_NAME, "ab-update", true),
+ is(true));
+ verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
+ }
+
+ @Test
+ public void rebootWithLskf_multiClient_BothClientsClear_Failure() throws Exception {
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTA_PACKAGE_NAME, null), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ assertThat(mRecoverySystemService.requestLskf(FAKE_OTHER_PACKAGE_NAME, null), is(true));
+
+ // Client A clears
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTA_PACKAGE_NAME), is(true));
+ assertThat(mRecoverySystemService.rebootWithLskf(FAKE_OTA_PACKAGE_NAME, null, true),
+ is(false));
+ verifyNoMoreInteractions(mIPowerManager);
+
+ // Client B clears
+ assertThat(mRecoverySystemService.clearLskf(FAKE_OTHER_PACKAGE_NAME), is(true));
+ verify(mLockSettingsInternal).clearRebootEscrow();
+ assertThat(
+ mRecoverySystemService.rebootWithLskf(FAKE_OTHER_PACKAGE_NAME, "ab-update", true),
+ is(false));
+ verifyNoMoreInteractions(mIPowerManager);
+ }
+
+ // TODO(xunchang) add more multi client tests
}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 0c463949b14a..225e3f760d20 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -651,4 +651,23 @@ public final class TelephonyPermissions {
throw new SecurityException(message + ": Only shell user can call it");
}
+
+ /**
+ * Returns the target SDK version number for a given package name.
+ *
+ * This call MUST be invoked before clearing the calling UID.
+ *
+ * @return target SDK if the package is found or INT_MAX.
+ */
+ public static int getTargetSdk(Context c, String packageName) {
+ try {
+ final ApplicationInfo ai = c.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0, UserHandle.getUserHandleForUid(Binder.getCallingUid()));
+ if (ai != null) return ai.targetSdkVersion;
+ } catch (PackageManager.NameNotFoundException unexpected) {
+ Log.e(LOG_TAG, "Failed to get package info for pkg="
+ + packageName + ", uid=" + Binder.getCallingUid());
+ }
+ return Integer.MAX_VALUE;
+ }
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 27ea6902146f..11bb8674d5a9 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4018,6 +4018,12 @@ public class CarrierConfigManager {
public static final String KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED =
"use_lower_mtu_value_if_both_received";
+ /**
+ * Indicates if auto-configuration server is used for the RCS config
+ * Reference: GSMA RCC.14
+ */
+ public static final String KEY_USE_ACS_FOR_RCS_BOOL = "use_acs_for_rcs_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -4561,6 +4567,7 @@ public class CarrierConfigManager {
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_USE_LOWER_MTU_VALUE_IF_BOTH_RECEIVED, false);
+ sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
}
/**
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index db7d10ae8ce4..7addf334e967 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -443,10 +443,12 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
/**
* Get table index for channel quality indicator
*
+ * Reference: 3GPP TS 136.213 section 7.2.3.
+ *
* @return the CQI table index if available or
* {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
- /** @hide */
+ @IntRange(from = 1, to = 6)
public int getCqiTableIndex() {
return mCqiTableIndex;
}
@@ -454,9 +456,12 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
/**
* Get channel quality indicator
*
+ * Reference: 3GPP TS 136.213 section 7.2.3.
+ *
* @return the CQI if available or
* {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
+ @IntRange(from = 0, to = 15)
public int getCqi() {
return mCqi;
}
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index 1518190bb7f7..bde62fb2977c 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -294,9 +294,10 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa
*
* Reference: 3GPP TS 138.214 section 5.2.2.1.
*
- * Range [1, 3].
+ * @return the CQI table index if available or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable.
*/
- /** @hide */
+ @IntRange(from = 1, to = 3)
public int getCsiCqiTableIndex() {
return mCsiCqiTableIndex;
}
@@ -310,10 +311,10 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa
*
* Reference: 3GPP TS 138.214 section 5.2.2.1.
*
- * Range [0, 15] for each CQI.
+ * @return the CQIs for all subbands if available or empty list if unavailable.
*/
- /** @hide */
@NonNull
+ @IntRange(from = 0, to = 15)
public List<Integer> getCsiCqiReport() {
return mCsiCqiReport;
}
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 83e63ef29e2c..904232b54b8f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -791,6 +791,13 @@ public class SubscriptionManager {
public static final String IMS_RCS_UCE_ENABLED = SimInfo.COLUMN_IMS_RCS_UCE_ENABLED;
/**
+ * Determines if the user has enabled cross SIM calling for this subscription.
+ *
+ * @hide
+ */
+ public static final String CROSS_SIM_CALLING_ENABLED = SimInfo.COLUMN_CROSS_SIM_CALLING_ENABLED;
+
+ /**
* TelephonyProvider column name for whether a subscription is opportunistic, that is,
* whether the network it connects to is limited in functionality or coverage.
* For example, CBRS.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 8368e3a72734..26fd99de0a04 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1494,6 +1494,16 @@ public class TelephonyManager {
public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL = 4;
/**
+ * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE}
+ * to indicate that default subscription for data/sms/voice is now determined, that
+ * it should dismiss any dialog or pop-ups that is asking user to select default sub.
+ * This is used when, for example, opportunistic subscription is configured. At that
+ * time the primary becomes default sub there's no need to ask user to select anymore.
+ * @hide
+ */
+ public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_DISMISS = 5;
+
+ /**
* Integer intent extra to be used with {@link #ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED}
* to indicate if the SIM combination in DSDS has limitation or compatible issue.
* e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios.
@@ -9328,9 +9338,10 @@ public class TelephonyManager {
* @return true if mobile data is enabled.
*/
@RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
- android.Manifest.permission.MODIFY_PHONE_STATE})
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ android.Manifest.permission.READ_PHONE_STATE})
public boolean isDataEnabled() {
- return getDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
+ return isDataEnabledForReason(DATA_ENABLED_REASON_USER);
}
/**
@@ -9575,7 +9586,7 @@ public class TelephonyManager {
@SystemApi
public boolean getDataEnabled(int subId) {
try {
- return isDataEnabledForReason(DATA_ENABLED_REASON_USER);
+ return isDataEnabledForReason(subId, DATA_ENABLED_REASON_USER);
} catch (RuntimeException e) {
Log.e(TAG, "Error calling isDataEnabledForReason e:" + e);
}
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 0fe76a04bf9d..f3c38bcba98a 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
import android.annotation.StringDef;
import android.annotation.SystemApi;
import android.annotation.WorkerThread;
@@ -31,6 +32,7 @@ import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.aidl.IRcsConfigCallback;
import android.telephony.ims.feature.MmTelFeature;
import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
@@ -936,6 +938,115 @@ public class ProvisioningManager {
private int mSubId;
/**
+ * The callback for RCS provisioning changes.
+ */
+ public static class RcsProvisioningCallback {
+ private static class CallbackBinder extends IRcsConfigCallback.Stub {
+
+ private final RcsProvisioningCallback mLocalCallback;
+ private Executor mExecutor;
+
+ private CallbackBinder(RcsProvisioningCallback localCallback) {
+ mLocalCallback = localCallback;
+ }
+
+ @Override
+ public void onConfigurationChanged(byte[] configXml) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mLocalCallback.onConfigurationChanged(configXml));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onAutoConfigurationErrorReceived(int errorCode, String errorString) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mLocalCallback.onAutoConfigurationErrorReceived(
+ errorCode, errorString));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onConfigurationReset() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mLocalCallback.onConfigurationReset());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void onRemoved() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mLocalCallback.onRemoved());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void setExecutor(Executor executor) {
+ mExecutor = executor;
+ }
+ }
+
+ private final CallbackBinder mBinder = new CallbackBinder(this);
+
+ /**
+ * RCS configuration received via OTA provisioning. Configuration may change
+ * due to various triggers defined in GSMA RCC.14 for ACS(auto configuration
+ * server) or other operator defined triggers. If RCS provisioning is already
+ * completed at the time of callback registration, then this method shall be
+ * invoked with the current configuration
+ * @param configXml The RCS configurationXML received OTA.
+ */
+ public void onConfigurationChanged(@NonNull byte[] configXml) {}
+
+ /**
+ * Errors during autoconfiguration connection setup are notified by the
+ * ACS(auto configuration server) client using this interface.
+ * @param errorCode HTTP error received during connection setup defined in
+ * GSMA RCC.14 2.4.3, like {@link java.net.HttpURLConnection#HTTP_UNAUTHORIZED},
+ * {@link java.net.HttpURLConnection#HTTP_FORBIDDEN}, etc.
+ * @param errorString reason phrase received with the error
+ */
+ public void onAutoConfigurationErrorReceived(int errorCode,
+ @NonNull String errorString) {}
+
+ /**
+ * When the previously valid RCS configuration is cleaned up by telephony for
+ * any case like SIM removed, default messaging application changed, etc.,
+ * this method will be invoked to notify the application regarding this change.
+ */
+ public void onConfigurationReset() {}
+
+ /**
+ * When the RCS application is no longer the Default messaging application,
+ * or when the subscription associated with this callback is removed (SIM
+ * removed, ESIM swap,etc...), callback will automatically be removed and
+ * the below method is invoked. There is a possibility that the method is
+ * invoked after the subscription has become inactive
+ */
+ public void onRemoved() {}
+
+ /**@hide*/
+ public final IRcsConfigCallback getBinder() {
+ return mBinder;
+ }
+
+ /**@hide*/
+ public void setExecutor(Executor executor) {
+ mBinder.setExecutor(executor);
+ }
+ }
+
+ /**
* Create a new {@link ProvisioningManager} for the subscription specified.
*
* @param subId The ID of the subscription that this ProvisioningManager will use.
@@ -1207,6 +1318,174 @@ public class ProvisioningManager {
}
+ /**
+ * Provides the single registration capability of the device and the carrier.
+ *
+ * <p>This intent only provides the capability and not the current provisioning status of
+ * the RCS VoLTE single registration feature. Only default messaging application may receive
+ * the intent.
+ *
+ * <p>Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to specify the subscription index for which
+ * the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration
+ * status.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE =
+ "android.telephony.ims.action.RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE";
+
+ /**
+ * Integer extra to specify subscription index.
+ */
+ public static final String EXTRA_SUBSCRIPTION_ID =
+ "android.telephony.ims.extra.SUBSCRIPTION_ID";
+
+ /**
+ * Integer extra to specify RCS single registration status
+ *
+ * <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}.
+ */
+ public static final String EXTRA_STATUS = "android.telephony.ims.extra.STATUS";
+
+ /**
+ * RCS VoLTE single registration is supported by the device and carrier.
+ */
+ public static final int STATUS_CAPABLE = 0;
+
+ /**
+ * RCS VoLTE single registration is not supported by the device.
+ */
+ public static final int STATUS_DEVICE_NOT_CAPABLE = 0x01;
+
+ /**
+ * RCS VoLTE single registration is not supported by the carrier
+ */
+ public static final int STATUS_CARRIER_NOT_CAPABLE = 0x01 << 1;
+
+ /**
+ * Provide the client configuration parameters of the RCS application.
+ *
+ * <p>When this application is also the default messaging application, and RCS
+ * 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 #registerRcsProvisioningChangedCallback()}
+ * @param rcc RCS client configuration {@link RcsClientConfiguration}
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setRcsClientConfiguration(
+ @NonNull RcsClientConfiguration rcc) throws ImsException {
+ try {
+ getITelephony().setRcsClientConfiguration(mSubId, rcc);
+ } catch (ServiceSpecificException e) {
+ throw new ImsException(e.getMessage(), e.errorCode);
+ } catch (RemoteException | IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ }
+
+ /**
+ * Returns a flag to indicate if the device software and the carrier
+ * have the capability to support RCS Volte single IMS registration.
+ * @return true if this single registration is capable, 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.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
+ try {
+ return getITelephony().isRcsVolteSingleRegistrationCapable(mSubId);
+ } catch (RemoteException | IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+ }
+
+ /**
+ * Registers a new {@link RcsProvisioningCallback} to listen to changes to
+ * RCS provisioning xml.
+ *
+ * <p>RCS application must be the default messaging application and must
+ * have already registered its {@link RcsClientConfiguration} by using
+ * {@link #setRcsClientConfiguration} before it registers the provisioning
+ * callback. If ProvisioningManager has a valid RCS configuration at the
+ * time of callback registration and a reconfiguration is not required
+ * due to RCS client parameters change, then the callback shall be invoked
+ * immediately with the xml.
+ * When the subscription associated with this callback is removed (SIM removed,
+ * ESIM swap,etc...), this callback will automatically be removed.
+ *
+ * @param executor The {@link Executor} to call the callback methods on
+ * @param callback The rcs provisioning callback to be registered.
+ * @see #unregisterRcsProvisioningChangedCallback(RcsProvisioningCallback)
+ * @see SubscriptionManager.OnSubscriptionsChangedListener
+ * @throws IllegalArgumentException if the subscription associated with this
+ * callback is not active (SIM is not inserted, ESIM inactive) or the
+ * subscription is invalid.
+ * @throws ImsException if the subscription associated with this callback is
+ * valid, but the {@link ImsService} associated with the subscription is not
+ * available. This can happen if the service crashed, for example.
+ * It shall also throw this exception when the RCS client parameters for the
+ * application are not valid. In that case application must set the client
+ * params (See {@link #setRcsClientConfiguration()}) and re register the
+ * callback.
+ * See {@link ImsException#getCode()} for a more detailed reason.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void registerRcsProvisioningChangedCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull RcsProvisioningCallback callback) throws ImsException {
+ callback.setExecutor(executor);
+ try {
+ getITelephony().registerRcsProvisioningChangedCallback(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);
+ }
+ }
+
+ /**
+ * Unregister an existing {@link RcsProvisioningCallback}. Application can
+ * unregister when its no longer interested in the provisioning updates
+ * like when a user disables RCS from the UI/settings.
+ * 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 RcsProvisioningCallback} to be
+ * removed.
+ * @see #registerRcsProvisioningChangedCallback(RcsClientConfiguration,
+ * Executor, RcsProvisioningCallback) @throws IllegalArgumentException
+ * if the subscription associated with this callback is invalid.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void unregisterRcsProvisioningChangedCallback(
+ @NonNull RcsProvisioningCallback callback) {
+ try {
+ getITelephony().unregisterRcsProvisioningChangedCallback(
+ mSubId, callback.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Reconfiguration triggered by the RCS application. Most likely cause
+ * is the 403 forbidden to a HTTP request.
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public void triggerRcsReconfiguration() {
+ try {
+ getITelephony().triggerRcsReconfiguration(mSubId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
TelephonyFrameworkInitializer
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.aidl b/telephony/java/android/telephony/ims/RcsClientConfiguration.aidl
new file mode 100644
index 000000000000..a702f0f42c54
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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;
+
+parcelable RcsClientConfiguration;
diff --git a/telephony/java/android/telephony/ims/RcsClientConfiguration.java b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
new file mode 100644
index 000000000000..793c37745de6
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsClientConfiguration.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * The container of RCS application related configs.
+ *
+ * @hide
+ */
+@SystemApi
+public final class RcsClientConfiguration implements Parcelable {
+
+ /**@hide*/
+ @StringDef(prefix = "RCS_PROFILE_",
+ value = {RCS_PROFILE_1_0, RCS_PROFILE_2_3})
+ public @interface StringRcsProfile {}
+
+ /**
+ * RCS profile UP 1.0
+ */
+ public static final String RCS_PROFILE_1_0 = "UP_1.0";
+ /**
+ * RCS profile UP 2.3
+ */
+ public static final String RCS_PROFILE_2_3 = "UP_2.3";
+
+ private String mRcsVersion;
+ private String mRcsProfile;
+ private String mClientVendor;
+ private String mClientVersion;
+
+ /**
+ * Create a RcsClientConfiguration object.
+ * Default messaging application must pass a valid configuration object
+ * @param rcsVersion The parameter identifies the RCS version supported
+ * by the client. Refer to GSMA RCC.07 "rcs_version" parameter.
+ * @param rcsProfile Identifies a fixed set of RCS services that are
+ * supported by the client. See {@link #RCS_PROFILE_1_0 } or
+ * {@link #RCS_PROFILE_2_3 }
+ * @param clientVendor Identifies the vendor providing the RCS client.
+ * @param clientVersion Identifies the RCS client version. Refer to GSMA
+ * RCC.07 "client_version" parameter.
+ * Example:client_version=RCSAndrd-1.0
+ */
+ public RcsClientConfiguration(@NonNull String rcsVersion,
+ @NonNull @StringRcsProfile String rcsProfile,
+ @NonNull String clientVendor, @NonNull String clientVersion) {
+ mRcsVersion = rcsVersion;
+ mRcsProfile = rcsProfile;
+ mClientVendor = clientVendor;
+ mClientVersion = clientVersion;
+ }
+
+ /**
+ * Returns RCS version supported.
+ */
+ public @NonNull String getRcsVersion() {
+ return mRcsVersion;
+ }
+
+ /**
+ * Returns RCS profile supported.
+ */
+ public @NonNull @StringRcsProfile String getRcsProfile() {
+ return mRcsProfile;
+ }
+
+ /**
+ * Returns the name of the vendor providing the RCS client.
+ */
+ public @NonNull String getClientVendor() {
+ return mClientVendor;
+ }
+
+ /**
+ * Returns the RCS client version.
+ */
+ public @NonNull String getClientVersion() {
+ return mClientVersion;
+ }
+
+ /**
+ * {@link Parcelable#writeToParcel}
+ */
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeString(mRcsVersion);
+ out.writeString(mRcsProfile);
+ out.writeString(mClientVendor);
+ out.writeString(mClientVersion);
+ }
+
+ /**
+ * {@link Parcelable.Creator}
+ *
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<
+ RcsClientConfiguration> CREATOR = new Creator<RcsClientConfiguration>() {
+ @Override
+ public RcsClientConfiguration createFromParcel(Parcel in) {
+ String rcsVersion = in.readString();
+ String rcsProfile = in.readString();
+ String clientVendor = in.readString();
+ String clientVersion = in.readString();
+ return new RcsClientConfiguration(rcsVersion, rcsProfile,
+ clientVendor, clientVersion);
+ }
+
+ @Override
+ public RcsClientConfiguration[] newArray(int size) {
+ return new RcsClientConfiguration[size];
+ }
+ };
+
+ /**
+ * {@link Parcelable#describeContents}
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RcsClientConfiguration)) {
+ return false;
+ }
+
+ RcsClientConfiguration other = (RcsClientConfiguration) obj;
+
+ return mRcsVersion.equals(other.mRcsVersion) && mRcsProfile.equals(other.mRcsProfile)
+ && mClientVendor.equals(other.mClientVendor)
+ && mClientVersion.equals(other.mClientVersion);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRcsVersion, mRcsProfile, mClientVendor, mClientVersion);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsConfig.aidl b/telephony/java/android/telephony/ims/RcsConfig.aidl
new file mode 100644
index 000000000000..cfd93fbe2edb
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2020 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;
+
+parcelable RcsConfig;
diff --git a/telephony/java/android/telephony/ims/RcsConfig.java b/telephony/java/android/telephony/ims/RcsConfig.java
new file mode 100644
index 000000000000..07e95cc99290
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsConfig.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.Telephony.SimInfo;
+import android.text.TextUtils;
+
+import com.android.telephony.Rlog;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * RCS config data and methods to process the config
+ * @hide
+ */
+public final class RcsConfig implements Parcelable {
+ private static final String LOG_TAG = "RcsConfig";
+ private static final boolean DBG = Build.IS_ENG;
+
+ private final HashMap<String, String> mValues = new HashMap<>();
+
+ private RcsConfig(HashMap<String, String> values) {
+ mValues.putAll(values);
+ }
+
+ public RcsConfig(byte[] data) throws IllegalArgumentException {
+ if (data == null || data.length == 0) {
+ throw new IllegalArgumentException("Empty data");
+ }
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ try {
+ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ factory.setNamespaceAware(true);
+ XmlPullParser xpp = factory.newPullParser();
+ xpp.setInput(inputStream, null);
+ int eventType = xpp.getEventType();
+ String tag = null;
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ tag = xpp.getName().trim();
+ } else if (eventType == XmlPullParser.END_TAG) {
+ tag = null;
+ } else if (eventType == XmlPullParser.TEXT) {
+ String value = xpp.getText().trim();
+ if (!TextUtils.isEmpty(tag) && !TextUtils.isEmpty(value)) {
+ mValues.put(tag, value);
+ }
+ }
+ eventType = xpp.next();
+ }
+ } catch (IOException | XmlPullParserException e) {
+ throw new IllegalArgumentException(e);
+ } finally {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ loge("error to close input stream, skip.");
+ }
+ }
+ }
+
+ /**
+ * Retrieve a String value of the config item with the tag
+ *
+ * @param tag The name of the config to retrieve.
+ * @param defaultVal Value to return if the config does not exist.
+ *
+ * @return Returns the config value if it exists, or defaultVal.
+ */
+ public @Nullable String getString(@NonNull String tag, @Nullable String defaultVal) {
+ return mValues.containsKey(tag) ? mValues.get(tag) : defaultVal;
+ }
+
+ /**
+ * Retrieve a int value of the config item with the tag
+ *
+ * @param tag The name of the config to retrieve.
+ * @param defaultVal Value to return if the config does not exist or not valid.
+ *
+ * @return Returns the config value if it exists and is a valid int, or defaultVal.
+ */
+ public int getInteger(@NonNull String tag, int defaultVal) {
+ try {
+ return Integer.parseInt(mValues.get(tag));
+ } catch (NumberFormatException e) {
+ logd("error to getInteger for " + tag + " due to " + e);
+ }
+ return defaultVal;
+ }
+
+ /**
+ * Retrieve a boolean value of the config item with the tag
+ *
+ * @param tag The name of the config to retrieve.
+ * @param defaultVal Value to return if the config does not exist.
+ *
+ * @return Returns the config value if it exists, or defaultVal.
+ */
+ public boolean getBoolean(@NonNull String tag, boolean defaultVal) {
+ if (!mValues.containsKey(tag)) {
+ return defaultVal;
+ }
+ return Boolean.parseBoolean(mValues.get(tag));
+ }
+
+ /**
+ * Check whether the config item exists
+ *
+ * @param tag The name of the config to retrieve.
+ *
+ * @return Returns true if it exists, or false.
+ */
+ public boolean hasConfig(@NonNull String tag) {
+ return mValues.containsKey(tag);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("[RCS Config]");
+ if (DBG) {
+ mValues.forEach((t, v) -> {
+ sb.append("\n");
+ sb.append(t);
+ sb.append(" : ");
+ sb.append(v);
+ });
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RcsConfig)) {
+ return false;
+ }
+
+ RcsConfig other = (RcsConfig) obj;
+
+ return mValues.equals(other.mValues);
+ }
+
+ @Override
+ public int hashCode() {
+ return mValues.hashCode();
+ }
+
+ /**
+ * compress the gzip format data
+ */
+ public static @Nullable byte[] compressGzip(@NonNull byte[] data) {
+ if (data == null || data.length == 0) {
+ return data;
+ }
+ byte[] out = null;
+ try {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
+ GZIPOutputStream gzipCompressingStream =
+ new GZIPOutputStream(outputStream);
+ gzipCompressingStream.write(data);
+ gzipCompressingStream.close();
+ out = outputStream.toByteArray();
+ outputStream.close();
+ } catch (IOException e) {
+ loge("Error to compressGzip due to " + e);
+ }
+ return out;
+ }
+
+ /**
+ * decompress the gzip format data
+ */
+ public static @Nullable byte[] decompressGzip(@NonNull byte[] data) {
+ if (data == null || data.length == 0) {
+ return data;
+ }
+ byte[] out = null;
+ try {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ GZIPInputStream gzipDecompressingStream =
+ new GZIPInputStream(inputStream);
+ byte[] buf = new byte[1024];
+ int size = gzipDecompressingStream.read(buf);
+ while (size >= 0) {
+ outputStream.write(buf, 0, size);
+ size = gzipDecompressingStream.read(buf);
+ }
+ gzipDecompressingStream.close();
+ inputStream.close();
+ out = outputStream.toByteArray();
+ outputStream.close();
+ } catch (IOException e) {
+ loge("Error to decompressGzip due to " + e);
+ }
+ return out;
+ }
+
+ /**
+ * save the config to siminfo db. It is only used internally.
+ */
+ public static void updateConfigForSub(@NonNull Context cxt, int subId,
+ @NonNull byte[] config, boolean isCompressed) {
+ //always store gzip compressed data
+ byte[] data = isCompressed ? config : compressGzip(config);
+ ContentValues values = new ContentValues();
+ values.put(SimInfo.COLUMN_RCS_CONFIG, data);
+ cxt.getContentResolver().update(SimInfo.CONTENT_URI, values,
+ SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null);
+ }
+
+ /**
+ * load the config from siminfo db. It is only used internally.
+ */
+ public static @Nullable byte[] loadRcsConfigForSub(@NonNull Context cxt,
+ int subId, boolean isCompressed) {
+
+ byte[] data = null;
+
+ Cursor cursor = cxt.getContentResolver().query(SimInfo.CONTENT_URI, null,
+ SimInfo.COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID + "=" + subId, null, null);
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ data = cursor.getBlob(cursor.getColumnIndexOrThrow(SimInfo.COLUMN_RCS_CONFIG));
+ }
+ } catch (Exception e) {
+ loge("error to load rcs config for sub:" + subId + " due to " + e);
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return isCompressed ? data : decompressGzip(data);
+ }
+
+ /**
+ * {@link Parcelable#writeToParcel}
+ */
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeMap(mValues);
+ }
+
+ /**
+ * {@link Parcelable.Creator}
+ *
+ */
+ public static final @NonNull Parcelable.Creator<RcsConfig>
+ CREATOR = new Creator<RcsConfig>() {
+ @Override
+ public RcsConfig createFromParcel(Parcel in) {
+ HashMap<String, String> values = in.readHashMap(null);
+ return values == null ? null : new RcsConfig(values);
+ }
+
+ @Override
+ public RcsConfig[] newArray(int size) {
+ return new RcsConfig[size];
+ }
+ };
+
+ /**
+ * {@link Parcelable#describeContents}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ private static void logd(String msg) {
+ Rlog.d(LOG_TAG, msg);
+ }
+
+ private static void loge(String msg) {
+ Rlog.e(LOG_TAG, msg);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java
index 2ec88ff27f93..2e9eb94605a5 100644
--- a/telephony/java/android/telephony/ims/SipDelegateManager.java
+++ b/telephony/java/android/telephony/ims/SipDelegateManager.java
@@ -18,7 +18,9 @@ package android.telephony.ims;
import android.Manifest;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
@@ -372,4 +374,37 @@ public class SipDelegateManager {
+ " into this method");
}
}
+
+ /**
+ * Trigger a full network registration as required by receiving a SIP message containing a
+ * permanent error from the network or never receiving a response to a SIP transaction request.
+ *
+ * @param connection The {@link SipDelegateConnection} that was being used when this error was
+ * received.
+ * @param sipCode The SIP code response associated with the SIP message request that
+ * triggered this condition.
+ * @param sipReason The SIP reason code associated with the SIP message request that triggered
+ * this condition. May be {@code null} if there was no reason String provided from the
+ * network.
+ */
+ public void triggerFullNetworkRegistration(@NonNull SipDelegateConnection connection,
+ @IntRange(from = 100, to = 699) int sipCode, @Nullable String sipReason) {
+ if (connection == null) {
+ throw new IllegalArgumentException("invalid connection.");
+ }
+ if (connection instanceof SipDelegateConnectionAidlWrapper) {
+ SipDelegateConnectionAidlWrapper w = (SipDelegateConnectionAidlWrapper) connection;
+ try {
+ IImsRcsController controller = mBinderCache.getBinder();
+ controller.triggerNetworkRegistration(mSubId, w.getSipDelegateBinder(), sipCode,
+ sipReason);
+ } catch (RemoteException e) {
+ // Connection to telephony died, but this will signal destruction of SipDelegate
+ // eventually anyway, so return.
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown SipDelegateConnection implementation passed"
+ + " into this method");
+ }
+ }
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 57206c9f059a..5eee3890f1dc 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -18,8 +18,9 @@
package android.telephony.ims.aidl;
import android.os.PersistableBundle;
-
import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.ims.RcsClientConfiguration;
import com.android.ims.ImsConfigListener;
@@ -41,4 +42,9 @@ interface IImsConfig {
int setConfigString(int item, String value);
void updateImsCarrierConfigs(in PersistableBundle bundle);
void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed);
+ void notifyRcsAutoConfigurationRemoved();
+ void addRcsConfigCallback(IRcsConfigCallback c);
+ void removeRcsConfigCallback(IRcsConfigCallback c);
+ void triggerRcsReconfiguration();
+ void setRcsClientConfiguration(in RcsClientConfiguration rcc);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index c6d9a8629556..36349895c35b 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -67,6 +67,8 @@ interface IImsRcsController {
ISipDelegateConnectionStateCallback delegateState,
ISipDelegateMessageCallback delegateMessage);
void destroySipDelegate(int subId, ISipDelegate connection, int reason);
+ void triggerNetworkRegistration(int subId, ISipDelegate connection, int sipCode,
+ String sipReason);
// Internal commands that should not be made public
void registerRcsFeatureCallback(int slotId, in IImsServiceFeatureCallback callback);
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
index 4ae0a75ad027..4fd904041365 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
@@ -28,4 +28,7 @@ interface IImsRegistration {
int getRegistrationTechnology();
oneway void addRegistrationCallback(IImsRegistrationCallback c);
oneway void removeRegistrationCallback(IImsRegistrationCallback c);
-} \ No newline at end of file
+ oneway void triggerFullNetworkRegistration(int sipCode, String sipReason);
+ oneway void triggerUpdateSipDelegateRegistration();
+ oneway void triggerSipDelegateDeregistration();
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl
new file mode 100644
index 000000000000..5a8973e37bce
--- /dev/null
+++ b/telephony/java/android/telephony/ims/aidl/IRcsConfigCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 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;
+
+/**
+ * The callback for RCS provisioning changes.
+ * {@hide}
+ */
+oneway interface IRcsConfigCallback {
+ void onConfigurationChanged(in byte[] config);
+ void onAutoConfigurationErrorReceived(int errorCode, String errorString);
+ void onConfigurationReset();
+ void onRemoved();
+}
+
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index e757d9f70ccc..cc050becfb25 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -23,8 +23,11 @@ import android.content.Context;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.telephony.ims.ProvisioningManager;
+import android.telephony.ims.RcsClientConfiguration;
+import android.telephony.ims.RcsConfig;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
+import android.telephony.ims.aidl.IRcsConfigCallback;
import android.util.Log;
import com.android.ims.ImsConfig;
@@ -202,7 +205,13 @@ public class ImsConfigImplBase {
@Override
public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
throws RemoteException {
- getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed);
+ getImsConfigImpl().onNotifyRcsAutoConfigurationReceived(config, isCompressed);
+ }
+
+ @Override
+ public void notifyRcsAutoConfigurationRemoved()
+ throws RemoteException {
+ getImsConfigImpl().onNotifyRcsAutoConfigurationRemoved();
}
private void notifyImsConfigChanged(int item, int value) throws RemoteException {
@@ -228,6 +237,26 @@ public class ImsConfigImplBase {
notifyImsConfigChanged(item, value);
}
}
+
+ @Override
+ public void addRcsConfigCallback(IRcsConfigCallback c) throws RemoteException {
+ getImsConfigImpl().addRcsConfigCallback(c);
+ }
+
+ @Override
+ public void removeRcsConfigCallback(IRcsConfigCallback c) throws RemoteException {
+ getImsConfigImpl().removeRcsConfigCallback(c);
+ }
+
+ @Override
+ public void triggerRcsReconfiguration() throws RemoteException {
+ getImsConfigImpl().triggerAutoConfiguration();
+ }
+
+ @Override
+ public void setRcsClientConfiguration(RcsClientConfiguration rcc) throws RemoteException {
+ getImsConfigImpl().setRcsClientConfiguration(rcc);
+ }
}
/**
@@ -257,6 +286,9 @@ public class ImsConfigImplBase {
private final RemoteCallbackListExt<IImsConfigCallback> mCallbacks =
new RemoteCallbackListExt<>();
+ private final RemoteCallbackListExt<IRcsConfigCallback> mRcsCallbacks =
+ new RemoteCallbackListExt<>();
+ private byte[] mRcsConfigData;
ImsConfigStub mImsConfigStub;
/**
@@ -320,6 +352,50 @@ public class ImsConfigImplBase {
});
}
+ private void addRcsConfigCallback(IRcsConfigCallback c) {
+ mRcsCallbacks.register(c);
+ if (mRcsConfigData != null) {
+ try {
+ c.onConfigurationChanged(mRcsConfigData);
+ } catch (RemoteException e) {
+ Log.w(TAG, "dead binder to call onConfigurationChanged, skipping.");
+ }
+ }
+ }
+
+ private void removeRcsConfigCallback(IRcsConfigCallback c) {
+ mRcsCallbacks.unregister(c);
+ }
+
+ private void onNotifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
+ mRcsConfigData = isCompressed ? RcsConfig.decompressGzip(config) : config;
+ // can be null in testing
+ if (mRcsCallbacks != null) {
+ mRcsCallbacks.broadcastAction(c -> {
+ try {
+ c.onConfigurationChanged(mRcsConfigData);
+ } catch (RemoteException e) {
+ Log.w(TAG, "dead binder in notifyRcsAutoConfigurationReceived, skipping.");
+ }
+ });
+ }
+ notifyRcsAutoConfigurationReceived(config, isCompressed);
+ }
+
+ private void onNotifyRcsAutoConfigurationRemoved() {
+ mRcsConfigData = null;
+ if (mRcsCallbacks != null) {
+ mRcsCallbacks.broadcastAction(c -> {
+ try {
+ c.onConfigurationReset();
+ } catch (RemoteException e) {
+ Log.w(TAG, "dead binder in notifyRcsAutoConfigurationRemoved, skipping.");
+ }
+ });
+ }
+ notifyRcsAutoConfigurationRemoved();
+ }
+
/**
* @hide
*/
@@ -369,6 +445,12 @@ public class ImsConfigImplBase {
}
/**
+ * The RCS autoconfiguration XML file is removed or invalid.
+ */
+ public void notifyRcsAutoConfigurationRemoved() {
+ }
+
+ /**
* Sets the configuration value for this ImsService.
*
* @param item an integer key.
@@ -421,4 +503,43 @@ public class ImsConfigImplBase {
public void updateImsCarrierConfigs(PersistableBundle bundle) {
// Base Implementation - Should be overridden
}
+
+ /**
+ * Default messaging application parameters are sent to the ACS client
+ * using this interface.
+ * @param rcc RCS client configuration {@link RcsClientConfiguration}
+ */
+ public void setRcsClientConfiguration(@NonNull RcsClientConfiguration rcc) {
+ // Base Implementation - Should be overridden
+ }
+
+ /**
+ * Reconfiguration triggered by the RCS application. Most likely cause
+ * is the 403 forbidden to a SIP/HTTP request
+ */
+ public void triggerAutoConfiguration() {
+ // Base Implementation - Should be overridden
+ }
+
+ /**
+ * Errors during autoconfiguration connection setup are notified by the
+ * ACS client using this interface.
+ * @param errorCode HTTP error received during connection setup.
+ * @param errorString reason phrase received with the error
+ */
+ public final void notifyAutoConfigurationErrorReceived(int errorCode,
+ @NonNull String errorString) {
+ // can be null in testing
+ if (mRcsCallbacks == null) {
+ return;
+ }
+ mRcsCallbacks.broadcastAction(c -> {
+ try {
+ //TODO compressed by default?
+ c.onAutoConfigurationErrorReceived(errorCode, errorString);
+ } catch (RemoteException e) {
+ Log.w(TAG, "dead binder in notifyAutoConfigurationErrorReceived, skipping.");
+ }
+ });
+ }
}
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 153d687dd84f..088a7e26a9d0 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -38,6 +38,9 @@ import java.lang.annotation.RetentionPolicy;
/**
* Controls IMS registration for this ImsService and notifies the framework when the IMS
* registration for this ImsService has changed status.
+ * <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
@@ -92,6 +95,21 @@ public class ImsRegistrationImplBase {
public void removeRegistrationCallback(IImsRegistrationCallback c) throws RemoteException {
ImsRegistrationImplBase.this.removeRegistrationCallback(c);
}
+
+ @Override
+ public void triggerFullNetworkRegistration(int sipCode, String sipReason) {
+ ImsRegistrationImplBase.this.triggerFullNetworkRegistration(sipCode, sipReason);
+ }
+
+ @Override
+ public void triggerUpdateSipDelegateRegistration() {
+ ImsRegistrationImplBase.this.updateSipDelegateRegistration();
+ }
+
+ @Override
+ public void triggerSipDelegateDeregistration() {
+ ImsRegistrationImplBase.this.triggerSipDelegateDeregistration();
+ }
};
private final RemoteCallbackListExt<IImsRegistrationCallback> mCallbacks =
@@ -133,7 +151,6 @@ 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
*/
public void updateSipDelegateRegistration() {
// Stub implementation, ImsService should implement this
@@ -150,7 +167,6 @@ 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
*/
public void triggerSipDelegateDeregistration() {
// Stub implementation, ImsService should implement this
@@ -169,9 +185,8 @@ 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
*/
- public void triggerNetworkReregistration(@IntRange(from = 100, to = 699) int sipCode,
+ public void triggerFullNetworkRegistration(@IntRange(from = 100, to = 699) int sipCode,
@Nullable String sipReason) {
// Stub implementation, ImsService should implement this
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 205a425a5161..7c5047c2deaf 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -52,6 +52,7 @@ import android.telephony.SignalStrength;
import android.telephony.TelephonyHistogram;
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.RcsClientConfiguration;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
@@ -59,6 +60,7 @@ import android.telephony.ims.aidl.IImsMmTelFeature;
import android.telephony.ims.aidl.IImsRcsFeature;
import android.telephony.ims.aidl.IImsRegistration;
import android.telephony.ims.aidl.IImsRegistrationCallback;
+import android.telephony.ims.aidl.IRcsConfigCallback;
import com.android.ims.internal.IImsServiceFeatureCallback;
import com.android.internal.telephony.CellNetworkScanResult;
import com.android.internal.telephony.IBooleanConsumer;
@@ -2303,4 +2305,51 @@ interface ITelephony {
* Return the release time for telephony to unbind GbaService.
*/
int getGbaReleaseTime(int subId);
+
+ /**
+ * Provide the client configuration parameters of the RCS application.
+ */
+ void setRcsClientConfiguration(int subId, in RcsClientConfiguration rcc);
+
+ /**
+ * return value to indicate whether the device and the carrier can support RCS VoLTE
+ * single registration.
+ */
+ boolean isRcsVolteSingleRegistrationCapable(int subId);
+
+ /**
+ * Register RCS provisioning callback.
+ */
+ void registerRcsProvisioningChangedCallback(int subId,
+ IRcsConfigCallback callback);
+
+ /**
+ * Unregister RCS provisioning callback.
+ */
+ void unregisterRcsProvisioningChangedCallback(int subId, IRcsConfigCallback callback);
+
+ /**
+ * trigger RCS reconfiguration.
+ */
+ void triggerRcsReconfiguration(int subId);
+
+ /**
+ * Overrides the config of RCS VoLTE single registration enabled for the device.
+ */
+ void setDeviceSingleRegistrationEnabledOverride(String enabled);
+
+ /**
+ * Gets the config of RCS VoLTE single registration enabled for the device.
+ */
+ boolean getDeviceSingleRegistrationEnabled();
+
+ /**
+ * Overrides the config of RCS VoLTE single registration enabled for the carrier/subscription.
+ */
+ boolean setCarrierSingleRegistrationEnabledOverride(int subId, String enabled);
+
+ /**
+ * Gets the config of RCS VoLTE single registration enabled for the carrier/subscription.
+ */
+ boolean getCarrierSingleRegistrationEnabled(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 21cca4ca8204..52f263fad695 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -521,6 +521,8 @@ public interface RILConstants {
int RIL_REQUEST_CANCEL_HANDOVER = 218;
int RIL_REQUEST_GET_SYSTEM_SELECTION_CHANNELS = 219;
int RIL_REQUEST_SET_DATA_THROTTLING = 221;
+ int RIL_REQUEST_SET_ALLOWED_NETWORK_TYPE_BITMAP = 222;
+ int RIL_REQUEST_GET_ALLOWED_NETWORK_TYPE_BITMAP = 223;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/test-mock/src/android/test/mock/OWNERS b/test-mock/src/android/test/mock/OWNERS
index 9551245bd5c9..72e8ffc0db8a 100644
--- a/test-mock/src/android/test/mock/OWNERS
+++ b/test-mock/src/android/test/mock/OWNERS
@@ -1,3 +1 @@
-set noparent
-
*
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8c403f1b6898..a68044a985be 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -39,6 +39,7 @@ import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA;
import static android.net.ConnectivityManager.TYPE_MOBILE_MMS;
import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL;
+import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
@@ -101,6 +102,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.AdditionalMatchers.aryEq;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
@@ -132,6 +135,7 @@ import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
@@ -140,6 +144,8 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.location.LocationManager;
@@ -176,6 +182,7 @@ import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
@@ -332,12 +339,13 @@ public class ConnectivityServiceTest {
private static final String WIFI_WOL_IFNAME = "test_wlan_wol";
private static final String VPN_IFNAME = "tun10042";
private static final String TEST_PACKAGE_NAME = "com.android.test.package";
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn";
private static final String INTERFACE_NAME = "interface";
private MockContext mServiceContext;
private HandlerThread mCsHandlerThread;
+ private ConnectivityService.Dependencies mDeps;
private ConnectivityService mService;
private WrappedConnectivityManager mCm;
private TestNetworkAgentWrapper mWiFiNetworkAgent;
@@ -353,6 +361,7 @@ public class ConnectivityServiceTest {
@Mock IIpConnectivityMetrics mIpConnectivityMetrics;
@Mock IpConnectivityMetrics.Logger mMetricsService;
@Mock DefaultNetworkMetrics mDefaultNetworkMetrics;
+ @Mock DeviceIdleInternal mDeviceIdleInternal;
@Mock INetworkManagementService mNetworkManagementService;
@Mock INetworkStatsService mStatsService;
@Mock IBatteryStats mBatteryStatsService;
@@ -450,6 +459,15 @@ public class ConnectivityServiceTest {
}
@Override
+ public ComponentName startService(Intent service) {
+ final String action = service.getAction();
+ if (!VpnConfig.SERVICE_INTERFACE.equals(action)) {
+ fail("Attempt to start unknown service, action=" + action);
+ }
+ return new ComponentName(service.getPackage(), "com.android.test.Service");
+ }
+
+ @Override
public Object getSystemService(String name) {
if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager;
@@ -1055,9 +1073,19 @@ public class ConnectivityServiceTest {
private VpnInfo mVpnInfo;
public MockVpn(int userId) {
- super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
- mMockNetd, userId, mock(KeyStore.class));
- mConfig = new VpnConfig();
+ super(startHandlerThreadAndReturnLooper(), mServiceContext,
+ new Dependencies() {
+ @Override
+ public boolean isCallerSystem() {
+ return true;
+ }
+
+ @Override
+ public DeviceIdleInternal getDeviceIdleInternal() {
+ return mDeviceIdleInternal;
+ }
+ },
+ mNetworkManagementService, mMockNetd, userId, mock(KeyStore.class));
}
public void setUids(Set<UidRange> uids) {
@@ -1086,9 +1114,16 @@ public class ConnectivityServiceTest {
return mVpnType;
}
+ private LinkProperties makeLinkProperties() {
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(VPN_IFNAME);
+ return lp;
+ }
+
private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp)
throws Exception {
if (mAgentRegistered) throw new IllegalStateException("already registered");
+ mConfig = new VpnConfig();
setUids(uids);
if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
mInterface = VPN_IFNAME;
@@ -1101,12 +1136,13 @@ public class ConnectivityServiceTest {
verify(mMockNetd, never())
.networkRemoveUidRanges(eq(mMockVpn.getNetId()), any());
mAgentRegistered = true;
+ updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
mNetworkAgent = mMockNetworkAgent.getNetworkAgent();
}
private void registerAgent(Set<UidRange> uids) throws Exception {
- registerAgent(false /* isAlwaysMetered */, uids, new LinkProperties());
+ registerAgent(false /* isAlwaysMetered */, uids, makeLinkProperties());
}
private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
@@ -1142,12 +1178,12 @@ public class ConnectivityServiceTest {
public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode)
throws Exception {
final int uid = Process.myUid();
- establish(new LinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet,
+ establish(makeLinkProperties(), uid, uidRangesForUid(uid), validated, hasInternet,
isStrictMode);
}
public void establishForMyUid() throws Exception {
- establishForMyUid(new LinkProperties());
+ establishForMyUid(makeLinkProperties());
}
public void sendLinkProperties(LinkProperties lp) {
@@ -1155,7 +1191,10 @@ public class ConnectivityServiceTest {
}
public void disconnect() {
- if (mMockNetworkAgent != null) mMockNetworkAgent.disconnect();
+ if (mMockNetworkAgent != null) {
+ mMockNetworkAgent.disconnect();
+ updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect");
+ }
mAgentRegistered = false;
}
@@ -1229,6 +1268,17 @@ public class ConnectivityServiceTest {
fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms");
}
+ private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback,
+ int uid) {
+ when(mDeps.getCallingUid()).thenReturn(uid);
+ try {
+ mCm.registerNetworkCallback(request, callback);
+ waitForIdle();
+ } finally {
+ returnRealCallingUid();
+ }
+ }
+
private static final int VPN_USER = 0;
private static final int APP1_UID = UserHandle.getUid(VPN_USER, 10100);
private static final int APP2_UID = UserHandle.getUid(VPN_USER, 10101);
@@ -1271,7 +1321,8 @@ public class ConnectivityServiceTest {
initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler());
mCsHandlerThread = new HandlerThread("TestConnectivityService");
- final ConnectivityService.Dependencies deps = makeDependencies();
+ mDeps = makeDependencies();
+ returnRealCallingUid();
mService = new ConnectivityService(mServiceContext,
mNetworkManagementService,
mStatsService,
@@ -1279,9 +1330,9 @@ public class ConnectivityServiceTest {
mMockDnsResolver,
mock(IpConnectivityLog.class),
mMockNetd,
- deps);
+ mDeps);
mService.mLingerDelayMs = TEST_LINGER_DELAY_MS;
- verify(deps).makeMultinetworkPolicyTracker(any(), any(), any());
+ verify(mDeps).makeMultinetworkPolicyTracker(any(), any(), any());
final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor =
ArgumentCaptor.forClass(INetworkPolicyListener.class);
@@ -1301,6 +1352,10 @@ public class ConnectivityServiceTest {
setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com");
}
+ private void returnRealCallingUid() {
+ doAnswer((invocationOnMock) -> Binder.getCallingUid()).when(mDeps).getCallingUid();
+ }
+
private ConnectivityService.Dependencies makeDependencies() {
doReturn(TEST_TCP_INIT_RWND).when(mSystemProperties)
.getInt("net.tcp.default_init_rwnd", 0);
@@ -1376,13 +1431,13 @@ public class ConnectivityServiceTest {
}
private void mockDefaultPackages() throws Exception {
- final String testPackageName = mContext.getPackageName();
- final PackageInfo testPackageInfo = mContext.getPackageManager().getPackageInfo(
- testPackageName, PackageManager.GET_PERMISSIONS);
+ final String myPackageName = mContext.getPackageName();
+ final PackageInfo myPackageInfo = mContext.getPackageManager().getPackageInfo(
+ myPackageName, PackageManager.GET_PERMISSIONS);
when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn(
- new String[] {testPackageName});
- when(mPackageManager.getPackageInfoAsUser(eq(testPackageName), anyInt(),
- eq(UserHandle.getCallingUserId()))).thenReturn(testPackageInfo);
+ new String[] {myPackageName});
+ when(mPackageManager.getPackageInfoAsUser(eq(myPackageName), anyInt(),
+ eq(UserHandle.getCallingUserId()))).thenReturn(myPackageInfo);
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn(
Arrays.asList(new PackageInfo[] {
@@ -1390,6 +1445,25 @@ public class ConnectivityServiceTest {
buildPackageInfo(/* SYSTEM */ false, APP2_UID),
buildPackageInfo(/* SYSTEM */ false, VPN_UID)
}));
+
+ // Create a fake always-on VPN package.
+ final int userId = UserHandle.getCallingUserId();
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; // Always-on supported in N+.
+ when(mPackageManager.getApplicationInfoAsUser(eq(ALWAYS_ON_PACKAGE), anyInt(),
+ eq(userId))).thenReturn(applicationInfo);
+
+ // Minimal mocking to keep Vpn#isAlwaysOnPackageSupported happy.
+ ResolveInfo rInfo = new ResolveInfo();
+ rInfo.serviceInfo = new ServiceInfo();
+ rInfo.serviceInfo.metaData = new Bundle();
+ final List<ResolveInfo> services = Arrays.asList(new ResolveInfo[]{rInfo});
+ when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA),
+ eq(userId))).thenReturn(services);
+ when(mPackageManager.getPackageUidAsUser(TEST_PACKAGE_NAME, userId))
+ .thenReturn(Process.myUid());
+ when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, userId))
+ .thenReturn(VPN_UID);
}
private void verifyActiveNetwork(int transport) {
@@ -2252,10 +2326,10 @@ public class ConnectivityServiceTest {
}
private void grantUsingBackgroundNetworksPermissionForUid(final int uid) throws Exception {
- final String testPackageName = mContext.getPackageName();
- when(mPackageManager.getPackageInfo(eq(testPackageName), eq(GET_PERMISSIONS)))
+ final String myPackageName = mContext.getPackageName();
+ when(mPackageManager.getPackageInfo(eq(myPackageName), eq(GET_PERMISSIONS)))
.thenReturn(buildPackageInfo(true, uid));
- mService.mPermissionMonitor.onPackageAdded(testPackageName, uid);
+ mService.mPermissionMonitor.onPackageAdded(myPackageName, uid);
}
@Test
@@ -5831,10 +5905,21 @@ public class ConnectivityServiceTest {
assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
}
+ private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) {
+ final NetworkCapabilities[] defaultCaps = mService.getDefaultNetworkCapabilitiesForUser(
+ userId, "com.android.calling.package");
+ final String defaultCapsString = Arrays.toString(defaultCaps);
+ assertEquals(defaultCapsString, defaultCaps.length, networks.length);
+ final Set<NetworkCapabilities> defaultCapsSet = new ArraySet<>(defaultCaps);
+ for (NetworkAgentWrapper network : networks) {
+ final NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork());
+ final String msg = "Did not find " + nc + " in " + Arrays.toString(defaultCaps);
+ assertTrue(msg, defaultCapsSet.contains(nc));
+ }
+ }
+
@Test
public void testVpnSetUnderlyingNetworks() throws Exception {
- final int uid = Process.myUid();
-
final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback();
final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_NOT_VPN)
@@ -5857,6 +5942,9 @@ public class ConnectivityServiceTest {
// A VPN without underlying networks is not suspended.
assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ final int userId = UserHandle.getUserId(Process.myUid());
+ assertDefaultNetworkCapabilities(userId /* no networks */);
+
// Connect cell and use it as an underlying network.
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED);
@@ -5870,6 +5958,7 @@ public class ConnectivityServiceTest {
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
&& caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ assertDefaultNetworkCapabilities(userId, mCellNetworkAgent);
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
@@ -5884,6 +5973,7 @@ public class ConnectivityServiceTest {
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
&& caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
// Don't disconnect, but note the VPN is not using wifi any more.
mService.setUnderlyingNetworksForVpn(
@@ -5894,6 +5984,9 @@ public class ConnectivityServiceTest {
&& caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
&& caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ // The return value of getDefaultNetworkCapabilitiesForUser always includes the default
+ // network (wifi) as well as the underlying networks (cell).
+ assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
// Remove NOT_SUSPENDED from the only network and observe VPN is now suspended.
mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
@@ -5922,6 +6015,7 @@ public class ConnectivityServiceTest {
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& caps.hasCapability(NET_CAPABILITY_NOT_METERED)
&& caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent);
// Use both again.
mService.setUnderlyingNetworksForVpn(
@@ -5932,6 +6026,7 @@ public class ConnectivityServiceTest {
&& caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED)
&& caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
+ assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
// Cell is suspended again. As WiFi is not, this should not cause a callback.
mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED);
@@ -5949,6 +6044,7 @@ public class ConnectivityServiceTest {
// a bug in ConnectivityService, but as the SUSPENDED and RESUMED callbacks have never
// been public and are deprecated and slated for removal, there is no sense in spending
// resources fixing this bug now.
+ assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
// Use both again.
mService.setUnderlyingNetworksForVpn(
@@ -5961,6 +6057,7 @@ public class ConnectivityServiceTest {
&& caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
// As above, the RESUMED callback not being sent here is a bug, but not a bug that's
// worth anybody's time to fix.
+ assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent);
// Disconnect cell. Receive update without even removing the dead network from the
// underlying networks – it's dead anyway. Not metered any more.
@@ -5969,6 +6066,7 @@ public class ConnectivityServiceTest {
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI)
&& caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent);
// Disconnect wifi too. No underlying networks means this is now metered.
mWiFiNetworkAgent.disconnect();
@@ -5976,6 +6074,11 @@ public class ConnectivityServiceTest {
(caps) -> caps.hasTransport(TRANSPORT_VPN)
&& !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI)
&& !caps.hasCapability(NET_CAPABILITY_NOT_METERED));
+ // When a network disconnects, the callbacks are fired before all state is updated, so for a
+ // short time, synchronous calls will behave as if the network is still connected. Wait for
+ // things to settle.
+ waitForIdle();
+ assertDefaultNetworkCapabilities(userId /* no networks */);
mMockVpn.disconnect();
}
@@ -6276,6 +6379,7 @@ public class ConnectivityServiceTest {
// Despite VPN using WiFi (which is unmetered), VPN itself is marked as always metered.
assertTrue(mCm.isActiveNetworkMetered());
+
// VPN explicitly declares WiFi as its underlying network.
mService.setUnderlyingNetworksForVpn(
new Network[] { mWiFiNetworkAgent.getNetwork() });
@@ -6389,6 +6493,189 @@ public class ConnectivityServiceTest {
mCm.unregisterNetworkCallback(defaultCallback);
}
+ private void expectNetworkRejectNonSecureVpn(InOrder inOrder, boolean add,
+ UidRangeParcel... expected) throws Exception {
+ inOrder.verify(mMockNetd).networkRejectNonSecureVpn(eq(add), aryEq(expected));
+ }
+
+ private void checkNetworkInfo(NetworkInfo ni, int type, DetailedState state) {
+ assertNotNull(ni);
+ assertEquals(type, ni.getType());
+ assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState());
+ }
+
+ private void assertActiveNetworkInfo(int type, DetailedState state) {
+ checkNetworkInfo(mCm.getActiveNetworkInfo(), type, state);
+ }
+ private void assertNetworkInfo(int type, DetailedState state) {
+ checkNetworkInfo(mCm.getNetworkInfo(type), type, state);
+ }
+
+ @Test
+ public void testNetworkBlockedStatusAlwaysOnVpn() throws Exception {
+ mServiceContext.setPermission(
+ Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
+ mServiceContext.setPermission(
+ Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
+ mServiceContext.setPermission(
+ Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .build();
+ mCm.registerNetworkCallback(request, callback);
+
+ final TestNetworkCallback defaultCallback = new TestNetworkCallback();
+ mCm.registerDefaultNetworkCallback(defaultCallback);
+
+ final TestNetworkCallback vpnUidCallback = new TestNetworkCallback();
+ final NetworkRequest vpnUidRequest = new NetworkRequest.Builder().build();
+ registerNetworkCallbackAsUid(vpnUidRequest, vpnUidCallback, VPN_UID);
+
+ final int uid = Process.myUid();
+ final int userId = UserHandle.getUserId(uid);
+ final ArrayList<String> allowList = new ArrayList<>();
+ mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+
+ UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
+ UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999);
+ InOrder inOrder = inOrder(mMockNetd);
+ expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+
+ // Connect a network when lockdown is active, expect to see it blocked.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false /* validated */);
+ callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+ defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+ vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertNull(mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
+ // Mobile is BLOCKED even though it's not actually connected.
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
+
+ // Disable lockdown, expect to see the network unblocked.
+ // There are no callbacks because they are not implemented yet.
+ mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
+ vpnUidCallback.assertNoCallback();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+
+ // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked.
+ allowList.add(TEST_PACKAGE_NAME);
+ mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ callback.assertNoCallback();
+ defaultCallback.assertNoCallback();
+ vpnUidCallback.assertNoCallback();
+
+ // The following requires that the UID of this test package is greater than VPN_UID. This
+ // is always true in practice because a plain AOSP build with no apps installed has almost
+ // 200 packages installed.
+ final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1);
+ final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1);
+ final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999);
+ expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+
+ // Connect a new network, expect it to be unblocked.
+ mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(false /* validated */);
+ callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ defaultCallback.assertNoCallback();
+ vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+ // Cellular is DISCONNECTED because it's not the default and there are no requests for it.
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+
+ // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown.
+ // Everything should now be blocked.
+ mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
+ allowList.clear();
+ mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+ vpnUidCallback.assertNoCallback();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertNull(mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
+
+ // Disable lockdown. Everything is unblocked.
+ mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ vpnUidCallback.assertNoCallback();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+
+ // Enable and disable an always-on VPN package without lockdown. Expect no changes.
+ reset(mMockNetd);
+ mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, allowList);
+ inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
+ callback.assertNoCallback();
+ defaultCallback.assertNoCallback();
+ vpnUidCallback.assertNoCallback();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+
+ mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+ inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any());
+ callback.assertNoCallback();
+ defaultCallback.assertNoCallback();
+ vpnUidCallback.assertNoCallback();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+
+ // Enable lockdown and connect a VPN. The VPN is not blocked.
+ mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+ vpnUidCallback.assertNoCallback();
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
+ assertNull(mCm.getActiveNetwork());
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
+
+ mMockVpn.establishForMyUid();
+ defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
+ vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability.
+ assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
+ assertEquals(null, mCm.getActiveNetworkForUid(VPN_UID)); // BUG?
+ assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
+ assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
+ assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
+
+ mMockVpn.disconnect();
+ defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn);
+ defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent);
+ assertNull(mCm.getActiveNetwork());
+
+ mCm.unregisterNetworkCallback(callback);
+ mCm.unregisterNetworkCallback(defaultCallback);
+ mCm.unregisterNetworkCallback(vpnUidCallback);
+ }
+
@Test
public final void testLoseTrusted() throws Exception {
final NetworkRequest trustedRequest = new NetworkRequest.Builder()
@@ -7822,7 +8109,16 @@ public class ConnectivityServiceTest {
@Test
public void testDumpDoesNotCrash() {
- StringWriter stringWriter = new StringWriter();
+ // Filing a couple requests prior to testing the dump.
+ final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback();
+ final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest genericRequest = new NetworkRequest.Builder()
+ .clearCapabilities().build();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ mCm.registerNetworkCallback(genericRequest, genericNetworkCallback);
+ mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+ final StringWriter stringWriter = new StringWriter();
mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
@@ -7844,11 +8140,11 @@ public class ConnectivityServiceTest {
mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
- ConnectivityService.NetworkRequestInfo[] nriOutput = mService.requestsSortedById();
+ final ConnectivityService.NetworkRequestInfo[] nriOutput = mService.requestsSortedById();
assertTrue(nriOutput.length > 1);
for (int i = 0; i < nriOutput.length - 1; i++) {
- boolean isRequestIdInOrder =
+ final boolean isRequestIdInOrder =
nriOutput[i].mRequests.get(0).requestId
< nriOutput[i + 1].mRequests.get(0).requestId;
assertTrue(isRequestIdInOrder);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index 6e380be6c583..cc473175540c 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -228,7 +228,6 @@ public class VpnTest {
R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS))
.thenReturn(true);
- when(mSystemServices.isCallerSystem()).thenReturn(true);
// Used by {@link Notification.Builder}
ApplicationInfo applicationInfo = new ApplicationInfo();
@@ -1102,6 +1101,11 @@ public class VpnTest {
}
@Override
+ public boolean isCallerSystem() {
+ return true;
+ }
+
+ @Override
public void startService(final String serviceName) {
mRunningServices.put(serviceName, true);
}
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index c91fdbffd760..633cf64bc274 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -16,14 +16,23 @@
package com.android.server;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.vcn.VcnConfig;
+import android.os.ParcelUuid;
+import android.os.Process;
+import android.os.UserHandle;
import android.os.test.TestLooper;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -31,27 +40,73 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Collections;
+import java.util.UUID;
+
/** Tests for {@link VcnManagementService}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
public class VcnManagementServiceTest {
+ private static final ParcelUuid TEST_UUID_1 = new ParcelUuid(new UUID(0, 0));
+ private static final SubscriptionInfo TEST_SUBSCRIPTION_INFO =
+ new SubscriptionInfo(
+ 1 /* id */,
+ "" /* iccId */,
+ 0 /* simSlotIndex */,
+ "Carrier" /* displayName */,
+ "Carrier" /* carrierName */,
+ 0 /* nameSource */,
+ 255 /* iconTint */,
+ "12345" /* number */,
+ 0 /* roaming */,
+ null /* icon */,
+ "0" /* mcc */,
+ "0" /* mnc */,
+ "0" /* countryIso */,
+ false /* isEmbedded */,
+ null /* nativeAccessRules */,
+ null /* cardString */,
+ false /* isOpportunistic */,
+ TEST_UUID_1.toString() /* groupUUID */,
+ 0 /* carrierId */,
+ 0 /* profileClass */);
+
private final Context mMockContext = mock(Context.class);
private final VcnManagementService.Dependencies mMockDeps =
mock(VcnManagementService.Dependencies.class);
private final TestLooper mTestLooper = new TestLooper();
private final ConnectivityManager mConnMgr = mock(ConnectivityManager.class);
+ private final TelephonyManager mTelMgr = mock(TelephonyManager.class);
+ private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class);
private final VcnManagementService mVcnMgmtSvc;
- public VcnManagementServiceTest() {
- doReturn(Context.CONNECTIVITY_SERVICE)
- .when(mMockContext)
- .getSystemServiceName(ConnectivityManager.class);
- doReturn(mConnMgr).when(mMockContext).getSystemService(Context.CONNECTIVITY_SERVICE);
+ public VcnManagementServiceTest() throws Exception {
+ setupSystemService(mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class);
+ setupSystemService(mTelMgr, Context.TELEPHONY_SERVICE, TelephonyManager.class);
+ setupSystemService(
+ mSubMgr, Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class);
doReturn(mTestLooper.getLooper()).when(mMockDeps).getLooper();
+ doReturn(Process.FIRST_APPLICATION_UID).when(mMockDeps).getBinderCallingUid();
+
+ setupMockedCarrierPrivilege(true);
mVcnMgmtSvc = new VcnManagementService(mMockContext, mMockDeps);
}
+ private void setupSystemService(Object service, String name, Class<?> serviceClass) {
+ doReturn(name).when(mMockContext).getSystemServiceName(serviceClass);
+ doReturn(service).when(mMockContext).getSystemService(name);
+ }
+
+ private void setupMockedCarrierPrivilege(boolean isPrivileged) {
+ doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO))
+ .when(mSubMgr)
+ .getSubscriptionsInGroup(any());
+ doReturn(isPrivileged)
+ .when(mTelMgr)
+ .hasCarrierPrivileges(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
+ }
+
@Test
public void testSystemReady() throws Exception {
mVcnMgmtSvc.systemReady();
@@ -59,4 +114,74 @@ public class VcnManagementServiceTest {
verify(mConnMgr)
.registerNetworkProvider(any(VcnManagementService.VcnNetworkProvider.class));
}
+
+ @Test
+ public void testSetVcnConfigRequiresNonSystemServer() throws Exception {
+ doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
+
+ try {
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build());
+ fail("Expected IllegalStateException exception for system server");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void testSetVcnConfigRequiresSystemUser() throws Exception {
+ doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
+ .when(mMockDeps)
+ .getBinderCallingUid();
+
+ try {
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build());
+ fail("Expected security exception for non system user");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testSetVcnConfigRequiresCarrierPrivileges() throws Exception {
+ setupMockedCarrierPrivilege(false);
+
+ try {
+ mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, new VcnConfig.Builder().build());
+ fail("Expected security exception for missing carrier privileges");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testClearVcnConfigRequiresNonSystemServer() throws Exception {
+ doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
+
+ try {
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ fail("Expected IllegalStateException exception for system server");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ @Test
+ public void testClearVcnConfigRequiresSystemUser() throws Exception {
+ doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, Process.FIRST_APPLICATION_UID))
+ .when(mMockDeps)
+ .getBinderCallingUid();
+
+ try {
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ fail("Expected security exception for non system user");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testClearVcnConfigRequiresCarrierPrivileges() throws Exception {
+ setupMockedCarrierPrivilege(false);
+
+ try {
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ fail("Expected security exception for missing carrier privileges");
+ } catch (SecurityException expected) {
+ }
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
new file mode 100644
index 000000000000..17b8f64a13fa
--- /dev/null
+++ b/tests/vcn/java/com/android/server/vcn/TelephonySubscriptionTrackerTest.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2020 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.vcn;
+
+import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
+import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
+import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
+import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.ParcelUuid;
+import android.os.test.TestLooper;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.util.ArraySet;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/** Tests for TelephonySubscriptionTracker */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TelephonySubscriptionTrackerTest {
+ private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID());
+ private static final int TEST_SIM_SLOT_INDEX = 1;
+ private static final int TEST_SUBSCRIPTION_ID_1 = 2;
+ private static final SubscriptionInfo TEST_SUBINFO_1 = mock(SubscriptionInfo.class);
+ private static final int TEST_SUBSCRIPTION_ID_2 = 3;
+ private static final SubscriptionInfo TEST_SUBINFO_2 = mock(SubscriptionInfo.class);
+ private static final Map<Integer, ParcelUuid> TEST_SUBID_TO_GROUP_MAP;
+
+ static {
+ final Map<Integer, ParcelUuid> subIdToGroupMap = new HashMap<>();
+ subIdToGroupMap.put(TEST_SUBSCRIPTION_ID_1, TEST_PARCEL_UUID);
+ subIdToGroupMap.put(TEST_SUBSCRIPTION_ID_2, TEST_PARCEL_UUID);
+ TEST_SUBID_TO_GROUP_MAP = Collections.unmodifiableMap(subIdToGroupMap);
+ }
+
+ @NonNull private final Context mContext;
+ @NonNull private final TestLooper mTestLooper;
+ @NonNull private final Handler mHandler;
+ @NonNull private final TelephonySubscriptionTracker.Dependencies mDeps;
+
+ @NonNull private final SubscriptionManager mSubscriptionManager;
+ @NonNull private final CarrierConfigManager mCarrierConfigManager;
+
+ @NonNull private TelephonySubscriptionTrackerCallback mCallback;
+ @NonNull private TelephonySubscriptionTracker mTelephonySubscriptionTracker;
+
+ public TelephonySubscriptionTrackerTest() {
+ mContext = mock(Context.class);
+ mTestLooper = new TestLooper();
+ mHandler = new Handler(mTestLooper.getLooper());
+ mDeps = mock(TelephonySubscriptionTracker.Dependencies.class);
+
+ mSubscriptionManager = mock(SubscriptionManager.class);
+ mCarrierConfigManager = mock(CarrierConfigManager.class);
+
+ doReturn(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
+ .when(mContext)
+ .getSystemServiceName(SubscriptionManager.class);
+ doReturn(mSubscriptionManager)
+ .when(mContext)
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+
+ doReturn(Context.CARRIER_CONFIG_SERVICE)
+ .when(mContext)
+ .getSystemServiceName(CarrierConfigManager.class);
+ doReturn(mCarrierConfigManager)
+ .when(mContext)
+ .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+
+ // subId 1, 2 are in same subGrp, only subId 1 is active
+ doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_1).getGroupUuid();
+ doReturn(TEST_PARCEL_UUID).when(TEST_SUBINFO_2).getGroupUuid();
+ doReturn(TEST_SIM_SLOT_INDEX).when(TEST_SUBINFO_1).getSimSlotIndex();
+ doReturn(INVALID_SIM_SLOT_INDEX).when(TEST_SUBINFO_2).getSimSlotIndex();
+ doReturn(TEST_SUBSCRIPTION_ID_1).when(TEST_SUBINFO_1).getSubscriptionId();
+ doReturn(TEST_SUBSCRIPTION_ID_2).when(TEST_SUBINFO_2).getSubscriptionId();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mCallback = mock(TelephonySubscriptionTrackerCallback.class);
+ mTelephonySubscriptionTracker =
+ new TelephonySubscriptionTracker(mContext, mHandler, mCallback, mDeps);
+ mTelephonySubscriptionTracker.register();
+
+ doReturn(true).when(mDeps).isConfigForIdentifiedCarrier(any());
+ doReturn(Arrays.asList(TEST_SUBINFO_1, TEST_SUBINFO_2))
+ .when(mSubscriptionManager)
+ .getAllSubscriptionInfoList();
+ }
+
+ private IntentFilter getIntentFilter() {
+ final ArgumentCaptor<IntentFilter> captor = ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mContext).registerReceiver(any(), captor.capture(), any(), any());
+
+ return captor.getValue();
+ }
+
+ private OnSubscriptionsChangedListener getOnSubscriptionsChangedListener() {
+ final ArgumentCaptor<OnSubscriptionsChangedListener> captor =
+ ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class);
+ verify(mSubscriptionManager).addOnSubscriptionsChangedListener(any(), captor.capture());
+
+ return captor.getValue();
+ }
+
+ private Intent buildTestBroadcastIntent(boolean hasValidSubscription) {
+ Intent intent = new Intent(ACTION_CARRIER_CONFIG_CHANGED);
+ intent.putExtra(EXTRA_SLOT_INDEX, TEST_SIM_SLOT_INDEX);
+ intent.putExtra(
+ EXTRA_SUBSCRIPTION_INDEX,
+ hasValidSubscription ? TEST_SUBSCRIPTION_ID_1 : INVALID_SUBSCRIPTION_ID);
+
+ return intent;
+ }
+
+ private TelephonySubscriptionSnapshot buildExpectedSnapshot(Set<ParcelUuid> activeSubGroups) {
+ return buildExpectedSnapshot(TEST_SUBID_TO_GROUP_MAP, activeSubGroups);
+ }
+
+ private TelephonySubscriptionSnapshot buildExpectedSnapshot(
+ Map<Integer, ParcelUuid> subIdToGroupMap, Set<ParcelUuid> activeSubGroups) {
+ return new TelephonySubscriptionSnapshot(subIdToGroupMap, activeSubGroups);
+ }
+
+ private void verifyNoActiveSubscriptions() {
+ verify(mCallback).onNewSnapshot(
+ argThat(snapshot -> snapshot.getActiveSubscriptionGroups().isEmpty()));
+ }
+
+ private void setupReadySubIds() {
+ mTelephonySubscriptionTracker.setReadySubIdsBySlotId(
+ Collections.singletonMap(TEST_SIM_SLOT_INDEX, TEST_SUBSCRIPTION_ID_1));
+ }
+
+ @Test
+ public void testRegister() throws Exception {
+ verify(mContext)
+ .registerReceiver(
+ eq(mTelephonySubscriptionTracker),
+ any(IntentFilter.class),
+ any(),
+ eq(mHandler));
+ final IntentFilter filter = getIntentFilter();
+ assertEquals(1, filter.countActions());
+ assertTrue(filter.hasAction(ACTION_CARRIER_CONFIG_CHANGED));
+
+ verify(mSubscriptionManager)
+ .addOnSubscriptionsChangedListener(any(HandlerExecutor.class), any());
+ assertNotNull(getOnSubscriptionsChangedListener());
+ }
+
+ @Test
+ public void testUnregister() throws Exception {
+ mTelephonySubscriptionTracker.unregister();
+
+ verify(mContext).unregisterReceiver(eq(mTelephonySubscriptionTracker));
+
+ final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
+ verify(mSubscriptionManager).removeOnSubscriptionsChangedListener(eq(listener));
+ }
+
+ @Test
+ public void testOnSubscriptionsChangedFired_NoReadySubIds() throws Exception {
+ final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
+ listener.onSubscriptionsChanged();
+ mTestLooper.dispatchAll();
+
+ verifyNoActiveSubscriptions();
+ }
+
+ @Test
+ public void testOnSubscriptionsChangedFired_WithReadySubIds() throws Exception {
+ setupReadySubIds();
+
+ final OnSubscriptionsChangedListener listener = getOnSubscriptionsChangedListener();
+ listener.onSubscriptionsChanged();
+ mTestLooper.dispatchAll();
+
+ final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
+ verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+ }
+
+ @Test
+ public void testReceiveBroadcast_ConfigReadyWithSubscriptions() throws Exception {
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ mTestLooper.dispatchAll();
+
+ final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
+ verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+ }
+
+ @Test
+ public void testReceiveBroadcast_ConfigReadyNoSubscriptions() throws Exception {
+ doReturn(new ArrayList<SubscriptionInfo>())
+ .when(mSubscriptionManager)
+ .getAllSubscriptionInfoList();
+
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ mTestLooper.dispatchAll();
+
+ // Expect an empty snapshot
+ verify(mCallback).onNewSnapshot(
+ eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
+ }
+
+ @Test
+ public void testReceiveBroadcast_SlotCleared() throws Exception {
+ setupReadySubIds();
+
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
+ mTestLooper.dispatchAll();
+
+ verifyNoActiveSubscriptions();
+ assertTrue(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().isEmpty());
+ }
+
+ @Test
+ public void testReceiveBroadcast_ConfigNotReady() throws Exception {
+ doReturn(false).when(mDeps).isConfigForIdentifiedCarrier(any());
+
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ mTestLooper.dispatchAll();
+
+ // No interactions expected; config was not loaded
+ verifyNoMoreInteractions(mCallback);
+ }
+
+ @Test
+ public void testSubscriptionsClearedAfterValidTriggersCallbacks() throws Exception {
+ final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
+
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ mTestLooper.dispatchAll();
+ verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+ assertNotNull(
+ mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
+
+ doReturn(Collections.emptyList()).when(mSubscriptionManager).getAllSubscriptionInfoList();
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ mTestLooper.dispatchAll();
+ verify(mCallback).onNewSnapshot(
+ eq(buildExpectedSnapshot(Collections.emptyMap(), Collections.emptySet())));
+ }
+
+ @Test
+ public void testSlotClearedAfterValidTriggersCallbacks() throws Exception {
+ final Set<ParcelUuid> activeSubGroups = Collections.singleton(TEST_PARCEL_UUID);
+
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(true));
+ mTestLooper.dispatchAll();
+ verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(activeSubGroups)));
+ assertNotNull(
+ mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
+
+ mTelephonySubscriptionTracker.onReceive(mContext, buildTestBroadcastIntent(false));
+ mTestLooper.dispatchAll();
+ verify(mCallback).onNewSnapshot(eq(buildExpectedSnapshot(Collections.emptySet())));
+ assertNull(mTelephonySubscriptionTracker.getReadySubIdsBySlotId().get(TEST_SIM_SLOT_INDEX));
+ }
+
+ @Test
+ public void testTelephonySubscriptionSnapshotGetGroupForSubId() throws Exception {
+ final TelephonySubscriptionSnapshot snapshot =
+ new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
+
+ assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_1));
+ assertEquals(TEST_PARCEL_UUID, snapshot.getGroupForSubId(TEST_SUBSCRIPTION_ID_2));
+ }
+
+ @Test
+ public void testTelephonySubscriptionSnapshotGetAllSubIdsInGroup() throws Exception {
+ final TelephonySubscriptionSnapshot snapshot =
+ new TelephonySubscriptionSnapshot(TEST_SUBID_TO_GROUP_MAP, Collections.emptySet());
+
+ assertEquals(
+ new ArraySet<>(Arrays.asList(TEST_SUBSCRIPTION_ID_1, TEST_SUBSCRIPTION_ID_2)),
+ snapshot.getAllSubIdsInGroup(TEST_PARCEL_UUID));
+ }
+}