diff options
175 files changed, 14570 insertions, 1114 deletions
diff --git a/Android.bp b/Android.bp index eb1718efbef6..570040f3ab93 100644 --- a/Android.bp +++ b/Android.bp @@ -744,6 +744,7 @@ filegroup { "core/java/com/android/internal/util/IState.java", "core/java/com/android/internal/util/State.java", "core/java/com/android/internal/util/StateMachine.java", + "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java", "telephony/java/android/telephony/Annotation.java", ], } diff --git a/StubLibraries.bp b/StubLibraries.bp index 7060347922b9..cb36e639545b 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -110,37 +110,9 @@ stubs_defaults { } ///////////////////////////////////////////////////////////////////// -// *-api-stubs-docs modules providing source files for the stub libraries +// These modules provide source files for the stub libraries ///////////////////////////////////////////////////////////////////// -// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs -// from the non-updatable part of the platform as well as from the updatable -// modules -droidstubs { - name: "api-stubs-docs", - defaults: ["metalava-full-api-stubs-default"], - arg_files: [ - "core/res/AndroidManifest.xml", - ], - args: metalava_framework_docs_args, - check_api: { - current: { - api_file: "api/current.txt", - removed_api_file: "api/removed.txt", - }, - last_released: { - api_file: ":android.api.public.latest", - removed_api_file: ":removed.api.public.latest", - baseline_file: ":public-api-incompatibilities-with-last-released", - }, - api_lint: { - enabled: true, - new_since: ":android.api.public.latest", - baseline_file: "api/lint-baseline.txt", - }, - }, -} - droidstubs { name: "api-stubs-docs-non-updatable", defaults: ["metalava-non-updatable-api-stubs-default"], @@ -177,31 +149,6 @@ module_libs = " " + "\\) " droidstubs { - name: "system-api-stubs-docs", - defaults: ["metalava-full-api-stubs-default"], - arg_files: [ - "core/res/AndroidManifest.xml", - ], - args: metalava_framework_docs_args + priv_apps, - check_api: { - current: { - api_file: "api/system-current.txt", - removed_api_file: "api/system-removed.txt", - }, - last_released: { - api_file: ":android.api.system.latest", - removed_api_file: ":removed.api.system.latest", - baseline_file: ":system-api-incompatibilities-with-last-released" - }, - api_lint: { - enabled: true, - new_since: ":android.api.system.latest", - baseline_file: "api/system-lint-baseline.txt", - }, - }, -} - -droidstubs { name: "system-api-stubs-docs-non-updatable", defaults: ["metalava-non-updatable-api-stubs-default"], arg_files: ["core/res/AndroidManifest.xml"], diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index 42725c51fd87..e0467df823dd 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -154,6 +154,7 @@ public abstract class JobScheduler { * @param tag Debugging tag for dumps associated with this job (instead of the service class) * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public abstract @Result int scheduleAsPackage(@NonNull JobInfo job, @NonNull String packageName, @@ -196,6 +197,7 @@ public abstract class JobScheduler { * Returns a list of all currently-executing jobs. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract List<JobInfo> getStartedJobs(); /** @@ -205,5 +207,6 @@ public abstract class JobScheduler { * <p class="note">This is a slow operation, so it should be called sparingly. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract List<JobSnapshot> getAllJobSnapshots(); }
\ No newline at end of file diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp index 71a52bb216ea..e30df05b2340 100644 --- a/apex/permission/Android.bp +++ b/apex/permission/Android.bp @@ -21,7 +21,7 @@ apex { apex_defaults { name: "com.android.permission-defaults", updatable: true, - min_sdk_version: "R", + min_sdk_version: "30", key: "com.android.permission.key", certificate: ":com.android.permission.certificate", java_libs: [ diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp index ede8852c5905..f13861e7ee85 100644 --- a/apex/statsd/Android.bp +++ b/apex/statsd/Android.bp @@ -35,7 +35,7 @@ apex_defaults { prebuilts: ["com.android.os.statsd.init.rc"], name: "com.android.os.statsd-defaults", updatable: true, - min_sdk_version: "R", + min_sdk_version: "30", key: "com.android.os.statsd.key", certificate: ":com.android.os.statsd.certificate", } diff --git a/api/Android.bp b/api/Android.bp index ae0d59635e8f..471b9930eddb 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -57,6 +57,25 @@ genrule { } genrule { + name: "frameworks-base-api-current.srcjar", + srcs: [ + ":api-stubs-docs-non-updatable", + ":conscrypt.module.public.api{.public.stubs.source}", + ":framework-media{.public.stubs.source}", + ":framework-mediaprovider{.public.stubs.source}", + ":framework-permission{.public.stubs.source}", + ":framework-sdkextensions{.public.stubs.source}", + ":framework-statsd{.public.stubs.source}", + ":framework-tethering{.public.stubs.source}", + ":framework-wifi{.public.stubs.source}", + ], + out: ["current.srcjar"], + tools: ["merge_zips"], + cmd: "$(location merge_zips) $(out) $(in)", + visibility: ["//visibility:private"], // Used by make module in //development, mind. +} + +genrule { name: "frameworks-base-api-removed.txt", srcs: [ ":conscrypt.module.public.api{.public.removed-api.txt}", diff --git a/api/current.txt b/api/current.txt index b5cd13b254af..6632db3d7acf 100644 --- a/api/current.txt +++ b/api/current.txt @@ -42742,6 +42742,12 @@ package android.security.identity { package android.security.keystore { + public class BackendBusyException extends java.security.ProviderException { + ctor public BackendBusyException(); + ctor public BackendBusyException(@NonNull String); + ctor public BackendBusyException(@NonNull String, @NonNull Throwable); + } + public class KeyExpiredException extends java.security.InvalidKeyException { ctor public KeyExpiredException(); ctor public KeyExpiredException(String); @@ -42821,10 +42827,11 @@ package android.security.keystore { method public String getKeystoreAlias(); method public int getOrigin(); method public int getPurposes(); + method public int getSecurityLevel(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); - method public boolean isInsideSecureHardware(); + method @Deprecated public boolean isInsideSecureHardware(); method public boolean isInvalidatedByBiometricEnrollment(); method public boolean isTrustedUserPresenceRequired(); method public boolean isUserAuthenticationRequired(); @@ -45850,6 +45857,8 @@ package android.telecom { field public static final String EXTRA_ANSWERING_DROPS_FG_CALL = "android.telecom.extra.ANSWERING_DROPS_FG_CALL"; field public static final String EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME = "android.telecom.extra.ANSWERING_DROPS_FG_CALL_APP_NAME"; field public static final String EXTRA_AUDIO_CODEC = "android.telecom.extra.AUDIO_CODEC"; + field public static final String EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ = "android.telecom.extra.AUDIO_CODEC_BANDWIDTH_KHZ"; + field public static final String EXTRA_AUDIO_CODEC_BITRATE_KBPS = "android.telecom.extra.AUDIO_CODEC_BITRATE_KBPS"; field public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT"; field public static final String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS"; field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT"; @@ -46296,6 +46305,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle); + method public boolean hasCompanionInCallServiceAccess(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInCall(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInManagedCall(); method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle); @@ -46695,6 +46705,7 @@ package android.telephony { field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int"; field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; @@ -46889,6 +46900,10 @@ package android.telephony { field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; field public static final int SERVICE_CLASS_NONE = 0; // 0x0 field public static final int SERVICE_CLASS_VOICE = 1; // 0x1 + field public static final int USSD_OVER_CS_ONLY = 2; // 0x2 + field public static final int USSD_OVER_CS_PREFERRED = 0; // 0x0 + field public static final int USSD_OVER_IMS_ONLY = 3; // 0x3 + field public static final int USSD_OVER_IMS_PREFERRED = 1; // 0x1 } public static final class CarrierConfigManager.Apn { diff --git a/api/system-current.txt b/api/system-current.txt index 042e2dc53b9c..dcffa06ead8f 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -9377,8 +9377,13 @@ package android.security.keystore { ctor public DeviceIdAttestationException(@Nullable String, @Nullable Throwable); } + public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec { + method public int getNamespace(); + } + public static final class KeyGenParameterSpec.Builder { - method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int); + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int); + method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int); } } @@ -10587,6 +10592,17 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR; } + public final class CarrierBandwidth implements android.os.Parcelable { + ctor public CarrierBandwidth(int, int, int, int); + method public int describeContents(); + method public int getPrimaryDownlinkCapacityKbps(); + method public int getPrimaryUplinkCapacityKbps(); + method public int getSecondaryDownlinkCapacityKbps(); + method public int getSecondaryUplinkCapacityKbps(); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CarrierBandwidth> CREATOR; + field public static final int INVALID = -1; // 0xffffffff + } + public class CarrierConfigManager { method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName(); method @NonNull public static android.os.PersistableBundle getDefaultConfig(); @@ -11203,6 +11219,7 @@ package android.telephony { method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierBandwidth getCarrierBandwidth(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int); @@ -11802,7 +11819,6 @@ package android.telephony.ims { method public int getEmergencyServiceCategories(); method @NonNull public java.util.List<java.lang.String> getEmergencyUrns(); method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile(); - method @NonNull public java.util.Set<android.telephony.ims.RtpHeaderExtensionType> getOfferedRtpHeaderExtensionTypes(); method @NonNull public android.os.Bundle getProprietaryCallExtras(); method public int getRestrictCause(); method public int getServiceType(); @@ -11824,7 +11840,6 @@ package android.telephony.ims { method public void setEmergencyServiceCategories(int); method public void setEmergencyUrns(@NonNull java.util.List<java.lang.String>); method public void setHasKnownUserIntentEmergency(boolean); - method public void setOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>); method public void updateCallExtras(android.telephony.ims.ImsCallProfile); method public void updateCallType(android.telephony.ims.ImsCallProfile); method public void updateMediaProfile(android.telephony.ims.ImsCallProfile); @@ -12333,6 +12348,7 @@ package android.telephony.ims.feature { public class MmTelFeature extends android.telephony.ims.feature.ImsFeature { ctor public MmTelFeature(); method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public void changeOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>); method @Nullable public android.telephony.ims.ImsCallProfile createCallProfile(int, int); method @Nullable public android.telephony.ims.stub.ImsCallSessionImplBase createCallSession(@NonNull android.telephony.ims.ImsCallProfile); method @NonNull public android.telephony.ims.stub.ImsEcbmImplBase getEcbm(); diff --git a/config/hiddenapi-temp-blocklist.txt b/config/hiddenapi-temp-blocklist.txt new file mode 100644 index 000000000000..246eeea35a19 --- /dev/null +++ b/config/hiddenapi-temp-blocklist.txt @@ -0,0 +1,55 @@ +Landroid/app/IActivityManager$Stub$Proxy;->setActivityController(Landroid/app/IActivityController;Z)V +Landroid/app/IActivityManager$Stub$Proxy;->updatePersistentConfiguration(Landroid/content/res/Configuration;)V +Landroid/app/IActivityManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/IActivityManager; +Landroid/app/IInstrumentationWatcher$Stub;-><init>()V +Landroid/app/INotificationManager$Stub;->TRANSACTION_enqueueNotificationWithTag:I +Landroid/bluetooth/IBluetooth$Stub$Proxy;->getConnectionState(Landroid/bluetooth/BluetoothDevice;)I +Landroid/bluetooth/IBluetooth$Stub;->TRANSACTION_enable:I +Landroid/bluetooth/IBluetoothManager$Stub;->TRANSACTION_enable:I +Landroid/companion/ICompanionDeviceDiscoveryService$Stub;-><init>()V +Landroid/content/om/IOverlayManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/om/IOverlayManager; +Landroid/content/pm/IPackageManager$Stub;->TRANSACTION_getApplicationInfo:I +Landroid/hardware/ICameraService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/ICameraService; +Landroid/hardware/input/IInputManager$Stub;->TRANSACTION_injectInputEvent:I +Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V +Landroid/hardware/usb/IUsbManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/usb/IUsbManager; +Landroid/location/ICountryDetector$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/ICountryDetector; +Landroid/location/IGeofenceProvider$Stub;-><init>()V +Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I +Landroid/Manifest$permission;->CAPTURE_SECURE_VIDEO_OUTPUT:Ljava/lang/String; +Landroid/Manifest$permission;->CAPTURE_VIDEO_OUTPUT:Ljava/lang/String; +Landroid/Manifest$permission;->READ_FRAME_BUFFER:Ljava/lang/String; +Landroid/media/IVolumeController$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IVolumeController; +Landroid/net/INetworkPolicyListener$Stub;-><init>()V +Landroid/net/nsd/INsdManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/nsd/INsdManager; +Landroid/net/sip/ISipSession$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/sip/ISipSession; +Landroid/net/wifi/IWifiManager$Stub;->TRANSACTION_getScanResults:I +Landroid/net/wifi/p2p/IWifiP2pManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/wifi/p2p/IWifiP2pManager; +Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enable:I +Landroid/os/IPowerManager$Stub;->TRANSACTION_acquireWakeLock:I +Landroid/os/IPowerManager$Stub;->TRANSACTION_goToSleep:I +Landroid/service/euicc/IEuiccService$Stub;-><init>()V +Landroid/service/media/IMediaBrowserServiceCallbacks$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/media/IMediaBrowserServiceCallbacks; +Landroid/telephony/mbms/IMbmsStreamingSessionCallback$Stub;-><init>()V +Landroid/telephony/mbms/IStreamingServiceCallback$Stub;-><init>()V +Landroid/telephony/mbms/vendor/IMbmsStreamingService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/telephony/mbms/vendor/IMbmsStreamingService; +Lcom/android/ims/ImsConfigListener$Stub;-><init>()V +Lcom/android/ims/internal/IImsCallSession$Stub;-><init>()V +Lcom/android/ims/internal/IImsConfig$Stub;-><init>()V +Lcom/android/ims/internal/IImsEcbm$Stub;-><init>()V +Lcom/android/ims/internal/IImsService$Stub;-><init>()V +Lcom/android/ims/internal/IImsService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/ims/internal/IImsService; +Lcom/android/ims/internal/IImsUt$Stub;-><init>()V +Lcom/android/ims/internal/IImsVideoCallProvider$Stub;-><init>()V +Lcom/android/ims/internal/uce/options/IOptionsService$Stub;-><init>()V +Lcom/android/ims/internal/uce/presence/IPresenceService$Stub;-><init>()V +Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub;-><init>()V +Lcom/android/ims/internal/uce/uceservice/IUceService$Stub;-><init>()V +Lcom/android/internal/app/IVoiceInteractionManagerService$Stub$Proxy;->showSessionFromSession(Landroid/os/IBinder;Landroid/os/Bundle;I)Z +Lcom/android/internal/appwidget/IAppWidgetService$Stub;->TRANSACTION_bindAppWidgetId:I +Lcom/android/internal/location/ILocationProvider$Stub;-><init>()V +Lcom/android/internal/location/ILocationProviderManager$Stub;-><init>()V +Lcom/android/internal/location/ILocationProviderManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProviderManager; +Lcom/android/internal/telephony/ITelephony$Stub;->DESCRIPTOR:Ljava/lang/String; +Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_dial:I +Lcom/android/internal/widget/IRemoteViewsFactory$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/widget/IRemoteViewsFactory; diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt index a3543dc7f4ee..8a377ac051b0 100644 --- a/config/hiddenapi-unsupported.txt +++ b/config/hiddenapi-unsupported.txt @@ -26,19 +26,14 @@ Landroid/app/IActivityManager$Stub$Proxy;->getLaunchedFromUid(Landroid/os/IBinde Landroid/app/IActivityManager$Stub$Proxy;->getProcessLimit()I Landroid/app/IActivityManager$Stub$Proxy;->getProcessPss([I)[J Landroid/app/IActivityManager$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/app/IActivityManager$Stub$Proxy;->setActivityController(Landroid/app/IActivityController;Z)V -Landroid/app/IActivityManager$Stub$Proxy;->updatePersistentConfiguration(Landroid/content/res/Configuration;)V -Landroid/app/IActivityManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/IActivityManager; Landroid/app/IAlarmManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/app/IAlarmManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/IAlarmManager; Landroid/app/IAlarmManager$Stub;->TRANSACTION_remove:I Landroid/app/IAlarmManager$Stub;->TRANSACTION_set:I Landroid/app/IAssistDataReceiver$Stub;-><init>()V -Landroid/app/IInstrumentationWatcher$Stub;-><init>()V Landroid/app/INotificationManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/app/INotificationManager$Stub$Proxy;->areNotificationsEnabledForPackage(Ljava/lang/String;I)Z Landroid/app/INotificationManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/INotificationManager; -Landroid/app/INotificationManager$Stub;->TRANSACTION_enqueueNotificationWithTag:I Landroid/app/IProcessObserver$Stub;-><init>()V Landroid/app/ISearchManager$Stub$Proxy;->getGlobalSearchActivity()Landroid/content/ComponentName; Landroid/app/ISearchManager$Stub$Proxy;->getWebSearchActivity()Landroid/content/ComponentName; @@ -68,9 +63,7 @@ Landroid/app/job/IJobService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/ap Landroid/app/trust/ITrustManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/app/usage/IUsageStatsManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/usage/IUsageStatsManager; Landroid/bluetooth/IBluetooth$Stub$Proxy;->getAddress()Ljava/lang/String; -Landroid/bluetooth/IBluetooth$Stub$Proxy;->getConnectionState(Landroid/bluetooth/BluetoothDevice;)I Landroid/bluetooth/IBluetooth$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetooth; -Landroid/bluetooth/IBluetooth$Stub;->TRANSACTION_enable:I Landroid/bluetooth/IBluetoothA2dp$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetoothA2dp; Landroid/bluetooth/IBluetoothCallback$Stub;-><init>()V Landroid/bluetooth/IBluetoothGattCallback$Stub;-><init>()V @@ -79,11 +72,9 @@ Landroid/bluetooth/IBluetoothHeadset$Stub;->asInterface(Landroid/os/IBinder;)Lan Landroid/bluetooth/IBluetoothHidDeviceCallback$Stub;-><init>()V Landroid/bluetooth/IBluetoothManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/bluetooth/IBluetoothManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetoothManager; -Landroid/bluetooth/IBluetoothManager$Stub;->TRANSACTION_enable:I Landroid/bluetooth/IBluetoothManagerCallback$Stub;-><init>()V Landroid/bluetooth/IBluetoothPbap$Stub;->asInterface(Landroid/os/IBinder;)Landroid/bluetooth/IBluetoothPbap; Landroid/bluetooth/IBluetoothStateChangeCallback$Stub;-><init>()V -Landroid/companion/ICompanionDeviceDiscoveryService$Stub;-><init>()V Landroid/content/IClipboard$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/IClipboard$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/IClipboard; Landroid/content/IContentService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -108,7 +99,6 @@ Landroid/content/ISyncStatusObserver$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/ISyncStatusObserver$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/content/ISyncStatusObserver$Stub;-><init>()V Landroid/content/ISyncStatusObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/ISyncStatusObserver; -Landroid/content/om/IOverlayManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/om/IOverlayManager; Landroid/content/pm/IPackageDataObserver$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/pm/IPackageDataObserver$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/content/pm/IPackageDataObserver$Stub;-><init>()V @@ -141,7 +131,6 @@ Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackageInfo(Ljava/lang/Strin Landroid/content/pm/IPackageManager$Stub$Proxy;->getPackagesForUid(I)[Ljava/lang/String; Landroid/content/pm/IPackageManager$Stub$Proxy;->getSystemSharedLibraryNames()[Ljava/lang/String; Landroid/content/pm/IPackageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageManager; -Landroid/content/pm/IPackageManager$Stub;->TRANSACTION_getApplicationInfo:I Landroid/content/pm/IPackageMoveObserver$Stub;-><init>()V Landroid/content/pm/IPackageMoveObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageMoveObserver; Landroid/content/pm/IPackageStatsObserver$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -155,30 +144,20 @@ Landroid/database/IContentObserver$Stub;->asInterface(Landroid/os/IBinder;)Landr Landroid/hardware/display/IDisplayManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/display/IDisplayManager; Landroid/hardware/fingerprint/IFingerprintService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/hardware/fingerprint/IFingerprintService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/fingerprint/IFingerprintService; -Landroid/hardware/ICameraService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/ICameraService; Landroid/hardware/input/IInputManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/hardware/input/IInputManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/input/IInputManager; -Landroid/hardware/input/IInputManager$Stub;->TRANSACTION_injectInputEvent:I -Landroid/hardware/location/IActivityRecognitionHardwareClient$Stub;-><init>()V Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService; Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V -Landroid/hardware/usb/IUsbManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/usb/IUsbManager; -Landroid/location/ICountryDetector$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/ICountryDetector; Landroid/location/ICountryListener$Stub;-><init>()V Landroid/location/IGeocodeProvider$Stub;-><init>()V Landroid/location/IGeocodeProvider$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/IGeocodeProvider; -Landroid/location/IGeofenceProvider$Stub;-><init>()V Landroid/location/ILocationListener$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/location/ILocationListener$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/location/ILocationListener$Stub;-><init>()V Landroid/location/ILocationListener$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/ILocationListener; Landroid/location/ILocationManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/location/ILocationManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/ILocationManager; -Landroid/location/ILocationManager$Stub;->TRANSACTION_getAllProviders:I Landroid/location/INetInitiatedListener$Stub;-><init>()V -Landroid/Manifest$permission;->CAPTURE_SECURE_VIDEO_OUTPUT:Ljava/lang/String; -Landroid/Manifest$permission;->CAPTURE_VIDEO_OUTPUT:Ljava/lang/String; -Landroid/Manifest$permission;->READ_FRAME_BUFFER:Ljava/lang/String; Landroid/media/IAudioRoutesObserver$Stub;-><init>()V Landroid/media/IAudioService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/media/IAudioService$Stub;-><init>()V @@ -186,7 +165,6 @@ Landroid/media/IAudioService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/me Landroid/media/IMediaRouterService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IMediaRouterService; Landroid/media/IMediaScannerListener$Stub;-><init>()V Landroid/media/IMediaScannerService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IMediaScannerService; -Landroid/media/IVolumeController$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/IVolumeController; Landroid/media/session/ISessionManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/media/session/ISessionManager; Landroid/media/tv/ITvRemoteProvider$Stub;-><init>()V Landroid/net/IConnectivityManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -200,23 +178,17 @@ Landroid/net/IConnectivityManager$Stub$Proxy;->getTetheredIfaces()[Ljava/lang/St Landroid/net/IConnectivityManager$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/net/IConnectivityManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/IConnectivityManager; Landroid/net/INetworkManagementEventObserver$Stub;-><init>()V -Landroid/net/INetworkPolicyListener$Stub;-><init>()V Landroid/net/INetworkPolicyManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/INetworkPolicyManager; Landroid/net/INetworkScoreService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/INetworkScoreService; Landroid/net/INetworkStatsService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/net/INetworkStatsService$Stub$Proxy;->getMobileIfaces()[Ljava/lang/String; Landroid/net/INetworkStatsService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/INetworkStatsService; -Landroid/net/nsd/INsdManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/nsd/INsdManager; -Landroid/net/sip/ISipSession$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/sip/ISipSession; Landroid/net/wifi/IWifiManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/net/wifi/IWifiManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/wifi/IWifiManager; -Landroid/net/wifi/IWifiManager$Stub;->TRANSACTION_getScanResults:I Landroid/net/wifi/IWifiScanner$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/net/wifi/IWifiScanner$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/net/wifi/IWifiScanner$Stub;-><init>()V Landroid/net/wifi/IWifiScanner$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/wifi/IWifiScanner; -Landroid/net/wifi/p2p/IWifiP2pManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/net/wifi/p2p/IWifiP2pManager; -Landroid/nfc/INfcAdapter$Stub;->TRANSACTION_enable:I Landroid/os/IBatteryPropertiesRegistrar$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/IDeviceIdentifiersPolicyService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IDeviceIdentifiersPolicyService; Landroid/os/IDeviceIdleController$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IDeviceIdleController; @@ -227,8 +199,6 @@ Landroid/os/IPermissionController$Stub;->asInterface(Landroid/os/IBinder;)Landro Landroid/os/IPowerManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/IPowerManager$Stub$Proxy;->isLightDeviceIdleMode()Z Landroid/os/IPowerManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IPowerManager; -Landroid/os/IPowerManager$Stub;->TRANSACTION_acquireWakeLock:I -Landroid/os/IPowerManager$Stub;->TRANSACTION_goToSleep:I Landroid/os/IRecoverySystem$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IRecoverySystem; Landroid/os/IRemoteCallback$Stub;-><init>()V Landroid/os/IUpdateEngine$Stub;-><init>()V @@ -241,16 +211,11 @@ Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Land Landroid/security/IKeyChainService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/IKeyChainService; Landroid/security/keystore/IKeystoreService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/security/keystore/IKeystoreService; Landroid/service/dreams/IDreamManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/dreams/IDreamManager; -Landroid/service/euicc/IEuiccService$Stub;-><init>()V -Landroid/service/media/IMediaBrowserServiceCallbacks$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/media/IMediaBrowserServiceCallbacks; Landroid/service/notification/INotificationListener$Stub;-><init>()V Landroid/service/persistentdata/IPersistentDataBlockService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/persistentdata/IPersistentDataBlockService; Landroid/service/vr/IVrManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/vr/IVrManager; Landroid/service/wallpaper/IWallpaperConnection$Stub;-><init>()V Landroid/service/wallpaper/IWallpaperService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/service/wallpaper/IWallpaperService; -Landroid/telephony/mbms/IMbmsStreamingSessionCallback$Stub;-><init>()V -Landroid/telephony/mbms/IStreamingServiceCallback$Stub;-><init>()V -Landroid/telephony/mbms/vendor/IMbmsStreamingService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/telephony/mbms/vendor/IMbmsStreamingService; Landroid/view/accessibility/IAccessibilityManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/view/accessibility/IAccessibilityManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/view/accessibility/IAccessibilityManager; Landroid/view/autofill/IAutoFillManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V @@ -274,19 +239,7 @@ Landroid/view/IWindowSession$Stub;->asInterface(Landroid/os/IBinder;)Landroid/vi Landroid/webkit/IWebViewUpdateService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/webkit/IWebViewUpdateService$Stub$Proxy;->waitForAndGetProvider()Landroid/webkit/WebViewProviderResponse; Landroid/webkit/IWebViewUpdateService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/webkit/IWebViewUpdateService; -Lcom/android/ims/ImsConfigListener$Stub;-><init>()V -Lcom/android/ims/internal/IImsCallSession$Stub;-><init>()V Lcom/android/ims/internal/IImsCallSession$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/ims/internal/IImsCallSession; -Lcom/android/ims/internal/IImsConfig$Stub;-><init>()V -Lcom/android/ims/internal/IImsEcbm$Stub;-><init>()V -Lcom/android/ims/internal/IImsService$Stub;-><init>()V -Lcom/android/ims/internal/IImsService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/ims/internal/IImsService; -Lcom/android/ims/internal/IImsUt$Stub;-><init>()V -Lcom/android/ims/internal/IImsVideoCallProvider$Stub;-><init>()V -Lcom/android/ims/internal/uce/options/IOptionsService$Stub;-><init>()V -Lcom/android/ims/internal/uce/presence/IPresenceService$Stub;-><init>()V -Lcom/android/ims/internal/uce/uceservice/IUceListener$Stub;-><init>()V -Lcom/android/ims/internal/uce/uceservice/IUceService$Stub;-><init>()V Lcom/android/internal/app/IAppOpsCallback$Stub;-><init>()V Lcom/android/internal/app/IAppOpsService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Lcom/android/internal/app/IAppOpsService$Stub$Proxy;->checkOperation(IILjava/lang/String;)I @@ -313,15 +266,10 @@ Lcom/android/internal/app/IAppOpsService$Stub;->TRANSACTION_stopWatchingMode:I Lcom/android/internal/app/IBatteryStats$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Lcom/android/internal/app/IBatteryStats$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IBatteryStats; Lcom/android/internal/app/IMediaContainerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IMediaContainerService; -Lcom/android/internal/app/IVoiceInteractionManagerService$Stub$Proxy;->showSessionFromSession(Landroid/os/IBinder;Landroid/os/Bundle;I)Z Lcom/android/internal/app/IVoiceInteractionManagerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/app/IVoiceInteractionManagerService; Lcom/android/internal/appwidget/IAppWidgetService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/appwidget/IAppWidgetService; -Lcom/android/internal/appwidget/IAppWidgetService$Stub;->TRANSACTION_bindAppWidgetId:I Lcom/android/internal/backup/IBackupTransport$Stub;-><init>()V -Lcom/android/internal/location/ILocationProvider$Stub;-><init>()V Lcom/android/internal/location/ILocationProvider$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProvider; -Lcom/android/internal/location/ILocationProviderManager$Stub;-><init>()V -Lcom/android/internal/location/ILocationProviderManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/location/ILocationProviderManager; Lcom/android/internal/os/IDropBoxManagerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/os/IDropBoxManagerService; Lcom/android/internal/policy/IKeyguardService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/policy/IKeyguardService; Lcom/android/internal/policy/IKeyguardStateCallback$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/policy/IKeyguardStateCallback; @@ -343,9 +291,7 @@ Lcom/android/internal/telephony/ISub$Stub;->asInterface(Landroid/os/IBinder;)Lco Lcom/android/internal/telephony/ITelephony$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Lcom/android/internal/telephony/ITelephony$Stub$Proxy;->mRemote:Landroid/os/IBinder; Lcom/android/internal/telephony/ITelephony$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ITelephony; -Lcom/android/internal/telephony/ITelephony$Stub;->DESCRIPTOR:Ljava/lang/String; Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_call:I -Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_dial:I Lcom/android/internal/telephony/ITelephony$Stub;->TRANSACTION_getDeviceId:I Lcom/android/internal/telephony/ITelephonyRegistry$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Lcom/android/internal/telephony/ITelephonyRegistry$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/telephony/ITelephonyRegistry; @@ -355,4 +301,3 @@ Lcom/android/internal/view/IInputMethodManager$Stub$Proxy;-><init>(Landroid/os/I Lcom/android/internal/view/IInputMethodManager$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethodManager; Lcom/android/internal/view/IInputMethodSession$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/view/IInputMethodSession; Lcom/android/internal/widget/ILockSettings$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/widget/ILockSettings; -Lcom/android/internal/widget/IRemoteViewsFactory$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/widget/IRemoteViewsFactory; diff --git a/core/api/current.txt b/core/api/current.txt index 61a14241e4df..02652b2c103c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -40910,6 +40910,12 @@ package android.security.identity { package android.security.keystore { + public class BackendBusyException extends java.security.ProviderException { + ctor public BackendBusyException(); + ctor public BackendBusyException(@NonNull String); + ctor public BackendBusyException(@NonNull String, @NonNull Throwable); + } + public class KeyExpiredException extends java.security.InvalidKeyException { ctor public KeyExpiredException(); ctor public KeyExpiredException(String); @@ -40989,10 +40995,11 @@ package android.security.keystore { method public String getKeystoreAlias(); method public int getOrigin(); method public int getPurposes(); + method public int getSecurityLevel(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); - method public boolean isInsideSecureHardware(); + method @Deprecated public boolean isInsideSecureHardware(); method public boolean isInvalidatedByBiometricEnrollment(); method public boolean isTrustedUserPresenceRequired(); method public boolean isUserAuthenticationRequired(); @@ -44018,6 +44025,8 @@ package android.telecom { field public static final String EXTRA_ANSWERING_DROPS_FG_CALL = "android.telecom.extra.ANSWERING_DROPS_FG_CALL"; field public static final String EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME = "android.telecom.extra.ANSWERING_DROPS_FG_CALL_APP_NAME"; field public static final String EXTRA_AUDIO_CODEC = "android.telecom.extra.AUDIO_CODEC"; + field public static final String EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ = "android.telecom.extra.AUDIO_CODEC_BANDWIDTH_KHZ"; + field public static final String EXTRA_AUDIO_CODEC_BITRATE_KBPS = "android.telecom.extra.AUDIO_CODEC_BITRATE_KBPS"; field public static final String EXTRA_CALL_SUBJECT = "android.telecom.extra.CALL_SUBJECT"; field public static final String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS"; field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT"; @@ -44464,6 +44473,7 @@ package android.telecom { method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber(android.telecom.PhoneAccountHandle); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handleMmi(String, android.telecom.PhoneAccountHandle); + method public boolean hasCompanionInCallServiceAccess(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInCall(); method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isInManagedCall(); method public boolean isIncomingCallPermitted(android.telecom.PhoneAccountHandle); @@ -44863,6 +44873,7 @@ package android.telephony { field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool"; field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool"; field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool"; + field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int"; field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool"; field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool"; field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool"; @@ -45057,6 +45068,10 @@ package android.telephony { field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool"; field public static final int SERVICE_CLASS_NONE = 0; // 0x0 field public static final int SERVICE_CLASS_VOICE = 1; // 0x1 + field public static final int USSD_OVER_CS_ONLY = 2; // 0x2 + field public static final int USSD_OVER_CS_PREFERRED = 0; // 0x0 + field public static final int USSD_OVER_IMS_ONLY = 3; // 0x3 + field public static final int USSD_OVER_IMS_PREFERRED = 1; // 0x1 } public static final class CarrierConfigManager.Apn { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f36f4f33d7fe..18d10641d0cd 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -8259,8 +8259,13 @@ package android.security.keystore { ctor public DeviceIdAttestationException(@Nullable String, @Nullable Throwable); } + public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec { + method public int getNamespace(); + } + public static final class KeyGenParameterSpec.Builder { - method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int); + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setNamespace(int); + method @Deprecated @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUid(int); } } @@ -9469,6 +9474,17 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallQuality> CREATOR; } + public final class CarrierBandwidth implements android.os.Parcelable { + ctor public CarrierBandwidth(int, int, int, int); + method public int describeContents(); + method public int getPrimaryDownlinkCapacityKbps(); + method public int getPrimaryUplinkCapacityKbps(); + method public int getSecondaryDownlinkCapacityKbps(); + method public int getSecondaryUplinkCapacityKbps(); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CarrierBandwidth> CREATOR; + field public static final int INVALID = -1; // 0xffffffff + } + public class CarrierConfigManager { method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName(); method @NonNull public static android.os.PersistableBundle getDefaultConfig(); @@ -10085,6 +10101,7 @@ package android.telephony { method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.ComponentName getAndUpdateDefaultRespondViaMessageApplication(); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallForwarding(int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CallForwardingInfoCallback); method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getCallWaitingStatus(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.CarrierBandwidth getCarrierBandwidth(); method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntent(android.content.Intent); method public java.util.List<java.lang.String> getCarrierPackageNamesForIntentAndPhone(android.content.Intent, int); @@ -10684,7 +10701,6 @@ package android.telephony.ims { method public int getEmergencyServiceCategories(); method @NonNull public java.util.List<java.lang.String> getEmergencyUrns(); method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile(); - method @NonNull public java.util.Set<android.telephony.ims.RtpHeaderExtensionType> getOfferedRtpHeaderExtensionTypes(); method @NonNull public android.os.Bundle getProprietaryCallExtras(); method public int getRestrictCause(); method public int getServiceType(); @@ -10706,7 +10722,6 @@ package android.telephony.ims { method public void setEmergencyServiceCategories(int); method public void setEmergencyUrns(@NonNull java.util.List<java.lang.String>); method public void setHasKnownUserIntentEmergency(boolean); - method public void setOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>); method public void updateCallExtras(android.telephony.ims.ImsCallProfile); method public void updateCallType(android.telephony.ims.ImsCallProfile); method public void updateMediaProfile(android.telephony.ims.ImsCallProfile); @@ -11215,6 +11230,7 @@ package android.telephony.ims.feature { public class MmTelFeature extends android.telephony.ims.feature.ImsFeature { ctor public MmTelFeature(); method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public void changeOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>); method @Nullable public android.telephony.ims.ImsCallProfile createCallProfile(int, int); method @Nullable public android.telephony.ims.stub.ImsCallSessionImplBase createCallSession(@NonNull android.telephony.ims.ImsCallProfile); method @NonNull public android.telephony.ims.stub.ImsEcbmImplBase getEcbm(); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 9a068674c3fc..a21a156e5459 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -103,7 +103,6 @@ final class ServiceConnectionLeaked extends AndroidRuntimeException { public final class LoadedApk { static final String TAG = "LoadedApk"; static final boolean DEBUG = false; - private static final String PROPERTY_NAME_APPEND_NATIVE = "pi.append_native_lib_paths"; @UnsupportedAppUsage private final ActivityThread mActivityThread; @@ -909,7 +908,7 @@ public final class LoadedApk { needToSetupJitProfiles = true; } - if (!libPaths.isEmpty() && SystemProperties.getBoolean(PROPERTY_NAME_APPEND_NATIVE, true)) { + if (!libPaths.isEmpty()) { // Temporarily disable logging of disk reads on the Looper thread as this is necessary StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads(); try { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6fa5a7b1fb65..6edc8ea6a3a6 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6680,7 +6680,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public boolean isDeviceManaged() { try { return mService.hasDeviceOwner(); @@ -10392,7 +10392,7 @@ public class DevicePolicyManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public @Nullable CharSequence getDeviceOwnerOrganizationName() { try { return mService.getDeviceOwnerOrganizationName(); diff --git a/core/java/android/app/backup/OWNERS b/core/java/android/app/backup/OWNERS index 7e7710b4d550..0f888113d730 100644 --- a/core/java/android/app/backup/OWNERS +++ b/core/java/android/app/backup/OWNERS @@ -1,4 +1,4 @@ # Bug component: 656484 -include platform/frameworks/base/services/backup:/OWNERS +include platform/frameworks/base:/services/backup/OWNERS diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index a941756595ad..a6089c31b140 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -835,6 +835,7 @@ public abstract class ContentResolver implements ContentInterface { } /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage protected abstract IContentProvider acquireProvider(Context c, String name); @@ -851,15 +852,19 @@ public abstract class ContentResolver implements ContentInterface { } /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean releaseProvider(IContentProvider icp); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage protected abstract IContentProvider acquireUnstableProvider(Context c, String name); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean releaseUnstableProvider(IContentProvider icp); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void unstableProviderDied(IContentProvider icp); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index a98bd6a3a492..7ffcead2d234 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -800,6 +800,7 @@ public abstract class Context { * case {@link #getOpPackageName()} will be the name of the primary package in * that process (so that app ops uid verification will work with the name). */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract String getBasePackageName(); @@ -916,6 +917,7 @@ public abstract class Context { * @see #MODE_PRIVATE * @removed */ + @SuppressWarnings("HiddenAbstractMethod") public abstract SharedPreferences getSharedPreferences(File file, @PreferencesMode int mode); /** @@ -946,6 +948,7 @@ public abstract class Context { public abstract boolean deleteSharedPreferences(String name); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void reloadSharedPreferences(); /** @@ -1034,6 +1037,7 @@ public abstract class Context { * @see #getSharedPreferences(String, int) * @removed */ + @SuppressWarnings("HiddenAbstractMethod") public abstract File getSharedPreferencesPath(String name); /** @@ -1505,6 +1509,7 @@ public abstract class Context { * There is no guarantee when these files will be deleted. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @SystemApi public abstract File getPreloadsFileCache(); @@ -2173,6 +2178,7 @@ public abstract class Context { * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, String[] receiverPermissions); @@ -2203,6 +2209,7 @@ public abstract class Context { * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract void sendBroadcast(Intent intent, @Nullable String receiverPermission, @@ -2213,6 +2220,7 @@ public abstract class Context { * of an associated app op as per {@link android.app.AppOpsManager}. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void sendBroadcast(Intent intent, String receiverPermission, int appOp); @@ -2328,6 +2336,7 @@ public abstract class Context { * @see android.app.Activity#RESULT_OK * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract void sendOrderedBroadcast(@NonNull Intent intent, @Nullable String receiverPermission, @Nullable Bundle options, @@ -2340,6 +2349,7 @@ public abstract class Context { * of an associated app op as per {@link android.app.AppOpsManager}. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void sendOrderedBroadcast(Intent intent, String receiverPermission, int appOp, BroadcastReceiver resultReceiver, @@ -2393,6 +2403,7 @@ public abstract class Context { * @see #sendBroadcast(Intent, String, Bundle) * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent, @@ -2415,6 +2426,7 @@ public abstract class Context { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public abstract void sendBroadcastAsUser(@RequiresPermission Intent intent, @@ -2461,6 +2473,7 @@ public abstract class Context { * BroadcastReceiver, Handler, int, String, Bundle) * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, @@ -2474,6 +2487,7 @@ public abstract class Context { * BroadcastReceiver, Handler, int, String, Bundle) * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage public abstract void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, @@ -2688,6 +2702,7 @@ public abstract class Context { * @hide * This is just here for sending CONNECTIVITY_ACTION. */ + @SuppressWarnings("HiddenAbstractMethod") @Deprecated @RequiresPermission(allOf = { android.Manifest.permission.INTERACT_ACROSS_USERS, @@ -2978,6 +2993,7 @@ public abstract class Context { * @see #sendBroadcast * @see #unregisterReceiver */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) @UnsupportedAppUsage @@ -3088,6 +3104,7 @@ public abstract class Context { /** * @hide like {@link #startForegroundService(Intent)} but for a specific user. */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract ComponentName startForegroundServiceAsUser(Intent service, UserHandle user); @@ -3126,6 +3143,7 @@ public abstract class Context { /** * @hide like {@link #startService(Intent)} but for a specific user. */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage @@ -3134,6 +3152,7 @@ public abstract class Context { /** * @hide like {@link #stopService(Intent)} but for a specific user. */ + @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract boolean stopServiceAsUser(Intent service, UserHandle user); @@ -5190,6 +5209,7 @@ public abstract class Context { public abstract int checkPermission(@NonNull String permission, int pid, int uid); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @PackageManager.PermissionResult @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public abstract int checkPermission(@NonNull String permission, int pid, int uid, @@ -5417,6 +5437,7 @@ public abstract class Context { @Intent.AccessUriMode int modeFlags); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @PackageManager.PermissionResult public abstract int checkUriPermission(Uri uri, int pid, int uid, @Intent.AccessUriMode int modeFlags, IBinder callerToken); @@ -5701,6 +5722,7 @@ public abstract class Context { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract Context createApplicationContext(ApplicationInfo application, @CreatePackageOptions int flags) throws PackageManager.NameNotFoundException; @@ -5920,6 +5942,7 @@ public abstract class Context { * @see #isCredentialProtectedStorage() * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract Context createCredentialProtectedStorageContext(); @@ -5932,6 +5955,7 @@ public abstract class Context { * @return The compatibility info holder, or null if not required by the application. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract DisplayAdjustments getDisplayAdjustments(int displayId); /** @@ -5966,12 +5990,14 @@ public abstract class Context { * @see #getDisplay() * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @TestApi public abstract int getDisplayId(); /** * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void updateDisplay(int displayId); /** @@ -6000,6 +6026,7 @@ public abstract class Context { * @see #createCredentialProtectedStorageContext() * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract boolean isCredentialProtectedStorage(); @@ -6007,6 +6034,7 @@ public abstract class Context { * Returns true if the context can load unsafe resources, e.g. fonts. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract boolean canLoadUnsafeResources(); /** diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 1cca53f369fa..81d9b11bc644 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -2101,6 +2101,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public boolean isOem() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0; } @@ -2148,11 +2149,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public boolean isVendor() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0; } /** @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public boolean isProduct() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index dbddc2217273..74463fd18887 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -40,7 +40,7 @@ import android.app.PropertyInvalidatedCache; import android.app.admin.DevicePolicyManager; import android.app.usage.StorageStatsManager; import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -3682,7 +3682,7 @@ public abstract class PackageManager { * @hide */ @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.R) public static final long FILTER_APPLICATION_QUERY = 135549675L; /** {@hide} */ @@ -3796,6 +3796,7 @@ public abstract class PackageManager { * found on the system. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @UnsupportedAppUsage public abstract PackageInfo getPackageInfoAsUser(@NonNull String packageName, @@ -3862,6 +3863,7 @@ public abstract class PackageManager { * does not contain such an activity. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract @Nullable Intent getCarLaunchIntentForPackage(@NonNull String packageName); /** @@ -3927,6 +3929,7 @@ public abstract class PackageManager { * found on the system. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract int getPackageUidAsUser(@NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException; @@ -3945,6 +3948,7 @@ public abstract class PackageManager { * found on the system. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract int getPackageUidAsUser(@NonNull String packageName, @PackageInfoFlags int flags, @UserIdInt int userId) throws NameNotFoundException; @@ -3987,6 +3991,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract boolean arePermissionsIndividuallyControlled(); @@ -3995,6 +4000,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract boolean isWirelessConsentModeEnabled(); /** @@ -4047,6 +4053,7 @@ public abstract class PackageManager { @ApplicationInfoFlags int flags) throws NameNotFoundException; /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName, @@ -4228,6 +4235,7 @@ public abstract class PackageManager { * deleted with {@code DELETE_KEEP_DATA} flag set). * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @SystemApi @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) @@ -4276,6 +4284,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage @NonNull @TestApi @@ -4389,6 +4398,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String packageName, @@ -4415,6 +4425,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public abstract void revokeRuntimePermission(@NonNull String packageName, @@ -4459,6 +4470,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, @@ -4481,6 +4493,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(anyOf = { android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, @@ -4704,6 +4717,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName); @@ -4819,6 +4833,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @TestApi public abstract @Nullable String[] getNamesForUids(int[] uids); @@ -4835,6 +4850,7 @@ public abstract class PackageManager { * found on the system. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract int getUidForSharedUser(@NonNull String sharedUserName) throws NameNotFoundException; @@ -4878,6 +4894,7 @@ public abstract class PackageManager { * deleted with {@code DELETE_KEEP_DATA} flag set). * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @TestApi public abstract List<ApplicationInfo> getInstalledApplicationsAsUser( @@ -4890,6 +4907,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS) public abstract @NonNull List<InstantAppInfo> getInstantApps(); @@ -4901,6 +4919,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(Manifest.permission.ACCESS_INSTANT_APPS) public abstract @Nullable Drawable getInstantAppIcon(String packageName); @@ -4949,6 +4968,7 @@ public abstract class PackageManager { * deprecated * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract int getInstantAppCookieMaxSize(); /** @@ -5006,6 +5026,7 @@ public abstract class PackageManager { /** * @removed */ + @SuppressWarnings("HiddenAbstractMethod") public abstract boolean setInstantAppCookie(@Nullable byte[] cookie); /** @@ -5044,6 +5065,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser( @InstallFlags int flags, @UserIdInt int userId); @@ -5056,6 +5078,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @RequiresPermission(Manifest.permission.ACCESS_SHARED_LIBRARIES) @SystemApi @@ -5075,6 +5098,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage @TestApi public abstract @NonNull String getServicesSystemSharedLibraryPackageName(); @@ -5086,6 +5110,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage @TestApi public abstract @NonNull String getSharedSystemSharedLibraryPackageName(); @@ -5192,6 +5217,7 @@ public abstract class PackageManager { * containing something else, such as the activity resolver. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @UnsupportedAppUsage public abstract ResolveInfo resolveActivityAsUser(@NonNull Intent intent, @@ -5233,6 +5259,7 @@ public abstract class PackageManager { * empty list is returned. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract List<ResolveInfo> queryIntentActivitiesAsUser(@NonNull Intent intent, @@ -5256,6 +5283,7 @@ public abstract class PackageManager { * empty list is returned. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @SystemApi @@ -5318,6 +5346,7 @@ public abstract class PackageManager { * no matching receivers, an empty list or null is returned. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @SystemApi @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS) @@ -5329,6 +5358,7 @@ public abstract class PackageManager { /** * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract List<ResolveInfo> queryBroadcastReceiversAsUser(@NonNull Intent intent, @@ -5367,6 +5397,7 @@ public abstract class PackageManager { /** * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable public abstract ResolveInfo resolveServiceAsUser(@NonNull Intent intent, @ResolveInfoFlags int flags, @UserIdInt int userId); @@ -5399,6 +5430,7 @@ public abstract class PackageManager { * empty list or null is returned. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent, @@ -5437,6 +5469,7 @@ public abstract class PackageManager { * no matching services, an empty list or null is returned. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract List<ResolveInfo> queryIntentContentProvidersAsUser( @@ -5504,6 +5537,7 @@ public abstract class PackageManager { * provider. If a provider was not found, returns null. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @UnsupportedAppUsage public abstract ProviderInfo resolveContentProviderAsUser(@NonNull String providerName, @@ -5888,6 +5922,7 @@ public abstract class PackageManager { * @return the drawable or null if no drawable is required. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @UnsupportedAppUsage public abstract Drawable getUserBadgeForDensity(@NonNull UserHandle user, int density); @@ -5906,6 +5941,7 @@ public abstract class PackageManager { * @return the drawable or null if no drawable is required. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @UnsupportedAppUsage public abstract Drawable getUserBadgeForDensityNoBackground(@NonNull UserHandle user, @@ -6030,6 +6066,7 @@ public abstract class PackageManager { throws NameNotFoundException; /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract Resources getResourcesForApplicationAsUser(@NonNull String packageName, @@ -6075,6 +6112,7 @@ public abstract class PackageManager { * * @deprecated use {@link PackageInstaller#installExistingPackage()} instead. */ + @SuppressWarnings("HiddenAbstractMethod") @Deprecated @SystemApi public abstract int installExistingPackage(@NonNull String packageName) @@ -6087,6 +6125,7 @@ public abstract class PackageManager { * * @deprecated use {@link PackageInstaller#installExistingPackage()} instead. */ + @SuppressWarnings("HiddenAbstractMethod") @Deprecated @SystemApi public abstract int installExistingPackage(@NonNull String packageName, @@ -6099,6 +6138,7 @@ public abstract class PackageManager { * * @deprecated use {@link PackageInstaller#installExistingPackage()} instead. */ + @SuppressWarnings("HiddenAbstractMethod") @Deprecated @RequiresPermission(anyOf = { Manifest.permission.INSTALL_EXISTING_PACKAGES, @@ -6179,6 +6219,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int verificationId, int verificationCode, @@ -6204,6 +6245,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String packageName, @@ -6229,6 +6271,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String packageName, @@ -6246,6 +6289,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @SystemApi public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications( @@ -6262,6 +6306,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @SystemApi public abstract List<IntentFilter> getAllIntentFilters(@NonNull String packageName); @@ -6276,6 +6321,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @SystemApi @RequiresPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL) @@ -6293,6 +6339,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(allOf = { Manifest.permission.SET_PREFERRED_APPLICATIONS, @@ -6319,6 +6366,7 @@ public abstract class PackageManager { @Nullable String installerPackageName); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String packageName, boolean updateAvaialble); @@ -6339,6 +6387,7 @@ public abstract class PackageManager { * indicate that no callback is desired. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(Manifest.permission.DELETE_PACKAGES) @UnsupportedAppUsage public abstract void deletePackage(@NonNull String packageName, @@ -6359,6 +6408,7 @@ public abstract class PackageManager { * @param userId The user Id * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @RequiresPermission(anyOf = { Manifest.permission.DELETE_PACKAGES, Manifest.permission.INTERACT_ACROSS_USERS_FULL}) @@ -6376,6 +6426,7 @@ public abstract class PackageManager { * * @deprecated use {@link #getInstallSourceInfo(String)} instead */ + @SuppressWarnings("HiddenAbstractMethod") @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String packageName); @@ -6415,6 +6466,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void clearApplicationUserData(@NonNull String packageName, @Nullable IPackageDataObserver observer); @@ -6434,6 +6486,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void deleteApplicationCacheFiles(@NonNull String packageName, @Nullable IPackageDataObserver observer); @@ -6456,6 +6509,7 @@ public abstract class PackageManager { * callback is desired. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void deleteApplicationCacheFilesAsUser(@NonNull String packageName, @UserIdInt int userId, @Nullable IPackageDataObserver observer); @@ -6489,6 +6543,7 @@ public abstract class PackageManager { } /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void freeStorageAndNotify(@Nullable String volumeUuid, long freeStorageSize, @Nullable IPackageDataObserver observer); @@ -6522,6 +6577,7 @@ public abstract class PackageManager { } /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void freeStorage(@Nullable String volumeUuid, long freeStorageSize, @Nullable IntentSender pi); @@ -6545,6 +6601,7 @@ public abstract class PackageManager { * @deprecated use {@link StorageStatsManager} instead. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Deprecated @UnsupportedAppUsage public abstract void getPackageSizeInfoAsUser(@NonNull String packageName, @@ -6676,6 +6733,7 @@ public abstract class PackageManager { * an app to be responsible for a particular role and to check current role * holders, see {@link android.app.role.RoleManager}. */ + @SuppressWarnings("HiddenAbstractMethod") @Deprecated @UnsupportedAppUsage public abstract void replacePreferredActivity(@NonNull IntentFilter filter, int match, @@ -6772,6 +6830,7 @@ public abstract class PackageManager { * default, if any. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @UnsupportedAppUsage public abstract ComponentName getHomeActivities(@NonNull List<ResolveInfo> outActivities); @@ -6873,6 +6932,7 @@ public abstract class PackageManager { * @param userId Ther userId of the user whose restrictions are to be flushed. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void flushPackageRestrictionsAsUser(@UserIdInt int userId); @@ -6883,6 +6943,7 @@ public abstract class PackageManager { * or by installing it, such as with {@link #installExistingPackage(String)} * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean setApplicationHiddenSettingAsUser(@NonNull String packageName, boolean hidden, @NonNull UserHandle userHandle); @@ -6892,6 +6953,7 @@ public abstract class PackageManager { * @see #setApplicationHiddenSettingAsUser(String, boolean, UserHandle) * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, @NonNull UserHandle userHandle); @@ -6918,6 +6980,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) public abstract void addOnPermissionsChangeListener( @@ -6930,6 +6993,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) public abstract void removeOnPermissionsChangeListener( @@ -6943,6 +7007,7 @@ public abstract class PackageManager { * application's AndroidManifest.xml. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract KeySet getKeySetByAlias(@NonNull String packageName, @NonNull String alias); @@ -6950,6 +7015,7 @@ public abstract class PackageManager { /** Return the signing {@link KeySet} for this application. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract KeySet getSigningKeySet(@NonNull String packageName); @@ -6961,6 +7027,7 @@ public abstract class PackageManager { * Compare to {@link #isSignedByExactly(String packageName, KeySet ks)}. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean isSignedBy(@NonNull String packageName, @NonNull KeySet ks); @@ -6970,6 +7037,7 @@ public abstract class PackageManager { * {@link #isSignedBy(String packageName, KeySet ks)}. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean isSignedByExactly(@NonNull String packageName, @NonNull KeySet ks); @@ -7187,6 +7255,7 @@ public abstract class PackageManager { * @throws IllegalArgumentException if the package was not found. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean isPackageSuspendedForUser(@NonNull String packageName, int userId); @@ -7262,6 +7331,7 @@ public abstract class PackageManager { * @param packageName the package to change the category hint for. * @param categoryHint the category hint to set. */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void setApplicationCategoryHint(@NonNull String packageName, @ApplicationInfo.Category int categoryHint); @@ -7277,34 +7347,43 @@ public abstract class PackageManager { } /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract int getMoveStatus(int moveId); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void registerMoveCallback(@NonNull MoveCallback callback, @NonNull Handler handler); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void unregisterMoveCallback(@NonNull MoveCallback callback); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public abstract int movePackage(@NonNull String packageName, @NonNull VolumeInfo vol); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public abstract @Nullable VolumeInfo getPackageCurrentVolume(@NonNull ApplicationInfo app); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public abstract List<VolumeInfo> getPackageCandidateVolumes( @NonNull ApplicationInfo app); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") public abstract int movePrimaryStorage(@NonNull VolumeInfo vol); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") public abstract @Nullable VolumeInfo getPrimaryStorageCurrentVolume(); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") public abstract @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes(); /** @@ -7314,6 +7393,7 @@ public abstract class PackageManager { * @return identity that uniquely identifies current device * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull public abstract VerifierDeviceIdentity getVerifierDeviceIdentity(); @@ -7322,6 +7402,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean isUpgrade(); @@ -7351,6 +7432,7 @@ public abstract class PackageManager { * {@link #ONLY_IF_NO_MATCH_FOUND}. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void addCrossProfileIntentFilter(@NonNull IntentFilter filter, @UserIdInt int sourceUserId, @UserIdInt int targetUserId, int flags); @@ -7362,12 +7444,14 @@ public abstract class PackageManager { * @param sourceUserId The source user id. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void clearCrossProfileIntentFilters(@UserIdInt int sourceUserId); /** * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract Drawable loadItemIcon(@NonNull PackageItemInfo itemInfo, @@ -7376,12 +7460,14 @@ public abstract class PackageManager { /** * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @NonNull @UnsupportedAppUsage public abstract Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo, @Nullable ApplicationInfo appInfo); /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean isPackageAvailable(@NonNull String packageName); @@ -7599,6 +7685,7 @@ public abstract class PackageManager { * user, {@code INSTALL_REASON_UNKNOWN} is returned. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @TestApi @InstallReason public abstract int getInstallReason(@NonNull String packageName, @NonNull UserHandle user); @@ -7627,6 +7714,7 @@ public abstract class PackageManager { * @see {@link android.content.Intent#ACTION_INSTANT_APP_RESOLVER_SETTINGS} * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @SystemApi public abstract ComponentName getInstantAppResolverSettingsComponent(); @@ -7638,6 +7726,7 @@ public abstract class PackageManager { * @see {@link android.content.Intent#ACTION_INSTALL_INSTANT_APP_PACKAGE} * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable @SystemApi public abstract ComponentName getInstantAppInstallerComponent(); @@ -7648,6 +7737,7 @@ public abstract class PackageManager { * @see {@link android.provider.Settings.Secure#ANDROID_ID} * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @Nullable public abstract String getInstantAppAndroidId(@NonNull String packageName, @NonNull UserHandle user); @@ -7692,6 +7782,7 @@ public abstract class PackageManager { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract void registerDexModule(@NonNull String dexModulePath, @Nullable DexModuleRegisterCallback callback); diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 2e48ce955a90..a33fb58a1367 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -188,6 +188,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void prepare(int maxCount, @NonNull Surface surface) throws CameraAccessException; @@ -227,6 +228,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void tearDown(@NonNull Surface surface) throws CameraAccessException; /** diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 4c96c54debc0..f25b06b5dc45 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -407,7 +407,7 @@ public final class HdmiControlManager { */ @Nullable @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public HdmiClient getClient(int type) { if (mService == null) { return null; @@ -440,7 +440,7 @@ public final class HdmiControlManager { */ @Nullable @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public HdmiPlaybackClient getPlaybackClient() { return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK); } @@ -458,7 +458,7 @@ public final class HdmiControlManager { */ @Nullable @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public HdmiTvClient getTvClient() { return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV); } @@ -476,7 +476,7 @@ public final class HdmiControlManager { * @hide */ @Nullable - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public HdmiAudioSystemClient getAudioSystemClient() { return (HdmiAudioSystemClient) getClient(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); } @@ -491,7 +491,7 @@ public final class HdmiControlManager { * @return {@link HdmiSwitchClient} instance. {@code null} on failure. */ @Nullable - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public HdmiSwitchClient getSwitchClient() { return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); } diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 1ed791d66f74..d44480796d75 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -654,7 +654,7 @@ public final class ContextHubManager { * register a {@link android.hardware.location.ContextHubClientCallback}. */ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public int registerCallback(@NonNull Callback callback) { return registerCallback(callback, null); } @@ -688,7 +688,7 @@ public final class ContextHubManager { * register a {@link android.hardware.location.ContextHubClientCallback}. */ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public int registerCallback(Callback callback, Handler handler) { synchronized(this) { if (mCallback != null) { @@ -892,7 +892,7 @@ public final class ContextHubManager { * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister * a {@link android.hardware.location.ContextHubClientCallback}. */ - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") @Deprecated public int unregisterCallback(@NonNull Callback callback) { synchronized(this) { diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java index 0edd05533dd3..969db96561d7 100644 --- a/core/java/android/hardware/radio/RadioTuner.java +++ b/core/java/android/hardware/radio/RadioTuner.java @@ -257,6 +257,7 @@ public abstract class RadioTuner { * @throws IllegalArgumentException if id==0 * @hide This API is not thoroughly elaborated yet */ + @SuppressWarnings("HiddenAbstractMethod") public abstract @Nullable Bitmap getMetadataImage(int id); /** diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java index 21634cc544e7..1c35cb66ada8 100644 --- a/core/java/android/hardware/usb/UsbDeviceConnection.java +++ b/core/java/android/hardware/usb/UsbDeviceConnection.java @@ -299,7 +299,7 @@ public class UsbDeviceConnection { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public boolean resetDevice() { return native_reset_device(); } diff --git a/core/java/android/net/OemNetworkPreferences.java b/core/java/android/net/OemNetworkPreferences.java index 07789433fbe7..6a8e3f9c01f2 100644 --- a/core/java/android/net/OemNetworkPreferences.java +++ b/core/java/android/net/OemNetworkPreferences.java @@ -36,12 +36,16 @@ public final class OemNetworkPreferences implements Parcelable { public static final int OEM_NETWORK_PREFERENCE_DEFAULT = 0; /** - * Prefer networks in order: NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_OEM_PAID, default. + * If an unmetered network is available, use it. + * Otherwise, if a network with the OEM_PAID capability is available, use it. + * Otherwise, use the general default network. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PAID = 1; /** - * Prefer networks in order: NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_OEM_PAID. + * If an unmetered network is available, use it. + * Otherwise, if a network with the OEM_PAID capability is available, use it. + * Otherwise, the app doesn't get a network. */ public static final int OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK = 2; diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index a985e934ed3c..4e019cf0732e 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -301,7 +301,7 @@ public class TrafficStats { * Changes only take effect during subsequent calls to * {@link #tagSocket(Socket)}. */ - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public static void setThreadStatsUid(int uid) { NetworkManagementSocketTagger.setThreadSocketStatsUid(uid); } @@ -339,7 +339,7 @@ public class TrafficStats { * * @see #setThreadStatsUid(int) */ - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public static void clearThreadStatsUid() { NetworkManagementSocketTagger.setThreadSocketStatsUid(-1); } @@ -976,11 +976,17 @@ public class TrafficStats { } } - // NOTE: keep these in sync with android_net_TrafficStats.cpp - private static final int TYPE_RX_BYTES = 0; - private static final int TYPE_RX_PACKETS = 1; - private static final int TYPE_TX_BYTES = 2; - private static final int TYPE_TX_PACKETS = 3; - private static final int TYPE_TCP_RX_PACKETS = 4; - private static final int TYPE_TCP_TX_PACKETS = 5; + // NOTE: keep these in sync with {@code com_android_server_net_NetworkStatsService.cpp}. + /** {@hide} */ + public static final int TYPE_RX_BYTES = 0; + /** {@hide} */ + public static final int TYPE_RX_PACKETS = 1; + /** {@hide} */ + public static final int TYPE_TX_BYTES = 2; + /** {@hide} */ + public static final int TYPE_TX_PACKETS = 3; + /** {@hide} */ + public static final int TYPE_TCP_RX_PACKETS = 4; + /** {@hide} */ + public static final int TYPE_TCP_TX_PACKETS = 5; } diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl index af06906ca2e9..9dd01140b413 100644 --- a/core/java/android/net/vcn/IVcnManagementService.aidl +++ b/core/java/android/net/vcn/IVcnManagementService.aidl @@ -16,8 +16,13 @@ package android.net.vcn; +import android.net.vcn.VcnConfig; +import android.os.ParcelUuid; + /** * @hide */ interface IVcnManagementService { + void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config); + void clearVcnConfig(in ParcelUuid subscriptionGroup); } diff --git a/core/java/android/net/vcn/VcnConfig.aidl b/core/java/android/net/vcn/VcnConfig.aidl new file mode 100644 index 000000000000..67006a42a701 --- /dev/null +++ b/core/java/android/net/vcn/VcnConfig.aidl @@ -0,0 +1,20 @@ +/* + * 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.net.vcn; + +/** @hide */ +parcelable VcnConfig; diff --git a/core/java/android/net/vcn/VcnConfig.java b/core/java/android/net/vcn/VcnConfig.java new file mode 100644 index 000000000000..148acf130857 --- /dev/null +++ b/core/java/android/net/vcn/VcnConfig.java @@ -0,0 +1,83 @@ +/* + * 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.net.vcn; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class represents a configuration for a Virtual Carrier Network. + * + * @hide + */ +public final class VcnConfig implements Parcelable { + @NonNull private static final String TAG = VcnConfig.class.getSimpleName(); + + private VcnConfig() { + validate(); + } + // TODO: Implement getters, validators, etc + + /** + * Validates this configuration. + * + * @hide + */ + private void validate() { + // TODO: implement validation logic + } + + // Parcelable methods + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) {} + + @NonNull + public static final Parcelable.Creator<VcnConfig> CREATOR = + new Parcelable.Creator<VcnConfig>() { + @NonNull + public VcnConfig createFromParcel(Parcel in) { + // TODO: Ensure all methods are pulled from the parcels + return new VcnConfig(); + } + + @NonNull + public VcnConfig[] newArray(int size) { + return new VcnConfig[size]; + } + }; + + /** This class is used to incrementally build {@link VcnConfig} objects. */ + public static class Builder { + // TODO: Implement this builder + + /** + * Builds and validates the VcnConfig. + * + * @return an immutable VcnConfig instance + */ + @NonNull + public VcnConfig build() { + return new VcnConfig(); + } + } +} diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java index d563b0350187..6769b9e46e4c 100644 --- a/core/java/android/net/vcn/VcnManager.java +++ b/core/java/android/net/vcn/VcnManager.java @@ -18,11 +18,14 @@ package android.net.vcn; import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.content.Context; +import android.os.ParcelUuid; +import android.os.RemoteException; /** - * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks + * VcnManager publishes APIs for applications to configure and manage Virtual Carrier Networks. * * @hide */ @@ -45,4 +48,56 @@ public final class VcnManager { mContext = requireNonNull(ctx, "missing context"); mService = requireNonNull(service, "missing service"); } + + // TODO: Make setVcnConfig(), clearVcnConfig() Public API + /** + * Sets the VCN configuration for a given subscription group. + * + * <p>An app that has carrier privileges for any of the subscriptions in the given group may set + * a VCN configuration. If a configuration already exists for the given subscription group, it + * will be overridden. Any active VCN(s) may be forced to restart to use the new configuration. + * + * <p>This API is ONLY permitted for callers running as the primary user. + * + * @param subscriptionGroup the subscription group that the configuration should be applied to + * @param config the configuration parameters for the VCN + * @throws SecurityException if the caller does not have carrier privileges, or is not running + * as the primary user + * @hide + */ + @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant + public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + requireNonNull(subscriptionGroup, "subscriptionGroup was null"); + requireNonNull(config, "config was null"); + + try { + mService.setVcnConfig(subscriptionGroup, config); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + // TODO: Make setVcnConfig(), clearVcnConfig() Public API + /** + * Clears the VCN configuration for a given subscription group. + * + * <p>An app that has carrier privileges for any of the subscriptions in the given group may + * clear a VCN configuration. This API is ONLY permitted for callers running as the primary + * user. Any active VCN will be torn down. + * + * @param subscriptionGroup the subscription group that the configuration should be applied to + * @throws SecurityException if the caller does not have carrier privileges, or is not running + * as the primary user + * @hide + */ + @RequiresPermission("carrier privileges") // TODO (b/72967236): Define a system-wide constant + public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { + requireNonNull(subscriptionGroup, "subscriptionGroup was null"); + + try { + mService.clearVcnConfig(subscriptionGroup); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java index d91c458a474b..010459d06e8d 100644 --- a/core/java/android/os/IBinder.java +++ b/core/java/android/os/IBinder.java @@ -170,6 +170,15 @@ public interface IBinder { int FLAG_ONEWAY = 0x00000001; /** + * Flag to {@link #transact}: request binder driver to clear transaction data. + * + * Be very careful when using this flag in Java, since Java objects read from a Java + * Parcel may be non-trivial to clear. + * @hide + */ + int FLAG_CLEAR_BUF = 0x00000020; + + /** * @hide */ int FLAG_COLLECT_NOTED_APP_OPS = 0x00000002; diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 9c7f8be6526b..cf90174924f1 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -277,6 +277,8 @@ public final class Parcel { private static final int EX_TRANSACTION_FAILED = -129; @CriticalNative + private static native void nativeMarkSensitive(long nativePtr); + @CriticalNative private static native int nativeDataSize(long nativePtr); @CriticalNative private static native int nativeDataAvail(long nativePtr); @@ -491,6 +493,14 @@ public final class Parcel { public static native long getGlobalAllocCount(); /** + * Parcel data should be zero'd before realloc'd or deleted. + * @hide + */ + public final void markSensitive() { + nativeMarkSensitive(mNativePtr); + } + + /** * Returns the total amount of data contained in the parcel. */ public final int dataSize() { diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 8cdcd49cb2cc..38e170402ae9 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -422,7 +422,7 @@ public class RecoverySystem { * {@hide} */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException { try (InputStream inputStream = new FileInputStream(compatibilityFile)) { return verifyPackageCompatibility(inputStream); diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index ded9be5eb74a..ab741990430f 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -60,7 +60,7 @@ public class SystemProperties { * uses reflection to read this whenever text is selected (http://b/36095274). * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage(trackingBug = 172649311) public static final int PROP_NAME_MAX = Integer.MAX_VALUE; /** @hide */ diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 684ea08ce1a4..648d93431018 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2221,7 +2221,7 @@ public class StorageManager { /** @hide */ @SystemApi @WorkerThread - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public long getAllocatableBytes(@NonNull UUID storageUuid, @RequiresPermission @AllocateFlags int flags) throws IOException { try { @@ -2270,7 +2270,7 @@ public class StorageManager { /** @hide */ @SystemApi @WorkerThread - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void allocateBytes(@NonNull UUID storageUuid, @BytesLong long bytes, @RequiresPermission @AllocateFlags int flags) throws IOException { try { @@ -2320,7 +2320,7 @@ public class StorageManager { /** @hide */ @SystemApi @WorkerThread - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void allocateBytes(FileDescriptor fd, @BytesLong long bytes, @RequiresPermission @AllocateFlags int flags) throws IOException { final File file = ParcelFileDescriptor.getFile(fd); diff --git a/core/java/android/service/autofill/InternalTransformation.java b/core/java/android/service/autofill/InternalTransformation.java index 0dba2b9bb9a6..d31ea99470cf 100644 --- a/core/java/android/service/autofill/InternalTransformation.java +++ b/core/java/android/service/autofill/InternalTransformation.java @@ -45,6 +45,7 @@ public abstract class InternalTransformation implements Transformation, Parcelab * @param template the {@link RemoteViews presentation template}. * @param childViewId resource id of the child view inside the template. */ + @SuppressWarnings("HiddenAbstractMethod") abstract void apply(@NonNull ValueFinder finder, @NonNull RemoteViews template, int childViewId) throws Exception; diff --git a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java index 2a809b1f099b..4ffffc6870cb 100644 --- a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java +++ b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java @@ -46,12 +46,13 @@ import java.util.List; * CarrierMessagingService. * @hide */ -public abstract class CarrierMessagingServiceWrapper { +public final class CarrierMessagingServiceWrapper { // Populated by bindToCarrierMessagingService. bindToCarrierMessagingService must complete // prior to calling disposeConnection so that mCarrierMessagingServiceConnection is initialized. private volatile CarrierMessagingServiceConnection mCarrierMessagingServiceConnection; private volatile ICarrierMessagingService mICarrierMessagingService; + private Runnable mOnServiceReadyCallback; /** * Binds to the carrier messaging service under package {@code carrierPackageName}. This method @@ -63,12 +64,14 @@ public abstract class CarrierMessagingServiceWrapper { * @hide */ public boolean bindToCarrierMessagingService(@NonNull Context context, - @NonNull String carrierPackageName) { + @NonNull String carrierPackageName, + @NonNull Runnable onServiceReadyCallback) { Preconditions.checkState(mCarrierMessagingServiceConnection == null); Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE); intent.setPackage(carrierPackageName); mCarrierMessagingServiceConnection = new CarrierMessagingServiceConnection(); + mOnServiceReadyCallback = onServiceReadyCallback; return context.bindService(intent, mCarrierMessagingServiceConnection, Context.BIND_AUTO_CREATE); } @@ -83,22 +86,17 @@ public abstract class CarrierMessagingServiceWrapper { Preconditions.checkNotNull(mCarrierMessagingServiceConnection); context.unbindService(mCarrierMessagingServiceConnection); mCarrierMessagingServiceConnection = null; + mOnServiceReadyCallback = null; } /** - * Implemented by subclasses to use the carrier messaging service once it is ready. - * @hide - */ - public abstract void onServiceReady(); - - /** * Called when connection with service is established. * * @param carrierMessagingService the carrier messaing service interface */ private void onServiceReady(ICarrierMessagingService carrierMessagingService) { mICarrierMessagingService = carrierMessagingService; - onServiceReady(); + if (mOnServiceReadyCallback != null) mOnServiceReadyCallback.run(); } /** @@ -113,11 +111,11 @@ public abstract class CarrierMessagingServiceWrapper { * @hide */ public void filterSms(@NonNull MessagePdu pdu, @NonNull String format, int destPort, - int subId, @NonNull final CarrierMessagingCallbackWrapper callback) { + int subId, @NonNull final CarrierMessagingCallback callback) { if (mICarrierMessagingService != null) { try { mICarrierMessagingService.filterSms(pdu, format, destPort, subId, - new CarrierMessagingCallbackWrapperInternal(callback)); + new CarrierMessagingCallbackInternal(callback)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -137,11 +135,11 @@ public abstract class CarrierMessagingServiceWrapper { * @hide */ public void sendTextSms(@NonNull String text, int subId, @NonNull String destAddress, - int sendSmsFlag, @NonNull final CarrierMessagingCallbackWrapper callback) { + int sendSmsFlag, @NonNull final CarrierMessagingCallback callback) { if (mICarrierMessagingService != null) { try { mICarrierMessagingService.sendTextSms(text, subId, destAddress, sendSmsFlag, - new CarrierMessagingCallbackWrapperInternal(callback)); + new CarrierMessagingCallbackInternal(callback)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -163,11 +161,11 @@ public abstract class CarrierMessagingServiceWrapper { */ public void sendDataSms(@NonNull byte[] data, int subId, @NonNull String destAddress, int destPort, int sendSmsFlag, - @NonNull final CarrierMessagingCallbackWrapper callback) { + @NonNull final CarrierMessagingCallback callback) { if (mICarrierMessagingService != null) { try { mICarrierMessagingService.sendDataSms(data, subId, destAddress, destPort, - sendSmsFlag, new CarrierMessagingCallbackWrapperInternal(callback)); + sendSmsFlag, new CarrierMessagingCallbackInternal(callback)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -188,11 +186,11 @@ public abstract class CarrierMessagingServiceWrapper { */ public void sendMultipartTextSms(@NonNull List<String> parts, int subId, @NonNull String destAddress, int sendSmsFlag, - @NonNull final CarrierMessagingCallbackWrapper callback) { + @NonNull final CarrierMessagingCallback callback) { if (mICarrierMessagingService != null) { try { mICarrierMessagingService.sendMultipartTextSms(parts, subId, destAddress, - sendSmsFlag, new CarrierMessagingCallbackWrapperInternal(callback)); + sendSmsFlag, new CarrierMessagingCallbackInternal(callback)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -212,11 +210,11 @@ public abstract class CarrierMessagingServiceWrapper { * @hide */ public void sendMms(@NonNull Uri pduUri, int subId, @NonNull Uri location, - @NonNull final CarrierMessagingCallbackWrapper callback) { + @NonNull final CarrierMessagingCallback callback) { if (mICarrierMessagingService != null) { try { mICarrierMessagingService.sendMms(pduUri, subId, location, - new CarrierMessagingCallbackWrapperInternal(callback)); + new CarrierMessagingCallbackInternal(callback)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -235,11 +233,11 @@ public abstract class CarrierMessagingServiceWrapper { * @hide */ public void downloadMms(@NonNull Uri pduUri, int subId, @NonNull Uri location, - @NonNull final CarrierMessagingCallbackWrapper callback) { + @NonNull final CarrierMessagingCallback callback) { if (mICarrierMessagingService != null) { try { mICarrierMessagingService.downloadMms(pduUri, subId, location, - new CarrierMessagingCallbackWrapperInternal(callback)); + new CarrierMessagingCallbackInternal(callback)); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -265,7 +263,7 @@ public abstract class CarrierMessagingServiceWrapper { * {@link CarrierMessagingServiceWrapper}. * @hide */ - public abstract static class CarrierMessagingCallbackWrapper { + public interface CarrierMessagingCallback { /** * Response callback for {@link CarrierMessagingServiceWrapper#filterSms}. @@ -277,7 +275,7 @@ public abstract class CarrierMessagingServiceWrapper { * {@see CarrierMessagingService#onReceiveTextSms}. * @hide */ - public void onFilterComplete(int result) { + default void onFilterComplete(int result) { } @@ -291,7 +289,7 @@ public abstract class CarrierMessagingServiceWrapper { * only if result is {@link CarrierMessagingService#SEND_STATUS_OK}. * @hide */ - public void onSendSmsComplete(int result, int messageRef) { + default void onSendSmsComplete(int result, int messageRef) { } @@ -305,7 +303,7 @@ public abstract class CarrierMessagingServiceWrapper { * {@link CarrierMessagingService#SEND_STATUS_OK}. * @hide */ - public void onSendMultipartSmsComplete(int result, @Nullable int[] messageRefs) { + default void onSendMultipartSmsComplete(int result, @Nullable int[] messageRefs) { } @@ -319,7 +317,7 @@ public abstract class CarrierMessagingServiceWrapper { * {@link CarrierMessagingService#SEND_STATUS_OK}. * @hide */ - public void onSendMmsComplete(int result, @Nullable byte[] sendConfPdu) { + default void onSendMmsComplete(int result, @Nullable byte[] sendConfPdu) { } @@ -330,43 +328,43 @@ public abstract class CarrierMessagingServiceWrapper { * and {@link CarrierMessagingService#SEND_STATUS_ERROR}. * @hide */ - public void onDownloadMmsComplete(int result) { + default void onDownloadMmsComplete(int result) { } } - private final class CarrierMessagingCallbackWrapperInternal + private final class CarrierMessagingCallbackInternal extends ICarrierMessagingCallback.Stub { - CarrierMessagingCallbackWrapper mCarrierMessagingCallbackWrapper; + CarrierMessagingCallback mCarrierMessagingCallback; - CarrierMessagingCallbackWrapperInternal(CarrierMessagingCallbackWrapper callback) { - mCarrierMessagingCallbackWrapper = callback; + CarrierMessagingCallbackInternal(CarrierMessagingCallback callback) { + mCarrierMessagingCallback = callback; } @Override public void onFilterComplete(int result) throws RemoteException { - mCarrierMessagingCallbackWrapper.onFilterComplete(result); + mCarrierMessagingCallback.onFilterComplete(result); } @Override public void onSendSmsComplete(int result, int messageRef) throws RemoteException { - mCarrierMessagingCallbackWrapper.onSendSmsComplete(result, messageRef); + mCarrierMessagingCallback.onSendSmsComplete(result, messageRef); } @Override public void onSendMultipartSmsComplete(int result, int[] messageRefs) throws RemoteException { - mCarrierMessagingCallbackWrapper.onSendMultipartSmsComplete(result, messageRefs); + mCarrierMessagingCallback.onSendMultipartSmsComplete(result, messageRefs); } @Override public void onSendMmsComplete(int result, byte[] sendConfPdu) throws RemoteException { - mCarrierMessagingCallbackWrapper.onSendMmsComplete(result, sendConfPdu); + mCarrierMessagingCallback.onSendMmsComplete(result, sendConfPdu); } @Override public void onDownloadMmsComplete(int result) throws RemoteException { - mCarrierMessagingCallbackWrapper.onDownloadMmsComplete(result); + mCarrierMessagingCallback.onDownloadMmsComplete(result); } } -} +}
\ No newline at end of file diff --git a/core/java/android/service/persistentdata/PersistentDataBlockManager.java b/core/java/android/service/persistentdata/PersistentDataBlockManager.java index 0bf68b734b8a..8242f4e2c9dc 100644 --- a/core/java/android/service/persistentdata/PersistentDataBlockManager.java +++ b/core/java/android/service/persistentdata/PersistentDataBlockManager.java @@ -90,7 +90,7 @@ public class PersistentDataBlockManager { * * @param data the data to write */ - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public int write(byte[] data) { try { return sService.write(data); @@ -102,7 +102,7 @@ public class PersistentDataBlockManager { /** * Returns the data block stored on the persistent partition. */ - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public byte[] read() { try { return sService.read(); @@ -130,7 +130,7 @@ public class PersistentDataBlockManager { * * Returns -1 on error. */ - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public long getMaximumDataBlockSize() { try { return sService.getMaximumDataBlockSize(); diff --git a/core/java/android/uwb/RangingParams.java b/core/java/android/uwb/RangingParams.java deleted file mode 100644 index f23d9ed0dd3a..000000000000 --- a/core/java/android/uwb/RangingParams.java +++ /dev/null @@ -1,477 +0,0 @@ -/* - * 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.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.PersistableBundle; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -/** - * An object used when requesting to open a new {@link RangingSession}. - * <p>Use {@link RangingParams.Builder} to create an instance of this class. - * - * @hide - */ -public final class RangingParams implements Parcelable { - private final boolean mIsInitiator; - private final boolean mIsController; - private final Duration mSamplePeriod; - private final UwbAddress mLocalDeviceAddress; - private final List<UwbAddress> mRemoteDeviceAddresses; - private final int mChannelNumber; - private final int mTransmitPreambleCodeIndex; - private final int mReceivePreambleCodeIndex; - private final int mStsPhyPacketType; - private final PersistableBundle mSpecificationParameters; - - private RangingParams(boolean isInitiator, boolean isController, - @NonNull Duration samplingPeriod, @NonNull UwbAddress localDeviceAddress, - @NonNull List<UwbAddress> remoteDeviceAddresses, int channelNumber, - int transmitPreambleCodeIndex, int receivePreambleCodeIndex, - @StsPhyPacketType int stsPhyPacketType, - @NonNull PersistableBundle specificationParameters) { - mIsInitiator = isInitiator; - mIsController = isController; - mSamplePeriod = samplingPeriod; - mLocalDeviceAddress = localDeviceAddress; - mRemoteDeviceAddresses = remoteDeviceAddresses; - mChannelNumber = channelNumber; - mTransmitPreambleCodeIndex = transmitPreambleCodeIndex; - mReceivePreambleCodeIndex = receivePreambleCodeIndex; - mStsPhyPacketType = stsPhyPacketType; - mSpecificationParameters = specificationParameters; - } - - /** - * Get if the local device is the initiator - * - * @return true if the device is the initiator - */ - public boolean isInitiator() { - return mIsInitiator; - } - - /** - * Get if the local device is the controller - * - * @return true if the device is the controller - */ - public boolean isController() { - return mIsController; - } - - /** - * The desired amount of time between two adjacent samples of measurement - * - * @return the ranging sample period - */ - @NonNull - public Duration getSamplingPeriod() { - return mSamplePeriod; - } - - /** - * Local device's {@link UwbAddress} - * - * <p>Simultaneous {@link RangingSession}s on the same device can have different results for - * {@link #getLocalDeviceAddress()}. - * - * @return the local device's {@link UwbAddress} - */ - @NonNull - public UwbAddress getLocalDeviceAddress() { - return mLocalDeviceAddress; - } - - /** - * Gets a list of all remote device's {@link UwbAddress} - * - * @return a {@link List} of {@link UwbAddress} representing the remote devices - */ - @NonNull - public List<UwbAddress> getRemoteDeviceAddresses() { - return mRemoteDeviceAddresses; - } - - /** - * Channel number used between this device pair as defined by 802.15.4z - * - * Range: -1, 0-15 - * - * @return the channel to use - */ - public int getChannelNumber() { - return mChannelNumber; - } - - /** - * Preamble index used between this device pair as defined by 802.15.4z - * - * Range: 0, 0-32 - * - * @return the preamble index to use for transmitting - */ - public int getTxPreambleIndex() { - return mTransmitPreambleCodeIndex; - } - - /** - * preamble index used between this device pair as defined by 802.15.4z - * - * Range: 0, 13-16, 21-32 - * - * @return the preamble index to use for receiving - */ - public int getRxPreambleIndex() { - return mReceivePreambleCodeIndex; - } - - @Retention(RetentionPolicy.SOURCE) - @IntDef(value = { - STS_PHY_PACKET_TYPE_SP0, - STS_PHY_PACKET_TYPE_SP1, - STS_PHY_PACKET_TYPE_SP2, - STS_PHY_PACKET_TYPE_SP3}) - public @interface StsPhyPacketType {} - - /** - * PHY packet type SP0 when STS is used as defined by 802.15.4z - */ - public static final int STS_PHY_PACKET_TYPE_SP0 = 0; - - /** - * PHY packet type SP1 when STS is used as defined by 802.15.4z - */ - public static final int STS_PHY_PACKET_TYPE_SP1 = 1; - - /** - * PHY packet type SP2 when STS is used as defined by 802.15.4z - */ - public static final int STS_PHY_PACKET_TYPE_SP2 = 2; - - /** - * PHY packet type SP3 when STS is used as defined by 802.15.4z - */ - public static final int STS_PHY_PACKET_TYPE_SP3 = 3; - - /** - * Get the type of PHY packet when STS is used as defined by 802.15.4z - * - * @return the {@link StsPhyPacketType} to use - */ - @StsPhyPacketType - public int getStsPhyPacketType() { - return mStsPhyPacketType; - } - - /** - * Parameters for a specific UWB protocol constructed using a support library. - * - * <p>Android reserves the '^android.*' namespace - * - * @return a {@link PersistableBundle} copy of protocol specific parameters - */ - public @Nullable PersistableBundle getSpecificationParameters() { - return new PersistableBundle(mSpecificationParameters); - } - - /** - * @hide - */ - @Override - public boolean equals(@Nullable Object obj) { - if (this == obj) { - return true; - } - - if (obj instanceof RangingParams) { - RangingParams other = (RangingParams) obj; - - return mIsInitiator == other.mIsInitiator - && mIsController == other.mIsController - && mSamplePeriod.equals(other.mSamplePeriod) - && mLocalDeviceAddress.equals(other.mLocalDeviceAddress) - && mRemoteDeviceAddresses.equals(other.mRemoteDeviceAddresses) - && mChannelNumber == other.mChannelNumber - && mTransmitPreambleCodeIndex == other.mTransmitPreambleCodeIndex - && mReceivePreambleCodeIndex == other.mReceivePreambleCodeIndex - && mStsPhyPacketType == other.mStsPhyPacketType - && mSpecificationParameters.size() == other.mSpecificationParameters.size() - && mSpecificationParameters.kindofEquals(other.mSpecificationParameters); - } - return false; - } - - /** - * @hide - */ - @Override - public int hashCode() { - return Objects.hash(mIsInitiator, mIsController, mSamplePeriod, mLocalDeviceAddress, - mRemoteDeviceAddresses, mChannelNumber, mTransmitPreambleCodeIndex, - mReceivePreambleCodeIndex, mStsPhyPacketType, mSpecificationParameters); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeBoolean(mIsInitiator); - dest.writeBoolean(mIsController); - dest.writeLong(mSamplePeriod.getSeconds()); - dest.writeInt(mSamplePeriod.getNano()); - dest.writeParcelable(mLocalDeviceAddress, flags); - - UwbAddress[] remoteAddresses = new UwbAddress[mRemoteDeviceAddresses.size()]; - mRemoteDeviceAddresses.toArray(remoteAddresses); - dest.writeParcelableArray(remoteAddresses, flags); - - dest.writeInt(mChannelNumber); - dest.writeInt(mTransmitPreambleCodeIndex); - dest.writeInt(mReceivePreambleCodeIndex); - dest.writeInt(mStsPhyPacketType); - dest.writePersistableBundle(mSpecificationParameters); - } - - public static final @android.annotation.NonNull Creator<RangingParams> CREATOR = - new Creator<RangingParams>() { - @Override - public RangingParams createFromParcel(Parcel in) { - Builder builder = new Builder(); - builder.setIsInitiator(in.readBoolean()); - builder.setIsController(in.readBoolean()); - builder.setSamplePeriod(Duration.ofSeconds(in.readLong(), in.readInt())); - builder.setLocalDeviceAddress( - in.readParcelable(UwbAddress.class.getClassLoader())); - - UwbAddress[] remoteAddresses = - in.readParcelableArray(null, UwbAddress.class); - for (UwbAddress remoteAddress : remoteAddresses) { - builder.addRemoteDeviceAddress(remoteAddress); - } - - builder.setChannelNumber(in.readInt()); - builder.setTransmitPreambleCodeIndex(in.readInt()); - builder.setReceivePreambleCodeIndex(in.readInt()); - builder.setStsPhPacketType(in.readInt()); - builder.setSpecificationParameters(in.readPersistableBundle()); - - return builder.build(); - } - - @Override - public RangingParams[] newArray(int size) { - return new RangingParams[size]; - } - }; - - /** - * Builder class for {@link RangingParams}. - */ - public static final class Builder { - private boolean mIsInitiator = false; - private boolean mIsController = false; - private Duration mSamplePeriod = null; - private UwbAddress mLocalDeviceAddress = null; - private List<UwbAddress> mRemoteDeviceAddresses = new ArrayList<>(); - private int mChannelNumber = 0; - private int mTransmitPreambleCodeIndex = 0; - private int mReceivePreambleCodeIndex = 0; - private int mStsPhyPacketType = STS_PHY_PACKET_TYPE_SP0; - private PersistableBundle mSpecificationParameters = new PersistableBundle(); - - /** - * Set whether the device is the initiator or responder as defined by IEEE 802.15.4z - * - * @param isInitiator whether the device is the initiator (true) or responder (false) - */ - public Builder setIsInitiator(boolean isInitiator) { - mIsInitiator = isInitiator; - return this; - } - - /** - * Set whether the local device is the controller or controlee as defined by IEEE 802.15.4z - * - * @param isController whether the device is the controller (true) or controlee (false) - */ - public Builder setIsController(boolean isController) { - mIsController = isController; - return this; - } - - /** - * Set the time between ranging samples - * - * @param samplePeriod the time between ranging samples - */ - public Builder setSamplePeriod(@NonNull Duration samplePeriod) { - mSamplePeriod = samplePeriod; - return this; - } - - /** - * Set the local device address - * - * @param localDeviceAddress the local device's address for the {@link RangingSession} - */ - public Builder setLocalDeviceAddress(@NonNull UwbAddress localDeviceAddress) { - mLocalDeviceAddress = localDeviceAddress; - return this; - } - - /** - * Add a remote device's address to the ranging session - * - * @param remoteDeviceAddress a remote device's address for the {@link RangingSession} - * @throws IllegalArgumentException if {@code remoteDeviceAddress} is already present. - */ - public Builder addRemoteDeviceAddress(@NonNull UwbAddress remoteDeviceAddress) { - if (mRemoteDeviceAddresses.contains(remoteDeviceAddress)) { - throw new IllegalArgumentException( - "Remote device address already added: " + remoteDeviceAddress.toString()); - } - mRemoteDeviceAddresses.add(remoteDeviceAddress); - return this; - } - - /** - * Set the IEEE 802.15.4z channel to use for the {@link RangingSession} - * <p>Valid values are in the range [-1, 15] - * - * @param channelNumber the channel to use for the {@link RangingSession} - * @throws IllegalArgumentException if {@code channelNumber} is invalid. - */ - public Builder setChannelNumber(int channelNumber) { - if (channelNumber < -1 || channelNumber > 15) { - throw new IllegalArgumentException("Invalid channel number"); - } - mChannelNumber = channelNumber; - return this; - } - - private static final Set<Integer> VALID_TX_PREAMBLE_CODES = new HashSet<Integer>( - Arrays.asList(0, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)); - - /** - * Set the IEEE 802.15.4z preamble code index to use when transmitting - * - * <p>Valid values are in the ranges: [0], [13-16], [21-32] - * - * @param transmitPreambleCodeIndex preamble code index to use for transmitting - * @throws IllegalArgumentException if {@code transmitPreambleCodeIndex} is invalid. - */ - public Builder setTransmitPreambleCodeIndex(int transmitPreambleCodeIndex) { - if (!VALID_TX_PREAMBLE_CODES.contains(transmitPreambleCodeIndex)) { - throw new IllegalArgumentException( - "Invalid transmit preamble: " + transmitPreambleCodeIndex); - } - mTransmitPreambleCodeIndex = transmitPreambleCodeIndex; - return this; - } - - private static final Set<Integer> VALID_RX_PREAMBLE_CODES = new HashSet<Integer>( - Arrays.asList(0, 16, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)); - - /** - * Set the IEEE 802.15.4z preamble code index to use when receiving - * - * Valid values are in the ranges: [0], [16-32] - * - * @param receivePreambleCodeIndex preamble code index to use for receiving - * @throws IllegalArgumentException if {@code receivePreambleCodeIndex} is invalid. - */ - public Builder setReceivePreambleCodeIndex(int receivePreambleCodeIndex) { - if (!VALID_RX_PREAMBLE_CODES.contains(receivePreambleCodeIndex)) { - throw new IllegalArgumentException( - "Invalid receive preamble: " + receivePreambleCodeIndex); - } - mReceivePreambleCodeIndex = receivePreambleCodeIndex; - return this; - } - - /** - * Set the IEEE 802.15.4z PHY packet type when STS is used - * - * @param stsPhyPacketType PHY packet type when STS is used - * @throws IllegalArgumentException if {@code stsPhyPacketType} is invalid. - */ - public Builder setStsPhPacketType(@StsPhyPacketType int stsPhyPacketType) { - if (stsPhyPacketType != STS_PHY_PACKET_TYPE_SP0 - && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP1 - && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP2 - && stsPhyPacketType != STS_PHY_PACKET_TYPE_SP3) { - throw new IllegalArgumentException("unknown StsPhyPacketType: " + stsPhyPacketType); - } - - mStsPhyPacketType = stsPhyPacketType; - return this; - } - - /** - * Set the specification parameters - * - * <p>Creates a copy of the parameters - * - * @param parameters specification parameters built from support library - */ - public Builder setSpecificationParameters(@NonNull PersistableBundle parameters) { - mSpecificationParameters = new PersistableBundle(parameters); - return this; - } - - /** - * Build the {@link RangingParams} object. - * - * @throws IllegalStateException if required parameters are missing - */ - public RangingParams build() { - if (mSamplePeriod == null) { - throw new IllegalStateException("No sample period provided"); - } - - if (mLocalDeviceAddress == null) { - throw new IllegalStateException("Local device address not provided"); - } - - if (mRemoteDeviceAddresses.size() == 0) { - throw new IllegalStateException("No remote device address(es) provided"); - } - - return new RangingParams(mIsInitiator, mIsController, mSamplePeriod, - mLocalDeviceAddress, mRemoteDeviceAddresses, mChannelNumber, - mTransmitPreambleCodeIndex, mReceivePreambleCodeIndex, mStsPhyPacketType, - mSpecificationParameters); - } - } -} diff --git a/core/java/android/uwb/RangingSession.java b/core/java/android/uwb/RangingSession.java index f4033fe0d29e..863926924aad 100644 --- a/core/java/android/uwb/RangingSession.java +++ b/core/java/android/uwb/RangingSession.java @@ -30,7 +30,7 @@ import java.util.concurrent.Executor; * {@link RangingSession}. * * <p>To get an instance of {@link RangingSession}, first use - * {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} to request to open a + * {@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 @@ -44,7 +44,7 @@ public final class RangingSession implements AutoCloseable { */ public interface Callback { /** - * Invoked when {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} + * Invoked when {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} * is successful * * @param session the newly opened {@link RangingSession} @@ -77,7 +77,7 @@ public final class RangingSession implements AutoCloseable { /** * Indicates that the session failed to open due to erroneous parameters passed - * to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} + * to {@link UwbManager#openRangingSession(PersistableBundle, Executor, Callback)} */ int CLOSE_REASON_LOCAL_BAD_PARAMETERS = 2; @@ -137,8 +137,8 @@ public final class RangingSession implements AutoCloseable { * will still be invoked. * * <p>{@link Callback#onClosed(int)} will be invoked using the same callback - * object given to {@link UwbManager#openRangingSession(RangingParams, Executor, Callback)} when - * the {@link RangingSession} was opened. The callback will be invoked after each call to + * 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 diff --git a/core/java/android/uwb/UwbManager.java b/core/java/android/uwb/UwbManager.java index d58d5bfd8de3..2f1e2ded26ac 100644 --- a/core/java/android/uwb/UwbManager.java +++ b/core/java/android/uwb/UwbManager.java @@ -176,15 +176,14 @@ public final class UwbManager { * arrangement, a platform may only support hemi-spherical azimuth angles * ranging from -pi/2 to pi/2 */ - public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 2; + public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_HEMISPHERICAL = 3; /** * Indicate support for three dimensional angle of arrival measurement. * Typically requires at least three antennas. This mode supports full * azimuth angles ranging from -pi to pi. */ - public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 3; - + public static final int ANGLE_OF_ARRIVAL_SUPPORT_TYPE_3D_SPHERICAL = 4; /** * Gets the {@link AngleOfArrivalSupportType} supported on this platform @@ -280,7 +279,10 @@ public final class UwbManager { * <p>An open {@link RangingSession} will be automatically closed if client application process * dies. * - * @param params {@link RangingParams} used to initialize this {@link RangingSession} + * <p>A UWB support library must be used in order to construct the {@code parameter} + * {@link PersistableBundle}. + * + * @param parameters 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. @@ -291,8 +293,9 @@ public final class UwbManager { * {@link RangingSession.Callback#onOpenSuccess}. */ @NonNull - public AutoCloseable openRangingSession(@NonNull RangingParams params, - @NonNull Executor executor, @NonNull RangingSession.Callback callbacks) { + public AutoCloseable openRangingSession(@NonNull PersistableBundle parameters, + @NonNull Executor executor, + @NonNull RangingSession.Callback callbacks) { throw new UnsupportedOperationException(); } } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index 606e8f99999a..29ce231d5d87 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -97,6 +97,7 @@ public abstract class ViewStructure { public abstract void setVisibility(int visibility); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void setAssistBlocked(boolean state); /** @@ -431,6 +432,7 @@ public abstract class ViewStructure { public abstract void asyncCommit(); /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract Rect getTempRect(); /** diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index f2772d6d041c..fb2412bd51e9 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1384,6 +1384,7 @@ public abstract class Window { } /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void alwaysReadCloseOnTouchAttr(); @@ -1564,6 +1565,7 @@ public abstract class Window { * * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void clearContentView(); /** @@ -2632,18 +2634,21 @@ public abstract class Window { * Called when the activity changes from fullscreen mode to multi-window mode and visa-versa. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void onMultiWindowModeChanged(); /** * Called when the activity changes to/from picture-in-picture mode. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void onPictureInPictureModeChanged(boolean isInPictureInPictureMode); /** * Called when the activity just relaunched. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void reportActivityRelaunched(); /** diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index f62a28ec0d07..023d9ff28f41 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -154,6 +154,7 @@ public abstract class CookieManager { * HTTP request header * @hide Used by Browser and by WebViewProvider implementations. */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract String getCookie(String url, boolean privateBrowsing); @@ -230,6 +231,7 @@ public abstract class CookieManager { * @param privateBrowsing whether to use the private browsing cookie jar * @hide Used by Browser and WebViewProvider implementations. */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract boolean hasCookies(boolean privateBrowsing); @@ -264,6 +266,7 @@ public abstract class CookieManager { * * @hide Only for use by WebViewProvider implementations */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi protected abstract boolean allowFileSchemeCookiesImpl(); @@ -299,6 +302,7 @@ public abstract class CookieManager { * * @hide Only for use by WebViewProvider implementations */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi protected abstract void setAcceptFileSchemeCookiesImpl(boolean accept); } diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java index b9e704285f84..2cb37b440128 100644 --- a/core/java/android/webkit/WebHistoryItem.java +++ b/core/java/android/webkit/WebHistoryItem.java @@ -35,6 +35,7 @@ public abstract class WebHistoryItem implements Cloneable { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract int getId(); diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java index 08956e0f1b78..b70565816037 100644 --- a/core/java/android/webkit/WebIconDatabase.java +++ b/core/java/android/webkit/WebIconDatabase.java @@ -75,6 +75,7 @@ public abstract class WebIconDatabase { /** {@hide} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract void bulkRequestIconForPageUrl(ContentResolver cr, String where, IconListener listener); diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 91b9390745ef..9b753f1b2d44 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -268,6 +268,7 @@ public abstract class WebSettings { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract void setNavDump(boolean enabled); @@ -280,6 +281,7 @@ public abstract class WebSettings { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract boolean getNavDump(); @@ -457,6 +459,7 @@ public abstract class WebSettings { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract void setUseWebViewBackgroundForOverscrollBackground(boolean view); @@ -469,6 +472,7 @@ public abstract class WebSettings { * @deprecated This method is now obsolete. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract boolean getUseWebViewBackgroundForOverscrollBackground(); @@ -534,6 +538,7 @@ public abstract class WebSettings { * Developers should access this via {@link CookieManager#setShouldAcceptThirdPartyCookies}. * @hide Internal API. */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract void setAcceptThirdPartyCookies(boolean accept); @@ -542,6 +547,7 @@ public abstract class WebSettings { * Developers should access this via {@link CookieManager#getShouldAcceptThirdPartyCookies}. * @hide Internal API */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract boolean getAcceptThirdPartyCookies(); @@ -669,6 +675,7 @@ public abstract class WebSettings { * @deprecated Please use {@link #setUserAgentString} instead. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract void setUserAgent(int ua); @@ -687,6 +694,7 @@ public abstract class WebSettings { * @deprecated Please use {@link #getUserAgentString} instead. * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract int getUserAgent(); @@ -1050,6 +1058,7 @@ public abstract class WebSettings { * {@link #setPluginState} * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract void setPluginsEnabled(boolean flag); @@ -1259,6 +1268,7 @@ public abstract class WebSettings { * @deprecated This method has been replaced by {@link #getPluginState} * @hide Since API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi @Deprecated public abstract boolean getPluginsEnabled(); @@ -1445,6 +1455,7 @@ public abstract class WebSettings { * WebView. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean flag); @@ -1455,6 +1466,7 @@ public abstract class WebSettings { * @see #setVideoOverlayForEmbeddedEncryptedVideoEnabled * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @SystemApi public abstract boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled(); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 64e1ca84cb81..a785a1ab9f0e 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -5346,6 +5346,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * * @param down true if the scroll is going down, false if it is going up */ + @SuppressWarnings("HiddenAbstractMethod") abstract void fillGap(boolean down); void hideSelector() { @@ -5383,6 +5384,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * @param y Where the user touched * @return The position of the first (or only) item in the row containing y */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage abstract int findMotionRow(int y); @@ -5430,6 +5432,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * * @param position the position of the new selection */ + @SuppressWarnings("HiddenAbstractMethod") abstract void setSelectionInt(int position); /** diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java index daf6914ff030..76e97ad7a0f6 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -310,6 +310,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { } } + @SuppressWarnings("HiddenAbstractMethod") abstract void layout(int delta, boolean animate); @Override diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 60f1b4438f54..8d1f16b4b259 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -24,7 +24,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.type.DefaultMimeMapFactory; import android.os.Build; import android.os.DeadObjectException; -import android.os.Debug; import android.os.IBinder; import android.os.Process; import android.os.SystemProperties; @@ -257,18 +256,6 @@ public class RuntimeInit { */ NetworkManagementSocketTagger.install(); - /* - * If we're running in an emulator launched with "-trace", put the - * VM into emulator trace profiling mode so that the user can hit - * F9/F10 at any time to capture traces. This has performance - * consequences, so it's not something you want to do always. - */ - String trace = SystemProperties.get("ro.kernel.android.tracing"); - if (trace.equals("1")) { - Slog.i(TAG, "NOTE: emulator trace profiling enabled"); - Debug.enableEmulatorTraceOutput(); - } - initialized = true; } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index aa37334b2c54..6a67670d8160 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -73,6 +73,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.security.Provider; import java.security.Security; +import java.util.Optional; /** * Startup class for the zygote process. @@ -225,7 +226,17 @@ public class ZygoteInit { // AndroidKeyStoreProvider.install() manipulates the list of JCA providers to insert // preferred providers. Note this is not done via security.properties as the JCA providers // are not on the classpath in the case of, for example, raw dalvikvm runtimes. - AndroidKeyStoreProvider.install(); + // TODO b/171305684 This code is used to conditionally enable the installation of the + // Keystore 2.0 provider to enable teams adjusting to Keystore 2.0 at their own + // pace. This code will be removed when all calling code was adjusted to + // Keystore 2.0. + Optional<Boolean> keystore2_enabled = + android.sysprop.Keystore2Properties.keystore2_enabled(); + if (keystore2_enabled.isPresent() && keystore2_enabled.get()) { + android.security.keystore2.AndroidKeyStoreProvider.install(); + } else { + AndroidKeyStoreProvider.install(); + } Log.i(TAG, "Installed AndroidKeyStoreProvider in " + (SystemClock.uptimeMillis() - startTime) + "ms."); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index 9c7ee0c641a6..241570a7f9d3 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -90,6 +90,14 @@ void recycleJavaParcelObject(JNIEnv* env, jobject parcelObj) env->CallVoidMethod(parcelObj, gParcelOffsets.recycle); } +static void android_os_Parcel_markSensitive(jlong nativePtr) +{ + Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); + if (parcel) { + parcel->markSensitive(); + } +} + static jint android_os_Parcel_dataSize(jlong nativePtr) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); @@ -758,6 +766,8 @@ static jboolean android_os_Parcel_replaceCallingWorkSourceUid(jlong nativePtr, j static const JNINativeMethod gParcelMethods[] = { // @CriticalNative + {"nativeMarkSensitive", "(J)V", (void*)android_os_Parcel_markSensitive}, + // @CriticalNative {"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize}, // @CriticalNative {"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail}, diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 95c295a4784c..70c2afb74c72 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -804,10 +804,14 @@ static void PrepareDirIfNotPresent(const std::string& dir, mode_t mode, uid_t ui PrepareDir(dir, mode, uid, gid, fail_fn); } +static bool BindMount(const std::string& source_dir, const std::string& target_dir) { + return !(TEMP_FAILURE_RETRY(mount(source_dir.c_str(), target_dir.c_str(), nullptr, + MS_BIND | MS_REC, nullptr)) == -1); +} + static void BindMount(const std::string& source_dir, const std::string& target_dir, fail_fn_t fail_fn) { - if (TEMP_FAILURE_RETRY(mount(source_dir.c_str(), target_dir.c_str(), nullptr, - MS_BIND | MS_REC, nullptr)) == -1) { + if (!BindMount(source_dir, target_dir)) { fail_fn(CREATE_ERROR("Failed to mount %s to %s: %s", source_dir.c_str(), target_dir.c_str(), strerror(errno))); } @@ -1194,9 +1198,9 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, // Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it // from the actual app data directory in data mirror. -static void createAndMountAppData(std::string_view package_name, +static bool createAndMountAppData(std::string_view package_name, std::string_view mirror_pkg_dir_name, std::string_view mirror_data_path, - std::string_view actual_data_path, fail_fn_t fail_fn) { + std::string_view actual_data_path, fail_fn_t fail_fn, bool call_fail_fn) { char mirrorAppDataPath[PATH_MAX]; char actualAppDataPath[PATH_MAX]; @@ -1207,6 +1211,29 @@ static void createAndMountAppData(std::string_view package_name, PrepareDir(actualAppDataPath, 0700, AID_ROOT, AID_ROOT, fail_fn); // Bind mount from original app data directory in mirror. + if (call_fail_fn) { + BindMount(mirrorAppDataPath, actualAppDataPath, fail_fn); + } else if(!BindMount(mirrorAppDataPath, actualAppDataPath)) { + ALOGW("Failed to mount %s to %s: %s", + mirrorAppDataPath, actualAppDataPath, strerror(errno)); + return false; + } + return true; +} + +// There is an app data directory over tmpfs overlaid CE / DE storage +// bind mount it from the actual app data directory in data mirror. +static void mountAppData(std::string_view package_name, + std::string_view mirror_pkg_dir_name, std::string_view mirror_data_path, + std::string_view actual_data_path, fail_fn_t fail_fn) { + + char mirrorAppDataPath[PATH_MAX]; + char actualAppDataPath[PATH_MAX]; + snprintf(mirrorAppDataPath, PATH_MAX, "%s/%s", mirror_data_path.data(), + mirror_pkg_dir_name.data()); + snprintf(actualAppDataPath, PATH_MAX, "%s/%s", actual_data_path.data(), package_name.data()); + + // Bind mount from original app data directory in mirror. BindMount(mirrorAppDataPath, actualAppDataPath, fail_fn); } @@ -1284,10 +1311,17 @@ static void isolateAppDataPerPackage(int userId, std::string_view package_name, snprintf(mirrorCePath, PATH_MAX, "%s/%d", mirrorCeParent, userId); snprintf(mirrorDePath, PATH_MAX, "/data_mirror/data_de/%s/%d", volume_uuid.data(), userId); - createAndMountAppData(package_name, package_name, mirrorDePath, actualDePath, fail_fn); + createAndMountAppData(package_name, package_name, mirrorDePath, actualDePath, fail_fn, + true /*call_fail_fn*/); std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn); - createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn); + if (!createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn, + false /*call_fail_fn*/)) { + // CE might unlocks and the name is decrypted + // get the name and mount again + ce_data_path=getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn); + mountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn); + } } // Relabel directory diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 38981b0caaf7..c73aae58fe7f 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -33,16 +33,6 @@ // Static whitelist of open paths that the zygote is allowed to keep open. static const char* kPathWhitelist[] = { - "/apex/com.android.conscrypt/javalib/conscrypt.jar", - "/apex/com.android.ipsec/javalib/ike.jar", - "/apex/com.android.i18n/javalib/core-icu4j.jar", - "/apex/com.android.media/javalib/updatable-media.jar", - "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar", - "/apex/com.android.os.statsd/javalib/framework-statsd.jar", - "/apex/com.android.permission/javalib/framework-permission.jar", - "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar", - "/apex/com.android.wifi/javalib/framework-wifi.jar", - "/apex/com.android.tethering/javalib/framework-tethering.jar", "/dev/null", "/dev/socket/zygote", "/dev/socket/zygote_secondary", @@ -100,11 +90,12 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { } } - // Jars from the ART APEX are allowed. - static const char* kArtApexPrefix = "/apex/com.android.art/javalib/"; - if (android::base::StartsWith(path, kArtApexPrefix) - && android::base::EndsWith(path, kJarSuffix)) { - return true; + // Jars from APEXes are allowed. This matches /apex/**/javalib/*.jar. + static const char* kApexPrefix = "/apex/"; + static const char* kApexJavalibPathSuffix = "/javalib"; + if (android::base::StartsWith(path, kApexPrefix) && android::base::EndsWith(path, kJarSuffix) && + android::base::EndsWith(android::base::Dirname(path), kApexJavalibPathSuffix)) { + return true; } // the in-memory file created by ART through memfd_create is allowed. diff --git a/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java b/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java deleted file mode 100644 index 8095c995b8a9..000000000000 --- a/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import android.os.Parcel; -import android.os.PersistableBundle; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.time.Duration; - -/** - * Test of {@link RangingParams}. - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class RangingParamsTest { - - @Test - public void testParams_Build() { - UwbAddress local = UwbAddress.fromBytes(new byte[] {(byte) 0xA0, (byte) 0x57}); - UwbAddress remote = UwbAddress.fromBytes(new byte[] {(byte) 0x4D, (byte) 0x8C}); - int channel = 9; - int rxPreamble = 16; - int txPreamble = 21; - boolean isController = true; - boolean isInitiator = false; - @RangingParams.StsPhyPacketType int stsPhyType = RangingParams.STS_PHY_PACKET_TYPE_SP2; - Duration samplePeriod = Duration.ofSeconds(1, 234); - PersistableBundle specParams = new PersistableBundle(); - specParams.putString("protocol", "some_protocol"); - - RangingParams params = new RangingParams.Builder() - .setChannelNumber(channel) - .setReceivePreambleCodeIndex(rxPreamble) - .setTransmitPreambleCodeIndex(txPreamble) - .setLocalDeviceAddress(local) - .addRemoteDeviceAddress(remote) - .setIsController(isController) - .setIsInitiator(isInitiator) - .setSamplePeriod(samplePeriod) - .setStsPhPacketType(stsPhyType) - .setSpecificationParameters(specParams) - .build(); - - assertEquals(params.getLocalDeviceAddress(), local); - assertEquals(params.getRemoteDeviceAddresses().size(), 1); - assertEquals(params.getRemoteDeviceAddresses().get(0), remote); - assertEquals(params.getChannelNumber(), channel); - assertEquals(params.isController(), isController); - assertEquals(params.isInitiator(), isInitiator); - assertEquals(params.getRxPreambleIndex(), rxPreamble); - assertEquals(params.getTxPreambleIndex(), txPreamble); - assertEquals(params.getStsPhyPacketType(), stsPhyType); - assertEquals(params.getSamplingPeriod(), samplePeriod); - assertTrue(params.getSpecificationParameters().kindofEquals(specParams)); - } - - @Test - public void testParcel() { - Parcel parcel = Parcel.obtain(); - RangingParams params = new RangingParams.Builder() - .setChannelNumber(9) - .setReceivePreambleCodeIndex(16) - .setTransmitPreambleCodeIndex(21) - .setLocalDeviceAddress(UwbTestUtils.getUwbAddress(false)) - .addRemoteDeviceAddress(UwbTestUtils.getUwbAddress(true)) - .setIsController(false) - .setIsInitiator(true) - .setSamplePeriod(Duration.ofSeconds(2)) - .setStsPhPacketType(RangingParams.STS_PHY_PACKET_TYPE_SP1) - .build(); - params.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - RangingParams fromParcel = RangingParams.CREATOR.createFromParcel(parcel); - assertEquals(params, fromParcel); - } -} diff --git a/keystore/java/android/security/CheckedRemoteRequest.java b/keystore/java/android/security/CheckedRemoteRequest.java new file mode 100644 index 000000000000..b6e7c1fa61b9 --- /dev/null +++ b/keystore/java/android/security/CheckedRemoteRequest.java @@ -0,0 +1,33 @@ +/* + * 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.security; + +import android.os.RemoteException; + +/** + * This is a Producer of {@code R} that is expected to throw a {@link RemoteException}. + * + * It is used by Keystore2 service wrappers to handle and convert {@link RemoteException} + * and {@link android.os.ServiceSpecificException} into {@link KeyStoreException}. + * + * @hide + * @param <R> + */ +@FunctionalInterface +interface CheckedRemoteRequest<R> { + R execute() throws RemoteException; +} diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java new file mode 100644 index 000000000000..92d87aa0fed6 --- /dev/null +++ b/keystore/java/android/security/KeyStore2.java @@ -0,0 +1,277 @@ +/* + * 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.security; + +import android.annotation.NonNull; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; +import android.os.Build; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.system.keystore2.IKeystoreService; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +import java.util.Calendar; + +/** + * @hide This should not be made public in its present form because it + * assumes that private and secret key bytes are available and would + * preclude the use of hardware crypto. + */ +public class KeyStore2 { + private static final String TAG = "KeyStore"; + + private static final int RECOVERY_GRACE_PERIOD_MS = 50; + + /** + * Keystore operation creation may fail + * + * Keystore used to work under the assumption that the creation of cryptographic operations + * always succeeds. However, the KeyMint backend has only a limited number of operation slots. + * In order to keep up the appearance of "infinite" operation slots, the Keystore daemon + * would prune least recently used operations if there is no available operation slot. + * As a result, good operations could be terminated prematurely. + * + * This opens AndroidKeystore up to denial-of-service and unintended livelock situations. + * E.g.: if multiple apps wake up at the same time, e.g., due to power management optimizations, + * and attempt to perform crypto operations, they start terminating each others operations + * without making any progress. + * + * To break out of livelocks and to discourage DoS attempts we have changed the pruning + * strategy such that it prefers clients that use few operation slots and only briefly. + * As a result we can, almost, guarantee that single operations that don't linger inactive + * for more than 5 seconds will conclude unhampered by the pruning strategy. "Almost", + * because there are operations related to file system encryption that can prune even + * these operations, but those are extremely rare. + * + * As a side effect of this new pruning strategy operation creation can now fail if the + * client has a lower pruning power than all of the existing operations. + * + * Pruning strategy + * + * To find a suitable candidate we compute the malus for the caller and each existing + * operation. The malus is the inverse of the pruning power (caller) or pruning + * resistance (existing operation). For the caller to be able to prune an operation it must + * find an operation with a malus higher than its own. + * + * For more detail on the pruning strategy consult the implementation at + * https://android.googlesource.com/platform/system/security/+/refs/heads/master/keystore2/src/operation.rs + * + * For older SDK version, KeyStore2 will poll the Keystore daemon for a free operation + * slot. So to applications, targeting earlier SDK versions, it will still look like cipher and + * signature object initialization always succeeds, however, it may take longer to get an + * operation. + * + * All SDK version benefit from fairer operation slot scheduling and a better chance to + * successfully conclude an operation. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + static final long KEYSTORE_OPERATION_CREATION_MAY_FAIL = 169897160L; + + // Never use mBinder directly, use KeyStore2.getService() instead or better yet + // handleRemoteExceptionWithRetry which retries connecting to Keystore once in case + // of a remote exception. + private IKeystoreService mBinder; + + + @FunctionalInterface + interface CheckedRemoteRequest<R> { + R execute(IKeystoreService service) throws RemoteException; + } + + private <R> R handleRemoteExceptionWithRetry(@NonNull CheckedRemoteRequest<R> request) + throws KeyStoreException { + IKeystoreService service = getService(false /* retryLookup */); + boolean firstTry = true; + while (true) { + try { + return request.execute(service); + } catch (ServiceSpecificException e) { + Log.e(TAG, "KeyStore exception", e); + throw new KeyStoreException(e.errorCode, ""); + } catch (RemoteException e) { + if (firstTry) { + Log.w(TAG, "Looks like we may have lost connection to the Keystore " + + "daemon."); + Log.w(TAG, "Retrying after giving Keystore " + + RECOVERY_GRACE_PERIOD_MS + "ms to recover."); + interruptedPreservingSleep(RECOVERY_GRACE_PERIOD_MS); + service = getService(true /* retry Lookup */); + firstTry = false; + } else { + Log.e(TAG, "Cannot connect to Keystore daemon.", e); + throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, ""); + } + } + } + } + + + private KeyStore2() { + mBinder = null; + } + + public static KeyStore2 getInstance() { + return new KeyStore2(); + } + + private synchronized IKeystoreService getService(boolean retryLookup) { + if (mBinder == null || retryLookup) { + mBinder = IKeystoreService.Stub.asInterface(ServiceManager + .getService("android.system.keystore2")); + } + return mBinder; + } + + void delete(KeyDescriptor descriptor) throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.deleteKey(descriptor); + return 0; + }); + } + + /** + * List all entries in the keystore for in the given namespace. + */ + public KeyDescriptor[] list(int domain, long namespace) throws KeyStoreException { + return handleRemoteExceptionWithRetry((service) -> service.listEntries(domain, namespace)); + } + + /** + * Create a grant that allows the grantee identified by {@code granteeUid} to use + * the key specified by {@code descriptor} withint the restrictions given by + * {@code accessVectore}. + * @see IKeystoreService#grant(KeyDescriptor, int, int) for more details. + * @param descriptor + * @param granteeUid + * @param accessVector + * @return + * @throws KeyStoreException + * @hide + */ + public KeyDescriptor grant(KeyDescriptor descriptor, int granteeUid, int accessVector) + throws KeyStoreException { + return handleRemoteExceptionWithRetry( + (service) -> service.grant(descriptor, granteeUid, accessVector) + ); + } + + /** + * Destroys a grant. + * @see IKeystoreService#ungrant(KeyDescriptor, int) for more details. + * @param descriptor + * @param granteeUid + * @throws KeyStoreException + * @hide + */ + public void ungrant(KeyDescriptor descriptor, int granteeUid) + throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.ungrant(descriptor, granteeUid); + return 0; + }); + } + + /** + * Retrieves a key entry from the keystore backend. + * @see IKeystoreService#getKeyEntry(KeyDescriptor) for more details. + * @param descriptor + * @return + * @throws KeyStoreException + * @hide + */ + public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor) + throws KeyStoreException { + return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor)); + } + + /** + * Get the security level specific keystore interface from the keystore daemon. + * @see IKeystoreService#getSecurityLevel(int) for more details. + * @param securityLevel + * @return + * @throws KeyStoreException + * @hide + */ + public KeyStoreSecurityLevel getSecurityLevel(int securityLevel) + throws KeyStoreException { + return handleRemoteExceptionWithRetry((service) -> + new KeyStoreSecurityLevel( + service.getSecurityLevel(securityLevel) + ) + ); + } + + /** + * Update the subcomponents of a key entry designated by the key descriptor. + * @see IKeystoreService#updateSubcomponent(KeyDescriptor, byte[], byte[]) for more details. + * @param key + * @param publicCert + * @param publicCertChain + * @throws KeyStoreException + * @hide + */ + public void updateSubcomponents(@NonNull KeyDescriptor key, byte[] publicCert, + byte[] publicCertChain) throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.updateSubcomponent(key, publicCert, publicCertChain); + return 0; + }); + } + + /** + * Delete the key designed by the key descriptor. + * @see IKeystoreService#deleteKey(KeyDescriptor) for more details. + * @param descriptor + * @throws KeyStoreException + * @hide + */ + public void deleteKey(@NonNull KeyDescriptor descriptor) + throws KeyStoreException { + handleRemoteExceptionWithRetry((service) -> { + service.deleteKey(descriptor); + return 0; + }); + } + + protected static void interruptedPreservingSleep(long millis) { + boolean wasInterrupted = false; + Calendar calendar = Calendar.getInstance(); + long target = calendar.getTimeInMillis() + millis; + while (true) { + try { + Thread.sleep(target - calendar.getTimeInMillis()); + break; + } catch (InterruptedException e) { + wasInterrupted = true; + } catch (IllegalArgumentException e) { + // This means that the argument to sleep was negative. + // So we are done sleeping. + break; + } + } + if (wasInterrupted) { + Thread.currentThread().interrupt(); + } + } + +} diff --git a/keystore/java/android/security/KeyStoreOperation.java b/keystore/java/android/security/KeyStoreOperation.java new file mode 100644 index 000000000000..9af15a5f4a16 --- /dev/null +++ b/keystore/java/android/security/KeyStoreOperation.java @@ -0,0 +1,141 @@ +/* + * 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.security; + +import android.annotation.NonNull; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.security.keymaster.KeymasterDefs; +import android.system.keystore2.IKeystoreOperation; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +/** + * @hide + */ +public class KeyStoreOperation { + static final String TAG = "KeyStoreOperation"; + private final IKeystoreOperation mOperation; + private final Long mChallenge; + private final KeyParameter[] mParameters; + + public KeyStoreOperation( + @NonNull IKeystoreOperation operation, + Long challenge, + KeyParameter[] parameters + ) { + this.mOperation = operation; + this.mChallenge = challenge; + this.mParameters = parameters; + } + + /** + * Gets the challenge associated with this operation. + * @return null if the operation does not required authorization. A 64bit operation + * challenge otherwise. + */ + public Long getChallenge() { + return mChallenge; + } + + /** + * Gets the parameters associated with this operation. + * @return + */ + public KeyParameter[] getParameters() { + return mParameters; + } + + private <R> R handleExceptions(@NonNull CheckedRemoteRequest<R> request) + throws KeyStoreException { + try { + return request.execute(); + } catch (ServiceSpecificException e) { + switch(e.errorCode) { + case ResponseCode.OPERATION_BUSY: { + throw new IllegalThreadStateException( + "Cannot update the same operation concurrently." + ); + } + default: + // TODO Human readable string. Use something like KeyStore.getKeyStoreException + throw new KeyStoreException(e.errorCode, ""); + } + } catch (RemoteException e) { + // Log exception and report invalid operation handle. + // This should prompt the caller drop the reference to this operation and retry. + Log.e( + TAG, + "Remote exception while advancing a KeyStoreOperation.", + e + ); + throw new KeyStoreException(KeymasterDefs.KM_ERROR_INVALID_OPERATION_HANDLE, ""); + } + } + + /** + * Updates the Keystore operation represented by this object with more associated data. + * @see IKeystoreOperation#updateAad(byte[]) for more details. + * @param input + * @throws KeyStoreException + */ + public void updateAad(@NonNull byte[] input) throws KeyStoreException { + handleExceptions(() -> { + mOperation.updateAad(input); + return 0; + }); + } + + /** + * Updates the Keystore operation represented by this object. + * @see IKeystoreOperation#update(byte[]) for more details. + * @param input + * @return + * @throws KeyStoreException + * @hide + */ + public byte[] update(@NonNull byte[] input) throws KeyStoreException { + return handleExceptions(() -> mOperation.update(input)); + } + + /** + * Finalizes the Keystore operation represented by this object. + * @see IKeystoreOperation#finish(byte[], byte[]) for more details. + * @param input + * @param signature + * @return + * @throws KeyStoreException + * @hide + */ + public byte[] finish(byte[] input, byte[] signature) throws KeyStoreException { + return handleExceptions(() -> mOperation.finish(input, signature)); + } + + /** + * Aborts the Keystore operation represented by this object. + * @see IKeystoreOperation#abort() for more details. + * @throws KeyStoreException + * @hide + */ + public void abort() throws KeyStoreException { + handleExceptions(() -> { + mOperation.abort(); + return 0; + }); + } +} diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java new file mode 100644 index 000000000000..9d3b62278ba0 --- /dev/null +++ b/keystore/java/android/security/KeyStoreSecurityLevel.java @@ -0,0 +1,217 @@ +/* + * 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.security; + +import android.annotation.NonNull; +import android.app.compat.CompatChanges; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.security.keystore.BackendBusyException; +import android.security.keystore.KeyStoreConnectException; +import android.system.keystore2.AuthenticatorSpec; +import android.system.keystore2.CreateOperationResponse; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +import java.util.Calendar; +import java.util.Collection; + +/** + * This is a shim around the security level specific interface of Keystore 2.0. Services with + * this interface are instantiated per KeyMint backend, each having there own security level. + * Thus this object representation of a security level. + * @hide + */ +public class KeyStoreSecurityLevel { + private static final String TAG = "KeyStoreSecurityLevel"; + private final IKeystoreSecurityLevel mSecurityLevel; + + public KeyStoreSecurityLevel(IKeystoreSecurityLevel securityLevel) { + this.mSecurityLevel = securityLevel; + } + + private <R> R handleExceptions(CheckedRemoteRequest<R> request) throws KeyStoreException { + try { + return request.execute(); + } catch (ServiceSpecificException e) { + throw new KeyStoreException(e.errorCode, ""); + } catch (RemoteException e) { + // Log exception and report invalid operation handle. + // This should prompt the caller drop the reference to this operation and retry. + Log.e(TAG, "Could not connect to Keystore.", e); + throw new KeyStoreException(ResponseCode.SYSTEM_ERROR, ""); + } + } + + /** + * Creates a new keystore operation. + * @see IKeystoreSecurityLevel#createOperation(KeyDescriptor, KeyParameter[], boolean) for more + * details. + * @param keyDescriptor + * @param args + * @return + * @throws KeyStoreException + * @hide + */ + public KeyStoreOperation createOperation(@NonNull KeyDescriptor keyDescriptor, + Collection<KeyParameter> args) throws KeyStoreException { + while (true) { + try { + CreateOperationResponse createOperationResponse = + mSecurityLevel.createOperation( + keyDescriptor, + args.toArray(new KeyParameter[args.size()]), + false /* forced */ + ); + Long challenge = null; + if (createOperationResponse.operationChallenge != null) { + challenge = createOperationResponse.operationChallenge.challenge; + } + KeyParameter[] parameters = null; + if (createOperationResponse.parameters != null) { + parameters = createOperationResponse.parameters.keyParameter; + } + return new KeyStoreOperation( + createOperationResponse.iOperation, + challenge, + parameters); + } catch (ServiceSpecificException e) { + switch (e.errorCode) { + case ResponseCode.BACKEND_BUSY: { + if (CompatChanges.isChangeEnabled( + KeyStore2.KEYSTORE_OPERATION_CREATION_MAY_FAIL)) { + // Starting with Android S we inform the caller about the + // backend being busy. + throw new BackendBusyException(); + } else { + // Before Android S operation creation must always succeed. So we + // just have to retry. We do so with a randomized back-off between + // 50 and 250ms. + // It is a little awkward that we cannot break out of this loop + // by interrupting this thread. But that is the expected behavior. + // There is some comfort in the fact that interrupting a thread + // also does not unblock a thread waiting for a binder transaction. + interruptedPreservingSleep((long) (Math.random() * 200 + 50)); + } + break; + } + default: + throw new KeyStoreException(e.errorCode, ""); + } + } catch (RemoteException e) { + Log.w(TAG, "Cannot connect to keystore", e); + throw new KeyStoreConnectException(); + } + } + } + + /** + * Generates a new key in Keystore. + * @see IKeystoreSecurityLevel#generateKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int, + * byte[]) for more details. + * @param descriptor + * @param attestationKey + * @param args + * @param flags + * @param entropy + * @return + * @throws KeyStoreException + * @hide + */ + public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey, + Collection<KeyParameter> args, int flags, byte[] entropy) + throws KeyStoreException { + return handleExceptions(() -> mSecurityLevel.generateKey( + descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]), + flags, entropy)); + } + + /** + * Imports a key into Keystore. + * @see IKeystoreSecurityLevel#importKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int, + * byte[]) for more details. + * @param descriptor + * @param attestationKey + * @param args + * @param flags + * @param keyData + * @return + * @throws KeyStoreException + * @hide + */ + public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey, + Collection<KeyParameter> args, int flags, byte[] keyData) + throws KeyStoreException { + return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey, + args.toArray(new KeyParameter[args.size()]), flags, keyData)); + } + + /** + * Imports a wrapped key into Keystore. + * @see IKeystoreSecurityLevel#importWrappedKey(KeyDescriptor, KeyDescriptor, byte[], + * KeyParameter[], AuthenticatorSpec[]) for more details. + * @param wrappedKeyDescriptor + * @param wrappingKeyDescriptor + * @param wrappedKey + * @param maskingKey + * @param args + * @param authenticatorSpecs + * @return + * @throws KeyStoreException + * @hide + */ + public KeyMetadata importWrappedKey(@NonNull KeyDescriptor wrappedKeyDescriptor, + @NonNull KeyDescriptor wrappingKeyDescriptor, + @NonNull byte[] wrappedKey, byte[] maskingKey, + Collection<KeyParameter> args, @NonNull AuthenticatorSpec[] authenticatorSpecs) + throws KeyStoreException { + KeyDescriptor keyDescriptor = new KeyDescriptor(); + keyDescriptor.alias = wrappedKeyDescriptor.alias; + keyDescriptor.nspace = wrappedKeyDescriptor.nspace; + keyDescriptor.blob = wrappedKey; + keyDescriptor.domain = wrappedKeyDescriptor.domain; + + return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor, + wrappingKeyDescriptor, maskingKey, + args.toArray(new KeyParameter[args.size()]), authenticatorSpecs)); + } + + protected static void interruptedPreservingSleep(long millis) { + boolean wasInterrupted = false; + Calendar calendar = Calendar.getInstance(); + long target = calendar.getTimeInMillis() + millis; + while (true) { + try { + Thread.sleep(target - calendar.getTimeInMillis()); + break; + } catch (InterruptedException e) { + wasInterrupted = true; + } catch (IllegalArgumentException e) { + // This means that the argument to sleep was negative. + // So we are done sleeping. + break; + } + } + if (wasInterrupted) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java index 624321cbf5ea..5730234184ab 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreBCWorkaroundProvider.java @@ -34,7 +34,7 @@ import java.security.Provider; * * @hide */ -class AndroidKeyStoreBCWorkaroundProvider extends Provider { +public class AndroidKeyStoreBCWorkaroundProvider extends Provider { // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these // classes when this provider is instantiated and installed early on during each app's @@ -50,8 +50,14 @@ class AndroidKeyStoreBCWorkaroundProvider extends Provider { private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; - AndroidKeyStoreBCWorkaroundProvider() { - super("AndroidKeyStoreBCWorkaround", + /** @hide */ + public AndroidKeyStoreBCWorkaroundProvider() { + this("AndroidKeyStoreBCWorkaround"); + } + + /** @hide **/ + public AndroidKeyStoreBCWorkaroundProvider(String providerName) { + super(providerName, 1.0, "Android KeyStore security provider to work around Bouncy Castle"); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index 71e6559c53c6..3ac9d68d5a9f 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -23,6 +23,7 @@ import android.security.KeyStore; import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterDefs; +import android.sysprop.Keystore2Properties; import java.io.IOException; import java.security.KeyFactory; @@ -70,14 +71,20 @@ public class AndroidKeyStoreProvider extends Provider { private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; - /** @hide **/ + /** @hide */ public AndroidKeyStoreProvider() { - super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); + this(PROVIDER_NAME); + } + + /** @hide **/ + public AndroidKeyStoreProvider(String providerName) { + super(providerName, 1.0, "Android KeyStore security provider"); boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY)); // java.security.KeyStore put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi"); + put("alg.alias.KeyStore.AndroidKeyStoreLegacy", "AndroidKeyStore"); // java.security.KeyPairGenerator put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC"); @@ -111,6 +118,26 @@ public class AndroidKeyStoreProvider extends Provider { putSecretKeyFactoryImpl("HmacSHA512"); } + private static boolean sKeystore2Enabled; + + /** + * This function indicates whether or not Keystore 2.0 is enabled. Some parts of the + * Keystore SPI must behave subtly differently when Keystore 2.0 is enabled. However, + * the platform property that indicates that Keystore 2.0 is enabled is not readable + * by applications. So we set this value when {@code install()} is called because it + * is called by zygote, which can access Keystore2Properties. + * + * This function can be removed once the transition to Keystore 2.0 is complete. + * b/171305684 + * + * @return true if Keystore 2.0 is enabled. + * @hide + */ + public static boolean isKeystore2Enabled() { + return sKeystore2Enabled; + } + + /** * Installs a new instance of this provider (and the * {@link AndroidKeyStoreBCWorkaroundProvider}). @@ -138,6 +165,11 @@ public class AndroidKeyStoreProvider extends Provider { // priority. Security.addProvider(workaroundProvider); } + + // {@code install()} is run by zygote when this property is still accessible. We store its + // value so that the Keystore SPI can act accordingly without having to access an internal + // property. + sKeystore2Enabled = Keystore2Properties.keystore2_enabled().orElse(false); } private void putSecretKeyFactoryImpl(String algorithm) { @@ -412,8 +444,12 @@ public class AndroidKeyStoreProvider extends Provider { @NonNull public static java.security.KeyStore getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException { + String providerName = PROVIDER_NAME; + if (android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) { + providerName = "AndroidKeyStoreLegacy"; + } java.security.KeyStore result = - java.security.KeyStore.getInstance("AndroidKeyStore", PROVIDER_NAME); + java.security.KeyStore.getInstance(providerName); try { result.load(new AndroidKeyStoreLoadStoreParameter(uid)); } catch (NoSuchAlgorithmException | CertificateException | IOException e) { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index 970726051e11..3694d635422f 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -211,7 +211,11 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { userAuthenticationValidWhileOnBody, trustedUserPresenceRequired, invalidatedByBiometricEnrollment, - userConfirmationRequired); + userConfirmationRequired, + // Keystore 1.0 does not tell us the exact security level of the key + // so we report an unknown but secure security level. + insideSecureHardware ? KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE + : KeyProperties.SECURITY_LEVEL_SOFTWARE); } private static BigInteger getGateKeeperSecureUserId() throws ProviderException { diff --git a/keystore/java/android/security/keystore/ArrayUtils.java b/keystore/java/android/security/keystore/ArrayUtils.java index f519c7cdd3d2..c8c1de4a5e83 100644 --- a/keystore/java/android/security/keystore/ArrayUtils.java +++ b/keystore/java/android/security/keystore/ArrayUtils.java @@ -18,6 +18,8 @@ package android.security.keystore; import libcore.util.EmptyArray; +import java.util.function.Consumer; + /** * @hide */ @@ -107,4 +109,16 @@ public abstract class ArrayUtils { return result; } } + + /** + * Runs {@code consumer.accept()} for each element of {@code array}. + * @param array + * @param consumer + * @hide + */ + public static void forEach(int[] array, Consumer<Integer> consumer) { + for (int i : array) { + consumer.accept(i); + } + } } diff --git a/keystore/java/android/security/keystore/BackendBusyException.java b/keystore/java/android/security/keystore/BackendBusyException.java new file mode 100644 index 000000000000..1a88469d7e54 --- /dev/null +++ b/keystore/java/android/security/keystore/BackendBusyException.java @@ -0,0 +1,52 @@ +/* + * 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.security.keystore; + +import android.annotation.NonNull; + +import java.security.ProviderException; + +/** + * Indicates a transient error that prevented a key operation from being created. + * Callers should try again with a back-off period of 10-30 milliseconds. + */ +public class BackendBusyException extends ProviderException { + + /** + * Constructs a new {@code BackendBusyException} without detail message and cause. + */ + public BackendBusyException() { + super("The keystore backend has no operation slots available. Retry later."); + } + + /** + * Constructs a new {@code BackendBusyException} with the provided detail message and + * no cause. + */ + public BackendBusyException(@NonNull String message) { + super(message); + } + + /** + * Constructs a new {@code BackendBusyException} with the provided detail message and + * cause. + */ + public BackendBusyException(@NonNull String message, @NonNull Throwable cause) { + super(message, cause); + } + +} diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 688c4a7b5969..e9aac7ddb56d 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -27,7 +27,6 @@ import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricPrompt; import android.os.Build; import android.security.GateKeeper; -import android.security.KeyStore; import android.text.TextUtils; import java.math.BigInteger; @@ -246,7 +245,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048 private final String mKeystoreAlias; - private final int mUid; + private final int mNamespace; private final int mKeySize; private final AlgorithmParameterSpec mSpec; private final X500Principal mCertificateSubject; @@ -286,7 +285,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu */ public KeyGenParameterSpec( String keyStoreAlias, - int uid, + int namespace, int keySize, AlgorithmParameterSpec spec, X500Principal certificateSubject, @@ -337,7 +336,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } mKeystoreAlias = keyStoreAlias; - mUid = uid; + mNamespace = namespace; mKeySize = keySize; mSpec = spec; mCertificateSubject = certificateSubject; @@ -382,11 +381,43 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * Returns the UID which will own the key. {@code -1} is an alias for the UID of the current * process. * + * @deprecated See deprecation message on {@link KeyGenParameterSpec.Builder#setUid(int)}. + * Known namespaces will be translated to their legacy UIDs. Unknown + * Namespaces will yield {@link IllegalStateException}. + * * @hide */ @UnsupportedAppUsage + @Deprecated public int getUid() { - return mUid; + if (!AndroidKeyStoreProvider.isKeystore2Enabled()) { + // If Keystore2 has not been enabled we have to behave as if mNamespace is actually + // a UID, because we are still being used with the old Keystore SPI. + // TODO This if statement and body can be removed when the Keystore 2 migration is + // complete. b/171563717 + return mNamespace; + } + try { + return KeyProperties.namespaceToLegacyUid(mNamespace); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("getUid called on KeyGenParameterSpec with non legacy" + + " keystore namespace.", e); + } + } + + /** + * Returns the target namespace for the key. + * See {@link KeyGenParameterSpec.Builder#setNamespace(int)}. + * + * @return The numeric namespace as configured in the keystore2_key_contexts files of Android's + * SEPolicy. + * TODO b/171806779 link to public Keystore 2.0 documentation. + * See bug for more details for now. + * @hide + */ + @SystemApi + public int getNamespace() { + return mNamespace; } /** @@ -767,7 +798,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final String mKeystoreAlias; private @KeyProperties.PurposeEnum int mPurposes; - private int mUid = KeyStore.UID_SELF; + private int mNamespace = KeyProperties.NAMESPACE_APPLICATION; private int mKeySize = -1; private AlgorithmParameterSpec mSpec; private X500Principal mCertificateSubject; @@ -830,7 +861,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu */ public Builder(@NonNull KeyGenParameterSpec sourceSpec) { this(sourceSpec.getKeystoreAlias(), sourceSpec.getPurposes()); - mUid = sourceSpec.getUid(); + mNamespace = sourceSpec.getNamespace(); mKeySize = sourceSpec.getKeySize(); mSpec = sourceSpec.getAlgorithmParameterSpec(); mCertificateSubject = sourceSpec.getCertificateSubject(); @@ -873,12 +904,51 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * * @param uid UID or {@code -1} for the UID of the current process. * + * @deprecated Setting the UID of the target namespace is based on a hardcoded + * hack in the Keystore service. This is no longer supported with Keystore 2.0/Android S. + * Instead, dedicated non UID based namespaces can be configured in SEPolicy using + * the keystore2_key_contexts files. The functionality of this method will be supported + * by mapping knows special UIDs, such as WIFI, to the newly configured SELinux based + * namespaces. Unknown UIDs will yield {@link IllegalArgumentException}. + * * @hide */ @SystemApi @NonNull + @Deprecated public Builder setUid(int uid) { - mUid = uid; + if (!AndroidKeyStoreProvider.isKeystore2Enabled()) { + // If Keystore2 has not been enabled we have to behave as if mNamespace is actually + // a UID, because we are still being used with the old Keystore SPI. + // TODO This if statement and body can be removed when the Keystore 2 migration is + // complete. b/171563717 + mNamespace = uid; + return this; + } + mNamespace = KeyProperties.legacyUidToNamespace(uid); + return this; + } + + /** + * Set the designated SELinux namespace that the key shall live in. The caller must + * have sufficient permissions to install a key in the given namespace. Namespaces + * can be created using SEPolicy. The keystore2_key_contexts files map numeric + * namespaces to SELinux labels, and SEPolicy can be used to grant access to these + * namespaces to the desired target context. This is the preferred way to share + * keys between system and vendor components, e.g., WIFI settings and WPA supplicant. + * + * @param namespace Numeric SELinux namespace as configured in keystore2_key_contexts + * of Android's SEPolicy. + * TODO b/171806779 link to public Keystore 2.0 documentation. + * See bug for more details for now. + * @return this Builder object. + * + * @hide + */ + @SystemApi + @NonNull + public Builder setNamespace(int namespace) { + mNamespace = namespace; return this; } @@ -1489,7 +1559,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu public KeyGenParameterSpec build() { return new KeyGenParameterSpec( mKeystoreAlias, - mUid, + mNamespace, mKeySize, mSpec, mCertificateSubject, diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index d891a25dba68..7158d0cf248e 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -84,6 +84,7 @@ public class KeyInfo implements KeySpec { private final boolean mTrustedUserPresenceRequired; private final boolean mInvalidatedByBiometricEnrollment; private final boolean mUserConfirmationRequired; + private final @KeyProperties.SecurityLevelEnum int mSecurityLevel; /** * @hide @@ -107,7 +108,8 @@ public class KeyInfo implements KeySpec { boolean userAuthenticationValidWhileOnBody, boolean trustedUserPresenceRequired, boolean invalidatedByBiometricEnrollment, - boolean userConfirmationRequired) { + boolean userConfirmationRequired, + @KeyProperties.SecurityLevelEnum int securityLevel) { mKeystoreAlias = keystoreKeyAlias; mInsideSecureHardware = insideSecureHardware; mOrigin = origin; @@ -131,6 +133,7 @@ public class KeyInfo implements KeySpec { mTrustedUserPresenceRequired = trustedUserPresenceRequired; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mUserConfirmationRequired = userConfirmationRequired; + mSecurityLevel = securityLevel; } /** @@ -144,7 +147,10 @@ public class KeyInfo implements KeySpec { * Returns {@code true} if the key resides inside secure hardware (e.g., Trusted Execution * Environment (TEE) or Secure Element (SE)). Key material of such keys is available in * plaintext only inside the secure hardware and is not exposed outside of it. + * + * @deprecated This method is superseded by @see getSecurityLevel. */ + @Deprecated public boolean isInsideSecureHardware() { return mInsideSecureHardware; } @@ -355,4 +361,17 @@ public class KeyInfo implements KeySpec { public boolean isTrustedUserPresenceRequired() { return mTrustedUserPresenceRequired; } + + /** + * Returns the security level that the key is protected by. + * {@code KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT} and + * {@code KeyProperties.SecurityLevelEnum.STRONGBOX} indicate that the key material resides + * in secure hardware. Key material of such keys is available in + * plaintext only inside the secure hardware and is not exposed outside of it. + * + * <p>See {@link KeyProperties}.{@code SecurityLevelEnum} constants. + */ + public @KeyProperties.SecurityLevelEnum int getSecurityLevel() { + return mSecurityLevel; + } } diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 9050c695eba7..5928540b19bf 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.security.KeyStore; import android.security.keymaster.KeymasterDefs; import libcore.util.EmptyArray; @@ -857,4 +858,43 @@ public abstract class KeyProperties { } } + /** + * This value indicates the implicit keystore namespace of the calling application. + * It is used by default. Only select system components can choose a different namespace + * which it must be configured in SEPolicy. + * @hide + */ + public static final int NAMESPACE_APPLICATION = -1; + + /** + * For legacy support, translate namespaces into known UIDs. + * @hide + */ + public static int namespaceToLegacyUid(int namespace) { + switch (namespace) { + case NAMESPACE_APPLICATION: + return KeyStore.UID_SELF; + // TODO Translate WIFI and VPN UIDs once the namespaces are defined. + // b/171305388 and b/171305607 + default: + throw new IllegalArgumentException("No UID corresponding to namespace " + + namespace); + } + } + + /** + * For legacy support, translate namespaces into known UIDs. + * @hide + */ + public static int legacyUidToNamespace(int uid) { + switch (uid) { + case KeyStore.UID_SELF: + return NAMESPACE_APPLICATION; + // TODO Translate WIFI and VPN UIDs once the namespaces are defined. + // b/171305388 and b/171305607 + default: + throw new IllegalArgumentException("No namespace corresponding to uid " + + uid); + } + } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java new file mode 100644 index 000000000000..70713a47ad6d --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStore3DESCipherSpi.java @@ -0,0 +1,317 @@ +/* + * 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyParameter; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; +import java.util.List; + +import javax.crypto.CipherSpi; +import javax.crypto.spec.IvParameterSpec; + +/** + * Base class for Android Keystore 3DES {@link CipherSpi} implementations. + * + * @hide + */ +public class AndroidKeyStore3DESCipherSpi extends AndroidKeyStoreCipherSpiBase { + + private static final int BLOCK_SIZE_BYTES = 8; + + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + /** Whether this transformation requires an IV. */ + private final boolean mIvRequired; + + private byte[] mIv; + + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + AndroidKeyStore3DESCipherSpi( + int keymasterBlockMode, + int keymasterPadding, + boolean ivRequired) { + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + mIvRequired = ivRequired; + } + + abstract static class ECB extends AndroidKeyStore3DESCipherSpi { + protected ECB(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); + } + + public static class NoPadding extends ECB { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends ECB { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CBC extends AndroidKeyStore3DESCipherSpi { + protected CBC(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); + } + + public static class NoPadding extends CBC { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends CBC { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + @Override + protected void initKey(int i, Key key) throws InvalidKeyException { + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + if (!KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException( + "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " + + KeyProperties.KEY_ALGORITHM_3DES + " supported"); + } + setKey((AndroidKeyStoreSecretKey) key); + } + + @Override + protected int engineGetBlockSize() { + return BLOCK_SIZE_BYTES; + } + + @Override + protected int engineGetOutputSize(int inputLen) { + return inputLen + 3 * BLOCK_SIZE_BYTES; + } + + @Override + protected final byte[] engineGetIV() { + return ArrayUtils.cloneIfNotEmpty(mIv); + } + + @Override + protected AlgorithmParameters engineGetParameters() { + if (!mIvRequired) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("DESede"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain 3DES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize 3DES AlgorithmParameters with an IV", + e); + } + } + return null; + } + + @Override + protected void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvRequired) { + return; + } + + // IV is used + if (!isEncrypting()) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + @Override + protected void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + @Override + protected void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + if (!"DESede".equalsIgnoreCase(params.getAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm() + + ". Supported: DESede"); + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + if ((mIvRequired) && (mIv == null) && (isEncrypting())) { + // IV will need to be generated + return BLOCK_SIZE_BYTES; + } + + return 0; + } + + @Override + protected int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override + protected void addAlgorithmSpecificParametersToBegin(@NonNull List<KeyParameter> parameters) { + if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeymasterDefs.KM_ALGORITHM_3DES + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + mKeymasterBlockMode + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + mKeymasterPadding + )); + + if (mIvRequired && (mIv != null)) { + parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv)); + } + } + + @Override + protected void loadAlgorithmSpecificParametersFromBeginResult( + KeyParameter[] parameters) { + mIvHasBeenUsed = true; + + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = null; + if (parameters != null) { + for (KeyParameter p : parameters) { + if (p.tag == KeymasterDefs.KM_TAG_NONCE) { + returnedIv = p.blob; + break; + } + } + } + + if (mIvRequired) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new ProviderException( + "IV in use despite IV not being used by this transformation"); + } + } + } + + @Override + protected final void resetAll() { + mIv = null; + mIvHasBeenUsed = false; + super.resetAll(); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java new file mode 100644 index 000000000000..dd094b7a5fd0 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreAuthenticatedAESCipherSpi.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyProperties; +import android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.Stream; +import android.system.keystore2.KeyParameter; + +import libcore.util.EmptyArray; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; +import java.util.List; + +import javax.crypto.CipherSpi; +import javax.crypto.spec.GCMParameterSpec; + +/** + * Base class for Android Keystore authenticated AES {@link CipherSpi} implementations. + * + * @hide + */ +abstract class AndroidKeyStoreAuthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase { + + abstract static class GCM extends AndroidKeyStoreAuthenticatedAESCipherSpi { + static final int MIN_SUPPORTED_TAG_LENGTH_BITS = 96; + private static final int MAX_SUPPORTED_TAG_LENGTH_BITS = 128; + private static final int DEFAULT_TAG_LENGTH_BITS = 128; + private static final int IV_LENGTH_BYTES = 12; + + private int mTagLengthBits = DEFAULT_TAG_LENGTH_BITS; + + GCM(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_GCM, keymasterPadding); + } + + @Override + protected final void resetAll() { + mTagLengthBits = DEFAULT_TAG_LENGTH_BITS; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!isEncrypting()) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "GCMParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof GCMParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only GCMParameterSpec supported"); + } + GCMParameterSpec spec = (GCMParameterSpec) params; + byte[] iv = spec.getIV(); + if (iv == null) { + throw new InvalidAlgorithmParameterException("Null IV in GCMParameterSpec"); + } else if (iv.length != IV_LENGTH_BYTES) { + throw new InvalidAlgorithmParameterException("Unsupported IV length: " + + iv.length + " bytes. Only " + IV_LENGTH_BYTES + + " bytes long IV supported"); + } + int tagLengthBits = spec.getTLen(); + if ((tagLengthBits < MIN_SUPPORTED_TAG_LENGTH_BITS) + || (tagLengthBits > MAX_SUPPORTED_TAG_LENGTH_BITS) + || ((tagLengthBits % 8) != 0)) { + throw new InvalidAlgorithmParameterException( + "Unsupported tag length: " + tagLengthBits + " bits" + + ". Supported lengths: 96, 104, 112, 120, 128"); + } + setIv(iv); + mTagLengthBits = tagLengthBits; + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use GCMParameterSpec or GCM AlgorithmParameters to provide it."); + } + return; + } + + if (!"GCM".equalsIgnoreCase(params.getAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm() + + ". Supported: GCM"); + } + + GCMParameterSpec spec; + try { + spec = params.getParameterSpec(GCMParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV and tag length required when" + + " decrypting, but not found in parameters: " + params, e); + } + setIv(null); + return; + } + initAlgorithmSpecificParameters(spec); + } + + @Nullable + @Override + protected final AlgorithmParameters engineGetParameters() { + byte[] iv = getIv(); + if ((iv != null) && (iv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("GCM"); + params.init(new GCMParameterSpec(mTagLengthBits, iv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain GCM AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize GCM AlgorithmParameters", e); + } + } + return null; + } + + @NonNull + @Override + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + KeyStoreOperation operation) { + KeyStoreCryptoOperationStreamer streamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + operation), 0); + if (isEncrypting()) { + return streamer; + } else { + // When decrypting, to avoid leaking unauthenticated plaintext, do not return any + // plaintext before ciphertext is authenticated by KeyStore.finish. + return new BufferAllOutputUntilDoFinalStreamer(streamer); + } + } + + @NonNull + @Override + protected final KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( + KeyStoreOperation operation) { + return new KeyStoreCryptoOperationChunkedStreamer( + new AdditionalAuthenticationDataStream(operation), 0); + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + if ((getIv() == null) && (isEncrypting())) { + // IV will need to be generated + return IV_LENGTH_BYTES; + } + + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters) { + super.addAlgorithmSpecificParametersToBegin(parameters); + parameters.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MAC_LENGTH, + mTagLengthBits + )); + } + + protected final int getTagLengthBits() { + return mTagLengthBits; + } + + public static final class NoPadding extends GCM { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + + @Override + protected final int engineGetOutputSize(int inputLen) { + int tagLengthBytes = (getTagLengthBits() + 7) / 8; + long result; + if (isEncrypting()) { + result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen + + tagLengthBytes; + } else { + result = getConsumedInputSizeBytes() - getProducedOutputSizeBytes() + inputLen + - tagLengthBytes; + } + if (result < 0) { + return 0; + } else if (result > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } + return (int) result; + } + } + } + + private static final int BLOCK_SIZE_BYTES = 16; + + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + + private byte[] mIv; + + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + AndroidKeyStoreAuthenticatedAESCipherSpi( + int keymasterBlockMode, + int keymasterPadding) { + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + } + + @Override + protected void resetAll() { + mIv = null; + mIvHasBeenUsed = false; + super.resetAll(); + } + + @Override + protected final void initKey(int opmode, Key key) throws InvalidKeyException { + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException( + "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " + + KeyProperties.KEY_ALGORITHM_AES + " supported"); + } + setKey((AndroidKeyStoreSecretKey) key); + } + + @Override + protected void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters) { + if ((isEncrypting()) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeymasterDefs.KM_ALGORITHM_AES + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + mKeymasterBlockMode + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + mKeymasterPadding + )); + + if (mIv != null) { + parameters.add(KeyStore2ParameterUtils.makeBytes(KeymasterDefs.KM_TAG_NONCE, mIv)); + } + } + + @Override + protected final void loadAlgorithmSpecificParametersFromBeginResult( + KeyParameter[] parameters) { + mIvHasBeenUsed = true; + + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = null; + if (parameters != null) { + for (KeyParameter p : parameters) { + if (p.tag == KeymasterDefs.KM_TAG_NONCE) { + returnedIv = p.blob; + break; + } + } + } + + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } + + @Override + protected final int engineGetBlockSize() { + return BLOCK_SIZE_BYTES; + } + + @Override + protected final byte[] engineGetIV() { + return ArrayUtils.cloneIfNotEmpty(mIv); + } + + protected void setIv(byte[] iv) { + mIv = iv; + } + + protected byte[] getIv() { + return mIv; + } + + /** + * {@link KeyStoreCryptoOperationStreamer} which buffers all output until {@code doFinal} from + * which it returns all output in one go, provided {@code doFinal} succeeds. + */ + private static class BufferAllOutputUntilDoFinalStreamer + implements KeyStoreCryptoOperationStreamer { + + private final KeyStoreCryptoOperationStreamer mDelegate; + private ByteArrayOutputStream mBufferedOutput = new ByteArrayOutputStream(); + private long mProducedOutputSizeBytes; + + private BufferAllOutputUntilDoFinalStreamer(KeyStoreCryptoOperationStreamer delegate) { + mDelegate = delegate; + } + + @Override + public byte[] update(byte[] input, int inputOffset, int inputLength) + throws KeyStoreException { + byte[] output = mDelegate.update(input, inputOffset, inputLength); + if (output != null) { + try { + mBufferedOutput.write(output); + } catch (IOException e) { + throw new ProviderException("Failed to buffer output", e); + } + } + return EmptyArray.BYTE; + } + + @Override + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, + byte[] signature) throws KeyStoreException { + byte[] output = mDelegate.doFinal(input, inputOffset, inputLength, signature); + if (output != null) { + try { + mBufferedOutput.write(output); + } catch (IOException e) { + throw new ProviderException("Failed to buffer output", e); + } + } + byte[] result = mBufferedOutput.toByteArray(); + mBufferedOutput.reset(); + mProducedOutputSizeBytes += result.length; + return result; + } + + @Override + public long getConsumedInputSizeBytes() { + return mDelegate.getConsumedInputSizeBytes(); + } + + @Override + public long getProducedOutputSizeBytes() { + return mProducedOutputSizeBytes; + } + } + + /** + * Additional Authentication Data (AAD) stream via a KeyStore streaming operation. This stream + * sends AAD into the KeyStore. + */ + private static class AdditionalAuthenticationDataStream implements Stream { + + private final KeyStoreOperation mOperation; + + private AdditionalAuthenticationDataStream(KeyStoreOperation operation) { + mOperation = operation; + } + + @Override + public byte[] update(byte[] input) throws KeyStoreException { + mOperation.updateAad(input); + return null; + } + + @Override + public byte[] finish(byte[] input, byte[] signature) { + return null; + } + } +}
\ No newline at end of file diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java new file mode 100644 index 000000000000..dd943d422e62 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreBCWorkaroundProvider.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import java.security.Provider; + +/** + * {@link Provider} of JCA crypto operations operating on Android KeyStore keys. + * + * <p>This provider was separated out of {@link AndroidKeyStoreProvider} to work around the issue + * that Bouncy Castle provider incorrectly declares that it accepts arbitrary keys (incl. Android + * KeyStore ones). This causes JCA to select the Bouncy Castle's implementation of JCA crypto + * operations for Android KeyStore keys unless Android KeyStore's own implementations are installed + * as higher-priority than Bouncy Castle ones. The purpose of this provider is to do just that: to + * offer crypto operations operating on Android KeyStore keys and to be installed at higher priority + * than the Bouncy Castle provider. + * + * <p>Once Bouncy Castle provider is fixed, this provider can be merged into the + * {@code AndroidKeyStoreProvider}. + * + * @hide + */ +class AndroidKeyStoreBCWorkaroundProvider extends Provider { + + // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these + // classes when this provider is instantiated and installed early on during each app's + // initialization process. + + private static final String PACKAGE_NAME = "android.security.keystore2"; + private static final String KEYSTORE_SECRET_KEY_CLASS_NAME = + PACKAGE_NAME + ".AndroidKeyStoreSecretKey"; + private static final String KEYSTORE_PRIVATE_KEY_CLASS_NAME = + PACKAGE_NAME + ".AndroidKeyStorePrivateKey"; + private static final String KEYSTORE_PUBLIC_KEY_CLASS_NAME = + PACKAGE_NAME + ".AndroidKeyStorePublicKey"; + + private static final String DESEDE_SYSTEM_PROPERTY = "ro.hardware.keystore_desede"; + + AndroidKeyStoreBCWorkaroundProvider() { + super("AndroidKeyStoreBCWorkaround", + 1.0, + "Android KeyStore security provider to work around Bouncy Castle"); + + // --------------------- javax.crypto.Mac + putMacImpl("HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA1"); + put("Alg.Alias.Mac.1.2.840.113549.2.7", "HmacSHA1"); + put("Alg.Alias.Mac.HMAC-SHA1", "HmacSHA1"); + put("Alg.Alias.Mac.HMAC/SHA1", "HmacSHA1"); + + putMacImpl("HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA224"); + put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA224"); + put("Alg.Alias.Mac.HMAC-SHA224", "HmacSHA224"); + put("Alg.Alias.Mac.HMAC/SHA224", "HmacSHA224"); + + putMacImpl("HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA256"); + put("Alg.Alias.Mac.1.2.840.113549.2.9", "HmacSHA256"); + put("Alg.Alias.Mac.HMAC-SHA256", "HmacSHA256"); + put("Alg.Alias.Mac.HMAC/SHA256", "HmacSHA256"); + + putMacImpl("HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA384"); + put("Alg.Alias.Mac.1.2.840.113549.2.10", "HmacSHA384"); + put("Alg.Alias.Mac.HMAC-SHA384", "HmacSHA384"); + put("Alg.Alias.Mac.HMAC/SHA384", "HmacSHA384"); + + putMacImpl("HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreHmacSpi$HmacSHA512"); + put("Alg.Alias.Mac.1.2.840.113549.2.11", "HmacSHA512"); + put("Alg.Alias.Mac.HMAC-SHA512", "HmacSHA512"); + put("Alg.Alias.Mac.HMAC/SHA512", "HmacSHA512"); + + // --------------------- javax.crypto.Cipher + putSymmetricCipherImpl("AES/ECB/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$NoPadding"); + putSymmetricCipherImpl("AES/ECB/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$ECB$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CBC/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$NoPadding"); + putSymmetricCipherImpl("AES/CBC/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CBC$PKCS7Padding"); + + putSymmetricCipherImpl("AES/CTR/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreUnauthenticatedAESCipherSpi$CTR$NoPadding"); + + if ("true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY))) { + putSymmetricCipherImpl("DESede/CBC/NoPadding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$NoPadding"); + putSymmetricCipherImpl("DESede/CBC/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$CBC$PKCS7Padding"); + + putSymmetricCipherImpl("DESede/ECB/NoPadding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$NoPadding"); + putSymmetricCipherImpl("DESede/ECB/PKCS7Padding", + PACKAGE_NAME + ".AndroidKeyStore3DESCipherSpi$ECB$PKCS7Padding"); + } + + putSymmetricCipherImpl("AES/GCM/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreAuthenticatedAESCipherSpi$GCM$NoPadding"); + + putAsymmetricCipherImpl("RSA/ECB/NoPadding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$NoPadding"); + put("Alg.Alias.Cipher.RSA/None/NoPadding", "RSA/ECB/NoPadding"); + putAsymmetricCipherImpl("RSA/ECB/PKCS1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$PKCS1Padding"); + put("Alg.Alias.Cipher.RSA/None/PKCS1Padding", "RSA/ECB/PKCS1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPPadding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPPadding", "RSA/ECB/OAEPPadding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-1AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA1AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-1AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-224AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA224AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-224AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-256AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA256AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-256AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-384AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA384AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-384AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-384AndMGF1Padding"); + putAsymmetricCipherImpl("RSA/ECB/OAEPWithSHA-512AndMGF1Padding", + PACKAGE_NAME + ".AndroidKeyStoreRSACipherSpi$OAEPWithSHA512AndMGF1Padding"); + put("Alg.Alias.Cipher.RSA/None/OAEPWithSHA-512AndMGF1Padding", + "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"); + + // --------------------- java.security.Signature + putSignatureImpl("NONEwithRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$NONEWithPKCS1Padding"); + + putSignatureImpl("MD5withRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$MD5WithPKCS1Padding"); + put("Alg.Alias.Signature.MD5WithRSAEncryption", "MD5withRSA"); + put("Alg.Alias.Signature.MD5/RSA", "MD5withRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.4", "MD5withRSA"); + put("Alg.Alias.Signature.1.2.840.113549.2.5with1.2.840.113549.1.1.1", "MD5withRSA"); + + putSignatureImpl("SHA1withRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPKCS1Padding"); + put("Alg.Alias.Signature.SHA1WithRSAEncryption", "SHA1withRSA"); + put("Alg.Alias.Signature.SHA1/RSA", "SHA1withRSA"); + put("Alg.Alias.Signature.SHA-1/RSA", "SHA1withRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.5", "SHA1withRSA"); + put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.1", "SHA1withRSA"); + put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.113549.1.1.5", "SHA1withRSA"); + put("Alg.Alias.Signature.1.3.14.3.2.29", "SHA1withRSA"); + + putSignatureImpl("SHA224withRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPKCS1Padding"); + put("Alg.Alias.Signature.SHA224WithRSAEncryption", "SHA224withRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA224withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.1", + "SHA224withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.113549.1.1.11", + "SHA224withRSA"); + + putSignatureImpl("SHA256withRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPKCS1Padding"); + put("Alg.Alias.Signature.SHA256WithRSAEncryption", "SHA256withRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.11", "SHA256withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.1", + "SHA256withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.113549.1.1.11", + "SHA256withRSA"); + + putSignatureImpl("SHA384withRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPKCS1Padding"); + put("Alg.Alias.Signature.SHA384WithRSAEncryption", "SHA384withRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.12", "SHA384withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.113549.1.1.1", + "SHA384withRSA"); + + putSignatureImpl("SHA512withRSA", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPKCS1Padding"); + put("Alg.Alias.Signature.SHA512WithRSAEncryption", "SHA512withRSA"); + put("Alg.Alias.Signature.1.2.840.113549.1.1.13", "SHA512withRSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.113549.1.1.1", + "SHA512withRSA"); + + putSignatureImpl("SHA1withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA1WithPSSPadding"); + putSignatureImpl("SHA224withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA224WithPSSPadding"); + putSignatureImpl("SHA256withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA256WithPSSPadding"); + putSignatureImpl("SHA384withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA384WithPSSPadding"); + putSignatureImpl("SHA512withRSA/PSS", + PACKAGE_NAME + ".AndroidKeyStoreRSASignatureSpi$SHA512WithPSSPadding"); + + putSignatureImpl("NONEwithECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$NONE"); + + putSignatureImpl("SHA1withECDSA", PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA1"); + put("Alg.Alias.Signature.ECDSA", "SHA1withECDSA"); + put("Alg.Alias.Signature.ECDSAwithSHA1", "SHA1withECDSA"); + // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA1(1) + put("Alg.Alias.Signature.1.2.840.10045.4.1", "SHA1withECDSA"); + put("Alg.Alias.Signature.1.3.14.3.2.26with1.2.840.10045.2.1", "SHA1withECDSA"); + + // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3) + putSignatureImpl("SHA224withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA224"); + // ecdsa-with-SHA224(1) + put("Alg.Alias.Signature.1.2.840.10045.4.3.1", "SHA224withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.4with1.2.840.10045.2.1", "SHA224withECDSA"); + + // iso(1) member-body(2) us(840) ansi-x962(10045) signatures(4) ecdsa-with-SHA2(3) + putSignatureImpl("SHA256withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA256"); + // ecdsa-with-SHA256(2) + put("Alg.Alias.Signature.1.2.840.10045.4.3.2", "SHA256withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.1with1.2.840.10045.2.1", "SHA256withECDSA"); + + putSignatureImpl("SHA384withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA384"); + // ecdsa-with-SHA384(3) + put("Alg.Alias.Signature.1.2.840.10045.4.3.3", "SHA384withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.2with1.2.840.10045.2.1", "SHA384withECDSA"); + + putSignatureImpl("SHA512withECDSA", + PACKAGE_NAME + ".AndroidKeyStoreECDSASignatureSpi$SHA512"); + // ecdsa-with-SHA512(4) + put("Alg.Alias.Signature.1.2.840.10045.4.3.4", "SHA512withECDSA"); + put("Alg.Alias.Signature.2.16.840.1.101.3.4.2.3with1.2.840.10045.2.1", "SHA512withECDSA"); + } + + private void putMacImpl(String algorithm, String implClass) { + put("Mac." + algorithm, implClass); + put("Mac." + algorithm + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } + + private void putSymmetricCipherImpl(String transformation, String implClass) { + put("Cipher." + transformation, implClass); + put("Cipher." + transformation + " SupportedKeyClasses", KEYSTORE_SECRET_KEY_CLASS_NAME); + } + + 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); + } + + 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); + } + + public static String[] getSupportedEcdsaSignatureDigests() { + return new String[] {"NONE", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } + + public static String[] getSupportedRsaSignatureWithPkcs1PaddingDigests() { + return new String[] {"NONE", "MD5", "SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512"}; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java new file mode 100644 index 000000000000..b785ee5c6966 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreCipherSpiBase.java @@ -0,0 +1,905 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyStoreCryptoOperation; +import android.system.keystore2.KeyParameter; + +import libcore.util.EmptyArray; + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.SecretKeySpec; + +/** + * Base class for {@link CipherSpi} implementations of Android KeyStore backed ciphers. + * + * @hide + */ +abstract class AndroidKeyStoreCipherSpiBase extends CipherSpi implements KeyStoreCryptoOperation { + private static final String TAG = "AndroidKeyStoreCipherSpiBase"; + + // Fields below are populated by Cipher.init and KeyStore.begin and should be preserved after + // doFinal finishes. + private boolean mEncrypting; + private int mKeymasterPurposeOverride = -1; + private AndroidKeyStoreKey mKey; + private SecureRandom mRng; + + /** + * Object representing this operation inside keystore service. It is initialized + * by {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some + * error conditions in between. + */ + private KeyStoreOperation mOperation; + /** + * The operation challenge is required when an operation needs user authorization. + * The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric + * authenticator, and included in the authentication token minted by this authenticator. + * It may be null, if the operation does not require authorization. + */ + private long mOperationChallenge; + private KeyStoreCryptoOperationStreamer mMainDataStreamer; + private KeyStoreCryptoOperationStreamer mAdditionalAuthenticationDataStreamer; + private boolean mAdditionalAuthenticationDataStreamerClosed; + + /** + * Encountered exception which could not be immediately thrown because it was encountered inside + * a method that does not throw checked exception. This exception will be thrown from + * {@code engineDoFinal}. Once such an exception is encountered, {@code engineUpdate} and + * {@code engineDoFinal} start ignoring input data. + */ + private Exception mCachedException; + + AndroidKeyStoreCipherSpiBase() { + mOperation = null; + mEncrypting = false; + mKeymasterPurposeOverride = -1; + mKey = null; + mRng = null; + mOperationChallenge = 0; + mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; + mCachedException = null; + } + + @Override + protected final void engineInit(int opmode, Key key, SecureRandom random) + throws InvalidKeyException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(); + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidAlgorithmParameterException e) { + throw new InvalidKeyException(e); + } + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected final void engineInit(int opmode, Key key, AlgorithmParameters params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + protected final void engineInit(int opmode, Key key, AlgorithmParameterSpec params, + SecureRandom random) throws InvalidKeyException, InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(opmode, key, random); + initAlgorithmSpecificParameters(params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void init(int opmode, Key key, SecureRandom random) throws InvalidKeyException { + switch (opmode) { + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + mEncrypting = true; + break; + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + mEncrypting = false; + break; + default: + throw new InvalidParameterException("Unsupported opmode: " + opmode); + } + initKey(opmode, key); + if (mKey == null) { + throw new ProviderException("initKey did not initialize the key"); + } + mRng = random; + } + + private void abortOperation() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperation = null; + } + + /** + * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new + * cipher instance. + * + * <p>Subclasses storing additional state should override this method, reset the additional + * state, and then chain to superclass. + */ + @CallSuper + protected void resetAll() { + abortOperation(); + mEncrypting = false; + mKeymasterPurposeOverride = -1; + mKey = null; + mRng = null; + mOperationChallenge = 0; + mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; + mCachedException = null; + } + + /** + * Resets this cipher while preserving the initialized state. This must be equivalent to + * rolling back the cipher's state to just after the most recent {@code engineInit} completed + * successfully. + * + * <p>Subclasses storing additional post-init state should override this method, reset the + * additional state, and then chain to superclass. + */ + @CallSuper + protected void resetWhilePreservingInitState() { + abortOperation(); + mOperationChallenge = 0; + mMainDataStreamer = null; + mAdditionalAuthenticationDataStreamer = null; + mAdditionalAuthenticationDataStreamerClosed = false; + mCachedException = null; + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (mMainDataStreamer != null) { + return; + } + if (mCachedException != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + List<KeyParameter> parameters = new ArrayList<>(); + addAlgorithmSpecificParametersToBegin(parameters); + + int purpose; + if (mKeymasterPurposeOverride != -1) { + purpose = mKeymasterPurposeOverride; + } else { + purpose = mEncrypting + ? KeymasterDefs.KM_PURPOSE_ENCRYPT : KeymasterDefs.KM_PURPOSE_DECRYPT; + } + + parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose)); + + try { + mOperation = mKey.getSecurityLevel().createOperation( + mKey.getKeyIdDescriptor(), + parameters + ); + } catch (KeyStoreException keyStoreException) { + GeneralSecurityException e = KeyStoreCryptoOperationUtils.getExceptionForCipherInit( + mKey, keyStoreException); + if (e != null) { + if (e instanceof InvalidKeyException) { + throw (InvalidKeyException) e; + } else if (e instanceof InvalidAlgorithmParameterException) { + throw (InvalidAlgorithmParameterException) e; + } else { + throw new ProviderException("Unexpected exception type", e); + } + } + } + + // Now we check if we got an operation challenge. This indicates that user authorization + // is required. And if we got a challenge we check if the authorization can possibly + // succeed. + mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge( + mOperation, mKey); + + loadAlgorithmSpecificParametersFromBeginResult(mOperation.getParameters()); + mMainDataStreamer = createMainDataStreamer(mOperation); + mAdditionalAuthenticationDataStreamer = + createAdditionalAuthenticationDataStreamer(mOperation); + mAdditionalAuthenticationDataStreamerClosed = false; + } + + /** + * Creates a streamer which sends plaintext/ciphertext into the provided KeyStore and receives + * the corresponding ciphertext/plaintext from the KeyStore. + * + * <p>This implementation returns a working streamer. + */ + @NonNull + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + KeyStoreOperation operation) { + return new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + operation), 0); + } + + /** + * Creates a streamer which sends Additional Authentication Data (AAD) into the KeyStore. + * + * <p>This implementation returns {@code null}. + * + * @return stream or {@code null} if AAD is not supported by this cipher. + */ + @Nullable + protected KeyStoreCryptoOperationStreamer createAdditionalAuthenticationDataStreamer( + @SuppressWarnings("unused") KeyStoreOperation operation) { + return null; + } + + @Override + protected final byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { + if (mCachedException != null) { + return null; + } + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + mCachedException = e; + return null; + } + + if (inputLen == 0) { + return null; + } + + byte[] output; + try { + flushAAD(); + output = mMainDataStreamer.update(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + mCachedException = e; + return null; + } + + if (output.length == 0) { + return null; + } + + return output; + } + + private void flushAAD() throws KeyStoreException { + if ((mAdditionalAuthenticationDataStreamer != null) + && (!mAdditionalAuthenticationDataStreamerClosed)) { + byte[] output; + try { + output = mAdditionalAuthenticationDataStreamer.doFinal( + EmptyArray.BYTE, 0, 0, + null); // no signature + } finally { + mAdditionalAuthenticationDataStreamerClosed = true; + } + if ((output != null) && (output.length > 0)) { + throw new ProviderException( + "AAD update unexpectedly returned data: " + output.length + " bytes"); + } + } + } + + @Override + protected final int engineUpdate(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException { + byte[] outputCopy = engineUpdate(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected final int engineUpdate(ByteBuffer input, ByteBuffer output) + throws ShortBufferException { + if (input == null) { + throw new NullPointerException("input == null"); + } + if (output == null) { + throw new NullPointerException("output == null"); + } + + int inputSize = input.remaining(); + byte[] outputArray; + if (input.hasArray()) { + outputArray = + engineUpdate( + input.array(), input.arrayOffset() + input.position(), inputSize); + input.position(input.position() + inputSize); + } else { + byte[] inputArray = new byte[inputSize]; + input.get(inputArray); + outputArray = engineUpdate(inputArray, 0, inputSize); + } + + int outputSize = (outputArray != null) ? outputArray.length : 0; + if (outputSize > 0) { + int outputBufferAvailable = output.remaining(); + try { + output.put(outputArray); + } catch (BufferOverflowException e) { + throw new ShortBufferException( + "Output buffer too small. Produced: " + outputSize + ", available: " + + outputBufferAvailable); + } + } + return outputSize; + } + + @Override + protected final void engineUpdateAAD(byte[] input, int inputOffset, int inputLen) { + if (mCachedException != null) { + return; + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + mCachedException = e; + return; + } + + if (mAdditionalAuthenticationDataStreamerClosed) { + throw new IllegalStateException( + "AAD can only be provided before Cipher.update is invoked"); + } + + if (mAdditionalAuthenticationDataStreamer == null) { + throw new IllegalStateException("This cipher does not support AAD"); + } + + byte[] output; + try { + output = mAdditionalAuthenticationDataStreamer.update(input, inputOffset, inputLen); + } catch (KeyStoreException e) { + mCachedException = e; + return; + } + + if ((output != null) && (output.length > 0)) { + throw new ProviderException("AAD update unexpectedly produced output: " + + output.length + " bytes"); + } + } + + @Override + protected final void engineUpdateAAD(ByteBuffer src) { + if (src == null) { + throw new IllegalArgumentException("src == null"); + } + if (!src.hasRemaining()) { + return; + } + + byte[] input; + int inputOffset; + int inputLen; + if (src.hasArray()) { + input = src.array(); + inputOffset = src.arrayOffset() + src.position(); + inputLen = src.remaining(); + src.position(src.limit()); + } else { + input = new byte[src.remaining()]; + inputOffset = 0; + inputLen = input.length; + src.get(input); + } + engineUpdateAAD(input, inputOffset, inputLen); + } + + @Override + protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen) + throws IllegalBlockSizeException, BadPaddingException { + if (mCachedException != null) { + throw (IllegalBlockSizeException) + new IllegalBlockSizeException().initCause(mCachedException); + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + + byte[] output; + try { + flushAAD(); + output = mMainDataStreamer.doFinal( + input, inputOffset, inputLen, + null); // no signature involved + } catch (KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_ARGUMENT: + throw (BadPaddingException) new BadPaddingException().initCause(e); + case KeymasterDefs.KM_ERROR_VERIFICATION_FAILED: + throw (AEADBadTagException) new AEADBadTagException().initCause(e); + default: + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + } + + resetWhilePreservingInitState(); + return output; + } + + @Override + protected final int engineDoFinal(byte[] input, int inputOffset, int inputLen, byte[] output, + int outputOffset) throws ShortBufferException, IllegalBlockSizeException, + BadPaddingException { + byte[] outputCopy = engineDoFinal(input, inputOffset, inputLen); + if (outputCopy == null) { + return 0; + } + int outputAvailable = output.length - outputOffset; + if (outputCopy.length > outputAvailable) { + throw new ShortBufferException("Output buffer too short. Produced: " + + outputCopy.length + ", available: " + outputAvailable); + } + System.arraycopy(outputCopy, 0, output, outputOffset, outputCopy.length); + return outputCopy.length; + } + + @Override + protected final int engineDoFinal(ByteBuffer input, ByteBuffer output) + throws ShortBufferException, IllegalBlockSizeException, BadPaddingException { + if (input == null) { + throw new NullPointerException("input == null"); + } + if (output == null) { + throw new NullPointerException("output == null"); + } + + int inputSize = input.remaining(); + byte[] outputArray; + if (input.hasArray()) { + outputArray = + engineDoFinal( + input.array(), input.arrayOffset() + input.position(), inputSize); + input.position(input.position() + inputSize); + } else { + byte[] inputArray = new byte[inputSize]; + input.get(inputArray); + outputArray = engineDoFinal(inputArray, 0, inputSize); + } + + int outputSize = (outputArray != null) ? outputArray.length : 0; + if (outputSize > 0) { + int outputBufferAvailable = output.remaining(); + try { + output.put(outputArray); + } catch (BufferOverflowException e) { + throw new ShortBufferException( + "Output buffer too small. Produced: " + outputSize + ", available: " + + outputBufferAvailable); + } + } + return outputSize; + } + + @Override + protected final byte[] engineWrap(Key key) + throws IllegalBlockSizeException, InvalidKeyException { + if (mKey == null) { + throw new IllegalStateException("Not initilized"); + } + + if (!isEncrypting()) { + throw new IllegalStateException( + "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); + } + + if (key == null) { + throw new NullPointerException("key == null"); + } + byte[] encoded = null; + if (key instanceof SecretKey) { + if ("RAW".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(key.getAlgorithm()); + SecretKeySpec spec = + (SecretKeySpec) keyFactory.getKeySpec( + (SecretKey) key, SecretKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else if (key instanceof PrivateKey) { + if ("PKCS8".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); + PKCS8EncodedKeySpec spec = + keyFactory.getKeySpec(key, PKCS8EncodedKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else if (key instanceof PublicKey) { + if ("X.509".equalsIgnoreCase(key.getFormat())) { + encoded = key.getEncoded(); + } + if (encoded == null) { + try { + KeyFactory keyFactory = KeyFactory.getInstance(key.getAlgorithm()); + X509EncodedKeySpec spec = + keyFactory.getKeySpec(key, X509EncodedKeySpec.class); + encoded = spec.getEncoded(); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material", + e); + } + } + } else { + throw new InvalidKeyException("Unsupported key type: " + key.getClass().getName()); + } + + if (encoded == null) { + throw new InvalidKeyException( + "Failed to wrap key because it does not export its key material"); + } + + try { + return engineDoFinal(encoded, 0, encoded.length); + } catch (BadPaddingException e) { + throw (IllegalBlockSizeException) new IllegalBlockSizeException().initCause(e); + } + } + + @Override + protected final Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, + int wrappedKeyType) throws InvalidKeyException, NoSuchAlgorithmException { + if (mKey == null) { + throw new IllegalStateException("Not initilized"); + } + + if (isEncrypting()) { + throw new IllegalStateException( + "Cipher must be initialized in Cipher.WRAP_MODE to wrap keys"); + } + + if (wrappedKey == null) { + throw new NullPointerException("wrappedKey == null"); + } + + byte[] encoded; + try { + encoded = engineDoFinal(wrappedKey, 0, wrappedKey.length); + } catch (IllegalBlockSizeException | BadPaddingException e) { + throw new InvalidKeyException("Failed to unwrap key", e); + } + + switch (wrappedKeyType) { + case Cipher.SECRET_KEY: + { + return new SecretKeySpec(encoded, wrappedKeyAlgorithm); + // break; + } + case Cipher.PRIVATE_KEY: + { + KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); + try { + return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to create private key from its PKCS#8 encoded form", e); + } + // break; + } + case Cipher.PUBLIC_KEY: + { + KeyFactory keyFactory = KeyFactory.getInstance(wrappedKeyAlgorithm); + try { + return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + } catch (InvalidKeySpecException e) { + throw new InvalidKeyException( + "Failed to create public key from its X.509 encoded form", e); + } + // break; + } + default: + throw new InvalidParameterException( + "Unsupported wrappedKeyType: " + wrappedKeyType); + } + } + + @Override + protected final void engineSetMode(String mode) throws NoSuchAlgorithmException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify block mode. + throw new UnsupportedOperationException(); + } + + @Override + protected final void engineSetPadding(String arg0) throws NoSuchPaddingException { + // This should never be invoked because all algorithms registered with the AndroidKeyStore + // provide explicitly specify padding mode. + throw new UnsupportedOperationException(); + } + + @Override + protected final int engineGetKeySize(Key key) throws InvalidKeyException { + throw new UnsupportedOperationException(); + } + + @CallSuper + @Override + public void finalize() throws Throwable { + try { + abortOperation(); + } finally { + super.finalize(); + } + } + + @Override + public final long getOperationHandle() { + return mOperationChallenge; + } + + protected final void setKey(@NonNull AndroidKeyStoreKey key) { + mKey = key; + } + + /** + * Overrides the default purpose/type of the crypto operation. + */ + protected final void setKeymasterPurposeOverride(int keymasterPurpose) { + mKeymasterPurposeOverride = keymasterPurpose; + } + + protected final int getKeymasterPurposeOverride() { + return mKeymasterPurposeOverride; + } + + /** + * Returns {@code true} if this cipher is initialized for encryption, {@code false} if this + * cipher is initialized for decryption. + */ + protected final boolean isEncrypting() { + return mEncrypting; + } + + protected final long getConsumedInputSizeBytes() { + if (mMainDataStreamer == null) { + throw new IllegalStateException("Not initialized"); + } + return mMainDataStreamer.getConsumedInputSizeBytes(); + } + + protected final long getProducedOutputSizeBytes() { + if (mMainDataStreamer == null) { + throw new IllegalStateException("Not initialized"); + } + return mMainDataStreamer.getProducedOutputSizeBytes(); + } + + static String opmodeToString(int opmode) { + switch (opmode) { + case Cipher.ENCRYPT_MODE: + return "ENCRYPT_MODE"; + case Cipher.DECRYPT_MODE: + return "DECRYPT_MODE"; + case Cipher.WRAP_MODE: + return "WRAP_MODE"; + case Cipher.UNWRAP_MODE: + return "UNWRAP_MODE"; + default: + return String.valueOf(opmode); + } + } + + // The methods below need to be implemented by subclasses. + + /** + * Initializes this cipher with the provided key. + * + * @throws InvalidKeyException if the {@code key} is not suitable for this cipher in the + * specified {@code opmode}. + * + * @see #setKey(AndroidKeyStoreKey) + */ + protected abstract void initKey(int opmode, @Nullable Key key) throws InvalidKeyException; + + /** + * Returns algorithm-specific parameters used by this cipher or {@code null} if no + * algorithm-specific parameters are used. + */ + @Nullable + @Override + protected abstract AlgorithmParameters engineGetParameters(); + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when no additional + * initialization parameters were provided. + * + * @throws InvalidKeyException if this cipher cannot be configured based purely on the provided + * key and needs additional parameters to be provided to {@code Cipher.init}. + */ + protected abstract void initAlgorithmSpecificParameters() throws InvalidKeyException; + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional + * parameters were provided. + * + * @param params additional algorithm parameters or {@code null} if not specified. + * + * @throws InvalidAlgorithmParameterException if there is insufficient information to configure + * this cipher or if the provided parameters are not suitable for this cipher. + */ + protected abstract void initAlgorithmSpecificParameters( + @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException; + + /** + * Invoked by {@code engineInit} to initialize algorithm-specific parameters when additional + * parameters were provided. + * + * @param params additional algorithm parameters or {@code null} if not specified. + * + * @throws InvalidAlgorithmParameterException if there is insufficient information to configure + * this cipher or if the provided parameters are not suitable for this cipher. + */ + protected abstract void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException; + + /** + * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's + * {@code begin} operation. This amount of entropy is typically what's consumed to generate + * random parameters, such as IV. + * + * <p>For decryption, the return value should be {@code 0} because decryption should not be + * consuming any entropy. For encryption, the value combined with + * {@link #getAdditionalEntropyAmountForFinish()} should match (or exceed) the amount of Shannon + * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all + * explicitly provided parameters to {@code Cipher.init} are known. For example, for AES CBC + * encryption with an explicitly provided IV the return value should be {@code 0}, whereas for + * the case where IV is generated by the KeyStore's {@code begin} operation it should be + * {@code 16}. + */ + protected abstract int getAdditionalEntropyAmountForBegin(); + + /** + * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's + * {@code finish} operation. This amount of entropy is typically what's consumed by encryption + * padding scheme. + * + * <p>For decryption, the return value should be {@code 0} because decryption should not be + * consuming any entropy. For encryption, the value combined with + * {@link #getAdditionalEntropyAmountForBegin()} should match (or exceed) the amount of Shannon + * entropy of the ciphertext produced by this cipher assuming the key, the plaintext, and all + * explicitly provided parameters to {@code Cipher.init} are known. For example, for RSA with + * OAEP the return value should be the size of the OAEP hash output. For RSA with PKCS#1 padding + * the return value should be the size of the padding string or could be raised (for simplicity) + * to the size of the modulus. + */ + protected abstract int getAdditionalEntropyAmountForFinish(); + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + * @param parameters keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected abstract void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters); + + /** + * Invoked to obtain algorithm-specific parameters from the result of the KeyStore's + * {@code begin} operation. + * + * <p>Some parameters, such as IV, are not required to be provided to {@code Cipher.init}. Such + * parameters, if not provided, must be generated by KeyStore and returned to the user of + * {@code Cipher} and potentially reused after {@code doFinal}. + * + * @param parameters keystore/keymaster arguments returned by KeyStore {@code createOperation}. + */ + protected abstract void loadAlgorithmSpecificParametersFromBeginResult( + KeyParameter[] parameters); +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java new file mode 100644 index 000000000000..9f7f2383a416 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECDSASignatureSpi.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyParameter; + +import libcore.util.EmptyArray; + +import java.io.ByteArrayOutputStream; +import java.security.InvalidKeyException; +import java.security.SignatureSpi; +import java.util.List; + +/** + * Base class for {@link SignatureSpi} providing Android KeyStore backed ECDSA signatures. + * + * @hide + */ +abstract class AndroidKeyStoreECDSASignatureSpi extends AndroidKeyStoreSignatureSpiBase { + + public final static class NONE extends AndroidKeyStoreECDSASignatureSpi { + public NONE() { + super(KeymasterDefs.KM_DIGEST_NONE); + } + + @Override + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + KeyStoreOperation operation) { + return new TruncateToFieldSizeMessageStreamer( + super.createMainDataStreamer(operation), + getGroupSizeBits()); + } + + /** + * Streamer which buffers all input, then truncates it to field size, and then sends it into + * KeyStore via the provided delegate streamer. + */ + private static class TruncateToFieldSizeMessageStreamer + implements KeyStoreCryptoOperationStreamer { + + private final KeyStoreCryptoOperationStreamer mDelegate; + private final int mGroupSizeBits; + private final ByteArrayOutputStream mInputBuffer = new ByteArrayOutputStream(); + private long mConsumedInputSizeBytes; + + private TruncateToFieldSizeMessageStreamer( + KeyStoreCryptoOperationStreamer delegate, + int groupSizeBits) { + mDelegate = delegate; + mGroupSizeBits = groupSizeBits; + } + + @Override + public byte[] update(byte[] input, int inputOffset, int inputLength) + throws KeyStoreException { + if (inputLength > 0) { + mInputBuffer.write(input, inputOffset, inputLength); + mConsumedInputSizeBytes += inputLength; + } + return EmptyArray.BYTE; + } + + @Override + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature) + throws KeyStoreException { + if (inputLength > 0) { + mConsumedInputSizeBytes += inputLength; + mInputBuffer.write(input, inputOffset, inputLength); + } + + byte[] bufferedInput = mInputBuffer.toByteArray(); + mInputBuffer.reset(); + // Truncate input at field size (bytes) + return mDelegate.doFinal(bufferedInput, + 0, + Math.min(bufferedInput.length, ((mGroupSizeBits + 7) / 8)), + signature); + } + + @Override + public long getConsumedInputSizeBytes() { + return mConsumedInputSizeBytes; + } + + @Override + public long getProducedOutputSizeBytes() { + return mDelegate.getProducedOutputSizeBytes(); + } + } + } + + public final static class SHA1 extends AndroidKeyStoreECDSASignatureSpi { + public SHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public final static class SHA224 extends AndroidKeyStoreECDSASignatureSpi { + public SHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public final static class SHA256 extends AndroidKeyStoreECDSASignatureSpi { + public SHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public final static class SHA384 extends AndroidKeyStoreECDSASignatureSpi { + public SHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public final static class SHA512 extends AndroidKeyStoreECDSASignatureSpi { + public SHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final int mKeymasterDigest; + + private int mGroupSizeBits = -1; + + AndroidKeyStoreECDSASignatureSpi(int keymasterDigest) { + mKeymasterDigest = keymasterDigest; + } + + @Override + protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { + if (!KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() + + ". Only" + KeyProperties.KEY_ALGORITHM_EC + " supported"); + } + + long keySizeBits = -1; + for (Authorization a : key.getAuthorizations()) { + if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) { + keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a); + } + } + + if (keySizeBits == -1) { + throw new InvalidKeyException("Size of key not known"); + } else if (keySizeBits > Integer.MAX_VALUE) { + throw new InvalidKeyException("Key too large: " + keySizeBits + " bits"); + } + mGroupSizeBits = (int) keySizeBits; + + super.initKey(key); + } + + @Override + protected final void resetAll() { + mGroupSizeBits = -1; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters) { + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_EC + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); + } + + @Override + protected final int getAdditionalEntropyAmountForSign() { + return (mGroupSizeBits + 7) / 8; + } + + protected final int getGroupSizeBits() { + if (mGroupSizeBits == -1) { + throw new IllegalStateException("Not initialized"); + } + return mGroupSizeBits; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java new file mode 100644 index 000000000000..35effde6234b --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPrivateKey.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyDescriptor; + +import java.security.PrivateKey; +import java.security.interfaces.ECKey; +import java.security.spec.ECParameterSpec; + +/** + * EC private key (instance of {@link PrivateKey} and {@link ECKey}) backed by keystore. + * + * @hide + */ +public class AndroidKeyStoreECPrivateKey extends AndroidKeyStorePrivateKey implements ECKey { + private final ECParameterSpec mParams; + + public AndroidKeyStoreECPrivateKey(@NonNull KeyDescriptor descriptor, + long keyId, + Authorization[] authorizations, + @NonNull KeyStoreSecurityLevel securityLevel, + @NonNull ECParameterSpec params) { + super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_EC, securityLevel); + mParams = params; + } + + @Override + public ECParameterSpec getParams() { + return mParams; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java new file mode 100644 index 000000000000..6ddaa704afa8 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreECPublicKey.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; + +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; + +/** + * {@link ECPublicKey} backed by keystore. + * + * @hide + */ +public class AndroidKeyStoreECPublicKey extends AndroidKeyStorePublicKey implements ECPublicKey { + + private final ECParameterSpec mParams; + private final ECPoint mW; + + public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, + @NonNull ECParameterSpec params, @NonNull ECPoint w) { + super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_EC, securityLevel); + mParams = params; + mW = w; + } + + public AndroidKeyStoreECPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull ECPublicKey info) { + this(descriptor, metadata, securityLevel, info.getParams(), info.getW()); + if (!"X.509".equalsIgnoreCase(info.getFormat())) { + throw new IllegalArgumentException( + "Unsupported key export format: " + info.getFormat()); + } + } + + @Override + public AndroidKeyStorePrivateKey getPrivateKey() { + return new AndroidKeyStoreECPrivateKey( + getUserKeyDescriptor(), getKeyIdDescriptor().nspace, getAuthorizations(), + getSecurityLevel(), mParams); + } + + @Override + public ECParameterSpec getParams() { + return mParams; + } + + @Override + public ECPoint getW() { + return mW; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java new file mode 100644 index 000000000000..3dde2e592259 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyStoreCryptoOperation; +import android.security.keystore.KeymasterUtils; +import android.system.keystore2.KeyParameter; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.List; + +import javax.crypto.MacSpi; + +/** + * {@link MacSpi} which provides HMAC implementations backed by Android KeyStore. + * + * @hide + */ +public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreCryptoOperation { + + private static final String TAG = "AndroidKeyStoreHmacSpi"; + + public static class HmacSHA1 extends AndroidKeyStoreHmacSpi { + public HmacSHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class HmacSHA224 extends AndroidKeyStoreHmacSpi { + public HmacSHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class HmacSHA256 extends AndroidKeyStoreHmacSpi { + public HmacSHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class HmacSHA384 extends AndroidKeyStoreHmacSpi { + public HmacSHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class HmacSHA512 extends AndroidKeyStoreHmacSpi { + public HmacSHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final int mKeymasterDigest; + private final int mMacSizeBits; + + // Fields below are populated by engineInit and should be preserved after engineDoFinal. + private AndroidKeyStoreSecretKey mKey; + + // Fields below are reset when engineDoFinal succeeds. + private KeyStoreCryptoOperationChunkedStreamer mChunkedStreamer; + private KeyStoreOperation mOperation; + private long mOperationChallenge; + + protected AndroidKeyStoreHmacSpi(int keymasterDigest) { + mKeymasterDigest = keymasterDigest; + mMacSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + mOperation = null; + mOperationChallenge = 0; + mKey = null; + mChunkedStreamer = null; + } + + @Override + protected int engineGetMacLength() { + return (mMacSizeBits + 7) / 8; + } + + @Override + protected void engineInit(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + init(key, params); + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void init(Key key, AlgorithmParameterSpec params) throws InvalidKeyException, + InvalidAlgorithmParameterException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Only Android KeyStore secret keys supported. Key: " + key); + } + mKey = (AndroidKeyStoreSecretKey) key; + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unsupported algorithm parameters: " + params); + } + + } + + private void abortOperation() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperation = null; + } + + private void resetAll() { + abortOperation(); + mOperationChallenge = 0; + mKey = null; + mChunkedStreamer = null; + } + + private void resetWhilePreservingInitState() { + abortOperation(); + mOperationChallenge = 0; + mChunkedStreamer = null; + } + + @Override + protected void engineReset() { + resetWhilePreservingInitState(); + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException { + if (mChunkedStreamer != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + List<KeyParameter> parameters = new ArrayList<>(); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); + parameters.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MAC_LENGTH, mMacSizeBits + )); + + try { + mOperation = mKey.getSecurityLevel().createOperation( + mKey.getKeyIdDescriptor(), + parameters + ); + } catch (KeyStoreException keyStoreException) { + // If necessary, throw an exception due to KeyStore operation having failed. + InvalidKeyException e = KeyStoreCryptoOperationUtils.getInvalidKeyException( + mKey, keyStoreException); + if (e != null) { + throw e; + } + } + + // Now we check if we got an operation challenge. This indicates that user authorization + // is required. And if we got a challenge we check if the authorization can possibly + // succeed. + mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge( + mOperation, mKey); + + mChunkedStreamer = new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + mOperation)); + } + + @Override + protected void engineUpdate(byte input) { + engineUpdate(new byte[] {input}, 0, 1); + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new ProviderException("Failed to reinitialize MAC", e); + } + + byte[] output; + try { + output = mChunkedStreamer.update(input, offset, len); + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } + if ((output != null) && (output.length != 0)) { + throw new ProviderException("Update operation unexpectedly produced output"); + } + } + + @Override + protected byte[] engineDoFinal() { + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new ProviderException("Failed to reinitialize MAC", e); + } + + byte[] result; + try { + result = mChunkedStreamer.doFinal( + null, 0, 0, + null); // no signature provided -- this invocation will generate one + } catch (KeyStoreException e) { + throw new ProviderException("Keystore operation failed", e); + } + + resetWhilePreservingInitState(); + return result; + } + + @Override + public void finalize() throws Throwable { + try { + abortOperation(); + } finally { + super.finalize(); + } + } + + @Override + public long getOperationHandle() { + return mOperationChallenge; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java new file mode 100644 index 000000000000..32650aeda1b1 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.system.keystore2.Authorization; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; +import android.util.Log; + +import java.security.Key; + +/** + * {@link Key} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreKey implements Key { + // This is the original KeyDescriptor by which the key was loaded from + // with alias and domain. + private final KeyDescriptor mDescriptor; + // The key id can be used make certain manipulations to the keystore database + // assuring that the manipulation is made to the exact key that was loaded + // from the database. Alias based manipulations can not assure this, because + // aliases can be rebound to other keys at any time. + private final long mKeyId; + private final Authorization[] mAuthorizations; + // TODO extract algorithm string from metadata. + private final String mAlgorithm; + + // This is the security level interface, that this key is associated with. + // We do not include this member in comparisons. + private final KeyStoreSecurityLevel mSecurityLevel; + + AndroidKeyStoreKey(@NonNull KeyDescriptor descriptor, + long keyId, + @NonNull Authorization[] authorizations, + @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + mDescriptor = descriptor; + mKeyId = keyId; + mAuthorizations = authorizations; + mAlgorithm = algorithm; + mSecurityLevel = securityLevel; + } + + KeyDescriptor getUserKeyDescriptor() { + return mDescriptor; + } + + KeyDescriptor getKeyIdDescriptor() { + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.nspace = mKeyId; + descriptor.domain = Domain.KEY_ID; + descriptor.alias = null; + descriptor.blob = null; + return descriptor; + } + + Authorization[] getAuthorizations() { + return mAuthorizations; + } + + KeyStoreSecurityLevel getSecurityLevel() { + return mSecurityLevel; + } + + + @Override + public String getAlgorithm() { + return mAlgorithm; + } + + @Override + public String getFormat() { + // This key does not export its key material + return null; + } + + @Override + public byte[] getEncoded() { + // This key does not export its key material + return null; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + ((mDescriptor == null) ? 0 : mDescriptor.hashCode()); + result = prime * result + (int) (mKeyId >>> 32); + result = prime * result + (int) (mKeyId & 0xffffffff); + result = prime * result + ((mAuthorizations == null) ? 0 : mAuthorizations.hashCode()); + result = prime * result + ((mAlgorithm == null) ? 0 : mAlgorithm.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AndroidKeyStoreKey other = (AndroidKeyStoreKey) obj; + if (mKeyId != other.mKeyId) { + return false; + } + + // If the key ids are equal and the class matches all the other fields cannot differ + // unless we have a bug. + if (!mAlgorithm.equals(other.mAlgorithm) + || !mAuthorizations.equals(other.mAuthorizations) + || !mDescriptor.equals(other.mDescriptor)) { + Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata" + + "differs."); + return false; + } + return true; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java new file mode 100644 index 000000000000..a8dd7f3f8b14 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyFactorySpi.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.security.KeyStore; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyInfo; + +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactorySpi; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; +import java.security.spec.X509EncodedKeySpec; + +/** + * {@link KeyFactorySpi} backed by Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreKeyFactorySpi extends KeyFactorySpi { + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + @Override + protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpecClass) + throws InvalidKeySpecException { + if (key == null) { + throw new InvalidKeySpecException("key == null"); + } else if ((!(key instanceof AndroidKeyStorePrivateKey)) + && (!(key instanceof AndroidKeyStorePublicKey))) { + throw new InvalidKeySpecException( + "Unsupported key type: " + key.getClass().getName() + + ". This KeyFactory supports only Android Keystore asymmetric keys"); + } + + // key is an Android Keystore private or public key + + if (keySpecClass == null) { + throw new InvalidKeySpecException("keySpecClass == null"); + } else if (KeyInfo.class.equals(keySpecClass)) { + if (!(key instanceof AndroidKeyStorePrivateKey)) { + throw new InvalidKeySpecException( + "Unsupported key type: " + key.getClass().getName() + + ". KeyInfo can be obtained only for Android Keystore private keys"); + } + AndroidKeyStorePrivateKey keystorePrivateKey = (AndroidKeyStorePrivateKey) key; + @SuppressWarnings("unchecked") + T result = (T) AndroidKeyStoreSecretKeyFactorySpi.getKeyInfo(keystorePrivateKey); + return result; + } else if (X509EncodedKeySpec.class.equals(keySpecClass)) { + if (!(key instanceof AndroidKeyStorePublicKey)) { + throw new InvalidKeySpecException( + "Unsupported key type: " + key.getClass().getName() + + ". X509EncodedKeySpec can be obtained only for Android Keystore public" + + " keys"); + } + @SuppressWarnings("unchecked") + T result = (T) new X509EncodedKeySpec(((AndroidKeyStorePublicKey) key).getEncoded()); + return result; + } else if (PKCS8EncodedKeySpec.class.equals(keySpecClass)) { + if (key instanceof AndroidKeyStorePrivateKey) { + throw new InvalidKeySpecException( + "Key material export of Android Keystore private keys is not supported"); + } else { + throw new InvalidKeySpecException( + "Cannot export key material of public key in PKCS#8 format." + + " Only X.509 format (X509EncodedKeySpec) supported for public keys."); + } + } else if (RSAPublicKeySpec.class.equals(keySpecClass)) { + if (key instanceof AndroidKeyStoreRSAPublicKey) { + AndroidKeyStoreRSAPublicKey rsaKey = (AndroidKeyStoreRSAPublicKey) key; + @SuppressWarnings("unchecked") + T result = + (T) new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent()); + return result; + } else { + throw new InvalidKeySpecException( + "Obtaining RSAPublicKeySpec not supported for " + key.getAlgorithm() + " " + + ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public") + + " key"); + } + } else if (ECPublicKeySpec.class.equals(keySpecClass)) { + if (key instanceof AndroidKeyStoreECPublicKey) { + AndroidKeyStoreECPublicKey ecKey = (AndroidKeyStoreECPublicKey) key; + @SuppressWarnings("unchecked") + T result = (T) new ECPublicKeySpec(ecKey.getW(), ecKey.getParams()); + return result; + } else { + throw new InvalidKeySpecException( + "Obtaining ECPublicKeySpec not supported for " + key.getAlgorithm() + " " + + ((key instanceof AndroidKeyStorePrivateKey) ? "private" : "public") + + " key"); + } + } else { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec spec) throws InvalidKeySpecException { + throw new InvalidKeySpecException( + "To generate a key pair in Android Keystore, use KeyPairGenerator initialized with" + + " " + KeyGenParameterSpec.class.getName()); + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec spec) throws InvalidKeySpecException { + throw new InvalidKeySpecException( + "To generate a key pair in Android Keystore, use KeyPairGenerator initialized with" + + " " + KeyGenParameterSpec.class.getName()); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if ((!(key instanceof AndroidKeyStorePrivateKey)) + && (!(key instanceof AndroidKeyStorePublicKey))) { + throw new InvalidKeyException( + "To import a key into Android Keystore, use KeyStore.setEntry"); + } + return key; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java new file mode 100644 index 000000000000..ccd0a4bf92ff --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.security.KeyStore2; +import android.security.KeyStoreSecurityLevel; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeymasterUtils; +import android.security.keystore.StrongBoxUnavailableException; +import android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.SecurityLevel; +import android.util.Log; + +import libcore.util.EmptyArray; + +import java.security.InvalidAlgorithmParameterException; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.spec.AlgorithmParameterSpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; + +/** + * {@link KeyGeneratorSpi} backed by Android KeyStore. + * + * @hide + */ +public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { + private static final String TAG = "AndroidKeyStoreKeyGeneratorSpi"; + + public static class AES extends AndroidKeyStoreKeyGeneratorSpi { + public AES() { + super(KeymasterDefs.KM_ALGORITHM_AES, 128); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + super.engineInit(params, random); + if ((mKeySizeBits != 128) && (mKeySizeBits != 192) && (mKeySizeBits != 256)) { + throw new InvalidAlgorithmParameterException( + "Unsupported key size: " + mKeySizeBits + + ". Supported: 128, 192, 256."); + } + } + } + + public static class DESede extends AndroidKeyStoreKeyGeneratorSpi { + public DESede() { + super(KeymasterDefs.KM_ALGORITHM_3DES, 168); + } + } + + protected static abstract class HmacBase extends AndroidKeyStoreKeyGeneratorSpi { + protected HmacBase(int keymasterDigest) { + super(KeymasterDefs.KM_ALGORITHM_HMAC, + keymasterDigest, + KeymasterUtils.getDigestOutputSizeBits(keymasterDigest)); + } + } + + public static class HmacSHA1 extends HmacBase { + public HmacSHA1() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class HmacSHA224 extends HmacBase { + public HmacSHA224() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class HmacSHA256 extends HmacBase { + public HmacSHA256() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class HmacSHA384 extends HmacBase { + public HmacSHA384() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class HmacSHA512 extends HmacBase { + public HmacSHA512() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final KeyStore2 mKeyStore = KeyStore2.getInstance(); + private final int mKeymasterAlgorithm; + private final int mKeymasterDigest; + private final int mDefaultKeySizeBits; + + private KeyGenParameterSpec mSpec; + private SecureRandom mRng; + + protected int mKeySizeBits; + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterPaddings; + private int[] mKeymasterDigests; + + protected AndroidKeyStoreKeyGeneratorSpi( + int keymasterAlgorithm, + int defaultKeySizeBits) { + this(keymasterAlgorithm, -1, defaultKeySizeBits); + } + + protected AndroidKeyStoreKeyGeneratorSpi( + int keymasterAlgorithm, + int keymasterDigest, + int defaultKeySizeBits) { + mKeymasterAlgorithm = keymasterAlgorithm; + mKeymasterDigest = keymasterDigest; + mDefaultKeySizeBits = defaultKeySizeBits; + if (mDefaultKeySizeBits <= 0) { + throw new IllegalArgumentException("Default key size must be positive"); + } + + if ((mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) && (mKeymasterDigest == -1)) { + throw new IllegalArgumentException( + "Digest algorithm must be specified for HMAC key"); + } + } + + @Override + protected void engineInit(SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(int keySize, SecureRandom random) { + throw new UnsupportedOperationException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + + @Override + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if ((params == null) || (!(params instanceof KeyGenParameterSpec))) { + throw new InvalidAlgorithmParameterException("Cannot initialize without a " + + KeyGenParameterSpec.class.getName() + " parameter"); + } + KeyGenParameterSpec spec = (KeyGenParameterSpec) params; + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + mRng = random; + mSpec = spec; + + mKeySizeBits = (spec.getKeySize() != -1) ? spec.getKeySize() : mDefaultKeySizeBits; + if (mKeySizeBits <= 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be positive: " + mKeySizeBits); + } else if ((mKeySizeBits % 8) != 0) { + throw new InvalidAlgorithmParameterException( + "Key size must be a multiple of 8: " + mKeySizeBits); + } + + try { + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (spec.getSignaturePaddings().length > 0) { + throw new InvalidAlgorithmParameterException( + "Signature paddings not supported for symmetric key algorithms"); + } + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterBlockMode : mKeymasterBlockModes) { + if (!KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + keymasterBlockMode)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by block mode: " + + KeyProperties.BlockMode.fromKeymaster(keymasterBlockMode) + + ". See " + KeyGenParameterSpec.class.getName() + + " documentation."); + } + } + } + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) { + if (mKeySizeBits != 168) { + throw new InvalidAlgorithmParameterException( + "3DES key size must be 168 bits."); + } + } + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { + if (mKeySizeBits < 64 || mKeySizeBits > 512) { + throw new InvalidAlgorithmParameterException( + "HMAC key sizes must be within 64-512 bits, inclusive."); + } + + // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm + // implies SHA-256 digest). Because keymaster HMAC key is authorized only for + // one digest, we don't let algorithm parameter spec override the digest implied + // by the key. If the spec specifies digests at all, it must specify only one + // digest, the only implied by key algorithm. + mKeymasterDigests = new int[] {mKeymasterDigest}; + if (spec.isDigestsSpecified()) { + // Digest(s) explicitly specified in the spec. Check that the list + // consists of exactly one digest, the one implied by key algorithm. + int[] keymasterDigestsFromSpec = + KeyProperties.Digest.allToKeymaster(spec.getDigests()); + if ((keymasterDigestsFromSpec.length != 1) + || (keymasterDigestsFromSpec[0] != mKeymasterDigest)) { + throw new InvalidAlgorithmParameterException( + "Unsupported digests specification: " + + Arrays.asList(spec.getDigests()) + ". Only " + + KeyProperties.Digest.fromKeymaster(mKeymasterDigest) + + " supported for this HMAC key algorithm"); + } + } + } else { + // Key algorithm does not imply a digest. + if (spec.isDigestsSpecified()) { + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + } else { + mKeymasterDigests = EmptyArray.INT; + } + } + + // Check that user authentication related parameters are acceptable. This method + // will throw an IllegalStateException if there are issues (e.g., secure lock screen + // not set up). + KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), spec); + } catch (IllegalStateException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mSpec = null; + mRng = null; + mKeySizeBits = -1; + mKeymasterPurposes = null; + mKeymasterPaddings = null; + mKeymasterBlockModes = null; + } + + @Override + protected SecretKey engineGenerateKey() { + KeyGenParameterSpec spec = mSpec; + if (spec == null) { + throw new IllegalStateException("Not initialized"); + } + + List<KeyParameter> params = new ArrayList<>(); + + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits + )); + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm + )); + ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, purpose + )); + }); + ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> { + if (blockMode == KeymasterDefs.KM_MODE_GCM + && mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES) { + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, + AndroidKeyStoreAuthenticatedAESCipherSpi.GCM + .MIN_SUPPORTED_TAG_LENGTH_BITS + )); + } + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode + )); + }); + ArrayUtils.forEach(mKeymasterPaddings, (padding) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, padding + )); + }); + ArrayUtils.forEach(mKeymasterDigests, (digest) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, digest + )); + }); + + if (mKeymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC + && mKeymasterDigests.length != 0) { + int digestOutputSizeBits = KeymasterUtils.getDigestOutputSizeBits(mKeymasterDigests[0]); + if (digestOutputSizeBits == -1) { + throw new ProviderException( + "HMAC key authorized for unsupported digest: " + + KeyProperties.Digest.fromKeymaster(mKeymasterDigests[0])); + } + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, digestOutputSizeBits + )); + } + + KeyStore2ParameterUtils.addUserAuthArgs(params, spec); + + if (spec.getKeyValidityStart() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart() + )); + } + if (spec.getKeyValidityForOriginationEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + spec.getKeyValidityForOriginationEnd() + )); + } + if (spec.getKeyValidityForConsumptionEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + spec.getKeyValidityForConsumptionEnd() + )); + } + + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (!spec.isRandomizedEncryptionRequired())) { + // Permit caller-provided IV when encrypting with this key + params.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_CALLER_NONCE + )); + } + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); + + @SecurityLevel int securityLevel = SecurityLevel.TRUSTED_ENVIRONMENT; + if (spec.isStrongBoxBacked()) { + securityLevel = SecurityLevel.STRONGBOX; + } + + int flags = 0; + if (spec.isCriticalToDeviceEncryption()) { + flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; + } + + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.alias = spec.getKeystoreAlias(); + descriptor.nspace = spec.getNamespace(); + descriptor.domain = descriptor.nspace == KeyProperties.NAMESPACE_APPLICATION + ? Domain.APP + : Domain.SELINUX; + descriptor.blob = null; + + KeyMetadata metadata = null; + KeyStoreSecurityLevel iSecurityLevel = null; + try { + iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel); + metadata = iSecurityLevel.generateKey( + descriptor, + null, /* Attestation key not applicable to symmetric keys. */ + params, + flags, + additionalEntropy); + } catch (android.security.KeyStoreException e) { + switch (e.getErrorCode()) { + // TODO replace with ErrorCode.HARDWARE_TYPE_UNAVAILABLE when KeyMint spec + // becomes available. + case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: + throw new StrongBoxUnavailableException("Failed to generate key"); + default: + throw new ProviderException("Keystore key generation failed", e); + } + } + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmJCA; + try { + keyAlgorithmJCA = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + mKeymasterAlgorithm, mKeymasterDigest); + } catch (IllegalArgumentException e) { + try { + mKeyStore.deleteKey(descriptor); + } catch (android.security.KeyStoreException kse) { + Log.e(TAG, "Failed to delete key after generating successfully but" + + " failed to get the algorithm string.", kse); + } + throw new ProviderException("Failed to obtain JCA secret key algorithm name", e); + } + SecretKey result = new AndroidKeyStoreSecretKey(descriptor, metadata, keyAlgorithmJCA, + iSecurityLevel); + return result; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java new file mode 100644 index 000000000000..a747a0e727d8 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -0,0 +1,804 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Build; +import android.security.KeyPairGeneratorSpec; +import android.security.KeyStore2; +import android.security.KeyStoreException; +import android.security.KeyStoreSecurityLevel; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeymasterUtils; +import android.security.keystore.SecureKeyImportUnavailableException; +import android.security.keystore.StrongBoxUnavailableException; +import android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.system.keystore2.SecurityLevel; +import android.util.Log; + +import libcore.util.EmptyArray; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyPairGeneratorSpi; +import java.security.ProviderException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +/** + * Provides a way to create instances of a KeyPair which will be placed in the + * Android keystore service usable only by the application that called it. This + * can be used in conjunction with + * {@link java.security.KeyStore#getInstance(String)} using the + * {@code "AndroidKeyStore"} type. + * <p> + * This class can not be directly instantiated and must instead be used via the + * {@link KeyPairGenerator#getInstance(String) + * KeyPairGenerator.getInstance("AndroidKeyStore")} API. + * + * @hide + */ +public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGeneratorSpi { + private static final String TAG = "AndroidKeyStoreKeyPairGeneratorSpi"; + + public static class RSA extends AndroidKeyStoreKeyPairGeneratorSpi { + public RSA() { + super(KeymasterDefs.KM_ALGORITHM_RSA); + } + } + + public static class EC extends AndroidKeyStoreKeyPairGeneratorSpi { + public EC() { + super(KeymasterDefs.KM_ALGORITHM_EC); + } + } + + /* + * These must be kept in sync with system/security/keystore/defaults.h + */ + + /* EC */ + private static final int EC_DEFAULT_KEY_SIZE = 256; + + /* RSA */ + private static final int RSA_DEFAULT_KEY_SIZE = 2048; + private static final int RSA_MIN_KEY_SIZE = 512; + private static final int RSA_MAX_KEY_SIZE = 8192; + + private static final Map<String, Integer> SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE = + new HashMap<String, Integer>(); + private static final List<String> SUPPORTED_EC_NIST_CURVE_NAMES = new ArrayList<String>(); + private static final List<Integer> SUPPORTED_EC_NIST_CURVE_SIZES = new ArrayList<Integer>(); + static { + // Aliases for NIST P-224 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-224", 224); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp224r1", 224); + + + // Aliases for NIST P-256 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-256", 256); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp256r1", 256); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("prime256v1", 256); + + // Aliases for NIST P-384 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-384", 384); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp384r1", 384); + + // Aliases for NIST P-521 + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("p-521", 521); + SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.put("secp521r1", 521); + + SUPPORTED_EC_NIST_CURVE_NAMES.addAll(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.keySet()); + Collections.sort(SUPPORTED_EC_NIST_CURVE_NAMES); + + SUPPORTED_EC_NIST_CURVE_SIZES.addAll( + new HashSet<Integer>(SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.values())); + Collections.sort(SUPPORTED_EC_NIST_CURVE_SIZES); + } + + private final int mOriginalKeymasterAlgorithm; + + private KeyStore2 mKeyStore; + + private KeyGenParameterSpec mSpec; + + private String mEntryAlias; + private int mEntryUid; + private @KeyProperties.KeyAlgorithmEnum String mJcaKeyAlgorithm; + private int mKeymasterAlgorithm = -1; + private int mKeySizeBits; + private SecureRandom mRng; + + private int[] mKeymasterPurposes; + private int[] mKeymasterBlockModes; + private int[] mKeymasterEncryptionPaddings; + private int[] mKeymasterSignaturePaddings; + private int[] mKeymasterDigests; + + private Long mRSAPublicExponent; + + protected AndroidKeyStoreKeyPairGeneratorSpi(int keymasterAlgorithm) { + mOriginalKeymasterAlgorithm = keymasterAlgorithm; + } + + @SuppressWarnings("deprecation") + @Override + public void initialize(int keysize, SecureRandom random) { + throw new IllegalArgumentException( + KeyGenParameterSpec.class.getName() + " or " + KeyPairGeneratorSpec.class.getName() + + " required to initialize this KeyPairGenerator"); + } + + @SuppressWarnings("deprecation") + @Override + public void initialize(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + resetAll(); + + boolean success = false; + try { + if (params == null) { + throw new InvalidAlgorithmParameterException( + "Must supply params of type " + KeyGenParameterSpec.class.getName() + + " or " + KeyPairGeneratorSpec.class.getName()); + } + + KeyGenParameterSpec spec; + boolean encryptionAtRestRequired = false; + int keymasterAlgorithm = mOriginalKeymasterAlgorithm; + if (params instanceof KeyGenParameterSpec) { + spec = (KeyGenParameterSpec) params; + } else if (params instanceof KeyPairGeneratorSpec) { + // Legacy/deprecated spec + KeyPairGeneratorSpec legacySpec = (KeyPairGeneratorSpec) params; + try { + KeyGenParameterSpec.Builder specBuilder; + String specKeyAlgorithm = legacySpec.getKeyType(); + if (specKeyAlgorithm != null) { + // Spec overrides the generator's default key algorithm + try { + keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + specKeyAlgorithm); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Invalid key type in parameters", e); + } + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + // MD5 was never offered for Android Keystore for ECDSA. + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + specBuilder = new KeyGenParameterSpec.Builder( + legacySpec.getKeystoreAlias(), + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + // Authorized to be used with any encryption and signature padding + // schemes (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, + KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, + KeyProperties.SIGNATURE_PADDING_RSA_PSS); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + break; + default: + throw new ProviderException( + "Unsupported algorithm: " + mKeymasterAlgorithm); + } + + if (legacySpec.getKeySize() != -1) { + specBuilder.setKeySize(legacySpec.getKeySize()); + } + if (legacySpec.getAlgorithmParameterSpec() != null) { + specBuilder.setAlgorithmParameterSpec( + legacySpec.getAlgorithmParameterSpec()); + } + specBuilder.setCertificateSubject(legacySpec.getSubjectDN()); + specBuilder.setCertificateSerialNumber(legacySpec.getSerialNumber()); + specBuilder.setCertificateNotBefore(legacySpec.getStartDate()); + specBuilder.setCertificateNotAfter(legacySpec.getEndDate()); + specBuilder.setUserAuthenticationRequired(false); + + spec = specBuilder.build(); + } catch (NullPointerException | IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException(e); + } + } else { + throw new InvalidAlgorithmParameterException( + "Unsupported params class: " + params.getClass().getName() + + ". Supported: " + KeyGenParameterSpec.class.getName() + + ", " + KeyPairGeneratorSpec.class.getName()); + } + + mEntryAlias = spec.getKeystoreAlias(); + mEntryUid = spec.getUid(); + mSpec = spec; + mKeymasterAlgorithm = keymasterAlgorithm; + mKeySizeBits = spec.getKeySize(); + initAlgorithmSpecificParameters(); + if (mKeySizeBits == -1) { + mKeySizeBits = getDefaultKeySize(keymasterAlgorithm); + } + checkValidKeySize(keymasterAlgorithm, mKeySizeBits, mSpec.isStrongBoxBacked()); + + if (spec.getKeystoreAlias() == null) { + throw new InvalidAlgorithmParameterException("KeyStore entry alias not provided"); + } + + String jcaKeyAlgorithm; + try { + jcaKeyAlgorithm = KeyProperties.KeyAlgorithm.fromKeymasterAsymmetricKeyAlgorithm( + keymasterAlgorithm); + mKeymasterPurposes = KeyProperties.Purpose.allToKeymaster(spec.getPurposes()); + mKeymasterBlockModes = KeyProperties.BlockMode.allToKeymaster(spec.getBlockModes()); + mKeymasterEncryptionPaddings = KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterPadding : mKeymasterEncryptionPaddings) { + if (!KeymasterUtils + .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + keymasterPadding)) { + throw new InvalidAlgorithmParameterException( + "Randomized encryption (IND-CPA) required but may be violated" + + " by padding scheme: " + + KeyProperties.EncryptionPadding.fromKeymaster( + keymasterPadding) + + ". See " + KeyGenParameterSpec.class.getName() + + " documentation."); + } + } + } + mKeymasterSignaturePaddings = KeyProperties.SignaturePadding.allToKeymaster( + spec.getSignaturePaddings()); + if (spec.isDigestsSpecified()) { + mKeymasterDigests = KeyProperties.Digest.allToKeymaster(spec.getDigests()); + } else { + mKeymasterDigests = EmptyArray.INT; + } + + // Check that user authentication related parameters are acceptable. This method + // will throw an IllegalStateException if there are issues (e.g., secure lock screen + // not set up). + KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), mSpec); + } catch (IllegalArgumentException | IllegalStateException e) { + throw new InvalidAlgorithmParameterException(e); + } + + mJcaKeyAlgorithm = jcaKeyAlgorithm; + mRng = random; + mKeyStore = KeyStore2.getInstance(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + private void resetAll() { + mEntryAlias = null; + mEntryUid = KeyProperties.NAMESPACE_APPLICATION; + mJcaKeyAlgorithm = null; + mKeymasterAlgorithm = -1; + mKeymasterPurposes = null; + mKeymasterBlockModes = null; + mKeymasterEncryptionPaddings = null; + mKeymasterSignaturePaddings = null; + mKeymasterDigests = null; + mKeySizeBits = 0; + mSpec = null; + mRSAPublicExponent = null; + mRng = null; + mKeyStore = null; + } + + private void initAlgorithmSpecificParameters() throws InvalidAlgorithmParameterException { + AlgorithmParameterSpec algSpecificSpec = mSpec.getAlgorithmParameterSpec(); + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + { + BigInteger publicExponent = null; + if (algSpecificSpec instanceof RSAKeyGenParameterSpec) { + RSAKeyGenParameterSpec rsaSpec = (RSAKeyGenParameterSpec) algSpecificSpec; + if (mKeySizeBits == -1) { + mKeySizeBits = rsaSpec.getKeysize(); + } else if (mKeySizeBits != rsaSpec.getKeysize()) { + throw new InvalidAlgorithmParameterException("RSA key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + rsaSpec.getKeysize()); + } + publicExponent = rsaSpec.getPublicExponent(); + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "RSA may only use RSAKeyGenParameterSpec"); + } + if (publicExponent == null) { + publicExponent = RSAKeyGenParameterSpec.F4; + } + if (publicExponent.compareTo(BigInteger.ZERO) < 1) { + throw new InvalidAlgorithmParameterException( + "RSA public exponent must be positive: " + publicExponent); + } + if ((publicExponent.signum() == -1) + || (publicExponent.compareTo(KeymasterArguments.UINT64_MAX_VALUE) > 0)) { + throw new InvalidAlgorithmParameterException( + "Unsupported RSA public exponent: " + publicExponent + + ". Maximum supported value: " + KeymasterArguments.UINT64_MAX_VALUE); + } + mRSAPublicExponent = publicExponent.longValue(); + break; + } + case KeymasterDefs.KM_ALGORITHM_EC: + if (algSpecificSpec instanceof ECGenParameterSpec) { + ECGenParameterSpec ecSpec = (ECGenParameterSpec) algSpecificSpec; + String curveName = ecSpec.getName(); + Integer ecSpecKeySizeBits = SUPPORTED_EC_NIST_CURVE_NAME_TO_SIZE.get( + curveName.toLowerCase(Locale.US)); + if (ecSpecKeySizeBits == null) { + throw new InvalidAlgorithmParameterException( + "Unsupported EC curve name: " + curveName + + ". Supported: " + SUPPORTED_EC_NIST_CURVE_NAMES); + } + if (mKeySizeBits == -1) { + mKeySizeBits = ecSpecKeySizeBits; + } else if (mKeySizeBits != ecSpecKeySizeBits) { + throw new InvalidAlgorithmParameterException("EC key size must match " + + " between " + mSpec + " and " + algSpecificSpec + + ": " + mKeySizeBits + " vs " + ecSpecKeySizeBits); + } + } else if (algSpecificSpec != null) { + throw new InvalidAlgorithmParameterException( + "EC may only use ECGenParameterSpec"); + } + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } + } + + @Override + public KeyPair generateKeyPair() { + if (mKeyStore == null || mSpec == null) { + throw new IllegalStateException("Not initialized"); + } + + final @SecurityLevel int securityLevel = + mSpec.isStrongBoxBacked() + ? SecurityLevel.STRONGBOX + : SecurityLevel.TRUSTED_ENVIRONMENT; + + final int flags = + mSpec.isCriticalToDeviceEncryption() + ? IKeystoreSecurityLevel + .KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING + : 0; + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + mRng, (mKeySizeBits + 7) / 8); + + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.alias = mEntryAlias; + descriptor.domain = mEntryUid == KeyProperties.NAMESPACE_APPLICATION + ? Domain.APP + : Domain.SELINUX; + descriptor.nspace = mEntryUid; + descriptor.blob = null; + + boolean success = false; + try { + KeyStoreSecurityLevel iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel); + + KeyMetadata metadata = iSecurityLevel.generateKey(descriptor, null, + constructKeyGenerationArguments(), flags, additionalEntropy); + + AndroidKeyStorePublicKey publicKey = + AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse( + descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm); + + success = true; + return new KeyPair(publicKey, publicKey.getPrivateKey()); + } catch (android.security.KeyStoreException e) { + switch(e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE: + throw new StrongBoxUnavailableException("Failed to generated key pair.", e); + default: + ProviderException p = new ProviderException("Failed to generate key pair.", e); + if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) { + throw new SecureKeyImportUnavailableException(p); + } + throw p; + } + } catch (UnrecoverableKeyException e) { + throw new ProviderException( + "Failed to construct key object from newly generated key pair.", e); + } finally { + if (!success) { + try { + mKeyStore.deleteKey(descriptor); + } catch (KeyStoreException e) { + if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { + Log.e(TAG, "Failed to delete newly generated key after " + + "generation failed unexpectedly.", e); + } + } + } + } + } + + private void addAttestationParameters(@NonNull List<KeyParameter> params) + throws ProviderException { + byte[] challenge = mSpec.getAttestationChallenge(); + + if (challenge != null) { + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge + )); + + if (mSpec.isDevicePropertiesAttestationIncluded()) { + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_BRAND, + Build.BRAND.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_DEVICE, + Build.DEVICE.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_PRODUCT, + Build.PRODUCT.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_MANUFACTURER, + Build.MANUFACTURER.getBytes(StandardCharsets.UTF_8) + )); + params.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_ATTESTATION_ID_MODEL, + Build.MODEL.getBytes(StandardCharsets.UTF_8) + )); + } + } else { + if (mSpec.isDevicePropertiesAttestationIncluded()) { + throw new ProviderException("An attestation challenge must be provided when " + + "requesting device properties attestation."); + } + } + } + + private Collection<KeyParameter> constructKeyGenerationArguments() { + List<KeyParameter> params = new ArrayList<>(); + params.add(KeyStore2ParameterUtils.makeInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits)); + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm + )); + ArrayUtils.forEach(mKeymasterPurposes, (purpose) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, purpose + )); + }); + ArrayUtils.forEach(mKeymasterBlockModes, (blockMode) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, blockMode + )); + }); + ArrayUtils.forEach(mKeymasterEncryptionPaddings, (padding) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, padding + )); + }); + ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, padding + )); + }); + ArrayUtils.forEach(mKeymasterDigests, (digest) -> { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, digest + )); + }); + + KeyStore2ParameterUtils.addUserAuthArgs(params, mSpec); + + if (mSpec.getKeyValidityStart() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart() + )); + } + if (mSpec.getKeyValidityForOriginationEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + mSpec.getKeyValidityForOriginationEnd() + )); + } + if (mSpec.getKeyValidityForConsumptionEnd() != null) { + params.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + mSpec.getKeyValidityForConsumptionEnd() + )); + } + + addAlgorithmSpecificParameters(params); + + if (mSpec.isUniqueIdIncluded()) { + params.add(KeyStore2ParameterUtils.makeBool(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID)); + } + + addAttestationParameters(params); + + return params; + } + + private void addAlgorithmSpecificParameters(List<KeyParameter> params) { + switch (mKeymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_RSA: + params.add(KeyStore2ParameterUtils.makeLong( + KeymasterDefs.KM_TAG_RSA_PUBLIC_EXPONENT, mRSAPublicExponent + )); + break; + case KeymasterDefs.KM_ALGORITHM_EC: + break; + default: + throw new ProviderException("Unsupported algorithm: " + mKeymasterAlgorithm); + } + } + + private static int getDefaultKeySize(int keymasterAlgorithm) { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + return EC_DEFAULT_KEY_SIZE; + case KeymasterDefs.KM_ALGORITHM_RSA: + return RSA_DEFAULT_KEY_SIZE; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); + } + } + + private static void checkValidKeySize( + int keymasterAlgorithm, + int keySize, + boolean isStrongBoxBacked) + throws InvalidAlgorithmParameterException { + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + if (isStrongBoxBacked && keySize != 256) { + throw new InvalidAlgorithmParameterException( + "Unsupported StrongBox EC key size: " + + keySize + " bits. Supported: 256"); + } + if (!SUPPORTED_EC_NIST_CURVE_SIZES.contains(keySize)) { + throw new InvalidAlgorithmParameterException("Unsupported EC key size: " + + keySize + " bits. Supported: " + SUPPORTED_EC_NIST_CURVE_SIZES); + } + break; + case KeymasterDefs.KM_ALGORITHM_RSA: + if (keySize < RSA_MIN_KEY_SIZE || keySize > RSA_MAX_KEY_SIZE) { + throw new InvalidAlgorithmParameterException("RSA key size must be >= " + + RSA_MIN_KEY_SIZE + " and <= " + RSA_MAX_KEY_SIZE); + } + break; + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); + } + } + + /** + * Returns the {@code Signature} algorithm to be used for signing a certificate using the + * specified key or {@code null} if the key cannot be used for signing a certificate. + */ + @Nullable + private static String getCertificateSignatureAlgorithm( + int keymasterAlgorithm, + int keySizeBits, + KeyGenParameterSpec spec) { + // Constraints: + // 1. Key must be authorized for signing without user authentication. + // 2. Signature digest must be one of key's authorized digests. + // 3. For RSA keys, the digest output size must not exceed modulus size minus space overhead + // of RSA PKCS#1 signature padding scheme (about 30 bytes). + // 4. For EC keys, the there is no point in using a digest whose output size is longer than + // key/field size because the digest will be truncated to that size. + + if ((spec.getPurposes() & KeyProperties.PURPOSE_SIGN) == 0) { + // Key not authorized for signing + return null; + } + if (spec.isUserAuthenticationRequired()) { + // Key not authorized for use without user authentication + return null; + } + if (!spec.isDigestsSpecified()) { + // Key not authorized for any digests -- can't sign + return null; + } + switch (keymasterAlgorithm) { + case KeymasterDefs.KM_ALGORITHM_EC: + { + Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits == keySizeBits) { + // Perfect match -- use this digest + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + break; + } + // Not a perfect match -- check against the best digest so far + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // Prefer output size to be as close to key size as possible, with output + // sizes larger than key size preferred to those smaller than key size. + if (bestDigestOutputSizeBits < keySizeBits) { + // Output size of the best digest so far is smaller than key size. + // Anything larger is a win. + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } else { + // Output size of the best digest so far is larger than key size. + // Anything smaller is a win, as long as it's not smaller than key size. + if ((outputSizeBits < bestDigestOutputSizeBits) + && (outputSizeBits >= keySizeBits)) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } + } + } + if (bestKeymasterDigest == -1) { + return null; + } + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithECDSA"; + } + case KeymasterDefs.KM_ALGORITHM_RSA: + { + // Check whether this key is authorized for PKCS#1 signature padding. + // We use Bouncy Castle to generate self-signed RSA certificates. Bouncy Castle + // only supports RSA certificates signed using PKCS#1 padding scheme. The key needs + // to be authorized for PKCS#1 padding or padding NONE which means any padding. + boolean pkcs1SignaturePaddingSupported = + com.android.internal.util.ArrayUtils.contains( + KeyProperties.SignaturePadding.allToKeymaster( + spec.getSignaturePaddings()), + KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN); + if (!pkcs1SignaturePaddingSupported) { + // Key not authorized for PKCS#1 signature padding -- can't sign + return null; + } + + Set<Integer> availableKeymasterDigests = getAvailableKeymasterSignatureDigests( + spec.getDigests(), + AndroidKeyStoreBCWorkaroundProvider.getSupportedEcdsaSignatureDigests()); + + // The amount of space available for the digest is less than modulus size by about + // 30 bytes because padding must be at least 11 bytes long (00 || 01 || PS || 00, + // where PS must be at least 8 bytes long), and then there's also the 15--19 bytes + // overhead (depending the on chosen digest) for encoding digest OID and digest + // value in DER. + int maxDigestOutputSizeBits = keySizeBits - 30 * 8; + int bestKeymasterDigest = -1; + int bestDigestOutputSizeBits = -1; + for (int keymasterDigest : availableKeymasterDigests) { + int outputSizeBits = KeymasterUtils.getDigestOutputSizeBits(keymasterDigest); + if (outputSizeBits > maxDigestOutputSizeBits) { + // Digest too long (signature generation will fail) -- skip + continue; + } + if (bestKeymasterDigest == -1) { + // First digest tested -- definitely the best so far + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } else { + // The longer the better + if (outputSizeBits > bestDigestOutputSizeBits) { + bestKeymasterDigest = keymasterDigest; + bestDigestOutputSizeBits = outputSizeBits; + } + } + } + if (bestKeymasterDigest == -1) { + return null; + } + return KeyProperties.Digest.fromKeymasterToSignatureAlgorithmDigest( + bestKeymasterDigest) + "WithRSA"; + } + default: + throw new ProviderException("Unsupported algorithm: " + keymasterAlgorithm); + } + } + + private static Set<Integer> getAvailableKeymasterSignatureDigests( + @KeyProperties.DigestEnum String[] authorizedKeyDigests, + @KeyProperties.DigestEnum String[] supportedSignatureDigests) { + Set<Integer> authorizedKeymasterKeyDigests = new HashSet<Integer>(); + for (int keymasterDigest : KeyProperties.Digest.allToKeymaster(authorizedKeyDigests)) { + authorizedKeymasterKeyDigests.add(keymasterDigest); + } + Set<Integer> supportedKeymasterSignatureDigests = new HashSet<Integer>(); + for (int keymasterDigest + : KeyProperties.Digest.allToKeymaster(supportedSignatureDigests)) { + supportedKeymasterSignatureDigests.add(keymasterDigest); + } + Set<Integer> result = new HashSet<Integer>(supportedKeymasterSignatureDigests); + result.retainAll(authorizedKeymasterKeyDigests); + return result; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java new file mode 100644 index 000000000000..afb10547411b --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreLoadStoreParameter.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import java.security.KeyStore; +import java.security.KeyStore.ProtectionParameter; + +/** + * @hide + */ +class AndroidKeyStoreLoadStoreParameter implements KeyStore.LoadStoreParameter { + + private final int mNamespace; + + AndroidKeyStoreLoadStoreParameter(int namespace) { + mNamespace = namespace; + } + + @Override + public ProtectionParameter getProtectionParameter() { + return null; + } + + int getNamespace() { + return mNamespace; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java new file mode 100644 index 000000000000..8b331ee3b880 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePrivateKey.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyDescriptor; + +import java.security.PrivateKey; + +/** + * {@link PrivateKey} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStorePrivateKey extends AndroidKeyStoreKey implements PrivateKey { + + public AndroidKeyStorePrivateKey(@NonNull KeyDescriptor descriptor, + long keyId, @NonNull Authorization[] authorizations, @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + super(descriptor, keyId, authorizations, algorithm, securityLevel); + } + +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java new file mode 100644 index 000000000000..b2e32a3175e3 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java @@ -0,0 +1,415 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStore; +import android.security.KeyStore2; +import android.security.KeyStoreSecurityLevel; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyStoreCryptoOperation; +import android.system.keystore2.Authorization; +import android.system.keystore2.Domain; +import android.system.keystore2.KeyDescriptor; +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; +import java.security.Security; +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; + +/** + * A provider focused on providing JCA interfaces for the Android KeyStore. + * + * @hide + */ +public class AndroidKeyStoreProvider extends Provider { + private static final String PROVIDER_NAME = "AndroidKeyStore"; + + // IMPLEMENTATION NOTE: Class names are hard-coded in this provider to avoid loading these + // classes when this provider is instantiated and installed early on during each app's + // initialization process. + // + // Crypto operations operating on the AndroidKeyStore keys must not be offered by this provider. + // Instead, they need to be offered by AndroidKeyStoreBCWorkaroundProvider. See its Javadoc + // for details. + + private static final String PACKAGE_NAME = "android.security.keystore2"; + + private static final String DESEDE_SYSTEM_PROPERTY = + "ro.hardware.keystore_desede"; + + /** @hide **/ + public AndroidKeyStoreProvider() { + super(PROVIDER_NAME, 1.0, "Android KeyStore security provider"); + + boolean supports3DES = "true".equals(android.os.SystemProperties.get(DESEDE_SYSTEM_PROPERTY)); + + // java.security.KeyStore + put("KeyStore.AndroidKeyStore", PACKAGE_NAME + ".AndroidKeyStoreSpi"); + + // java.security.KeyPairGenerator + put("KeyPairGenerator.EC", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$EC"); + put("KeyPairGenerator.RSA", PACKAGE_NAME + ".AndroidKeyStoreKeyPairGeneratorSpi$RSA"); + + // java.security.KeyFactory + putKeyFactoryImpl("EC"); + putKeyFactoryImpl("RSA"); + + // javax.crypto.KeyGenerator + put("KeyGenerator.AES", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$AES"); + put("KeyGenerator.HmacSHA1", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA1"); + put("KeyGenerator.HmacSHA224", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA224"); + put("KeyGenerator.HmacSHA256", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA256"); + put("KeyGenerator.HmacSHA384", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA384"); + put("KeyGenerator.HmacSHA512", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$HmacSHA512"); + + if (supports3DES) { + put("KeyGenerator.DESede", PACKAGE_NAME + ".AndroidKeyStoreKeyGeneratorSpi$DESede"); + } + + // java.security.SecretKeyFactory + putSecretKeyFactoryImpl("AES"); + if (supports3DES) { + putSecretKeyFactoryImpl("DESede"); + } + putSecretKeyFactoryImpl("HmacSHA1"); + putSecretKeyFactoryImpl("HmacSHA224"); + putSecretKeyFactoryImpl("HmacSHA256"); + putSecretKeyFactoryImpl("HmacSHA384"); + putSecretKeyFactoryImpl("HmacSHA512"); + } + + private static boolean sInstalled = false; + + /** + * This function indicates whether or not this provider was installed. This is manly used + * as indicator for + * {@link android.security.keystore.AndroidKeyStoreProvider#getKeyStoreForUid(int)} + * to whether or not to retrieve the Keystore provider by "AndroidKeyStoreLegacy". + * This function can be removed once the transition to Keystore 2.0 is complete. + * b/171305684 + * + * @return true if this provider was installed. + * @hide + */ + public static boolean isInstalled() { + return sInstalled; + } + + /** + * Installs a new instance of this provider (and the + * {@link AndroidKeyStoreBCWorkaroundProvider}). + * @hide + */ + public static void install() { + Provider[] providers = Security.getProviders(); + int bcProviderIndex = -1; + for (int i = 0; i < providers.length; i++) { + Provider provider = providers[i]; + if ("BC".equals(provider.getName())) { + bcProviderIndex = i; + break; + } + } + sInstalled = true; + + Security.addProvider(new AndroidKeyStoreProvider()); + Security.addProvider( + new android.security.keystore.AndroidKeyStoreProvider( + "AndroidKeyStoreLegacy")); + Provider workaroundProvider = new AndroidKeyStoreBCWorkaroundProvider(); + Provider legacyWorkaroundProvider = + new android.security.keystore.AndroidKeyStoreBCWorkaroundProvider( + "AndroidKeyStoreBCWorkaroundLegacy"); + if (bcProviderIndex != -1) { + // Bouncy Castle provider found -- install the workaround provider above it. + // insertProviderAt uses 1-based positions. + Security.insertProviderAt(legacyWorkaroundProvider, bcProviderIndex + 1); + Security.insertProviderAt(workaroundProvider, bcProviderIndex + 1); + } else { + // Bouncy Castle provider not found -- install the workaround provider at lowest + // priority. + Security.addProvider(workaroundProvider); + Security.addProvider(legacyWorkaroundProvider); + } + } + + private void putSecretKeyFactoryImpl(String algorithm) { + put("SecretKeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreSecretKeyFactorySpi"); + } + + private void putKeyFactoryImpl(String algorithm) { + put("KeyFactory." + algorithm, PACKAGE_NAME + ".AndroidKeyStoreKeyFactorySpi"); + } + + /** + * Gets the {@link KeyStore} operation handle corresponding to the provided JCA crypto + * primitive. + * + * <p>The following primitives are supported: {@link Cipher} and {@link Mac}. + * + * @return KeyStore operation handle or {@code 0} if the provided primitive's KeyStore operation + * is not in progress. + * + * @throws IllegalArgumentException if the provided primitive is not supported or is not backed + * by AndroidKeyStore provider. + * @throws IllegalStateException if the provided primitive is not initialized. + * @hide + */ + public static long getKeyStoreOperationHandle(Object cryptoPrimitive) { + if (cryptoPrimitive == null) { + throw new NullPointerException(); + } + Object spi; + if (cryptoPrimitive instanceof Signature) { + spi = ((Signature) cryptoPrimitive).getCurrentSpi(); + } else if (cryptoPrimitive instanceof Mac) { + spi = ((Mac) cryptoPrimitive).getCurrentSpi(); + } else if (cryptoPrimitive instanceof Cipher) { + spi = ((Cipher) cryptoPrimitive).getCurrentSpi(); + } else { + throw new IllegalArgumentException("Unsupported crypto primitive: " + cryptoPrimitive + + ". Supported: Signature, Mac, Cipher"); + } + if (spi == null) { + throw new IllegalStateException("Crypto primitive not initialized"); + } else if (!(spi instanceof KeyStoreCryptoOperation)) { + throw new IllegalArgumentException( + "Crypto primitive not backed by AndroidKeyStore provider: " + cryptoPrimitive + + ", spi: " + spi); + } + return ((KeyStoreCryptoOperation) spi).getOperationHandle(); + } + + /** + * This helper function gets called if the key loaded from the keystore daemon + * is for an asymmetric algorithm. It constructs an instance of {@link AndroidKeyStorePublicKey} + * which implements {@link PublicKey}. + * + * @param descriptor The original key descriptor that was used to load the key. + * + * @param metadata The key metadata which includes the public key material, a reference to the + * stored private key material, the key characteristics. + * @param iSecurityLevel A binder interface that allows using the private key. + * @param algorithm Must indicate EC or RSA. + * @return AndroidKeyStorePublicKey + * @throws UnrecoverableKeyException + * @hide + */ + @NonNull + static AndroidKeyStorePublicKey makeAndroidKeyStorePublicKeyFromKeyEntryResponse( + @NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel iSecurityLevel, int algorithm) + throws UnrecoverableKeyException { + if (metadata.certificate == null) { + throw new UnrecoverableKeyException("Failed to obtain X.509 form of public key." + + " Keystore has no public certificate stored."); + } + final byte[] x509EncodedPublicKey = 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; + 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); + } + + KeyStoreSecurityLevel securityLevel = iSecurityLevel; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(jcaKeyAlgorithm)) { + + return new AndroidKeyStoreECPublicKey(descriptor, metadata, + iSecurityLevel, (ECPublicKey) publicKey); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(jcaKeyAlgorithm)) { + return new AndroidKeyStoreRSAPublicKey(descriptor, metadata, + iSecurityLevel, (RSAPublicKey) publicKey); + } else { + throw new ProviderException("Unsupported Android Keystore public key algorithm: " + + jcaKeyAlgorithm); + } + } + + /** @hide **/ + @NonNull + public static AndroidKeyStorePublicKey loadAndroidKeyStorePublicKeyFromKeystore( + @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + AndroidKeyStoreKey key = + loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace); + if (key instanceof AndroidKeyStorePublicKey) { + return (AndroidKeyStorePublicKey) key; + } else { + throw new UnrecoverableKeyException("No asymmetric key found by the given alias."); + } + } + + /** @hide **/ + @NonNull + public static KeyPair loadAndroidKeyStoreKeyPairFromKeystore( + @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + AndroidKeyStoreKey key = + loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace); + if (key instanceof AndroidKeyStorePublicKey) { + AndroidKeyStorePublicKey publicKey = (AndroidKeyStorePublicKey) key; + return new KeyPair(publicKey, publicKey.getPrivateKey()); + } else { + throw new UnrecoverableKeyException("No asymmetric key found by the given alias."); + } + } + + /** @hide **/ + @NonNull + public static AndroidKeyStorePrivateKey loadAndroidKeyStorePrivateKeyFromKeystore( + @NonNull KeyStore2 keyStore, @NonNull String privateKeyAlias, int namespace) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + AndroidKeyStoreKey key = + loadAndroidKeyStoreKeyFromKeystore(keyStore, privateKeyAlias, namespace); + if (key instanceof AndroidKeyStorePublicKey) { + return ((AndroidKeyStorePublicKey) key).getPrivateKey(); + } else { + throw new UnrecoverableKeyException("No asymmetric key found by the given alias."); + } + } + + + @NonNull + private static AndroidKeyStoreSecretKey makeAndroidKeyStoreSecretKeyFromKeyEntryResponse( + @NonNull KeyDescriptor descriptor, + @NonNull KeyEntryResponse response, int algorithm, int digest) + throws UnrecoverableKeyException { + + @KeyProperties.KeyAlgorithmEnum String keyAlgorithmString; + try { + keyAlgorithmString = KeyProperties.KeyAlgorithm.fromKeymasterSecretKeyAlgorithm( + algorithm, digest); + } catch (IllegalArgumentException e) { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Unsupported secret key type").initCause(e); + } + + return new AndroidKeyStoreSecretKey(descriptor, + response.metadata, keyAlgorithmString, + new KeyStoreSecurityLevel(response.iSecurityLevel)); + } + + /** + * Loads an an AndroidKeyStoreKey from the AndroidKeyStore backend. + * + * @param keyStore The keystore2 backend. + * @param alias The alias of the key in the Keystore database. + * @param namespace The a Keystore namespace. This is used by system api only to request + * Android system specific keystore namespace, which can be configured + * in the device's SEPolicy. Third party apps and most system components + * set this parameter to -1 to indicate their application specific namespace. + * TODO b/171806779 link to public Keystore 2.0 documentation. + * See bug for more details for now. + * @hide + **/ + @NonNull + public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore( + @NonNull KeyStore2 keyStore, @NonNull String alias, int namespace) + throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException { + + KeyDescriptor descriptor = new KeyDescriptor(); + if (namespace == KeyProperties.NAMESPACE_APPLICATION) { + descriptor.nspace = 0; // ignored; + descriptor.domain = Domain.APP; + } else { + descriptor.nspace = namespace; + descriptor.domain = Domain.SELINUX; + } + descriptor.alias = alias; + descriptor.blob = null; + KeyEntryResponse response = null; + try { + response = keyStore.getKeyEntry(descriptor); + } catch (android.security.KeyStoreException e) { + if (e.getErrorCode() == ResponseCode.KEY_PERMANENTLY_INVALIDATED) { + throw new KeyPermanentlyInvalidatedException( + "User changed or deleted their auth credentials", + e); + } else { + throw (UnrecoverableKeyException) + new UnrecoverableKeyException("Failed to obtain information about key") + .initCause(e); + } + } + + Integer keymasterAlgorithm = null; + // We just need one digest for the algorithm name + int keymasterDigest = -1; + for (Authorization a : response.metadata.authorizations) { + switch (a.keyParameter.tag) { + case KeymasterDefs.KM_TAG_ALGORITHM: + keymasterAlgorithm = a.keyParameter.integer; + break; + case KeymasterDefs.KM_TAG_DIGEST: + if (keymasterDigest == -1) keymasterDigest = a.keyParameter.integer; + break; + } + } + if (keymasterAlgorithm == null) { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + + if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC || + keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES || + keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_3DES) { + return makeAndroidKeyStoreSecretKeyFromKeyEntryResponse(descriptor, response, + keymasterAlgorithm, keymasterDigest); + } else if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_RSA || + keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_EC) { + return makeAndroidKeyStorePublicKeyFromKeyEntryResponse(descriptor, response.metadata, + new KeyStoreSecurityLevel(response.iSecurityLevel), + keymasterAlgorithm); + } else { + throw new UnrecoverableKeyException("Key algorithm unknown"); + } + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java new file mode 100644 index 000000000000..49dd77e3a3db --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStorePublicKey.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.security.keystore.ArrayUtils; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; + +import java.security.PublicKey; + +/** + * {@link PublicKey} backed by Android Keystore. + * + * @hide + */ +public abstract class AndroidKeyStorePublicKey extends AndroidKeyStoreKey implements PublicKey { + private final byte[] mCertificate; + private final byte[] mCertificateChain; + + public AndroidKeyStorePublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel); + mCertificate = metadata.certificate; + mCertificateChain = metadata.certificateChain; + } + + abstract AndroidKeyStorePrivateKey getPrivateKey(); + + @Override + public String getFormat() { + return "X.509"; + } + + @Override + public byte[] getEncoded() { + return ArrayUtils.cloneIfNotEmpty(mCertificate); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + super.hashCode(); + result = prime * result + ((mCertificate == null) ? 0 : mCertificate.hashCode()); + result = prime * result + ((mCertificateChain == null) ? 0 : mCertificateChain.hashCode()); + + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + return true; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java new file mode 100644 index 000000000000..a6ea9723db24 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSACipherSpi.java @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeymasterUtils; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyParameter; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.security.spec.MGF1ParameterSpec; +import java.util.List; + +import javax.crypto.Cipher; +import javax.crypto.CipherSpi; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; + +/** + * Base class for {@link CipherSpi} providing Android KeyStore backed RSA encryption/decryption. + * + * @hide + */ +abstract class AndroidKeyStoreRSACipherSpi extends AndroidKeyStoreCipherSpiBase { + + /** + * Raw RSA cipher without any padding. + */ + public static final class NoPadding extends AndroidKeyStoreRSACipherSpi { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + + @Override + protected boolean adjustConfigForEncryptingWithPrivateKey() { + // RSA encryption with no padding using private key is a way to implement raw RSA + // signatures which JCA does not expose via Signature. We thus have to support this. + setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN); + return true; + } + + @Override + protected void initAlgorithmSpecificParameters() throws InvalidKeyException {} + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + } + + /** + * RSA cipher with PKCS#1 v1.5 encryption padding. + */ + public static final class PKCS1Padding extends AndroidKeyStoreRSACipherSpi { + public PKCS1Padding() { + super(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_ENCRYPT); + } + + @Override + protected boolean adjustConfigForEncryptingWithPrivateKey() { + // RSA encryption with PCKS#1 padding using private key is a way to implement RSA + // signatures with PKCS#1 padding. We have to support this for legacy reasons. + setKeymasterPurposeOverride(KeymasterDefs.KM_PURPOSE_SIGN); + setKeymasterPaddingOverride(KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN); + return true; + } + + @Override + protected void initAlgorithmSpecificParameters() throws InvalidKeyException {} + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + + if (params != null) { + throw new InvalidAlgorithmParameterException( + "Unexpected parameters: " + params + ". No parameters supported"); + } + } + + @Override + protected AlgorithmParameters engineGetParameters() { + return null; + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return (isEncrypting()) ? getModulusSizeBytes() : 0; + } + } + + /** + * RSA cipher with OAEP encryption padding. Only SHA-1 based MGF1 is supported as MGF. + */ + abstract static class OAEPWithMGF1Padding extends AndroidKeyStoreRSACipherSpi { + + private static final String MGF_ALGORITGM_MGF1 = "MGF1"; + + private int mKeymasterDigest = -1; + private int mDigestOutputSizeBytes; + + OAEPWithMGF1Padding(int keymasterDigest) { + super(KeymasterDefs.KM_PAD_RSA_OAEP); + mKeymasterDigest = keymasterDigest; + mDigestOutputSizeBytes = + (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8; + } + + @Override + protected final void initAlgorithmSpecificParameters() throws InvalidKeyException {} + + @Override + protected final void initAlgorithmSpecificParameters( + @Nullable AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException { + if (params == null) { + return; + } + + if (!(params instanceof OAEPParameterSpec)) { + throw new InvalidAlgorithmParameterException( + "Unsupported parameter spec: " + params + + ". Only OAEPParameterSpec supported"); + } + OAEPParameterSpec spec = (OAEPParameterSpec) params; + if (!MGF_ALGORITGM_MGF1.equalsIgnoreCase(spec.getMGFAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported MGF: " + spec.getMGFAlgorithm() + + ". Only " + MGF_ALGORITGM_MGF1 + " supported"); + } + String jcaDigest = spec.getDigestAlgorithm(); + int keymasterDigest; + try { + keymasterDigest = KeyProperties.Digest.toKeymaster(jcaDigest); + } catch (IllegalArgumentException e) { + throw new InvalidAlgorithmParameterException( + "Unsupported digest: " + jcaDigest, e); + } + switch (keymasterDigest) { + case KeymasterDefs.KM_DIGEST_SHA1: + case KeymasterDefs.KM_DIGEST_SHA_2_224: + case KeymasterDefs.KM_DIGEST_SHA_2_256: + case KeymasterDefs.KM_DIGEST_SHA_2_384: + case KeymasterDefs.KM_DIGEST_SHA_2_512: + // Permitted. + break; + default: + throw new InvalidAlgorithmParameterException( + "Unsupported digest: " + jcaDigest); + } + AlgorithmParameterSpec mgfParams = spec.getMGFParameters(); + if (mgfParams == null) { + throw new InvalidAlgorithmParameterException("MGF parameters must be provided"); + } + // Check whether MGF parameters match the OAEPParameterSpec + if (!(mgfParams instanceof MGF1ParameterSpec)) { + throw new InvalidAlgorithmParameterException("Unsupported MGF parameters" + + ": " + mgfParams + ". Only MGF1ParameterSpec supported"); + } + MGF1ParameterSpec mgfSpec = (MGF1ParameterSpec) mgfParams; + String mgf1JcaDigest = mgfSpec.getDigestAlgorithm(); + if (!KeyProperties.DIGEST_SHA1.equalsIgnoreCase(mgf1JcaDigest)) { + throw new InvalidAlgorithmParameterException( + "Unsupported MGF1 digest: " + mgf1JcaDigest + + ". Only " + KeyProperties.DIGEST_SHA1 + " supported"); + } + PSource pSource = spec.getPSource(); + if (!(pSource instanceof PSource.PSpecified)) { + throw new InvalidAlgorithmParameterException( + "Unsupported source of encoding input P: " + pSource + + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported"); + } + PSource.PSpecified pSourceSpecified = (PSource.PSpecified) pSource; + byte[] pSourceValue = pSourceSpecified.getValue(); + if ((pSourceValue != null) && (pSourceValue.length > 0)) { + throw new InvalidAlgorithmParameterException( + "Unsupported source of encoding input P: " + pSource + + ". Only pSpecifiedEmpty (PSource.PSpecified.DEFAULT) supported"); + } + mKeymasterDigest = keymasterDigest; + mDigestOutputSizeBytes = + (KeymasterUtils.getDigestOutputSizeBits(keymasterDigest) + 7) / 8; + } + + @Override + protected final void initAlgorithmSpecificParameters(@Nullable AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (params == null) { + return; + } + + OAEPParameterSpec spec; + try { + spec = params.getParameterSpec(OAEPParameterSpec.class); + } catch (InvalidParameterSpecException e) { + throw new InvalidAlgorithmParameterException("OAEP parameters required" + + ", but not found in parameters: " + params, e); + } + if (spec == null) { + throw new InvalidAlgorithmParameterException("OAEP parameters required" + + ", but not provided in parameters: " + params); + } + initAlgorithmSpecificParameters(spec); + } + + @Override + protected final AlgorithmParameters engineGetParameters() { + OAEPParameterSpec spec = + new OAEPParameterSpec( + KeyProperties.Digest.fromKeymaster(mKeymasterDigest), + MGF_ALGORITGM_MGF1, + MGF1ParameterSpec.SHA1, + PSource.PSpecified.DEFAULT); + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("OAEP"); + params.init(spec); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain OAEP AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize OAEP AlgorithmParameters with an IV", + e); + } + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters) { + super.addAlgorithmSpecificParametersToBegin(parameters); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); + } + + @Override + protected final void loadAlgorithmSpecificParametersFromBeginResult( + KeyParameter[] parameters) { + super.loadAlgorithmSpecificParametersFromBeginResult(parameters); + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return (isEncrypting()) ? mDigestOutputSizeBytes : 0; + } + } + + public static class OAEPWithSHA1AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA1AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static class OAEPWithSHA224AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA224AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static class OAEPWithSHA256AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA256AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static class OAEPWithSHA384AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA384AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static class OAEPWithSHA512AndMGF1Padding extends OAEPWithMGF1Padding { + public OAEPWithSHA512AndMGF1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final int mKeymasterPadding; + private int mKeymasterPaddingOverride; + + private int mModulusSizeBytes = -1; + + AndroidKeyStoreRSACipherSpi(int keymasterPadding) { + mKeymasterPadding = keymasterPadding; + } + + @Override + protected final void initKey(int opmode, Key key) throws InvalidKeyException { + if (key == null) { + throw new InvalidKeyException("Unsupported key: null"); + } + if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() + + ". Only " + KeyProperties.KEY_ALGORITHM_RSA + " supported"); + } + AndroidKeyStoreKey keystoreKey; + if (key instanceof AndroidKeyStorePrivateKey) { + keystoreKey = (AndroidKeyStoreKey) key; + } else if (key instanceof AndroidKeyStorePublicKey) { + keystoreKey = (AndroidKeyStoreKey) key; + } else { + throw new InvalidKeyException("Unsupported key type: " + key); + } + + if (keystoreKey instanceof PrivateKey) { + // Private key + switch (opmode) { + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + // Permitted + break; + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + if (!adjustConfigForEncryptingWithPrivateKey()) { + throw new InvalidKeyException( + "RSA private keys cannot be used with " + opmodeToString(opmode) + + " and padding " + + KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding) + + ". Only RSA public keys supported for this mode"); + } + break; + default: + throw new InvalidKeyException( + "RSA private keys cannot be used with opmode: " + opmode); + } + } else { + // Public key + switch (opmode) { + case Cipher.ENCRYPT_MODE: + case Cipher.WRAP_MODE: + // Permitted + break; + case Cipher.DECRYPT_MODE: + case Cipher.UNWRAP_MODE: + throw new InvalidKeyException( + "RSA public keys cannot be used with " + opmodeToString(opmode) + + " and padding " + + KeyProperties.EncryptionPadding.fromKeymaster(mKeymasterPadding) + + ". Only RSA private keys supported for this opmode."); + // break; + default: + throw new InvalidKeyException( + "RSA public keys cannot be used with " + opmodeToString(opmode)); + } + } + + long keySizeBits = -1; + for (Authorization a : keystoreKey.getAuthorizations()) { + if (a.keyParameter.tag == KeymasterDefs.KM_TAG_KEY_SIZE) { + keySizeBits = KeyStore2ParameterUtils.getUnsignedInt(a); + } + } + + if (keySizeBits == -1) { + throw new InvalidKeyException("Size of key not known"); + } else if (keySizeBits > Integer.MAX_VALUE) { + throw new InvalidKeyException("Key too large: " + keySizeBits + " bits"); + } + mModulusSizeBytes = (int) ((keySizeBits + 7) / 8); + + setKey(keystoreKey); + } + + /** + * Adjusts the configuration of this cipher for encrypting using the private key. + * + * <p>The default implementation does nothing and refuses to adjust the configuration. + * + * @return {@code true} if the configuration has been adjusted, {@code false} if encrypting + * using private key is not permitted for this cipher. + */ + protected boolean adjustConfigForEncryptingWithPrivateKey() { + return false; + } + + @Override + protected final void resetAll() { + mModulusSizeBytes = -1; + mKeymasterPaddingOverride = -1; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters) { + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA + )); + int keymasterPadding = getKeymasterPaddingOverride(); + if (keymasterPadding == -1) { + keymasterPadding = mKeymasterPadding; + } + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, keymasterPadding + )); + int purposeOverride = getKeymasterPurposeOverride(); + if ((purposeOverride != -1) + && ((purposeOverride == KeymasterDefs.KM_PURPOSE_SIGN) + || (purposeOverride == KeymasterDefs.KM_PURPOSE_VERIFY))) { + // Keymaster sign/verify requires digest to be specified. + // For raw sign/verify it's NONE. + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, KeymasterDefs.KM_DIGEST_NONE + )); + } + } + + @Override + protected void loadAlgorithmSpecificParametersFromBeginResult( + KeyParameter[] parameters) { + } + + @Override + protected final int engineGetBlockSize() { + // Not a block cipher, according to the RI + return 0; + } + + @Override + protected final byte[] engineGetIV() { + // IV never used + return null; + } + + @Override + protected final int engineGetOutputSize(int inputLen) { + return getModulusSizeBytes(); + } + + protected final int getModulusSizeBytes() { + if (mModulusSizeBytes == -1) { + throw new IllegalStateException("Not initialized"); + } + return mModulusSizeBytes; + } + + /** + * Overrides the default padding of the crypto operation. + */ + protected final void setKeymasterPaddingOverride(int keymasterPadding) { + mKeymasterPaddingOverride = keymasterPadding; + } + + protected final int getKeymasterPaddingOverride() { + return mKeymasterPaddingOverride; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java new file mode 100644 index 000000000000..ef0d3bc4d46a --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPrivateKey.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyDescriptor; + +import java.math.BigInteger; +import java.security.PrivateKey; +import java.security.interfaces.RSAKey; + +/** + * RSA private key (instance of {@link PrivateKey} and {@link RSAKey}) backed by keystore. + * + * @hide + */ +public class AndroidKeyStoreRSAPrivateKey extends AndroidKeyStorePrivateKey implements RSAKey { + + private final BigInteger mModulus; + + + public AndroidKeyStoreRSAPrivateKey(@NonNull KeyDescriptor descriptor, + long keyId, + @NonNull Authorization[] authorizations, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus) { + super(descriptor, keyId, authorizations, KeyProperties.KEY_ALGORITHM_RSA, securityLevel); + mModulus = modulus; + } + + @Override + public BigInteger getModulus() { + return mModulus; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java new file mode 100644 index 000000000000..b578ea9baa06 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSAPublicKey.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; + +import java.math.BigInteger; +import java.security.interfaces.RSAPublicKey; + +/** + * {@link RSAPublicKey} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreRSAPublicKey extends AndroidKeyStorePublicKey implements RSAPublicKey { + private final BigInteger mModulus; + private final BigInteger mPublicExponent; + + public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull BigInteger modulus, + @NonNull BigInteger publicExponent) { + super(descriptor, metadata, KeyProperties.KEY_ALGORITHM_RSA, securityLevel); + mModulus = modulus; + mPublicExponent = publicExponent; + } + + public AndroidKeyStoreRSAPublicKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, + @NonNull KeyStoreSecurityLevel securityLevel, @NonNull RSAPublicKey info) { + this(descriptor, metadata, securityLevel, info.getModulus(), info.getPublicExponent()); + if (!"X.509".equalsIgnoreCase(info.getFormat())) { + throw new IllegalArgumentException( + "Unsupported key export format: " + info.getFormat()); + } + } + + @Override + public AndroidKeyStorePrivateKey getPrivateKey() { + return new AndroidKeyStoreRSAPrivateKey(getUserKeyDescriptor(), getKeyIdDescriptor().nspace, + getAuthorizations(), getSecurityLevel(), mModulus); + } + + @Override + public BigInteger getModulus() { + return mModulus; + } + + @Override + public BigInteger getPublicExponent() { + return mPublicExponent; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java new file mode 100644 index 000000000000..5f1b9c0586a1 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreRSASignatureSpi.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyParameter; + +import java.security.InvalidKeyException; +import java.security.SignatureSpi; +import java.util.List; + +/** + * Base class for {@link SignatureSpi} providing Android KeyStore backed RSA signatures. + * + * @hide + */ +abstract class AndroidKeyStoreRSASignatureSpi extends AndroidKeyStoreSignatureSpiBase { + + abstract static class PKCS1Padding extends AndroidKeyStoreRSASignatureSpi { + PKCS1Padding(int keymasterDigest) { + super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN); + } + + @Override + protected final int getAdditionalEntropyAmountForSign() { + // No entropy required for this deterministic signature scheme. + return 0; + } + } + + public static final class NONEWithPKCS1Padding extends PKCS1Padding { + public NONEWithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_NONE); + } + } + + public static final class MD5WithPKCS1Padding extends PKCS1Padding { + public MD5WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_MD5); + } + } + + public static final class SHA1WithPKCS1Padding extends PKCS1Padding { + public SHA1WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static final class SHA224WithPKCS1Padding extends PKCS1Padding { + public SHA224WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static final class SHA256WithPKCS1Padding extends PKCS1Padding { + public SHA256WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static final class SHA384WithPKCS1Padding extends PKCS1Padding { + public SHA384WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static final class SHA512WithPKCS1Padding extends PKCS1Padding { + public SHA512WithPKCS1Padding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + abstract static class PSSPadding extends AndroidKeyStoreRSASignatureSpi { + private static final int SALT_LENGTH_BYTES = 20; + + PSSPadding(int keymasterDigest) { + super(keymasterDigest, KeymasterDefs.KM_PAD_RSA_PSS); + } + + @Override + protected final int getAdditionalEntropyAmountForSign() { + return SALT_LENGTH_BYTES; + } + } + + public static final class SHA1WithPSSPadding extends PSSPadding { + public SHA1WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA1); + } + } + + public static final class SHA224WithPSSPadding extends PSSPadding { + public SHA224WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_224); + } + } + + public static final class SHA256WithPSSPadding extends PSSPadding { + public SHA256WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_256); + } + } + + public static final class SHA384WithPSSPadding extends PSSPadding { + public SHA384WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_384); + } + } + + public static final class SHA512WithPSSPadding extends PSSPadding { + public SHA512WithPSSPadding() { + super(KeymasterDefs.KM_DIGEST_SHA_2_512); + } + } + + private final int mKeymasterDigest; + private final int mKeymasterPadding; + + AndroidKeyStoreRSASignatureSpi(int keymasterDigest, int keymasterPadding) { + mKeymasterDigest = keymasterDigest; + mKeymasterPadding = keymasterPadding; + } + + @Override + protected final void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { + if (!KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException("Unsupported key algorithm: " + key.getAlgorithm() + + ". Only" + KeyProperties.KEY_ALGORITHM_RSA + " supported"); + } + super.initKey(key); + } + + @Override + protected final void resetAll() { + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters) { + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_RSA + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigest + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding + )); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java new file mode 100644 index 000000000000..4e459137e875 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKey.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyMetadata; + +import javax.crypto.SecretKey; + +/** + * {@link SecretKey} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreSecretKey extends AndroidKeyStoreKey implements SecretKey { + + public AndroidKeyStoreSecretKey(@NonNull KeyDescriptor descriptor, + @NonNull KeyMetadata metadata, @NonNull String algorithm, + @NonNull KeyStoreSecurityLevel securityLevel) { + super(descriptor, metadata.key.nspace, metadata.authorizations, algorithm, securityLevel); + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java new file mode 100644 index 000000000000..9d3b9704d711 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.GateKeeper; +import android.security.KeyStore; +import android.security.keymaster.KeymasterArguments; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyInfo; +import android.security.keystore.KeyProperties; +import android.system.keystore2.Authorization; + +import java.math.BigInteger; +import java.security.InvalidKeyException; +import java.security.ProviderException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactorySpi; +import javax.crypto.spec.SecretKeySpec; + +/** + * {@link SecretKeyFactorySpi} backed by Android Keystore. + * + * @hide + */ +public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + @Override + protected KeySpec engineGetKeySpec(SecretKey key, + @SuppressWarnings("rawtypes") Class keySpecClass) throws InvalidKeySpecException { + if (keySpecClass == null) { + throw new InvalidKeySpecException("keySpecClass == null"); + } + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeySpecException("Only Android KeyStore secret keys supported: " + + ((key != null) ? key.getClass().getName() : "null")); + } + if (SecretKeySpec.class.isAssignableFrom(keySpecClass)) { + throw new InvalidKeySpecException( + "Key material export of Android KeyStore keys is not supported"); + } + if (!KeyInfo.class.equals(keySpecClass)) { + throw new InvalidKeySpecException("Unsupported key spec: " + keySpecClass.getName()); + } + AndroidKeyStoreKey keystoreKey = (AndroidKeyStoreKey) key; + + return getKeyInfo(keystoreKey); + } + + static @NonNull KeyInfo getKeyInfo(@NonNull AndroidKeyStoreKey key) { + + @KeyProperties.SecurityLevelEnum int securityLevel = + KeyProperties.SECURITY_LEVEL_SOFTWARE; + boolean insideSecureHardware = false; + @KeyProperties.OriginEnum int origin = -1; + int keySize = -1; + @KeyProperties.PurposeEnum int purposes = 0; + String[] encryptionPaddings; + String[] signaturePaddings; + List<String> digestsList = new ArrayList<>(); + List<String> blockModesList = new ArrayList<>(); + int keymasterSwEnforcedUserAuthenticators = 0; + int keymasterHwEnforcedUserAuthenticators = 0; + List<BigInteger> keymasterSecureUserIds = new ArrayList<BigInteger>(); + List<String> encryptionPaddingsList = new ArrayList<String>(); + List<String> signaturePaddingsList = new ArrayList<String>(); + Date keyValidityStart = null; + Date keyValidityForOriginationEnd = null; + Date keyValidityForConsumptionEnd = null; + long userAuthenticationValidityDurationSeconds = 0; + boolean userAuthenticationRequired = true; + boolean userAuthenticationValidWhileOnBody = false; + boolean trustedUserPresenceRequired = false; + boolean trustedUserConfirmationRequired = false; + try { + for (Authorization a : key.getAuthorizations()) { + switch (a.keyParameter.tag) { + case KeymasterDefs.KM_TAG_ORIGIN: + insideSecureHardware = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + securityLevel = a.securityLevel; + origin = KeyProperties.Origin.fromKeymaster(a.keyParameter.integer); + break; + case KeymasterDefs.KM_TAG_KEY_SIZE: + long keySizeUnsigned = KeyStore2ParameterUtils.getUnsignedInt(a); + if (keySizeUnsigned > Integer.MAX_VALUE) { + throw new ProviderException( + "Key too large: " + keySizeUnsigned + " bits"); + } + keySize = (int) keySizeUnsigned; + break; + case KeymasterDefs.KM_TAG_PURPOSE: + purposes |= KeyProperties.Purpose.fromKeymaster(a.keyParameter.integer); + break; + case KeymasterDefs.KM_TAG_PADDING: + try { + if (a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PKCS1_1_5_SIGN + || a.keyParameter.integer == KeymasterDefs.KM_PAD_RSA_PSS) { + @KeyProperties.SignaturePaddingEnum String padding = + KeyProperties.SignaturePadding.fromKeymaster( + a.keyParameter.integer); + signaturePaddingsList.add(padding); + } else { + @KeyProperties.EncryptionPaddingEnum String jcaPadding = + KeyProperties.EncryptionPadding.fromKeymaster( + a.keyParameter.integer); + encryptionPaddingsList.add(jcaPadding); + } + } catch (IllegalArgumentException e) { + throw new ProviderException("Unsupported padding: " + + a.keyParameter.integer); + } + break; + case KeymasterDefs.KM_TAG_DIGEST: + digestsList.add(KeyProperties.Digest.fromKeymaster(a.keyParameter.integer)); + break; + case KeymasterDefs.KM_TAG_BLOCK_MODE: + blockModesList.add( + KeyProperties.BlockMode.fromKeymaster(a.keyParameter.integer) + ); + break; + case KeymasterDefs.KM_TAG_USER_AUTH_TYPE: + if (KeyStore2ParameterUtils.isSecureHardware(a.securityLevel)) { + keymasterHwEnforcedUserAuthenticators = a.keyParameter.integer; + } else { + keymasterSwEnforcedUserAuthenticators = a.keyParameter.integer; + } + break; + case KeymasterDefs.KM_TAG_USER_SECURE_ID: + keymasterSecureUserIds.add( + KeymasterArguments.toUint64(a.keyParameter.longInteger)); + break; + case KeymasterDefs.KM_TAG_ACTIVE_DATETIME: + keyValidityStart = KeyStore2ParameterUtils.getDate(a); + break; + case KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME: + keyValidityForOriginationEnd = + KeyStore2ParameterUtils.getDate(a); + break; + case KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME: + keyValidityForConsumptionEnd = + KeyStore2ParameterUtils.getDate(a); + break; + case KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED: + userAuthenticationRequired = false; + break; + case KeymasterDefs.KM_TAG_AUTH_TIMEOUT: + userAuthenticationValidityDurationSeconds = + KeyStore2ParameterUtils.getUnsignedInt(a); + if (userAuthenticationValidityDurationSeconds > Integer.MAX_VALUE) { + throw new ProviderException( + "User authentication timeout validity too long: " + + userAuthenticationValidityDurationSeconds + " seconds"); + } + break; + case KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY: + userAuthenticationValidWhileOnBody = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + break; + case KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED: + trustedUserPresenceRequired = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + break; + case KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED: + trustedUserConfirmationRequired = + KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); + break; + } + } + } catch (IllegalArgumentException e) { + throw new ProviderException("Unsupported key characteristic", e); + } + if (keySize == -1) { + throw new ProviderException("Key size not available"); + } + if (origin == -1) { + throw new ProviderException("Key origin not available"); + } + + encryptionPaddings = + encryptionPaddingsList.toArray(new String[0]); + signaturePaddings = + signaturePaddingsList.toArray(new String[0]); + + boolean userAuthenticationRequirementEnforcedBySecureHardware = (userAuthenticationRequired) + && (keymasterHwEnforcedUserAuthenticators != 0) + && (keymasterSwEnforcedUserAuthenticators == 0); + + String[] digests = digestsList.toArray(new String[0]); + String[] blockModes = blockModesList.toArray(new String[0]); + + boolean invalidatedByBiometricEnrollment = false; + if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC + || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) { + // Fingerprint-only key; will be invalidated if the root SID isn't in the SID list. + invalidatedByBiometricEnrollment = !keymasterSecureUserIds.isEmpty() + && !keymasterSecureUserIds.contains(getGateKeeperSecureUserId()); + } + + return new KeyInfo(key.getUserKeyDescriptor().alias, + insideSecureHardware, + origin, + keySize, + keyValidityStart, + keyValidityForOriginationEnd, + keyValidityForConsumptionEnd, + purposes, + encryptionPaddings, + signaturePaddings, + digests, + blockModes, + userAuthenticationRequired, + (int) userAuthenticationValidityDurationSeconds, + keymasterHwEnforcedUserAuthenticators, + userAuthenticationRequirementEnforcedBySecureHardware, + userAuthenticationValidWhileOnBody, + trustedUserPresenceRequired, + invalidatedByBiometricEnrollment, + trustedUserConfirmationRequired, + securityLevel); + } + + private static BigInteger getGateKeeperSecureUserId() throws ProviderException { + try { + return BigInteger.valueOf(GateKeeper.getSecureUserId()); + } catch (IllegalStateException e) { + throw new ProviderException("Failed to get GateKeeper secure user ID", e); + } + } + + @Override + protected SecretKey engineGenerateSecret(KeySpec keySpec) throws InvalidKeySpecException { + throw new InvalidKeySpecException( + "To generate secret key in Android Keystore, use KeyGenerator initialized with " + + KeyGenParameterSpec.class.getName()); + } + + @Override + protected SecretKey engineTranslateKey(SecretKey key) throws InvalidKeyException { + if (key == null) { + throw new InvalidKeyException("key == null"); + } else if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "To import a secret key into Android Keystore, use KeyStore.setEntry"); + } + + return key; + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java new file mode 100644 index 000000000000..55414b70d403 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSignatureSpiBase.java @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyStoreCryptoOperation; +import android.system.keystore2.KeyParameter; + +import libcore.util.EmptyArray; + +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.InvalidParameterException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.SignatureException; +import java.security.SignatureSpi; +import java.util.ArrayList; +import java.util.List; + +/** + * Base class for {@link SignatureSpi} implementations of Android KeyStore backed ciphers. + * + * @hide + */ +abstract class AndroidKeyStoreSignatureSpiBase extends SignatureSpi + implements KeyStoreCryptoOperation { + private static final String TAG = "AndroidKeyStoreSignatureSpiBase"; + + // Fields below are populated by SignatureSpi.engineInitSign/engineInitVerify and KeyStore.begin + // and should be preserved after SignatureSpi.engineSign/engineVerify finishes. + private boolean mSigning; + private AndroidKeyStoreKey mKey; + + /** + * Object representing this operation inside keystore service. It is initialized + * by {@code engineInit} and is invalidated when {@code engineDoFinal} succeeds and on some + * error conditions in between. + */ + private KeyStoreOperation mOperation; + /** + * The operation challenge is required when an operation needs user authorization. + * The challenge is subjected to an authenticator, e.g., Gatekeeper or a biometric + * authenticator, and included in the authentication token minted by this authenticator. + * It may be null, if the operation does not require authorization. + */ + private long mOperationChallenge; + private KeyStoreCryptoOperationStreamer mMessageStreamer; + + /** + * Encountered exception which could not be immediately thrown because it was encountered inside + * a method that does not throw checked exception. This exception will be thrown from + * {@code engineSign} or {@code engineVerify}. Once such an exception is encountered, + * {@code engineUpdate} starts ignoring input data. + */ + private Exception mCachedException; + + AndroidKeyStoreSignatureSpiBase() { + mOperation = null; + mOperationChallenge = 0; + mSigning = false; + mKey = null; + appRandom = null; + mMessageStreamer = null; + mCachedException = null; + } + + @Override + protected final void engineInitSign(PrivateKey key) throws InvalidKeyException { + engineInitSign(key, null); + } + + @Override + protected final void engineInitSign(PrivateKey privateKey, SecureRandom random) + throws InvalidKeyException { + resetAll(); + + boolean success = false; + try { + if (privateKey == null) { + throw new InvalidKeyException("Unsupported key: null"); + } + AndroidKeyStoreKey keystoreKey; + if (privateKey instanceof AndroidKeyStorePrivateKey) { + keystoreKey = (AndroidKeyStoreKey) privateKey; + } else { + throw new InvalidKeyException("Unsupported private key type: " + privateKey); + } + mSigning = true; + initKey(keystoreKey); + appRandom = random; + ensureKeystoreOperationInitialized(); + success = true; + } finally { + if (!success) { + resetAll(); + } + } + } + + @Override + 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(); + } + } + } + + /** + * Configures this signature instance to use the provided key. + * + * @throws InvalidKeyException if the {@code key} is not suitable. + */ + @CallSuper + protected void initKey(AndroidKeyStoreKey key) throws InvalidKeyException { + mKey = key; + } + + private void abortOperation() { + KeyStoreCryptoOperationUtils.abortOperation(mOperation); + mOperation = null; + } + + /** + * Resets this cipher to its pristine pre-init state. This must be equivalent to obtaining a new + * cipher instance. + * + * <p>Subclasses storing additional state should override this method, reset the additional + * state, and then chain to superclass. + */ + @CallSuper + protected void resetAll() { + abortOperation(); + mOperationChallenge = 0; + mSigning = false; + mKey = null; + appRandom = null; + mMessageStreamer = null; + mCachedException = null; + } + + /** + * Resets this cipher while preserving the initialized state. This must be equivalent to + * rolling back the cipher's state to just after the most recent {@code engineInit} completed + * successfully. + * + * <p>Subclasses storing additional post-init state should override this method, reset the + * additional state, and then chain to superclass. + */ + @CallSuper + protected void resetWhilePreservingInitState() { + abortOperation(); + mOperationChallenge = 0; + mMessageStreamer = null; + mCachedException = null; + } + + private void ensureKeystoreOperationInitialized() throws InvalidKeyException { + if (mMessageStreamer != null) { + return; + } + if (mCachedException != null) { + return; + } + if (mKey == null) { + throw new IllegalStateException("Not initialized"); + } + + List<KeyParameter> parameters = new ArrayList<>(); + addAlgorithmSpecificParametersToBegin(parameters); + + int purpose = mSigning ? KeymasterDefs.KM_PURPOSE_SIGN : KeymasterDefs.KM_PURPOSE_VERIFY; + + parameters.add(KeyStore2ParameterUtils.makeEnum(KeymasterDefs.KM_TAG_PURPOSE, purpose)); + + try { + mOperation = mKey.getSecurityLevel().createOperation( + mKey.getKeyIdDescriptor(), + parameters); + } catch (KeyStoreException keyStoreException) { + throw KeyStoreCryptoOperationUtils.getInvalidKeyException( + mKey, keyStoreException); + } + + // Now we check if we got an operation challenge. This indicates that user authorization + // is required. And if we got a challenge we check if the authorization can possibly + // succeed. + mOperationChallenge = KeyStoreCryptoOperationUtils.getOrMakeOperationChallenge( + mOperation, mKey); + + mMessageStreamer = createMainDataStreamer(mOperation); + } + + /** + * Creates a streamer which sends the message to be signed/verified into the provided KeyStore + * + * <p>This implementation returns a working streamer. + */ + @NonNull + protected KeyStoreCryptoOperationStreamer createMainDataStreamer( + @NonNull KeyStoreOperation operation) { + return new KeyStoreCryptoOperationChunkedStreamer( + new KeyStoreCryptoOperationChunkedStreamer.MainDataStream( + operation)); + } + + @Override + public final long getOperationHandle() { + return mOperationChallenge; + } + + @Override + protected final void engineUpdate(byte[] b, int off, int len) throws SignatureException { + if (mCachedException != null) { + throw new SignatureException(mCachedException); + } + + try { + ensureKeystoreOperationInitialized(); + } catch (InvalidKeyException e) { + throw new SignatureException(e); + } + + if (len == 0) { + return; + } + + byte[] output; + try { + output = mMessageStreamer.update(b, off, len); + } catch (KeyStoreException e) { + throw new SignatureException(e); + } + + if (output.length != 0) { + throw new ProviderException( + "Update operation unexpectedly produced output: " + output.length + " bytes"); + } + } + + @Override + protected final void engineUpdate(byte b) throws SignatureException { + engineUpdate(new byte[] {b}, 0, 1); + } + + @Override + protected final void engineUpdate(ByteBuffer input) { + byte[] b; + int off; + int len = input.remaining(); + if (input.hasArray()) { + b = input.array(); + off = input.arrayOffset() + input.position(); + input.position(input.limit()); + } else { + b = new byte[len]; + off = 0; + input.get(b); + } + + try { + engineUpdate(b, off, len); + } catch (SignatureException e) { + mCachedException = e; + } + } + + @Override + protected final int engineSign(byte[] out, int outOffset, int outLen) + throws SignatureException { + return super.engineSign(out, outOffset, outLen); + } + + @Override + protected final byte[] engineSign() throws SignatureException { + if (mCachedException != null) { + throw new SignatureException(mCachedException); + } + + byte[] signature; + try { + ensureKeystoreOperationInitialized(); + + byte[] additionalEntropy = + KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( + appRandom, getAdditionalEntropyAmountForSign()); + signature = mMessageStreamer.doFinal( + EmptyArray.BYTE, 0, 0, + null); // no signature provided -- it'll be generated by this invocation + } catch (InvalidKeyException | KeyStoreException e) { + throw new SignatureException(e); + } + + resetWhilePreservingInitState(); + return signature; + } + + @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); + } + } + + resetWhilePreservingInitState(); + return verified; + } + + @Override + protected final boolean engineVerify(byte[] sigBytes, int offset, int len) + throws SignatureException { + return engineVerify(ArrayUtils.subarray(sigBytes, offset, len)); + } + + @Deprecated + @Override + protected final Object engineGetParameter(String param) throws InvalidParameterException { + throw new InvalidParameterException(); + } + + @Deprecated + @Override + protected final void engineSetParameter(String param, Object value) + throws InvalidParameterException { + throw new InvalidParameterException(); + } + + /** + * Returns {@code true} if this signature is initialized for signing, {@code false} if this + * signature is initialized for verification. + */ + protected final boolean isSigning() { + return mSigning; + } + + // The methods below need to be implemented by subclasses. + + /** + * Returns the amount of additional entropy (in bytes) to be provided to the KeyStore's + * {@code finish} operation when generating a signature. + * + * <p>This value should match (or exceed) the amount of Shannon entropy of the produced + * signature assuming the key and the message are known. For example, for ECDSA signature this + * should be the size of {@code R}, whereas for the RSA signature with PKCS#1 padding this + * should be {@code 0}. + */ + protected abstract int getAdditionalEntropyAmountForSign(); + + /** + * Invoked to add algorithm-specific parameters for the KeyStore's {@code begin} operation. + * + * @param parameters keystore/keymaster arguments to be populated with algorithm-specific + * parameters. + */ + protected abstract void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters); +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java new file mode 100644 index 000000000000..4c26864cb02b --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -0,0 +1,1167 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.security.keystore2; + +import android.annotation.NonNull; +import android.hardware.biometrics.BiometricManager; +import android.security.GateKeeper; +import android.security.KeyStore2; +import android.security.KeyStoreParameter; +import android.security.KeyStoreSecurityLevel; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.KeyProperties; +import android.security.keystore.KeyProtection; +import android.security.keystore.KeymasterUtils; +import android.security.keystore.SecureKeyImportUnavailableException; +import android.security.keystore.WrappedKeyEntry; +import android.system.keystore2.AuthenticatorSpec; +import android.system.keystore2.Domain; +import android.system.keystore2.IKeystoreSecurityLevel; +import android.system.keystore2.KeyDescriptor; +import android.system.keystore2.KeyEntryResponse; +import android.system.keystore2.KeyMetadata; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.ResponseCode; +import android.system.keystore2.SecurityLevel; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Key; +import java.security.KeyStore.Entry; +import java.security.KeyStore.LoadStoreParameter; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStore.ProtectionParameter; +import java.security.KeyStore.SecretKeyEntry; +import java.security.KeyStoreException; +import java.security.KeyStoreSpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.crypto.SecretKey; + +/** + * A java.security.KeyStore interface for the Android KeyStore. An instance of + * it can be created via the {@link java.security.KeyStore#getInstance(String) + * KeyStore.getInstance("AndroidKeyStore")} interface. This returns a + * java.security.KeyStore backed by this "AndroidKeyStore" implementation. + * <p> + * This is built on top of Android's keystore daemon. The convention of alias + * use is: + * <p> + * PrivateKeyEntry will have a Credentials.USER_PRIVATE_KEY as the private key, + * Credentials.USER_CERTIFICATE as the first certificate in the chain (the one + * that corresponds to the private key), and then a Credentials.CA_CERTIFICATE + * entry which will have the rest of the chain concatenated in BER format. + * <p> + * TrustedCertificateEntry will just have a Credentials.CA_CERTIFICATE entry + * with a single certificate. + * + * @hide + */ +public class AndroidKeyStoreSpi extends KeyStoreSpi { + public static final String TAG = "AndroidKeyStoreSpi"; + public static final String NAME = "AndroidKeyStore"; + + private KeyStore2 mKeyStore; + private int mNamespace = KeyProperties.NAMESPACE_APPLICATION; + + @Override + public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, + UnrecoverableKeyException { + try { + return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore, + alias, + mNamespace); + } catch (KeyPermanentlyInvalidatedException e) { + throw new UnrecoverableKeyException(e.getMessage()); + } catch (UnrecoverableKeyException e) { + Throwable cause = e.getCause(); + if (cause instanceof android.security.KeyStoreException) { + if (((android.security.KeyStoreException) cause).getErrorCode() + == ResponseCode.KEY_NOT_FOUND) { + return null; + } + } + throw e; + } + } + + /** + * Make a key descriptor from the given alias and the mNamespace member. + * If mNamespace is -1 it sets the domain field to {@link Domain#APP} and {@link Domain#SELINUX} + * otherwise. The blob field is always set to null and the alias field to {@code alias} + * @param alias The alias of the new key descriptor. + * @return A new key descriptor. + */ + private KeyDescriptor makeKeyDescriptor(@NonNull String alias) { + KeyDescriptor descriptor = new KeyDescriptor(); + descriptor.domain = getTargetDomain(); + descriptor.nspace = mNamespace; // ignored if Domain.App; + descriptor.alias = alias; + descriptor.blob = null; + return descriptor; + } + + private @Domain int getTargetDomain() { + return mNamespace == KeyProperties.NAMESPACE_APPLICATION + ? Domain.APP + : Domain.SELINUX; + } + private KeyEntryResponse getKeyMetadata(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + KeyDescriptor descriptor = makeKeyDescriptor(alias); + + try { + return mKeyStore.getKeyEntry(descriptor); + } catch (android.security.KeyStoreException e) { + if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { + Log.w(TAG, "Could not get key metadata from Keystore.", e); + } + return null; + } + } + + @Override + public Certificate[] engineGetCertificateChain(String alias) { + KeyEntryResponse response = getKeyMetadata(alias); + + if (response == null || response.metadata.certificate == null) { + return null; + } + + final X509Certificate leaf = (X509Certificate) toCertificate(response.metadata.certificate); + if (leaf == null) { + return null; + } + + final Certificate[] caList; + + final byte[] caBytes = response.metadata.certificateChain; + + if (caBytes != null) { + final Collection<X509Certificate> caChain = toCertificates(caBytes); + + caList = new Certificate[caChain.size() + 1]; + + final Iterator<X509Certificate> it = caChain.iterator(); + int i = 1; + while (it.hasNext()) { + caList[i++] = it.next(); + } + } else { + caList = new Certificate[1]; + } + + caList[0] = leaf; + + return caList; + } + + @Override + public Certificate engineGetCertificate(String alias) { + KeyEntryResponse response = getKeyMetadata(alias); + + if (response == null) { + return null; + } + + byte[] encodedCert = response.metadata.certificate; + if (encodedCert != null) { + return toCertificate(encodedCert); + } + + encodedCert = response.metadata.certificateChain; + if (encodedCert != null) { + return toCertificate(encodedCert); + } + + // This entry/alias does not contain a certificate. + return null; + } + + private static X509Certificate toCertificate(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certFactory.generateCertificate( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(NAME, "Couldn't parse certificate in keystore", e); + return null; + } + } + + @SuppressWarnings("unchecked") + private static Collection<X509Certificate> toCertificates(byte[] bytes) { + try { + final CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (Collection<X509Certificate>) certFactory.generateCertificates( + new ByteArrayInputStream(bytes)); + } catch (CertificateException e) { + Log.w(NAME, "Couldn't parse certificates in keystore", e); + return new ArrayList<X509Certificate>(); + } + } + + @Override + public Date engineGetCreationDate(String alias) { + KeyEntryResponse response = getKeyMetadata(alias); + + if (response == null) { + return null; + } + + + // TODO add modification time to key metadata. + return null; + // if (response.metadata.modificationTime == -1) { + // return null; + // } + // return new Date(response.metadata.modificationTime); + } + + @Override + public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) + throws KeyStoreException { + if ((password != null) && (password.length > 0)) { + throw new KeyStoreException("entries cannot be protected with passwords"); + } + + if (key instanceof PrivateKey) { + setPrivateKeyEntry(alias, (PrivateKey) key, chain, null); + } else if (key instanceof SecretKey) { + setSecretKeyEntry(alias, (SecretKey) key, null); + } else { + throw new KeyStoreException("Only PrivateKey and SecretKey are supported"); + } + } + + private static KeyProtection getLegacyKeyProtectionParameter(PrivateKey key) + throws KeyStoreException { + String keyAlgorithm = key.getAlgorithm(); + KeyProtection.Builder specBuilder; + if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + // MD5 was never offered for Android Keystore for ECDSA. + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) { + specBuilder = + new KeyProtection.Builder( + KeyProperties.PURPOSE_ENCRYPT + | KeyProperties.PURPOSE_DECRYPT + | KeyProperties.PURPOSE_SIGN + | KeyProperties.PURPOSE_VERIFY); + // Authorized to be used with any digest (including no digest). + specBuilder.setDigests( + KeyProperties.DIGEST_NONE, + KeyProperties.DIGEST_MD5, + KeyProperties.DIGEST_SHA1, + KeyProperties.DIGEST_SHA224, + KeyProperties.DIGEST_SHA256, + KeyProperties.DIGEST_SHA384, + KeyProperties.DIGEST_SHA512); + // Authorized to be used with any encryption and signature padding + // schemes (including no padding). + specBuilder.setEncryptionPaddings( + KeyProperties.ENCRYPTION_PADDING_NONE, + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1, + KeyProperties.ENCRYPTION_PADDING_RSA_OAEP); + specBuilder.setSignaturePaddings( + KeyProperties.SIGNATURE_PADDING_RSA_PKCS1, + KeyProperties.SIGNATURE_PADDING_RSA_PSS); + // Disable randomized encryption requirement to support encryption + // padding NONE above. + specBuilder.setRandomizedEncryptionRequired(false); + } else { + throw new KeyStoreException("Unsupported key algorithm: " + keyAlgorithm); + } + specBuilder.setUserAuthenticationRequired(false); + + return specBuilder.build(); + } + + private void setPrivateKeyEntry(String alias, PrivateKey key, Certificate[] chain, + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { + @SecurityLevel int securitylevel = SecurityLevel.TRUSTED_ENVIRONMENT; + int flags = 0; + KeyProtection spec; + if (param == null) { + spec = getLegacyKeyProtectionParameter(key); + } else if (param instanceof KeyStoreParameter) { + spec = getLegacyKeyProtectionParameter(key); + KeyStoreParameter legacySpec = (KeyStoreParameter) param; + } else if (param instanceof KeyProtection) { + spec = (KeyProtection) param; + if (spec.isCriticalToDeviceEncryption()) { + // This key is should not be bound to the LSKF even if it is auth bound. + // This indicates that this key is used in the derivation for of the + // master key, that is used for the LSKF binding of other auth bound + // keys. This breaks up a circular dependency while retaining logical + // authentication binding of the key. + flags |= IKeystoreSecurityLevel + .KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; + } + + if (spec.isStrongBoxBacked()) { + securitylevel = SecurityLevel.STRONGBOX; + } + } else { + throw new KeyStoreException( + "Unsupported protection parameter class:" + param.getClass().getName() + + ". Supported: " + KeyProtection.class.getName() + ", " + + KeyStoreParameter.class.getName()); + } + + // Make sure the chain exists since this is a PrivateKey + if ((chain == null) || (chain.length == 0)) { + throw new KeyStoreException("Must supply at least one Certificate with PrivateKey"); + } + + // Do chain type checking. + X509Certificate[] x509chain = new X509Certificate[chain.length]; + for (int i = 0; i < chain.length; i++) { + if (!"X.509".equals(chain[i].getType())) { + throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" + + i); + } + + if (!(chain[i] instanceof X509Certificate)) { + throw new KeyStoreException("Certificates must be in X.509 format: invalid cert #" + + i); + } + + x509chain[i] = (X509Certificate) chain[i]; + } + + final byte[] userCertBytes; + try { + userCertBytes = x509chain[0].getEncoded(); + } catch (CertificateEncodingException e) { + throw new KeyStoreException("Failed to encode certificate #0", e); + } + + /* + * If we have a chain, store it in the CA certificate slot for this + * alias as concatenated DER-encoded certificates. These can be + * deserialized by {@link CertificateFactory#generateCertificates}. + */ + final byte[] chainBytes; + if (chain.length > 1) { + /* + * The chain is passed in as {user_cert, ca_cert_1, ca_cert_2, ...} + * so we only need the certificates starting at index 1. + */ + final byte[][] certsBytes = new byte[x509chain.length - 1][]; + int totalCertLength = 0; + for (int i = 0; i < certsBytes.length; i++) { + try { + certsBytes[i] = x509chain[i + 1].getEncoded(); + totalCertLength += certsBytes[i].length; + } catch (CertificateEncodingException e) { + throw new KeyStoreException("Failed to encode certificate #" + i, e); + } + } + + /* + * Serialize this into one byte array so we can later call + * CertificateFactory#generateCertificates to recover them. + */ + chainBytes = new byte[totalCertLength]; + int outputOffset = 0; + for (int i = 0; i < certsBytes.length; i++) { + final int certLength = certsBytes[i].length; + System.arraycopy(certsBytes[i], 0, chainBytes, outputOffset, certLength); + outputOffset += certLength; + certsBytes[i] = null; + } + } else { + chainBytes = null; + } + + @Domain int targetDomain = getTargetDomain(); + + // If the given key is an AndroidKeyStorePrivateKey, we attempt to update + // its subcomponents with the given certificate and certificate chain. + if (key instanceof AndroidKeyStorePrivateKey) { + AndroidKeyStoreKey ksKey = (AndroidKeyStoreKey) key; + KeyDescriptor descriptor = ksKey.getUserKeyDescriptor(); + + // This throws if the request cannot replace the entry. + assertCanReplace(alias, targetDomain, mNamespace, descriptor); + + try { + mKeyStore.updateSubcomponents( + ((AndroidKeyStorePrivateKey) key).getKeyIdDescriptor(), + userCertBytes, chainBytes); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to store certificate and certificate chain", e); + } + return; + } + + // Make sure the PrivateKey format is the one we support. + final String keyFormat = key.getFormat(); + if ((keyFormat == null) || (!"PKCS#8".equals(keyFormat))) { + throw new KeyStoreException( + "Unsupported private key export format: " + keyFormat + + ". Only private keys which export their key material in PKCS#8 format are" + + " supported."); + } + + // Make sure we can actually encode the key. + byte[] pkcs8EncodedPrivateKeyBytes = key.getEncoded(); + if (pkcs8EncodedPrivateKeyBytes == null) { + throw new KeyStoreException("Private key did not export any key material"); + } + + final List<KeyParameter> importArgs = new ArrayList<>(); + + try { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeyProperties.KeyAlgorithm.toKeymasterAsymmetricKeyAlgorithm( + key.getAlgorithm())) + ); + KeyStore2ParameterUtils.forEachSetFlag(spec.getPurposes(), (purpose) -> { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.toKeymaster(purpose) + )); + }); + if (spec.isDigestsSpecified()) { + for (String digest : spec.getDigests()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, + KeyProperties.Digest.toKeymaster(digest) + )); + } + } + for (String blockMode : spec.getBlockModes()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyProperties.BlockMode.toKeymaster(blockMode) + )); + } + int[] keymasterEncryptionPaddings = + KeyProperties.EncryptionPadding.allToKeymaster( + spec.getEncryptionPaddings()); + if (((spec.getPurposes() & KeyProperties.PURPOSE_DECRYPT) != 0) + && (spec.isRandomizedEncryptionRequired())) { + for (int keymasterPadding : keymasterEncryptionPaddings) { + if (!KeymasterUtils + .isKeymasterPaddingSchemeIndCpaCompatibleWithAsymmetricCrypto( + keymasterPadding)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but is violated by" + + " encryption padding mode: " + + KeyProperties.EncryptionPadding.fromKeymaster( + keymasterPadding) + + ". See KeyProtection documentation."); + } + } + } + for (int padding : keymasterEncryptionPaddings) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + padding + )); + } + for (String padding : spec.getSignaturePaddings()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + KeyProperties.SignaturePadding.toKeymaster(padding) + )); + } + KeyStore2ParameterUtils.addUserAuthArgs(importArgs, spec); + if (spec.getKeyValidityStart() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart() + )); + } + if (spec.getKeyValidityForOriginationEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + spec.getKeyValidityForOriginationEnd() + )); + } + if (spec.getKeyValidityForConsumptionEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + spec.getKeyValidityForConsumptionEnd() + )); + } + } catch (IllegalArgumentException | IllegalStateException e) { + throw new KeyStoreException(e); + } + + try { + KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( + securitylevel); + + KeyDescriptor descriptor = makeKeyDescriptor(alias); + + KeyMetadata metadata = securityLevelInterface.importKey(descriptor, null, + importArgs, flags, pkcs8EncodedPrivateKeyBytes); + + try { + mKeyStore.updateSubcomponents(metadata.key, userCertBytes, chainBytes); + } catch (android.security.KeyStoreException e) { + mKeyStore.deleteKey(metadata.key); + throw new KeyStoreException("Failed to store certificate and certificate chain", e); + } + + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to store private key", e); + } + } + + private static void assertCanReplace(String alias, @Domain int targetDomain, + int targetNamespace, KeyDescriptor descriptor) + throws KeyStoreException { + // If + // * the alias does not match, or + // * the domain does not match, or + // * the domain is Domain.SELINUX and the namespaces don not match, + // then the designated key location is not equivalent to the location of the + // given key parameter and cannot be updated. + // + // Note: mNamespace == KeyProperties.NAMESPACE_APPLICATION implies that the target domain + // is Domain.APP and Domain.SELINUX is the target domain otherwise. + if (alias != descriptor.alias + || descriptor.domain != targetDomain + || (descriptor.domain == Domain.SELINUX && descriptor.nspace != targetNamespace)) { + throw new KeyStoreException("Can only replace keys with same alias: " + alias + + " != " + descriptor.alias + " in the same target domain: " + targetDomain + + " != " + descriptor.domain + + (targetDomain == Domain.SELINUX ? " in the same target namespace: " + + targetNamespace + " != " + descriptor.nspace : "") + ); + } + } + + private void setSecretKeyEntry(String alias, SecretKey key, + java.security.KeyStore.ProtectionParameter param) + throws KeyStoreException { + if ((param != null) && (!(param instanceof KeyProtection))) { + throw new KeyStoreException( + "Unsupported protection parameter class: " + param.getClass().getName() + + ". Supported: " + KeyProtection.class.getName()); + } + KeyProtection params = (KeyProtection) param; + + @SecurityLevel int securityLevel = params.isStrongBoxBacked() ? SecurityLevel.STRONGBOX : + SecurityLevel.TRUSTED_ENVIRONMENT; + @Domain int targetDomain = (getTargetDomain()); + + if (key instanceof AndroidKeyStoreSecretKey) { + String keyAliasInKeystore = + ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor().alias; + + KeyDescriptor descriptor = ((AndroidKeyStoreSecretKey) key).getUserKeyDescriptor(); + + // This throws if the request cannot replace the existing key. + assertCanReplace(alias, targetDomain, mNamespace, descriptor); + + // This is the entry where this key is already stored. No need to do anything. + if (params != null) { + throw new KeyStoreException("Modifying KeyStore-backed key using protection" + + " parameters not supported"); + } + return; + } + + if (params == null) { + throw new KeyStoreException( + "Protection parameters must be specified when importing a symmetric key"); + } + + // Not a KeyStore-backed secret key -- import its key material into keystore. + String keyExportFormat = key.getFormat(); + if (keyExportFormat == null) { + throw new KeyStoreException( + "Only secret keys that export their key material are supported"); + } else if (!"RAW".equals(keyExportFormat)) { + throw new KeyStoreException( + "Unsupported secret key material export format: " + keyExportFormat); + } + byte[] keyMaterial = key.getEncoded(); + if (keyMaterial == null) { + throw new KeyStoreException("Key did not export its key material despite supporting" + + " RAW format export"); + } + + final List<KeyParameter> importArgs = new ArrayList<>(); + + try { + int keymasterAlgorithm = + KeyProperties.KeyAlgorithm.toKeymasterSecretKeyAlgorithm( + key.getAlgorithm()); + + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + keymasterAlgorithm + )); + + if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_HMAC) { + // JCA HMAC key algorithm implies a digest (e.g., HmacSHA256 key algorithm + // implies SHA-256 digest). Because keymaster HMAC key is authorized only for one + // digest, we don't let import parameters override the digest implied by the key. + // If the parameters specify digests at all, they must specify only one digest, the + // only implied by key algorithm. + int keymasterImpliedDigest = + KeyProperties.KeyAlgorithm.toKeymasterDigest(key.getAlgorithm()); + if (keymasterImpliedDigest == -1) { + throw new ProviderException( + "HMAC key algorithm digest unknown for key algorithm " + + key.getAlgorithm()); + } + + if (params.isDigestsSpecified()) { + // Digest(s) explicitly specified in params -- check that the list consists of + // exactly one digest, the one implied by key algorithm. + int[] keymasterDigestsFromParams = + KeyProperties.Digest.allToKeymaster(params.getDigests()); + if ((keymasterDigestsFromParams.length != 1) + || (keymasterDigestsFromParams[0] != keymasterImpliedDigest)) { + throw new KeyStoreException( + "Unsupported digests specification: " + + Arrays.asList(params.getDigests()) + ". Only " + + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest) + + " supported for HMAC key algorithm " + + key.getAlgorithm()); + } + } + int outputBits = KeymasterUtils.getDigestOutputSizeBits(keymasterImpliedDigest); + if (outputBits == -1) { + throw new ProviderException( + "HMAC key authorized for unsupported digest: " + + KeyProperties.Digest.fromKeymaster(keymasterImpliedDigest)); + } + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, keymasterImpliedDigest + )); + importArgs.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, outputBits + )); + } else { + if (params.isDigestsSpecified()) { + for (String digest : params.getDigests()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, + KeyProperties.Digest.toKeymaster(digest) + )); + } + } + } + + KeyStore2ParameterUtils.forEachSetFlag(params.getPurposes(), (purpose) -> { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, + KeyProperties.Purpose.toKeymaster(purpose) + )); + }); + + boolean indCpa = false; + if ((params.getPurposes() & KeyProperties.PURPOSE_ENCRYPT) != 0) { + if (((KeyProtection) param).isRandomizedEncryptionRequired()) { + indCpa = true; + } else { + importArgs.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_CALLER_NONCE + )); + } + } + + for (String blockMode : params.getBlockModes()) { + int keymasterBlockMode = KeyProperties.BlockMode.toKeymaster(blockMode); + if (indCpa + && !KeymasterUtils.isKeymasterBlockModeIndCpaCompatibleWithSymmetricCrypto( + keymasterBlockMode)) { + throw new KeyStoreException( + "Randomized encryption (IND-CPA) required but may be violated by" + + " block mode: " + blockMode + + ". See KeyProtection documentation."); + + } + if (keymasterAlgorithm == KeymasterDefs.KM_ALGORITHM_AES + && keymasterBlockMode == KeymasterDefs.KM_MODE_GCM) { + importArgs.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_MIN_MAC_LENGTH, + AndroidKeyStoreAuthenticatedAESCipherSpi.GCM + .MIN_SUPPORTED_TAG_LENGTH_BITS + )); + } + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + keymasterBlockMode + )); + } + + if (params.getSignaturePaddings().length > 0) { + throw new KeyStoreException("Signature paddings not supported for symmetric keys"); + } + + for (String padding : params.getEncryptionPaddings()) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + KeyProperties.EncryptionPadding.toKeymaster(padding) + )); + } + + KeyStore2ParameterUtils.addUserAuthArgs(importArgs, params); + + if (params.getKeyValidityStart() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ACTIVE_DATETIME, params.getKeyValidityStart() + )); + } + if (params.getKeyValidityForOriginationEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, + params.getKeyValidityForOriginationEnd() + )); + } + if (params.getKeyValidityForConsumptionEnd() != null) { + importArgs.add(KeyStore2ParameterUtils.makeDate( + KeymasterDefs.KM_TAG_USAGE_EXPIRE_DATETIME, + params.getKeyValidityForConsumptionEnd() + )); + } + } catch (IllegalArgumentException | IllegalStateException e) { + throw new KeyStoreException(e); + } + + int flags = 0; + if (params.isCriticalToDeviceEncryption()) { + flags |= IKeystoreSecurityLevel.KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING; + } + + try { + KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel( + securityLevel); + + KeyDescriptor descriptor = makeKeyDescriptor(alias); + + securityLevelInterface.importKey(descriptor, null /* TODO attestationKey */, + importArgs, flags, keyMaterial); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to import secret key.", e); + } + } + + private void setWrappedKeyEntry(String alias, WrappedKeyEntry entry, + java.security.KeyStore.ProtectionParameter param) throws KeyStoreException { + if (param != null) { + throw new KeyStoreException("Protection parameters are specified inside wrapped keys"); + } + + byte[] maskingKey = new byte[32]; + + String[] parts = entry.getTransformation().split("/"); + + List<KeyParameter> args = new ArrayList<>(); + + String algorithm = parts[0]; + if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(algorithm)) { + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, + KeymasterDefs.KM_ALGORITHM_RSA + )); + } else { + throw new KeyStoreException("Algorithm \"" + algorithm + "\" not supported for " + + "wrapping. Only RSA wrapping keys are supported."); + } + + if (parts.length > 1) { + String mode = parts[1]; + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, + KeyProperties.BlockMode.toKeymaster(mode) + )); + } + + if (parts.length > 2) { + @KeyProperties.EncryptionPaddingEnum int padding = + KeyProperties.EncryptionPadding.toKeymaster(parts[2]); + if (padding != KeymasterDefs.KM_PAD_NONE) { + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, + padding + )); + } + } + + KeyGenParameterSpec spec = (KeyGenParameterSpec) entry.getAlgorithmParameterSpec(); + if (spec.isDigestsSpecified()) { + @KeyProperties.DigestEnum int digest = + KeyProperties.Digest.toKeymaster(spec.getDigests()[0]); + if (digest != KeymasterDefs.KM_DIGEST_NONE) { + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_DIGEST, + digest + )); + } + } + + KeyDescriptor wrappingkey = makeKeyDescriptor(entry.getWrappingKeyAlias()); + + KeyEntryResponse response = null; + try { + response = mKeyStore.getKeyEntry(wrappingkey); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Failed to load wrapping key.", e); + } + + KeyDescriptor wrappedKey = makeKeyDescriptor(alias); + + KeyStoreSecurityLevel securityLevel = new KeyStoreSecurityLevel(response.iSecurityLevel); + + final BiometricManager bm = android.app.AppGlobals.getInitialApplication() + .getSystemService(BiometricManager.class); + + long[] biometricSids = bm.getAuthenticatorIds(); + + List<AuthenticatorSpec> authenticatorSpecs = new ArrayList<>(); + + AuthenticatorSpec authenticatorSpec = new AuthenticatorSpec(); + // TODO Replace with HardwareAuthenticatorType.PASSWORD when KeyMint AIDL spec has landed. + authenticatorSpec.authenticatorType = 1; // HardwareAuthenticatorType.PASSWORD + authenticatorSpec.authenticatorId = GateKeeper.getSecureUserId(); + authenticatorSpecs.add(authenticatorSpec); + + for (long sid : biometricSids) { + AuthenticatorSpec authSpec = new AuthenticatorSpec(); + // TODO Replace with HardwareAuthenticatorType.FINGERPRINT when KeyMint AIDL spec has + // landed. + authSpec.authenticatorType = 2; // HardwareAuthenticatorType.FINGERPRINT + authSpec.authenticatorId = sid; + authenticatorSpecs.add(authSpec); + } + + try { + securityLevel.importWrappedKey( + wrappedKey, wrappingkey, + entry.getWrappedKeyBytes(), + null /* masking key is set to 32 bytes if null is given here */, + args, + authenticatorSpecs.toArray(new AuthenticatorSpec[0])); + } catch (android.security.KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_UNIMPLEMENTED: { + throw new SecureKeyImportUnavailableException("Could not import wrapped key"); + } + default: + throw new KeyStoreException("Failed to import wrapped key. Keystore error " + + "code: " + e.getErrorCode(), e); + } + } + } + + @Override + public void engineSetKeyEntry(String alias, byte[] userKey, Certificate[] chain) + throws KeyStoreException { + throw new KeyStoreException("Operation not supported because key encoding is unknown"); + } + + /** + * This function sets a trusted certificate entry. It fails if the given + * alias is already taken by an actual key entry. However, if the entry is a + * trusted certificate it will get silently replaced. + * @param alias the alias name + * @param cert the certificate + * + * @throws KeyStoreException if the alias is already taken by a secret or private + * key entry. + * @throws KeyStoreException with a nested {@link CertificateEncodingException} + * if the {@code cert.getEncoded()} throws. + * @throws KeyStoreException with a nested {@link android.security.KeyStoreException} if + * something went wrong while inserting the certificate into keystore. + * @throws NullPointerException if cert or alias is null. + * + * @hide + */ + @Override + public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException { + if (isKeyEntry(alias)) { + throw new KeyStoreException("Entry exists and is not a trusted certificate"); + } + + // We can't set something to null. + if (cert == null) { + throw new NullPointerException("cert == null"); + } + + final byte[] encoded; + try { + encoded = cert.getEncoded(); + } catch (CertificateEncodingException e) { + throw new KeyStoreException(e); + } + + try { + mKeyStore.updateSubcomponents(makeKeyDescriptor(alias), + null /* publicCert - unused when used as pure certificate store. */, + encoded); + } catch (android.security.KeyStoreException e) { + throw new KeyStoreException("Couldn't insert certificate.", e); + } + } + + @Override + public void engineDeleteEntry(String alias) throws KeyStoreException { + KeyDescriptor descriptor = makeKeyDescriptor(alias); + try { + mKeyStore.deleteKey(descriptor); + } catch (android.security.KeyStoreException e) { + if (e.getErrorCode() != ResponseCode.KEY_NOT_FOUND) { + throw new KeyStoreException("Failed to delete entry: " + alias, e); + } + } + } + + private Set<String> getUniqueAliases() { + + try { + final KeyDescriptor[] keys = mKeyStore.list( + getTargetDomain(), + mNamespace + ); + final Set<String> aliases = new HashSet<>(keys.length); + for (KeyDescriptor d : keys) { + aliases.add(d.alias); + } + return aliases; + } catch (android.security.KeyStoreException e) { + Log.e(TAG, "Failed to list keystore entries.", e); + return null; + } + } + + @Override + public Enumeration<String> engineAliases() { + return Collections.enumeration(getUniqueAliases()); + } + + @Override + public boolean engineContainsAlias(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + return getKeyMetadata(alias) != null; + } + + @Override + public int engineSize() { + return getUniqueAliases().size(); + } + + @Override + public boolean engineIsKeyEntry(String alias) { + return isKeyEntry(alias); + } + + private boolean isKeyEntry(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + + KeyEntryResponse response = getKeyMetadata(alias); + // If response is null, there is no such entry. + // If response.iSecurityLevel is null, there is no private or secret key material stored. + return response != null && response.iSecurityLevel != null; + } + + + @Override + public boolean engineIsCertificateEntry(String alias) { + if (alias == null) { + throw new NullPointerException("alias == null"); + } + KeyEntryResponse response = getKeyMetadata(alias); + // If response == null there is no such entry. + // If there is no certificateChain, then this is not a certificate entry. + // If there is a private key entry, this is the certificate chain for that + // key entry and not a CA certificate entry. + return response != null + && response.metadata.certificateChain != null + && response.iSecurityLevel == null; + } + + @Override + public String engineGetCertificateAlias(Certificate cert) { + if (cert == null) { + return null; + } + if (!"X.509".equalsIgnoreCase(cert.getType())) { + Log.e(TAG, "In engineGetCertificateAlias: only X.509 certificates are supported."); + return null; + } + byte[] targetCertBytes; + try { + targetCertBytes = cert.getEncoded(); + } catch (CertificateEncodingException e) { + Log.e(TAG, "While trying to get the alias for a certificate.", e); + return null; + } + if (targetCertBytes == null) { + return null; + } + + KeyDescriptor[] keyDescriptors = null; + try { + keyDescriptors = mKeyStore.list( + getTargetDomain(), + mNamespace + ); + } catch (android.security.KeyStoreException e) { + Log.w(TAG, "Failed to get list of keystore entries.", e); + } + + String caAlias = null; + for (KeyDescriptor d : keyDescriptors) { + KeyEntryResponse response = getKeyMetadata(d.alias); + if (response == null) { + continue; + } + /* + * The KeyStoreSpi documentation says to only compare the first certificate in the + * chain which is equivalent to the {@code response.metadata.certificate} field. + * So we look for a hit in this field first. For pure CA certificate entries, + * we check the {@code response.metadata.certificateChain} field. But we only + * return a CA alias if there was no hit in the certificate field of any other + * entry. + */ + if (response.metadata.certificate != null) { + if (Arrays.equals(response.metadata.certificate, targetCertBytes)) { + return d.alias; + } + } else if (response.metadata.certificateChain != null && caAlias == null) { + if (Arrays.equals(response.metadata.certificateChain, targetCertBytes)) { + caAlias = d.alias; + } + } + } + return caAlias; + } + + @Override + public void engineStore(OutputStream stream, char[] password) throws IOException, + NoSuchAlgorithmException, CertificateException { + throw new UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream"); + } + + @Override + public void engineLoad(InputStream stream, char[] password) throws IOException, + NoSuchAlgorithmException, CertificateException { + if (stream != null) { + throw new IllegalArgumentException("InputStream not supported"); + } + + if (password != null) { + throw new IllegalArgumentException("password not supported"); + } + + // Unfortunate name collision. + mKeyStore = KeyStore2.getInstance(); + mNamespace = KeyProperties.NAMESPACE_APPLICATION; + } + + @Override + public void engineLoad(LoadStoreParameter param) throws IOException, + NoSuchAlgorithmException, CertificateException { + int namespace = KeyProperties.NAMESPACE_APPLICATION; + if (param != null) { + if (param instanceof AndroidKeyStoreLoadStoreParameter) { + namespace = ((AndroidKeyStoreLoadStoreParameter) param).getNamespace(); + } else { + throw new IllegalArgumentException( + "Unsupported param type: " + param.getClass()); + } + } + mKeyStore = KeyStore2.getInstance(); + mNamespace = namespace; + } + + @Override + public void engineSetEntry(String alias, Entry entry, ProtectionParameter param) + throws KeyStoreException { + if (entry == null) { + throw new KeyStoreException("entry == null"); + } + + if (entry instanceof java.security.KeyStore.TrustedCertificateEntry) { + java.security.KeyStore.TrustedCertificateEntry trE = + (java.security.KeyStore.TrustedCertificateEntry) entry; + // engineSetCertificateEntry does not overwrite if the existing entry + // is a key entry, but the semantic of engineSetEntry is such that it + // overwrites any existing entry. Thus we delete any possible existing + // entry by this alias. + engineDeleteEntry(alias); + engineSetCertificateEntry(alias, trE.getTrustedCertificate()); + return; + } + + if (entry instanceof PrivateKeyEntry) { + PrivateKeyEntry prE = (PrivateKeyEntry) entry; + setPrivateKeyEntry(alias, prE.getPrivateKey(), prE.getCertificateChain(), param); + } else if (entry instanceof SecretKeyEntry) { + SecretKeyEntry secE = (SecretKeyEntry) entry; + setSecretKeyEntry(alias, secE.getSecretKey(), param); + } else if (entry instanceof WrappedKeyEntry) { + WrappedKeyEntry wke = (WrappedKeyEntry) entry; + setWrappedKeyEntry(alias, wke, param); + } else { + throw new KeyStoreException( + "Entry must be a PrivateKeyEntry, SecretKeyEntry, WrappedKeyEntry " + + "or TrustedCertificateEntry; was " + entry); + } + } +} diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java new file mode 100644 index 000000000000..3d5a8f63e7f9 --- /dev/null +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreUnauthenticatedAESCipherSpi.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; +import android.security.keystore.KeyProperties; +import android.system.keystore2.KeyParameter; + +import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.ProviderException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; +import java.util.List; + +import javax.crypto.CipherSpi; +import javax.crypto.spec.IvParameterSpec; + +/** + * Base class for Android Keystore unauthenticated AES {@link CipherSpi} implementations. + * + * @hide + */ +class AndroidKeyStoreUnauthenticatedAESCipherSpi extends AndroidKeyStoreCipherSpiBase { + + abstract static class ECB extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected ECB(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_ECB, keymasterPadding, false); + } + + public static class NoPadding extends ECB { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends ECB { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CBC extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected CBC(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CBC, keymasterPadding, true); + } + + public static class NoPadding extends CBC { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + + public static class PKCS7Padding extends CBC { + public PKCS7Padding() { + super(KeymasterDefs.KM_PAD_PKCS7); + } + } + } + + abstract static class CTR extends AndroidKeyStoreUnauthenticatedAESCipherSpi { + protected CTR(int keymasterPadding) { + super(KeymasterDefs.KM_MODE_CTR, keymasterPadding, true); + } + + public static class NoPadding extends CTR { + public NoPadding() { + super(KeymasterDefs.KM_PAD_NONE); + } + } + } + + private static final int BLOCK_SIZE_BYTES = 16; + + private final int mKeymasterBlockMode; + private final int mKeymasterPadding; + /** Whether this transformation requires an IV. */ + private final boolean mIvRequired; + + private byte[] mIv; + + /** Whether the current {@code #mIv} has been used by the underlying crypto operation. */ + private boolean mIvHasBeenUsed; + + AndroidKeyStoreUnauthenticatedAESCipherSpi( + int keymasterBlockMode, + int keymasterPadding, + boolean ivRequired) { + mKeymasterBlockMode = keymasterBlockMode; + mKeymasterPadding = keymasterPadding; + mIvRequired = ivRequired; + } + + @Override + protected final void resetAll() { + mIv = null; + mIvHasBeenUsed = false; + super.resetAll(); + } + + @Override + protected final void resetWhilePreservingInitState() { + super.resetWhilePreservingInitState(); + } + + @Override + protected final void initKey(int opmode, Key key) throws InvalidKeyException { + if (!(key instanceof AndroidKeyStoreSecretKey)) { + throw new InvalidKeyException( + "Unsupported key: " + ((key != null) ? key.getClass().getName() : "null")); + } + if (!KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(key.getAlgorithm())) { + throw new InvalidKeyException( + "Unsupported key algorithm: " + key.getAlgorithm() + ". Only " + + KeyProperties.KEY_ALGORITHM_AES + " supported"); + } + setKey((AndroidKeyStoreSecretKey) key); + } + + @Override + protected final void initAlgorithmSpecificParameters() throws InvalidKeyException { + if (!mIvRequired) { + return; + } + + // IV is used + if (!isEncrypting()) { + throw new InvalidKeyException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameterSpec params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException( + "IvParameterSpec must be provided when decrypting"); + } + return; + } + if (!(params instanceof IvParameterSpec)) { + throw new InvalidAlgorithmParameterException("Only IvParameterSpec supported"); + } + mIv = ((IvParameterSpec) params).getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in IvParameterSpec"); + } + } + + @Override + protected final void initAlgorithmSpecificParameters(AlgorithmParameters params) + throws InvalidAlgorithmParameterException { + if (!mIvRequired) { + if (params != null) { + throw new InvalidAlgorithmParameterException("Unsupported parameters: " + params); + } + return; + } + + // IV is used + if (params == null) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ". Use IvParameterSpec or AlgorithmParameters to provide it."); + } + return; + } + + if (!"AES".equalsIgnoreCase(params.getAlgorithm())) { + throw new InvalidAlgorithmParameterException( + "Unsupported AlgorithmParameters algorithm: " + params.getAlgorithm() + + ". Supported: AES"); + } + + IvParameterSpec ivSpec; + try { + ivSpec = params.getParameterSpec(IvParameterSpec.class); + } catch (InvalidParameterSpecException e) { + if (!isEncrypting()) { + // IV must be provided by the caller + throw new InvalidAlgorithmParameterException("IV required when decrypting" + + ", but not found in parameters: " + params, e); + } + mIv = null; + return; + } + mIv = ivSpec.getIV(); + if (mIv == null) { + throw new InvalidAlgorithmParameterException("Null IV in AlgorithmParameters"); + } + } + + @Override + protected final int getAdditionalEntropyAmountForBegin() { + if ((mIvRequired) && (mIv == null) && (isEncrypting())) { + // IV will need to be generated + return BLOCK_SIZE_BYTES; + } + + return 0; + } + + @Override + protected final int getAdditionalEntropyAmountForFinish() { + return 0; + } + + @Override + protected final void addAlgorithmSpecificParametersToBegin( + @NonNull List<KeyParameter> parameters) { + if ((isEncrypting()) && (mIvRequired) && (mIvHasBeenUsed)) { + // IV is being reused for encryption: this violates security best practices. + throw new IllegalStateException( + "IV has already been used. Reusing IV in encryption mode violates security best" + + " practices."); + } + + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_AES + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockMode + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PADDING, mKeymasterPadding + )); + if ((mIvRequired) && (mIv != null)) { + parameters.add(KeyStore2ParameterUtils.makeBytes( + KeymasterDefs.KM_TAG_NONCE, mIv + )); + } + } + + @Override + protected final void loadAlgorithmSpecificParametersFromBeginResult( + KeyParameter[] parameters) { + mIvHasBeenUsed = true; + + // NOTE: Keymaster doesn't always return an IV, even if it's used. + byte[] returnedIv = null; + if (parameters != null) { + for (KeyParameter p : parameters) { + if (p.tag == KeymasterDefs.KM_TAG_NONCE) { + returnedIv = p.blob; + break; + } + } + } + + if (mIvRequired) { + if (mIv == null) { + mIv = returnedIv; + } else if ((returnedIv != null) && (!Arrays.equals(returnedIv, mIv))) { + throw new ProviderException("IV in use differs from provided IV"); + } + } else { + if (returnedIv != null) { + throw new ProviderException( + "IV in use despite IV not being used by this transformation"); + } + } + } + + @Override + protected final int engineGetBlockSize() { + return BLOCK_SIZE_BYTES; + } + + @Override + protected final int engineGetOutputSize(int inputLen) { + return inputLen + 3 * BLOCK_SIZE_BYTES; + } + + @Override + protected final byte[] engineGetIV() { + return ArrayUtils.cloneIfNotEmpty(mIv); + } + + @Nullable + @Override + protected final AlgorithmParameters engineGetParameters() { + if (!mIvRequired) { + return null; + } + if ((mIv != null) && (mIv.length > 0)) { + try { + AlgorithmParameters params = AlgorithmParameters.getInstance("AES"); + params.init(new IvParameterSpec(mIv)); + return params; + } catch (NoSuchAlgorithmException e) { + throw new ProviderException( + "Failed to obtain AES AlgorithmParameters", e); + } catch (InvalidParameterSpecException e) { + throw new ProviderException( + "Failed to initialize AES AlgorithmParameters with an IV", + e); + } + } + return null; + } +} diff --git a/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java new file mode 100644 index 000000000000..ee67ed3f76d8 --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStore2ParameterUtils.java @@ -0,0 +1,313 @@ +/* + * 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.security.keystore2; + +import android.annotation.NonNull; +import android.hardware.biometrics.BiometricManager; +import android.security.GateKeeper; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyProperties; +import android.security.keystore.UserAuthArgs; +import android.system.keystore2.Authorization; +import android.system.keystore2.KeyParameter; +import android.system.keystore2.SecurityLevel; + +import java.security.ProviderException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.function.Consumer; + +/** + * @hide + */ +public abstract class KeyStore2ParameterUtils { + + /** + * This function constructs a {@link KeyParameter} expressing a boolean value. + * @param tag Must be KeyMint tag with the associated type BOOL. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeBool(int tag) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_BOOL) { + throw new IllegalArgumentException("Not a boolean tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.boolValue = true; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing an enum value. + * @param tag Must be KeyMint tag with the associated type ENUM or ENUM_REP. + * @param v A 32bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeEnum(int tag, int v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_ENUM && type != KeymasterDefs.KM_ENUM_REP) { + throw new IllegalArgumentException("Not an enum or repeatable enum tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.integer = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing an integer value. + * @param tag Must be KeyMint tag with the associated type UINT or UINT_REP. + * @param v A 32bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeInt(int tag, int v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_UINT && type != KeymasterDefs.KM_UINT_REP) { + throw new IllegalArgumentException("Not an int or repeatable int tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.integer = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing a long integer value. + * @param tag Must be KeyMint tag with the associated type ULONG or ULONG_REP. + * @param v A 64bit integer. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeLong(int tag, long v) { + int type = KeymasterDefs.getTagType(tag); + if (type != KeymasterDefs.KM_ULONG && type != KeymasterDefs.KM_ULONG_REP) { + throw new IllegalArgumentException("Not a long or repeatable long tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.longInteger = v; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing a blob. + * @param tag Must be KeyMint tag with the associated type BYTES. + * @param b A byte array to be stored in the new key parameter. + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeBytes(int tag, @NonNull byte[] b) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_BYTES) { + throw new IllegalArgumentException("Not a bytes tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.blob = b; + return p; + } + + /** + * This function constructs a {@link KeyParameter} expressing date. + * @param tag Must be KeyMint tag with the associated type DATE. + * @param date A date + * @return An instance of {@link KeyParameter}. + * @hide + */ + static @NonNull KeyParameter makeDate(int tag, @NonNull Date date) { + if (KeymasterDefs.getTagType(tag) != KeymasterDefs.KM_DATE) { + throw new IllegalArgumentException("Not a date tag: " + tag); + } + KeyParameter p = new KeyParameter(); + p.tag = tag; + p.longInteger = date.getTime(); + if (p.longInteger < 0) { + throw new IllegalArgumentException("Date tag value out of range: " + p.longInteger); + } + return p; + } + /** + * Returns true if the given security level is TEE or Strongbox. + * + * @param securityLevel the security level to query + * @return truw if the given security level is TEE or Strongbox. + */ + static boolean isSecureHardware(@SecurityLevel int securityLevel) { + return securityLevel == SecurityLevel.TRUSTED_ENVIRONMENT + || securityLevel == SecurityLevel.STRONGBOX; + } + + static long getUnsignedInt(@NonNull Authorization param) { + if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_UINT) { + throw new IllegalArgumentException("Not an int tag: " + param.keyParameter.tag); + } + // KM_UINT is 32 bits wide so we must suppress sign extension. + return ((long) param.keyParameter.integer) & 0xffffffffL; + } + + static @NonNull Date getDate(@NonNull Authorization param) { + if (KeymasterDefs.getTagType(param.keyParameter.tag) != KeymasterDefs.KM_DATE) { + throw new IllegalArgumentException("Not a date tag: " + param.keyParameter.tag); + } + if (param.keyParameter.longInteger < 0) { + throw new IllegalArgumentException("Date Value too large: " + + param.keyParameter.longInteger); + } + return new Date(param.keyParameter.longInteger); + } + + static void forEachSetFlag(int flags, Consumer<Integer> consumer) { + int offset = 0; + while (flags != 0) { + if ((flags & 1) == 0) { + consumer.accept(1 << offset); + } + offset += 1; + flags >>>= 1; + } + } + + private static long getRootSid() { + long rootSid = GateKeeper.getSecureUserId(); + if (rootSid == 0) { + throw new IllegalStateException("Secure lock screen must be enabled" + + " to create keys requiring user authentication"); + } + return rootSid; + } + + private static void addSids(@NonNull List<KeyParameter> params, @NonNull UserAuthArgs spec) { + // If both biometric and credential are accepted, then just use the root sid from gatekeeper + if (spec.getUserAuthenticationType() == (KeyProperties.AUTH_BIOMETRIC_STRONG + | KeyProperties.AUTH_DEVICE_CREDENTIAL)) { + if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { + params.add(makeLong( + KeymasterDefs.KM_TAG_USER_SECURE_ID, + spec.getBoundToSpecificSecureUserId() + )); + } else { + // The key is authorized for use for the specified amount of time after the user has + // authenticated. Whatever unlocks the secure lock screen should authorize this key. + params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, getRootSid())); + } + } else { + List<Long> sids = new ArrayList<>(); + if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_BIOMETRIC_STRONG) != 0) { + final BiometricManager bm = android.app.AppGlobals.getInitialApplication() + .getSystemService(BiometricManager.class); + + // TODO: Restore permission check in getAuthenticatorIds once the ID is no longer + // needed here. + + final long[] biometricSids = bm.getAuthenticatorIds(); + + if (biometricSids.length == 0) { + throw new IllegalStateException( + "At least one biometric must be enrolled to create keys requiring user" + + " authentication for every use"); + } + + if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { + sids.add(spec.getBoundToSpecificSecureUserId()); + } else if (spec.isInvalidatedByBiometricEnrollment()) { + // The biometric-only SIDs will change on biometric enrollment or removal of all + // enrolled templates, invalidating the key. + for (long sid : biometricSids) { + sids.add(sid); + } + } else { + // The root SID will *not* change on fingerprint enrollment, or removal of all + // enrolled fingerprints, allowing the key to remain valid. + sids.add(getRootSid()); + } + } else if ((spec.getUserAuthenticationType() & KeyProperties.AUTH_DEVICE_CREDENTIAL) + != 0) { + sids.add(getRootSid()); + } else { + throw new IllegalStateException("Invalid or no authentication type specified."); + } + + for (int i = 0; i < sids.size(); i++) { + params.add(makeLong(KeymasterDefs.KM_TAG_USER_SECURE_ID, sids.get(i))); + } + } + } + + /** + * Adds keymaster arguments to express the key's authorization policy supported by user + * authentication. + * + * @param args The arguments sent to keymaster that need to be populated from the spec + * @param spec The user authentication relevant portions of the spec passed in from the caller. + * This spec will be translated into the relevant keymaster tags to be loaded into args. + * @throws IllegalStateException if user authentication is required but the system is in a wrong + * state (e.g., secure lock screen not set up) for generating or importing keys that + * require user authentication. + */ + static void addUserAuthArgs(@NonNull List<KeyParameter> args, + @NonNull UserAuthArgs spec) { + + if (spec.isUserConfirmationRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED)); + } + if (spec.isUserPresenceRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED)); + } + if (spec.isUnlockedDeviceRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED)); + } + if (!spec.isUserAuthenticationRequired()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED)); + } else { + if (spec.getUserAuthenticationValidityDurationSeconds() == 0) { + // Every use of this key needs to be authorized by the user. + addSids(args, spec); + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType() + )); + + if (spec.isUserAuthenticationValidWhileOnBody()) { + throw new ProviderException( + "Key validity extension while device is on-body is not " + + "supported for keys requiring fingerprint authentication"); + } + } else { + addSids(args, spec); + args.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_USER_AUTH_TYPE, spec.getUserAuthenticationType() + )); + args.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_AUTH_TIMEOUT, + spec.getUserAuthenticationValidityDurationSeconds() + )); + if (spec.isUserAuthenticationValidWhileOnBody()) { + args.add(KeyStore2ParameterUtils.makeBool( + KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY + )); + } + } + } + } +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java new file mode 100644 index 000000000000..6c733ba712d5 --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationChunkedStreamer.java @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.annotation.NonNull; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.ArrayUtils; + +import libcore.util.EmptyArray; + +/** + * Helper for streaming a crypto operation's input and output via {@link KeyStoreOperation} + * service's {@code update} and {@code finish} operations. + * + * <p>The helper abstracts away issues that need to be solved in most code that uses KeyStore's + * update and finish operations. Firstly, KeyStore's update operation can consume only a limited + * amount of data in one go because the operations are marshalled via Binder. Secondly, the update + * operation may consume less data than provided, in which case the caller has to buffer the + * remainder for next time. Thirdly, when the input is smaller than a threshold, skipping update + * and passing input data directly to final improves performance. This threshold is configurable; + * using a threshold <= 1 causes the helper act eagerly, which may be required for some types of + * operations (e.g. ciphers). + * + * <p>The helper exposes {@link #update(byte[], int, int) update} and + * {@link #doFinal(byte[], int, int, byte[], byte[]) doFinal} operations which can be used to + * conveniently implement various JCA crypto primitives. + * + * <p>Bidirectional chunked streaming of data via a KeyStore crypto operation is abstracted away as + * a {@link Stream} to avoid having this class deal with operation tokens and occasional additional + * parameters to {@code update} and {@code final} operations. + * + * @hide + */ +class KeyStoreCryptoOperationChunkedStreamer implements KeyStoreCryptoOperationStreamer { + + /** + * Bidirectional chunked data stream over a KeyStore crypto operation. + */ + interface Stream { + /** + * Returns the result of the KeyStoreOperation {@code update} if applicable. + * The return value may be null, e.g., when supplying AAD or to-be-signed data. + * + * @param input Data to update a KeyStoreOperation with. + * + * @throws KeyStoreException in case of error. + */ + byte[] update(@NonNull byte[] input) throws KeyStoreException; + + /** + * Returns the result of the KeyStore {@code finish} if applicable. + * + * @param input Optional data to update the operation with one last time. + * + * @param signature Optional HMAC signature when verifying an HMAC signature, must be + * null otherwise. + * + * @return Optional output data. Depending on the operation this may be a signature, + * some final bit of cipher, or plain text. + * + * @throws KeyStoreException in case of error. + */ + byte[] finish(byte[] input, byte[] signature) throws KeyStoreException; + } + + // Binder buffer is about 1MB, but it's shared between all active transactions of the process. + // Thus, it's safer to use a much smaller upper bound. + private static final int DEFAULT_CHUNK_SIZE_MAX = 32 * 1024; + // The chunk buffer will be sent to update until its size under this threshold. + // This threshold should be <= the max input allowed for finish. + // Setting this threshold <= 1 will effectivley disable buffering between updates. + private static final int DEFAULT_CHUNK_SIZE_THRESHOLD = 2 * 1024; + + private final Stream mKeyStoreStream; + private final int mChunkSizeMax; + private final int mChunkSizeThreshold; + private final byte[] mChunk; + private int mChunkLength = 0; + private long mConsumedInputSizeBytes; + private long mProducedOutputSizeBytes; + + KeyStoreCryptoOperationChunkedStreamer(Stream operation) { + this(operation, DEFAULT_CHUNK_SIZE_THRESHOLD, DEFAULT_CHUNK_SIZE_MAX); + } + + KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold) { + this(operation, chunkSizeThreshold, DEFAULT_CHUNK_SIZE_MAX); + } + + KeyStoreCryptoOperationChunkedStreamer(Stream operation, int chunkSizeThreshold, + int chunkSizeMax) { + mChunkLength = 0; + mConsumedInputSizeBytes = 0; + mProducedOutputSizeBytes = 0; + mKeyStoreStream = operation; + mChunkSizeMax = chunkSizeMax; + if (chunkSizeThreshold <= 0) { + mChunkSizeThreshold = 1; + } else if (chunkSizeThreshold > chunkSizeMax) { + mChunkSizeThreshold = chunkSizeMax; + } else { + mChunkSizeThreshold = chunkSizeThreshold; + } + mChunk = new byte[mChunkSizeMax]; + } + + public byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException { + if (inputLength == 0 || input == null) { + // No input provided + return EmptyArray.BYTE; + } + if (inputLength < 0 || inputOffset < 0 || (inputOffset + inputLength) > input.length) { + throw new KeyStoreException(KeymasterDefs.KM_ERROR_UNKNOWN_ERROR, + "Input offset and length out of bounds of input array"); + } + + byte[] output = EmptyArray.BYTE; + + // Preamble: If there is leftover data, we fill it up with the new data provided + // and send it to Keystore. + if (mChunkLength > 0) { + // Fill current chunk and send it to Keystore + int inputConsumed = ArrayUtils.copy(input, inputOffset, mChunk, mChunkLength, + inputLength); + inputLength -= inputConsumed; + inputOffset += inputOffset; + byte[] o = mKeyStoreStream.update(mChunk); + if (o != null) { + output = ArrayUtils.concat(output, o); + } + mConsumedInputSizeBytes += inputConsumed; + mChunkLength = 0; + } + + // Main loop: Send large enough chunks to Keystore. + while (inputLength >= mChunkSizeThreshold) { + int nextChunkSize = inputLength < mChunkSizeMax ? inputLength : mChunkSizeMax; + byte[] o = mKeyStoreStream.update(ArrayUtils.subarray(input, inputOffset, + nextChunkSize)); + inputLength -= nextChunkSize; + inputOffset += nextChunkSize; + mConsumedInputSizeBytes += nextChunkSize; + if (o != null) { + output = ArrayUtils.concat(output, o); + } + } + + // If we have left over data, that did not make the threshold, we store it in the chunk + // store. + if (inputLength > 0) { + mChunkLength = ArrayUtils.copy(input, inputOffset, mChunk, 0, inputLength); + mConsumedInputSizeBytes += inputLength; + } + + mProducedOutputSizeBytes += output.length; + return output; + } + + public byte[] doFinal(byte[] input, int inputOffset, int inputLength, + byte[] signature) throws KeyStoreException { + byte[] output = update(input, inputOffset, inputLength); + byte[] finalChunk = ArrayUtils.subarray(mChunk, 0, mChunkLength); + byte[] o = mKeyStoreStream.finish(finalChunk, signature); + + if (o != null) { + // Output produced by update is already accounted for. We only add the bytes + // produced by finish. + mProducedOutputSizeBytes += o.length; + if (output != null) { + output = ArrayUtils.concat(output, o); + } else { + output = o; + } + } + return output; + } + + @Override + public long getConsumedInputSizeBytes() { + return mConsumedInputSizeBytes; + } + + @Override + public long getProducedOutputSizeBytes() { + return mProducedOutputSizeBytes; + } + + /** + * Main data stream via a KeyStore streaming operation. + * + * <p>For example, for an encryption operation, this is the stream through which plaintext is + * provided and ciphertext is obtained. + */ + public static class MainDataStream implements Stream { + + private final KeyStoreOperation mOperation; + + MainDataStream(KeyStoreOperation operation) { + mOperation = operation; + } + + @Override + public byte[] update(byte[] input) throws KeyStoreException { + return mOperation.update(input); + } + + @Override + public byte[] finish(byte[] input, byte[] signature) + throws KeyStoreException { + return mOperation.finish(input, signature); + } + } +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java new file mode 100644 index 000000000000..07d6a69eda01 --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationStreamer.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.security.KeyStore; +import android.security.KeyStoreException; + +/** + * Helper for streaming a crypto operation's input and output via {@link KeyStore} service's + * {@code update} and {@code finish} operations. + * + * <p>The helper abstracts away to issues that need to be solved in most code that uses KeyStore's + * update and finish operations. Firstly, KeyStore's update operation can consume only a limited + * amount of data in one go because the operations are marshalled via Binder. Secondly, the update + * operation may consume less data than provided, in which case the caller has to buffer the + * remainder for next time. The helper exposes {@link #update(byte[], int, int) update} and + * {@link #doFinal(byte[], int, int, byte[]) doFinal} operations which can be used to + * conveniently implement various JCA crypto primitives. + * + * @hide + */ +interface KeyStoreCryptoOperationStreamer { + byte[] update(byte[] input, int inputOffset, int inputLength) throws KeyStoreException; + byte[] doFinal(byte[] input, int inputOffset, int inputLength, byte[] signature) + throws KeyStoreException; + long getConsumedInputSizeBytes(); + long getProducedOutputSizeBytes(); +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java new file mode 100644 index 000000000000..3b11854bf7cb --- /dev/null +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2015 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.security.keystore2; + +import android.app.ActivityThread; +import android.hardware.biometrics.BiometricManager; +import android.security.GateKeeper; +import android.security.KeyStore; +import android.security.KeyStoreException; +import android.security.KeyStoreOperation; +import android.security.keymaster.KeymasterDefs; +import android.security.keystore.KeyExpiredException; +import android.security.keystore.KeyNotYetValidException; +import android.security.keystore.KeyPermanentlyInvalidatedException; +import android.security.keystore.UserNotAuthenticatedException; +import android.system.keystore2.Authorization; +import android.system.keystore2.ResponseCode; +import android.util.Log; + +import libcore.util.EmptyArray; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; + +/** + * Assorted utility methods for implementing crypto operations on top of KeyStore. + * + * @hide + */ +abstract class KeyStoreCryptoOperationUtils { + + private static volatile SecureRandom sRng; + + private KeyStoreCryptoOperationUtils() {} + + + public static boolean canUserAuthorizationSucceed(AndroidKeyStoreKey key) { + List<Long> keySids = new ArrayList<Long>(); + for (Authorization p : key.getAuthorizations()) { + switch(p.keyParameter.tag) { + case KeymasterDefs.KM_TAG_USER_SECURE_ID: + keySids.add(p.keyParameter.longInteger); + break; + default: + break; + } + } + if (keySids.isEmpty()) { + // Key is not bound to any SIDs -- no amount of authentication will help here. + return false; + } + long rootSid = GateKeeper.getSecureUserId(); + if ((rootSid != 0) && (keySids.contains(rootSid))) { + // One of the key's SIDs is the current root SID -- user can be authenticated + // against that SID. + return true; + } + + long[] biometricSids = ActivityThread + .currentApplication() + .getSystemService(BiometricManager.class) + .getAuthenticatorIds(); + + // The key must contain every biometric SID. This is because the current API surface + // treats all biometrics (capable of keystore integration) equally. e.g. if the + // device has multiple keystore-capable sensors, and one of the sensor's SIDs + // changed, 1) there is no way for a developer to specify authentication with a + // specific sensor (the one that hasn't changed), and 2) currently the only + // signal to developers is the UserNotAuthenticatedException, which doesn't + // indicate a specific sensor. + boolean canUnlockViaBiometrics = true; + for (long sid : biometricSids) { + if (!keySids.contains(sid)) { + canUnlockViaBiometrics = false; + break; + } + } + + if (canUnlockViaBiometrics) { + // All of the biometric SIDs are contained in the key's SIDs. + return true; + } + + // None of the key's SIDs can ever be authenticated + return false; + } + + /** + * Returns an {@link InvalidKeyException} corresponding to the provided + * {@link KeyStoreException}. + */ + public static InvalidKeyException getInvalidKeyException( + AndroidKeyStoreKey key, KeyStoreException e) { + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_KEY_EXPIRED: + return new KeyExpiredException(); + case KeymasterDefs.KM_ERROR_KEY_NOT_YET_VALID: + return new KeyNotYetValidException(); + case ResponseCode.KEY_NOT_FOUND: + // TODO is this the right exception in this case? + case ResponseCode.KEY_PERMANENTLY_INVALIDATED: + return new KeyPermanentlyInvalidatedException(); + case ResponseCode.LOCKED: + case ResponseCode.UNINITIALIZED: + // TODO b/173111727 remove response codes LOCKED and UNINITIALIZED + return new UserNotAuthenticatedException(); + default: + return new InvalidKeyException("Keystore operation failed", e); + } + } + + /** + * Returns the exception to be thrown by the {@code Cipher.init} method of the crypto operation + * in response to {@code KeyStore.begin} operation or {@code null} if the {@code init} method + * should succeed. + */ + public static GeneralSecurityException getExceptionForCipherInit( + AndroidKeyStoreKey key, KeyStoreException e) { + if (e.getErrorCode() == KeyStore.NO_ERROR) { + return null; + } + + // Cipher-specific cases + switch (e.getErrorCode()) { + case KeymasterDefs.KM_ERROR_INVALID_NONCE: + return new InvalidAlgorithmParameterException("Invalid IV"); + case KeymasterDefs.KM_ERROR_CALLER_NONCE_PROHIBITED: + return new InvalidAlgorithmParameterException("Caller-provided IV not permitted"); + } + + // General cases + return getInvalidKeyException(key, e); + } + + /** + * Returns the requested number of random bytes to mix into keystore/keymaster RNG. + * + * @param rng RNG from which to obtain the random bytes or {@code null} for the platform-default + * RNG. + */ + static byte[] getRandomBytesToMixIntoKeystoreRng(SecureRandom rng, int sizeBytes) { + if (sizeBytes <= 0) { + return EmptyArray.BYTE; + } + if (rng == null) { + rng = getRng(); + } + byte[] result = new byte[sizeBytes]; + rng.nextBytes(result); + return result; + } + + private static SecureRandom getRng() { + // IMPLEMENTATION NOTE: It's OK to share a SecureRandom instance because SecureRandom is + // required to be thread-safe. + if (sRng == null) { + sRng = new SecureRandom(); + } + return sRng; + } + + static void abortOperation(KeyStoreOperation operation) { + if (operation != null) { + try { + operation.abort(); + } catch (KeyStoreException e) { + // We log this error, but we can afford to ignore it. Dropping the reference + // to the KeyStoreOperation is enough to clean up all related resources even + // in the Keystore daemon. We log it anyway, because it may indicate some + // underlying problem that is worth debugging. + Log.w( + "KeyStoreCryptoOperationUtils", + "Encountered error trying to abort a keystore operation.", + e + ); + } + } + } + + static long getOrMakeOperationChallenge(KeyStoreOperation operation, AndroidKeyStoreKey key) + throws KeyPermanentlyInvalidatedException { + if (operation.getChallenge() != null) { + if (!KeyStoreCryptoOperationUtils.canUserAuthorizationSucceed(key)) { + throw new KeyPermanentlyInvalidatedException(); + } + return operation.getChallenge(); + } else { + // Keystore won't give us an operation challenge if the operation doesn't + // need user authorization. So we make our own. + return Math.randomLongInternal(); + } + } +} diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS index 4390004f5f93..e2c67fd8f8d4 100644 --- a/libs/WindowManager/Shell/OWNERS +++ b/libs/WindowManager/Shell/OWNERS @@ -1,4 +1,4 @@ # sysui owners hwwang@google.com -mrenouf@google.com -winsonc@google.com
\ No newline at end of file +winsonc@google.com +madym@google.com diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp index 1f3a89edb8af..19ead9583eb2 100644 --- a/libs/androidfw/ConfigDescription.cpp +++ b/libs/androidfw/ConfigDescription.cpp @@ -887,13 +887,16 @@ bool ConfigDescription::Dominates(const ConfigDescription& o) const { } // Locale de-duping is not-trivial, disable for now (b/62409213). - if (diff(o) & CONFIG_LOCALE) { + // We must also disable de-duping for all configuration qualifiers with precedence higher than + // locale (b/171892595) + if (diff(o) & (CONFIG_LOCALE | CONFIG_MCC | CONFIG_MNC)) { return false; } if (*this == DefaultConfig()) { return true; } + return MatchWithDensity(o) && !o.MatchWithDensity(*this) && !isMoreSpecificThan(o) && !o.HasHigherPrecedenceThan(*this); } diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 57b853332f3f..107d6565a29e 100755 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -3542,8 +3542,8 @@ public class AudioManager { * @deprecated use {@link #abandonAudioFocusRequest(AudioFocusRequest)} */ @SystemApi - @SuppressLint("Doclava125") // no permission enforcement, but only "undoes" what would have been - // done by a matching requestAudioFocus + @SuppressLint("RequiresPermission") // no permission enforcement, but only "undoes" what would + // have been done by a matching requestAudioFocus public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) { int status = AUDIOFOCUS_REQUEST_FAILED; unregisterAudioFocusRequest(l); @@ -5146,7 +5146,7 @@ public class AudioManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") // FIXME is this still used? + @SuppressLint("RequiresPermission") // FIXME is this still used? public boolean isHdmiSystemAudioSupported() { try { return getService().isHdmiSystemAudioSupported(); diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java index 610bffe13eae..0ef4b94f6e37 100644 --- a/media/java/android/media/Image.java +++ b/media/java/android/media/Image.java @@ -200,6 +200,7 @@ public abstract class Image implements AutoCloseable { * @return The window transformation that needs to be applied for this frame. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract int getTransform(); /** @@ -207,6 +208,7 @@ public abstract class Image implements AutoCloseable { * @return The scaling mode that needs to be applied for this frame. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract int getScalingMode(); /** diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS index 7e7710b4d550..0f888113d730 100644 --- a/packages/SettingsProvider/src/android/provider/settings/OWNERS +++ b/packages/SettingsProvider/src/android/provider/settings/OWNERS @@ -1,4 +1,4 @@ # Bug component: 656484 -include platform/frameworks/base/services/backup:/OWNERS +include platform/frameworks/base:/services/backup/OWNERS diff --git a/packages/SettingsProvider/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS index 7e7710b4d550..0f888113d730 100644 --- a/packages/SettingsProvider/test/src/android/provider/OWNERS +++ b/packages/SettingsProvider/test/src/android/provider/OWNERS @@ -1,4 +1,4 @@ # Bug component: 656484 -include platform/frameworks/base/services/backup:/OWNERS +include platform/frameworks/base:/services/backup/OWNERS diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index 299ae5b50aa9..08b700bc308d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -360,7 +360,8 @@ class MediaDataManager( val app = builder.loadHeaderAppName() // App Icon - val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawable(context) + val smallIconDrawable: Drawable = sbn.notification.smallIcon.loadDrawableAsUser(context, + sbn.user.identifier) // Song name var song: CharSequence? = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index fe322edff541..fba1e79ad79a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1917,10 +1917,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } if (DBG) log("Adding legacy route " + bestRoute + " for UID/PID " + uid + "/" + Binder.getCallingPid()); + + final String dst = bestRoute.getDestinationLinkAddress().toString(); + final String nextHop = bestRoute.hasGateway() + ? bestRoute.getGateway().getHostAddress() : ""; try { - mNMS.addLegacyRouteForNetId(netId, bestRoute, uid); - } catch (Exception e) { - // never crash - catch them all + mNetd.networkAddLegacyRoute(netId, bestRoute.getInterface(), dst, nextHop , uid); + } catch (RemoteException | ServiceSpecificException e) { if (DBG) loge("Exception trying to add a route: " + e); return false; } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index db7e16ca8b25..8ddcfc83ba06 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -21,6 +21,8 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.content.Context; import android.net.vcn.IVcnManagementService; +import android.net.vcn.VcnConfig; +import android.os.ParcelUuid; /** * VcnManagementService manages Virtual Carrier Network profiles and lifecycles. @@ -98,6 +100,33 @@ public class VcnManagementService extends IVcnManagementService.Stub { private static class Dependencies {} - /** Notifies the VcnManagementService that external dependencies can be set up */ - public void systemReady() {} + /** Notifies the VcnManagementService that external dependencies can be set up. */ + public void systemReady() { + // TODO: Retrieve existing profiles from KeyStore + } + + /** + * Sets a VCN config for a given subscription group. + * + * <p>Implements the IVcnManagementService Binder interface. + */ + @Override + public void setVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { + requireNonNull(subscriptionGroup, "subscriptionGroup was null"); + requireNonNull(config, "config was null"); + + // TODO: Store VCN configuration, trigger startup as necessary + } + + /** + * Clears the VcnManagementService for a given subscription group. + * + * <p>Implements the IVcnManagementService Binder interface. + */ + @Override + public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) { + requireNonNull(subscriptionGroup, "subscriptionGroup was null"); + + // 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 0563fcd1bf50..7f4fb4039d4e 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -75,10 +75,11 @@ public class NetworkNotificationManager { private static final boolean DBG = true; // Notification channels used by ConnectivityService mainline module, it should be aligned with - // SystemNotificationChannels. - public static final String NOTIFICATION_NETWORK_STATUS = "NETWORK_STATUS"; - public static final String NOTIFICATION_NETWORK_ALERTS = "NETWORK_ALERTS"; - public static final String NOTIFICATION_VPN = "VPN"; + // SystemNotificationChannels so the channels are the same as the ones used as the system + // server. + public static final String NOTIFICATION_CHANNEL_NETWORK_STATUS = "NETWORK_STATUS"; + public static final String NOTIFICATION_CHANNEL_NETWORK_ALERTS = "NETWORK_ALERTS"; + public static final String NOTIFICATION_CHANNEL_VPN = "VPN"; // The context is for the current user (system server) private final Context mContext; @@ -263,7 +264,7 @@ public class NetworkNotificationManager { // the tag. final boolean hasPreviousNotification = previousNotifyType != null; final String channelId = (highPriority && !hasPreviousNotification) - ? NOTIFICATION_NETWORK_ALERTS : NOTIFICATION_NETWORK_STATUS; + ? NOTIFICATION_CHANNEL_NETWORK_ALERTS : NOTIFICATION_CHANNEL_NETWORK_STATUS; Notification.Builder builder = new Notification.Builder(mContext, channelId) .setWhen(System.currentTimeMillis()) .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH) diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 2c29106c5cd3..0a71ecbe15d3 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -27,13 +27,12 @@ import static android.net.RouteInfo.RTN_UNREACHABLE; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_VPN; +import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; -import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; @@ -1333,16 +1332,17 @@ public class Vpn { // Restricted users are not allowed to create VPNs, they are tied to Owner enforceNotRestrictedUser(); - ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent, - null, 0, mUserId); + final PackageManager packageManager = mUserIdContext.getPackageManager(); + if (packageManager == null) { + throw new UnsupportedOperationException("Cannot get PackageManager."); + } + final ResolveInfo info = packageManager.resolveService(intent, 0 /* flags */); if (info == null) { throw new SecurityException("Cannot find " + config.user); } if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) { throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE); } - } catch (RemoteException e) { - throw new SecurityException("Cannot find " + config.user); } finally { Binder.restoreCallingIdentity(token); } @@ -1945,7 +1945,7 @@ public class Vpn { final PendingIntent configIntent = mSystemServices.pendingIntentGetActivityAsUser( intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT, user); final Notification.Builder builder = - new Notification.Builder(mContext, NOTIFICATION_VPN) + new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN) .setSmallIcon(R.drawable.vpn_connected) .setContentTitle(mContext.getString(R.string.vpn_lockdown_disconnected)) .setContentText(mContext.getString(R.string.vpn_lockdown_config)) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index fe97f701e089..ea2453216a59 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -148,9 +148,25 @@ public class HdmiCecMessageValidator { addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE, new AsciiValidator(3), DEST_BROADCAST); - // TODO: Handle messages for the Deck Control. + ParameterValidator statusRequestValidator = new OneByteRangeValidator(0x01, 0x03); + addValidationInfo( + Constants.MESSAGE_DECK_CONTROL, new OneByteRangeValidator(0x01, 0x04), DEST_DIRECT); + addValidationInfo( + Constants.MESSAGE_DECK_STATUS, new OneByteRangeValidator(0x11, 0x1F), DEST_DIRECT); + addValidationInfo(Constants.MESSAGE_GIVE_DECK_STATUS, statusRequestValidator, DEST_DIRECT); + addValidationInfo(Constants.MESSAGE_PLAY, new PlayModeValidator(), DEST_DIRECT); // TODO: Handle messages for the Tuner Control. + addValidationInfo( + Constants.MESSAGE_GIVE_TUNER_DEVICE_STATUS, statusRequestValidator, DEST_DIRECT); + addValidationInfo( + Constants.MESSAGE_SELECT_ANALOG_SERVICE, + new SelectAnalogueServiceValidator(), + DEST_DIRECT); + addValidationInfo( + Constants.MESSAGE_SELECT_DIGITAL_SERVICE, + new SelectDigitalServiceValidator(), + DEST_DIRECT); // Messages for the Vendor Specific Commands. VariableLengthValidator maxLengthValidator = new VariableLengthValidator(0, 14); @@ -176,9 +192,10 @@ public class HdmiCecMessageValidator { Constants.MESSAGE_MENU_STATUS, new OneByteRangeValidator(0x00, 0x01), DEST_DIRECT); // Messages for the Remote Control Passthrough. - // TODO: Parse the first parameter and determine if it can have the next parameter. - addValidationInfo(Constants.MESSAGE_USER_CONTROL_PRESSED, - new VariableLengthValidator(1, 2), DEST_DIRECT); + addValidationInfo( + Constants.MESSAGE_USER_CONTROL_PRESSED, + new UserControlPressedValidator(), + DEST_DIRECT); // Messages for the Power Status. addValidationInfo( @@ -515,6 +532,28 @@ public class HdmiCecMessageValidator { } /** + * Check if the given value is a valid Channel Identifier. A valid value is one which falls + * within the range description defined in CEC 1.4 Specification : Operand Descriptions (Section + * 17) + * + * @param params Channel Identifier parameters + * @param offset start offset of Channel Identifier + * @return true if the Channel Identifier is valid + */ + private boolean isValidChannelIdentifier(byte[] params, int offset) { + // First 6 bits contain Channel Number Format + int channelNumberFormat = params[offset] & 0xFC; + if (channelNumberFormat == 0x04) { + // Validate it contains 1-part Channel Number data (16 bits) + return params.length - offset >= 3; + } else if (channelNumberFormat == 0x08) { + // Validate it contains Major Channel Number and Minor Channel Number (26 bits) + return params.length - offset >= 4; + } + return false; + } + + /** * Check if the given value is a valid Digital Service Identification. A valid value is one * which falls within the range description defined in CEC 1.4 Specification : Operand * Descriptions (Section 17) @@ -544,15 +583,7 @@ public class HdmiCecMessageValidator { } else if (serviceIdentificationMethod == 0x80) { // Services identified by Channel if (isValidDigitalBroadcastSystem(digitalBroadcastSystem)) { - // First 6 bits contain Channel Number Format - int channelNumberFormat = params[offset] & 0xFC; - if (channelNumberFormat == 0x04) { - // Validate it contains 1-part Channel Number data (16 bits) - return params.length - offset >= 3; - } else if (channelNumberFormat == 0x08) { - // Validate it contains Major Channel Number and Minor Channel Number (26 bits) - return params.length - offset >= 4; - } + return isValidChannelIdentifier(params, offset); } } return false; @@ -632,6 +663,65 @@ public class HdmiCecMessageValidator { return false; } + /** + * Check if the given value is a valid Play mode. A valid value is one which falls within the + * range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17) + * + * @param value Play mode + * @return true if the Play mode is valid + */ + private boolean isValidPlayMode(int value) { + return (isWithinRange(value, 0x05, 0x07) + || isWithinRange(value, 0x09, 0x0B) + || isWithinRange(value, 0x15, 0x17) + || isWithinRange(value, 0x19, 0x1B) + || isWithinRange(value, 0x24, 0x25) + || (value == 0x20)); + } + + /** + * Check if the given value is a valid UI Broadcast type. A valid value is one which falls + * within the range description defined in CEC 1.4 Specification : Operand Descriptions (Section + * 17) + * + * @param value UI Broadcast type + * @return true if the UI Broadcast type is valid + */ + private boolean isValidUiBroadcastType(int value) { + return ((value == 0x00) + || (value == 0x01) + || (value == 0x10) + || (value == 0x20) + || (value == 0x30) + || (value == 0x40) + || (value == 0x50) + || (value == 0x60) + || (value == 0x70) + || (value == 0x80) + || (value == 0x90) + || (value == 0x91) + || (value == 0xA0)); + } + + /** + * Check if the given value is a valid UI Sound Presenation Control. A valid value is one which + * falls within the range description defined in CEC 1.4 Specification : Operand Descriptions + * (Section 17) + * + * @param value UI Sound Presenation Control + * @return true if the UI Sound Presenation Control is valid + */ + private boolean isValidUiSoundPresenationControl(int value) { + value = value & 0xFF; + return ((value == 0x20) + || (value == 0x30) + || (value == 0x80) + || (value == 0x90) + || (value == 0xA0) + || (isWithinRange(value, 0xB1, 0xB3)) + || (isWithinRange(value, 0xC1, 0xC3))); + } + private class PhysicalAddressValidator implements ParameterValidator { @Override public int isValid(byte[] params) { @@ -863,4 +953,78 @@ public class HdmiCecMessageValidator { return toErrorCode(isValidTimerStatusData(params, 0)); } } + + /** + * Check if the given play mode parameter is valid. A valid parameter should lie within the + * range description defined in CEC 1.4 Specification : Operand Descriptions (Section 17) + */ + private class PlayModeValidator implements ParameterValidator { + @Override + public int isValid(byte[] params) { + if (params.length < 1) { + return ERROR_PARAMETER_SHORT; + } + return toErrorCode(isValidPlayMode(params[0])); + } + } + + /** + * Check if the given select analogue service parameter is valid. A valid parameter should lie + * within the range description defined in CEC 1.4 Specification : Operand Descriptions + * (Section 17) + */ + private class SelectAnalogueServiceValidator implements ParameterValidator { + @Override + public int isValid(byte[] params) { + if (params.length < 4) { + return ERROR_PARAMETER_SHORT; + } + return toErrorCode(isValidAnalogueBroadcastType(params[0]) + && isValidAnalogueFrequency(HdmiUtils.twoBytesToInt(params, 1)) + && isValidBroadcastSystem(params[3])); + } + } + + /** + * Check if the given select digital service parameter is valid. A valid parameter should lie + * within the range description defined in CEC 1.4 Specification : Operand Descriptions + * (Section 17) + */ + private class SelectDigitalServiceValidator implements ParameterValidator { + @Override + public int isValid(byte[] params) { + if (params.length < 4) { + return ERROR_PARAMETER_SHORT; + } + return toErrorCode(isValidDigitalServiceIdentification(params, 0)); + } + } + + /** Check if the given user control press parameter is valid. */ + private class UserControlPressedValidator implements ParameterValidator { + @Override + public int isValid(byte[] params) { + if (params.length < 1) { + return ERROR_PARAMETER_SHORT; + } + if (params.length == 1) { + return OK; + } + int uiCommand = params[0]; + switch (uiCommand) { + case HdmiCecKeycode.CEC_KEYCODE_PLAY_FUNCTION: + return toErrorCode(isValidPlayMode(params[1])); + case HdmiCecKeycode.CEC_KEYCODE_TUNE_FUNCTION: + return (params.length >= 4 + ? toErrorCode(isValidChannelIdentifier(params, 1)) + : ERROR_PARAMETER_SHORT); + case HdmiCecKeycode.CEC_KEYCODE_SELECT_BROADCAST_TYPE: + return toErrorCode(isValidUiBroadcastType(params[1])); + case HdmiCecKeycode.CEC_KEYCODE_SELECT_SOUND_PRESENTATION: + return toErrorCode(isValidUiSoundPresenationControl(params[1])); + default: + return OK; + } + } + } } diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java index 661d38df12ae..0b774df3fe2f 100644 --- a/services/core/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java @@ -18,7 +18,7 @@ package com.android.server.net; import static android.provider.Settings.ACTION_VPN_SETTINGS; -import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_VPN; +import static com.android.server.connectivity.NetworkNotificationManager.NOTIFICATION_CHANNEL_VPN; import android.annotation.NonNull; import android.annotation.Nullable; @@ -257,7 +257,7 @@ public class LockdownVpnTracker { private void showNotification(int titleRes, int iconRes) { final Notification.Builder builder = - new Notification.Builder(mContext, NOTIFICATION_VPN) + new Notification.Builder(mContext, NOTIFICATION_CHANNEL_VPN) .setWhen(0) .setSmallIcon(iconRes) .setContentTitle(mContext.getString(titleRes)) diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 12c24d418611..81a6641de8a4 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -1084,12 +1084,10 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return nativeIfaceStats; } else { // When tethering offload is in use, nativeIfaceStats does not contain usage from - // offload, add it back here. - // When tethering offload is not in use, nativeIfaceStats contains tethering usage. - // this does not cause double-counting of tethering traffic, because - // NetdTetheringStatsProvider returns zero NetworkStats - // when called with STATS_PER_IFACE. - return nativeIfaceStats + getTetherStats(iface, type); + // offload, add it back here. Note that the included statistics might be stale + // since polling newest stats from hardware might impact system health and not + // suitable for TrafficStats API use cases. + return nativeIfaceStats + getProviderIfaceStats(iface, type); } } @@ -1100,39 +1098,28 @@ public class NetworkStatsService extends INetworkStatsService.Stub { return nativeTotalStats; } else { // Refer to comment in getIfaceStats - return nativeTotalStats + getTetherStats(IFACE_ALL, type); + return nativeTotalStats + getProviderIfaceStats(IFACE_ALL, type); } } - private long getTetherStats(String iface, int type) { - final NetworkStats tetherSnapshot; - final long token = Binder.clearCallingIdentity(); - try { - tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE); - } catch (RemoteException e) { - Slog.w(TAG, "Error get TetherStats: " + e); - return 0; - } finally { - Binder.restoreCallingIdentity(token); - } - HashSet<String> limitIfaces; + private long getProviderIfaceStats(@Nullable String iface, int type) { + final NetworkStats providerSnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE); + final HashSet<String> limitIfaces; if (iface == IFACE_ALL) { limitIfaces = null; } else { - limitIfaces = new HashSet<String>(); + limitIfaces = new HashSet<>(); limitIfaces.add(iface); } - NetworkStats.Entry entry = tetherSnapshot.getTotal(null, limitIfaces); - if (LOGD) Slog.d(TAG, "TetherStats: iface=" + iface + " type=" + type + - " entry=" + entry); + final NetworkStats.Entry entry = providerSnapshot.getTotal(null, limitIfaces); switch (type) { - case 0: // TYPE_RX_BYTES + case TrafficStats.TYPE_RX_BYTES: return entry.rxBytes; - case 1: // TYPE_RX_PACKETS + case TrafficStats.TYPE_RX_PACKETS: return entry.rxPackets; - case 2: // TYPE_TX_BYTES + case TrafficStats.TYPE_TX_BYTES: return entry.txBytes; - case 3: // TYPE_TX_PACKETS + case TrafficStats.TYPE_TX_PACKETS: return entry.txPackets; default: return 0; @@ -1429,14 +1416,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final NetworkStats devSnapshot = readNetworkStatsSummaryDev(); Trace.traceEnd(TRACE_TAG_NETWORK); - // Tethering snapshot for dev and xt stats. Counts per-interface data from tethering stats - // providers that isn't already counted by dev and XT stats. - Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotTether"); - final NetworkStats tetherSnapshot = getNetworkStatsTethering(STATS_PER_IFACE); - Trace.traceEnd(TRACE_TAG_NETWORK); - xtSnapshot.combineAllValues(tetherSnapshot); - devSnapshot.combineAllValues(tetherSnapshot); - // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data // from stats providers that isn't already counted by dev and XT stats. Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider"); @@ -1511,29 +1490,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0; final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0; - // Request asynchronous stats update from all providers for next poll. And wait a bit of - // time to allow providers report-in given that normally binder call should be fast. Note - // that size of list might be changed because addition/removing at the same time. For - // addition, the stats of the missed provider can only be collected in next poll; - // for removal, wait might take up to MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS - // once that happened. - // TODO: request with a valid token. - Trace.traceBegin(TRACE_TAG_NETWORK, "provider.requestStatsUpdate"); - final int registeredCallbackCount = mStatsProviderCbList.size(); - mStatsProviderSem.drainPermits(); - invokeForAllStatsProviderCallbacks( - (cb) -> cb.mProvider.onRequestStatsUpdate(0 /* unused */)); - try { - mStatsProviderSem.tryAcquire(registeredCallbackCount, - MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // Strictly speaking it's possible a provider happened to deliver between the timeout - // and the log, and that doesn't matter too much as this is just a debug log. - Log.d(TAG, "requestStatsUpdate - providers responded " - + mStatsProviderSem.availablePermits() - + "/" + registeredCallbackCount + " : " + e); - } - Trace.traceEnd(TRACE_TAG_NETWORK); + performPollFromProvidersLocked(); // TODO: consider marking "untrusted" times in historical stats final long currentTime = mClock.millis(); @@ -1578,6 +1535,33 @@ public class NetworkStatsService extends INetworkStatsService.Stub { Trace.traceEnd(TRACE_TAG_NETWORK); } + @GuardedBy("mStatsLock") + private void performPollFromProvidersLocked() { + // Request asynchronous stats update from all providers for next poll. And wait a bit of + // time to allow providers report-in given that normally binder call should be fast. Note + // that size of list might be changed because addition/removing at the same time. For + // addition, the stats of the missed provider can only be collected in next poll; + // for removal, wait might take up to MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS + // once that happened. + // TODO: request with a valid token. + Trace.traceBegin(TRACE_TAG_NETWORK, "provider.requestStatsUpdate"); + final int registeredCallbackCount = mStatsProviderCbList.size(); + mStatsProviderSem.drainPermits(); + invokeForAllStatsProviderCallbacks( + (cb) -> cb.mProvider.onRequestStatsUpdate(0 /* unused */)); + try { + mStatsProviderSem.tryAcquire(registeredCallbackCount, + MAX_STATS_PROVIDER_POLL_WAIT_TIME_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Strictly speaking it's possible a provider happened to deliver between the timeout + // and the log, and that doesn't matter too much as this is just a debug log. + Log.d(TAG, "requestStatsUpdate - providers responded " + + mStatsProviderSem.availablePermits() + + "/" + registeredCallbackCount + " : " + e); + } + Trace.traceEnd(TRACE_TAG_NETWORK); + } + /** * Sample recent statistics summary into {@link EventLog}. */ @@ -1931,9 +1915,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * Return snapshot of current tethering statistics. Will return empty - * {@link NetworkStats} if any problems are encountered. + * Return snapshot of current non-offloaded tethering statistics. Will return empty + * {@link NetworkStats} if any problems are encountered, or queried by {@code STATS_PER_IFACE} + * since it is already included by {@link #nativeGetIfaceStat}. + * See {@code OffloadTetheringStatsProvider} for offloaded tethering stats. */ + // TODO: Remove this by implementing {@link NetworkStatsProvider} for non-offloaded + // tethering stats. private NetworkStats getNetworkStatsTethering(int how) throws RemoteException { try { return mNetworkManager.getNetworkStatsTethering(how); @@ -2226,13 +2214,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } - private static int TYPE_RX_BYTES; - private static int TYPE_RX_PACKETS; - private static int TYPE_TX_BYTES; - private static int TYPE_TX_PACKETS; - private static int TYPE_TCP_RX_PACKETS; - private static int TYPE_TCP_TX_PACKETS; - private static native long nativeGetTotalStat(int type, boolean useBpfStats); private static native long nativeGetIfaceStat(String iface, int type, boolean useBpfStats); private static native long nativeGetUidStat(int uid, int type, boolean useBpfStats); diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index e48862e2e5e0..cca2b8394c63 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -45,17 +45,17 @@ per-file PackageSignatures.java = cbrubaker@google.com, nnk@google.com per-file SELinuxMMAC.java = cbrubaker@google.com, jeffv@google.com, jgalenson@google.com, nnk@google.com # shortcuts -per-file LauncherAppsService.java = omakoto@google.com, yamasani@google.com -per-file ShareTargetInfo.java = omakoto@google.com, yamasani@google.com -per-file ShortcutBitmapSaver.java = omakoto@google.com, yamasani@google.com -per-file ShortcutDumpFiles.java = omakoto@google.com, yamasani@google.com -per-file ShortcutLauncher.java = omakoto@google.com, yamasani@google.com -per-file ShortcutNonPersistentUser.java = omakoto@google.com, yamasani@google.com -per-file ShortcutPackage.java = omakoto@google.com, yamasani@google.com -per-file ShortcutPackageInfo.java = omakoto@google.com, yamasani@google.com -per-file ShortcutPackageItem.java = omakoto@google.com, yamasani@google.com -per-file ShortcutParser.java = omakoto@google.com, yamasani@google.com -per-file ShortcutRequestPinProcessor.java = omakoto@google.com, yamasani@google.com -per-file ShortcutService.java = omakoto@google.com, yamasani@google.com -per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com +per-file LauncherAppsService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShareTargetInfo.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutBitmapSaver.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutDumpFiles.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutLauncher.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutNonPersistentUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutPackage.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutPackageInfo.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutPackageItem.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutParser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutRequestPinProcessor.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutService.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com +per-file ShortcutUser.java = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3d6360696686..7fa322559d46 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -10564,7 +10564,7 @@ public class PackageManagerService extends IPackageManager.Stub continue; } final PackageSetting staticLibPkgSetting = getPackageSetting( - toStaticSharedLibraryPackageName(sharedLibraryInfo.getPackageName(), + toStaticSharedLibraryPackageName(sharedLibraryInfo.getName(), sharedLibraryInfo.getLongVersion())); if (staticLibPkgSetting == null) { Slog.wtf(TAG, "Shared lib without setting: " + sharedLibraryInfo); diff --git a/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java new file mode 100644 index 000000000000..5c1b5ffb2209 --- /dev/null +++ b/services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java @@ -0,0 +1,341 @@ +/* + * 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.util; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.ParcelUuid; +import android.os.PersistableBundle; + +import com.android.internal.util.HexDump; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** @hide */ +public class PersistableBundleUtils { + private static final String LIST_KEY_FORMAT = "LIST_ITEM_%d"; + private static final String COLLECTION_SIZE_KEY = "COLLECTION_LENGTH"; + private static final String MAP_KEY_FORMAT = "MAP_KEY_%d"; + private static final String MAP_VALUE_FORMAT = "MAP_VALUE_%d"; + + private static final String PARCEL_UUID_KEY = "PARCEL_UUID"; + private static final String BYTE_ARRAY_KEY = "BYTE_ARRAY_KEY"; + private static final String INTEGER_KEY = "INTEGER_KEY"; + + /** + * Functional interface to convert an object of the specified type to a PersistableBundle. + * + * @param <T> the type of the source object + */ + public interface Serializer<T> { + /** + * Converts this object to a PersistableBundle. + * + * @return the PersistableBundle representation of this object + */ + PersistableBundle toPersistableBundle(T obj); + } + + /** + * Functional interface used to create an object of the specified type from a PersistableBundle. + * + * @param <T> the type of the resultant object + */ + public interface Deserializer<T> { + /** + * Creates an instance of specified type from a PersistableBundle representation. + * + * @param in the PersistableBundle representation + * @return an instance of the specified type + */ + T fromPersistableBundle(PersistableBundle in); + } + + /** Serializer to convert an integer to a PersistableBundle. */ + public static final Serializer<Integer> INTEGER_SERIALIZER = + (i) -> { + final PersistableBundle result = new PersistableBundle(); + result.putInt(INTEGER_KEY, i); + return result; + }; + + /** Deserializer to convert a PersistableBundle to an integer. */ + public static final Deserializer<Integer> INTEGER_DESERIALIZER = + (bundle) -> { + Objects.requireNonNull(bundle, "PersistableBundle is null"); + return bundle.getInt(INTEGER_KEY); + }; + + /** + * Converts a ParcelUuid to a PersistableBundle. + * + * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned + * PersistableBundle object. + * + * @param uuid a ParcelUuid instance to persist + * @return the PersistableBundle instance + */ + public static PersistableBundle fromParcelUuid(ParcelUuid uuid) { + final PersistableBundle result = new PersistableBundle(); + + result.putString(PARCEL_UUID_KEY, uuid.toString()); + + return result; + } + + /** + * Converts from a PersistableBundle to a ParcelUuid. + * + * @param bundle the PersistableBundle containing the ParcelUuid + * @return the ParcelUuid instance + */ + public static ParcelUuid toParcelUuid(PersistableBundle bundle) { + return ParcelUuid.fromString(bundle.getString(PARCEL_UUID_KEY)); + } + + /** + * Converts from a list of Persistable objects to a single PersistableBundle. + * + * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned + * PersistableBundle object. + * + * @param <T> the type of the objects to convert to the PersistableBundle + * @param in the list of objects to be serialized into a PersistableBundle + * @param serializer an implementation of the {@link Serializer} functional interface that + * converts an object of type T to a PersistableBundle + */ + @NonNull + public static <T> PersistableBundle fromList( + @NonNull List<T> in, @NonNull Serializer<T> serializer) { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(COLLECTION_SIZE_KEY, in.size()); + for (int i = 0; i < in.size(); i++) { + final String key = String.format(LIST_KEY_FORMAT, i); + result.putPersistableBundle(key, serializer.toPersistableBundle(in.get(i))); + } + return result; + } + + /** + * Converts from a PersistableBundle to a list of objects. + * + * @param <T> the type of the objects to convert from a PersistableBundle + * @param in the PersistableBundle containing the persisted list + * @param deserializer an implementation of the {@link Deserializer} functional interface that + * builds the relevant type of objects. + */ + @NonNull + public static <T> List<T> toList( + @NonNull PersistableBundle in, @NonNull Deserializer<T> deserializer) { + final int listLength = in.getInt(COLLECTION_SIZE_KEY); + final ArrayList<T> result = new ArrayList<>(listLength); + + for (int i = 0; i < listLength; i++) { + final String key = String.format(LIST_KEY_FORMAT, i); + final PersistableBundle item = in.getPersistableBundle(key); + + result.add(deserializer.fromPersistableBundle(item)); + } + return result; + } + + // TODO: b/170513329 Delete #fromByteArray and #toByteArray once BaseBundle#putByteArray and + // BaseBundle#getByteArray are exposed. + + /** + * Converts a byte array to a PersistableBundle. + * + * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned + * PersistableBundle object. + * + * @param array a byte array instance to persist + * @return the PersistableBundle instance + */ + public static PersistableBundle fromByteArray(byte[] array) { + final PersistableBundle result = new PersistableBundle(); + + result.putString(BYTE_ARRAY_KEY, HexDump.toHexString(array)); + + return result; + } + + /** + * Converts from a PersistableBundle to a byte array. + * + * @param bundle the PersistableBundle containing the byte array + * @return the byte array instance + */ + public static byte[] toByteArray(PersistableBundle bundle) { + Objects.requireNonNull(bundle, "PersistableBundle is null"); + + String hex = bundle.getString(BYTE_ARRAY_KEY); + if (hex == null || hex.length() % 2 != 0) { + throw new IllegalArgumentException("PersistableBundle contains invalid byte array"); + } + + return HexDump.hexStringToByteArray(hex); + } + + /** + * Converts from a Map of Persistable objects to a single PersistableBundle. + * + * <p>To avoid key collisions, NO additional key/value pairs should be added to the returned + * PersistableBundle object. + * + * @param <K> the type of the map-key to convert to the PersistableBundle + * @param <V> the type of the map-value to convert to the PersistableBundle + * @param in the Map of objects implementing the {@link Persistable} interface + * @param keySerializer an implementation of the {@link Serializer} functional interface that + * converts a map-key of type T to a PersistableBundle + * @param valueSerializer an implementation of the {@link Serializer} functional interface that + * converts a map-value of type E to a PersistableBundle + */ + @NonNull + public static <K, V> PersistableBundle fromMap( + @NonNull Map<K, V> in, + @NonNull Serializer<K> keySerializer, + @NonNull Serializer<V> valueSerializer) { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(COLLECTION_SIZE_KEY, in.size()); + int i = 0; + for (Entry<K, V> entry : in.entrySet()) { + final String keyKey = String.format(MAP_KEY_FORMAT, i); + final String valueKey = String.format(MAP_VALUE_FORMAT, i); + result.putPersistableBundle(keyKey, keySerializer.toPersistableBundle(entry.getKey())); + result.putPersistableBundle( + valueKey, valueSerializer.toPersistableBundle(entry.getValue())); + + i++; + } + + return result; + } + + /** + * Converts from a PersistableBundle to a Map of objects. + * + * <p>In an attempt to preserve ordering, the returned map will be a LinkedHashMap. However, the + * guarantees on the ordering can only ever be as strong as the map that was serialized in + * {@link fromMap()}. If the initial map that was serialized had no ordering guarantees, the + * deserialized map similarly may be of a non-deterministic order. + * + * @param <K> the type of the map-key to convert from a PersistableBundle + * @param <V> the type of the map-value to convert from a PersistableBundle + * @param in the PersistableBundle containing the persisted Map + * @param keyDeserializer an implementation of the {@link Deserializer} functional interface + * that builds the relevant type of map-key. + * @param valueDeserializer an implementation of the {@link Deserializer} functional interface + * that builds the relevant type of map-value. + * @return An instance of the parsed map as a LinkedHashMap (in an attempt to preserve + * ordering). + */ + @NonNull + public static <K, V> LinkedHashMap<K, V> toMap( + @NonNull PersistableBundle in, + @NonNull Deserializer<K> keyDeserializer, + @NonNull Deserializer<V> valueDeserializer) { + final int mapSize = in.getInt(COLLECTION_SIZE_KEY); + final LinkedHashMap<K, V> result = new LinkedHashMap<>(mapSize); + + for (int i = 0; i < mapSize; i++) { + final String keyKey = String.format(MAP_KEY_FORMAT, i); + final String valueKey = String.format(MAP_VALUE_FORMAT, i); + final PersistableBundle keyBundle = in.getPersistableBundle(keyKey); + final PersistableBundle valueBundle = in.getPersistableBundle(valueKey); + + final K key = keyDeserializer.fromPersistableBundle(keyBundle); + final V value = valueDeserializer.fromPersistableBundle(valueBundle); + result.put(key, value); + } + return result; + } + + /** + * Ensures safe reading and writing of {@link PersistableBundle}s to and from disk. + * + * <p>This class will enforce exclusion between reads and writes using the standard semantics of + * a ReadWriteLock. Specifically, concurrent readers ARE allowed, but reads/writes from/to the + * file are mutually exclusive. In other words, for an unbounded number n, the acceptable states + * are n readers, OR 1 writer (but not both). + */ + public static class LockingReadWriteHelper { + private final ReadWriteLock mDiskLock = new ReentrantReadWriteLock(); + private final String mPath; + + public LockingReadWriteHelper(@NonNull String path) { + mPath = Objects.requireNonNull(path, "fileName was null"); + } + + /** + * Reads the {@link PersistableBundle} from the disk. + * + * @return the PersistableBundle, if the file existed, or null otherwise + */ + @Nullable + public PersistableBundle readFromDisk() throws IOException { + try { + mDiskLock.readLock().lock(); + final File file = new File(mPath); + if (!file.exists()) { + return null; + } + + try (FileInputStream fis = new FileInputStream(file)) { + return PersistableBundle.readFromStream(fis); + } + } finally { + mDiskLock.readLock().unlock(); + } + } + + /** + * Writes a {@link PersistableBundle} to disk. + * + * @param bundle the {@link PersistableBundle} to write to disk + */ + public void writeToDisk(@NonNull PersistableBundle bundle) throws IOException { + Objects.requireNonNull(bundle, "bundle was null"); + + try { + mDiskLock.writeLock().lock(); + final File file = new File(mPath); + if (!file.exists()) { + file.getParentFile().mkdirs(); + } + + try (FileOutputStream fos = new FileOutputStream(file)) { + bundle.writeToStream(fos); + } + } finally { + mDiskLock.writeLock().unlock(); + } + } + } +} diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp index 0275f3ea32f7..10b248a70e7e 100644 --- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp +++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp @@ -215,21 +215,6 @@ static const JNINativeMethod gMethods[] = { }; int register_android_server_net_NetworkStatsService(JNIEnv* env) { - jclass netStatsService = env->FindClass("com/android/server/net/NetworkStatsService"); - jfieldID rxBytesId = env->GetStaticFieldID(netStatsService, "TYPE_RX_BYTES", "I"); - jfieldID rxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_RX_PACKETS", "I"); - jfieldID txBytesId = env->GetStaticFieldID(netStatsService, "TYPE_TX_BYTES", "I"); - jfieldID txPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TX_PACKETS", "I"); - jfieldID tcpRxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TCP_RX_PACKETS", "I"); - jfieldID tcpTxPacketsId = env->GetStaticFieldID(netStatsService, "TYPE_TCP_TX_PACKETS", "I"); - - env->SetStaticIntField(netStatsService, rxBytesId, RX_BYTES); - env->SetStaticIntField(netStatsService, rxPacketsId, RX_PACKETS); - env->SetStaticIntField(netStatsService, txBytesId, TX_BYTES); - env->SetStaticIntField(netStatsService, txPacketsId, TX_PACKETS); - env->SetStaticIntField(netStatsService, tcpRxPacketsId, TCP_RX_PACKETS); - env->SetStaticIntField(netStatsService, tcpTxPacketsId, TCP_TX_PACKETS); - return jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsService", gMethods, NELEM(gMethods)); } diff --git a/services/net/Android.bp b/services/net/Android.bp index 72ad36677c23..29bf37460568 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -22,13 +22,14 @@ java_library_static { // Version of services.net for usage by the wifi mainline module. // Note: This is compiled against module_current. -// TODO(b/145825329): This should be moved to networkstack-client, +// TODO(b/172457099): This should be moved to networkstack-client, // with dependencies moved to frameworks/libs/net right. java_library { name: "services.net-module-wifi", srcs: [ ":framework-services-net-module-wifi-shared-srcs", ":net-module-utils-srcs", + ":net-utils-services-common-srcs", "java/android/net/ip/IpClientCallbacks.java", "java/android/net/ip/IpClientManager.java", "java/android/net/ip/IpClientUtil.java", @@ -39,6 +40,7 @@ java_library { "java/android/net/TcpKeepalivePacketData.java", ], sdk_version: "module_current", + min_sdk_version: "30", libs: [ "unsupportedappusage", "framework-wifi-util-lib", @@ -49,7 +51,6 @@ java_library { "netd_aidl_interface-V3-java", "netlink-client", "networkstack-client", - "net-utils-services-common", ], apex_available: [ "com.android.wifi", diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java index 19449654f2ec..0d878b401bee 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -28,7 +28,6 @@ import android.os.IBinder.DeathRecipient; import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.os.UpdateEngine; import android.os.UpdateEngineCallback; import android.provider.DeviceConfig; @@ -227,8 +226,8 @@ public final class ProfcollectForwardingService extends SystemService { } // Sample for a fraction of app launches. - int traceFrequency = - SystemProperties.getInt("persist.profcollectd.applaunch_trace_freq", 2); + int traceFrequency = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT, + "applaunch_trace_freq", 2); int randomNum = ThreadLocalRandom.current().nextInt(100); if (randomNum < traceFrequency) { try { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 553df3bafd00..63d7dbd15031 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -353,6 +353,147 @@ public class HdmiCecMessageValidatorTest { assertMessageValidity("40:35:EE:52:4A").isEqualTo(ERROR_PARAMETER); } + @Test + public void isValid_deckControl() { + assertMessageValidity("40:42:01:6E").isEqualTo(OK); + assertMessageValidity("40:42:04").isEqualTo(OK); + + assertMessageValidity("4F:42:01").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:42:04").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:42").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("40:42:05").isEqualTo(ERROR_PARAMETER); + } + + @Test + public void isValid_deckStatus() { + assertMessageValidity("40:1B:11:58").isEqualTo(OK); + assertMessageValidity("40:1B:1F").isEqualTo(OK); + + assertMessageValidity("4F:1B:11").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:1B:1F").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:1B").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("40:1B:10").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:1B:20").isEqualTo(ERROR_PARAMETER); + } + + @Test + public void isValid_statusRequest() { + assertMessageValidity("40:08:01").isEqualTo(OK); + assertMessageValidity("40:08:02:5C").isEqualTo(OK); + assertMessageValidity("40:1A:01:F8").isEqualTo(OK); + assertMessageValidity("40:1A:03").isEqualTo(OK); + + assertMessageValidity("4F:08:01").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:08:03").isEqualTo(ERROR_SOURCE); + assertMessageValidity("4F:1A:01").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:1A:03").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:08").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("40:1A").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("40:08:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:08:05").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:1A:00").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:1A:04").isEqualTo(ERROR_PARAMETER); + } + + @Test + public void isValid_play() { + assertMessageValidity("40:41:16:E3").isEqualTo(OK); + assertMessageValidity("40:41:20").isEqualTo(OK); + + assertMessageValidity("4F:41:16").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:41:20").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:41").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("40:41:04").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:41:18").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:41:23").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:41:26").isEqualTo(ERROR_PARAMETER); + } + + @Test + public void isValid_selectAnalogueService() { + assertMessageValidity("40:92:00:13:0F:00:96").isEqualTo(OK); + assertMessageValidity("40:92:02:EA:60:1F").isEqualTo(OK); + + assertMessageValidity("4F:92:00:13:0F:00").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:92:02:EA:60:1F").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:92:00:13:0F").isEqualTo(ERROR_PARAMETER_SHORT); + // Invalid Analogue Broadcast type + assertMessageValidity("40:92:03:EA:60:1F").isEqualTo(ERROR_PARAMETER); + // Invalid Analogue Frequency + assertMessageValidity("40:92:00:FF:FF:00").isEqualTo(ERROR_PARAMETER); + // Invalid Broadcast system + assertMessageValidity("40:92:02:EA:60:20").isEqualTo(ERROR_PARAMETER); + } + + @Test + public void isValid_selectDigitalService() { + assertMessageValidity("40:93:00:11:CE:90:0F:00:78").isEqualTo(OK); + assertMessageValidity("40:93:10:13:0B:34:38").isEqualTo(OK); + assertMessageValidity("40:93:9A:06:F9:D3:E6").isEqualTo(OK); + assertMessageValidity("40:93:91:09:F4:40:C8").isEqualTo(OK); + + assertMessageValidity("4F:93:00:11:CE:90:0F:00:78").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:93:10:13:0B:34:38").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:93:9A:06:F9").isEqualTo(ERROR_PARAMETER_SHORT); + // Invalid Digital Broadcast System + assertMessageValidity("40:93:14:11:CE:90:0F:00:78").isEqualTo(ERROR_PARAMETER); + // Invalid Digital Broadcast System + assertMessageValidity("40:93:A0:07:95:F1").isEqualTo(ERROR_PARAMETER); + // Insufficient data for ARIB Broadcast system + assertMessageValidity("40:93:00:11:CE:90:0F:00").isEqualTo(ERROR_PARAMETER); + // Insufficient data for ATSC Broadcast system + assertMessageValidity("40:93:10:13:0B:34").isEqualTo(ERROR_PARAMETER); + // Insufficient data for DVB Broadcast system + assertMessageValidity("40:93:18:BE:77:00:7D:01").isEqualTo(ERROR_PARAMETER); + // Invalid channel number format + assertMessageValidity("40:93:9A:10:F9:D3").isEqualTo(ERROR_PARAMETER); + // Insufficient data for 2 part channel number + assertMessageValidity("40:93:91:09:F4:40").isEqualTo(ERROR_PARAMETER); + } + + @Test + public void isValid_UserControlPressed() { + assertMessageValidity("40:44:07").isEqualTo(OK); + assertMessageValidity("40:44:52:A7").isEqualTo(OK); + + assertMessageValidity("40:44:60").isEqualTo(OK); + assertMessageValidity("40:44:60:1A").isEqualTo(OK); + + assertMessageValidity("40:44:67").isEqualTo(OK); + assertMessageValidity("40:44:67:04:00:B1").isEqualTo(OK); + assertMessageValidity("40:44:67:09:C8:72:C8").isEqualTo(OK); + + assertMessageValidity("40:44:68").isEqualTo(OK); + assertMessageValidity("40:44:68:93").isEqualTo(OK); + assertMessageValidity("40:44:69").isEqualTo(OK); + assertMessageValidity("40:44:69:7C").isEqualTo(OK); + assertMessageValidity("40:44:6A").isEqualTo(OK); + assertMessageValidity("40:44:6A:B4").isEqualTo(OK); + + assertMessageValidity("40:44:56").isEqualTo(OK); + assertMessageValidity("40:44:56:60").isEqualTo(OK); + + assertMessageValidity("40:44:57").isEqualTo(OK); + assertMessageValidity("40:44:57:A0").isEqualTo(OK); + + assertMessageValidity("4F:44:07").isEqualTo(ERROR_DESTINATION); + assertMessageValidity("F0:44:52:A7").isEqualTo(ERROR_SOURCE); + assertMessageValidity("40:44").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("40:44:67:04:B1").isEqualTo(ERROR_PARAMETER_SHORT); + // Invalid Play mode + assertMessageValidity("40:44:60:04").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:44:60:08").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:44:60:26").isEqualTo(ERROR_PARAMETER); + // Invalid Channel Identifier - Channel number format + assertMessageValidity("40:44:67:11:8A:42").isEqualTo(ERROR_PARAMETER); + // Insufficient data for 2 - part channel number + assertMessageValidity("40:44:67:09:C8:72").isEqualTo(ERROR_PARAMETER); + // Invalid UI Broadcast type + assertMessageValidity("40:44:56:11").isEqualTo(ERROR_PARAMETER); + // Invalid UI Sound Presentation Control + assertMessageValidity("40:44:57:40").isEqualTo(ERROR_PARAMETER); + } + private IntegerSubject assertMessageValidity(String message) { return assertThat(mHdmiCecMessageValidator.isValid(buildMessage(message))); } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index bbf34df8fe84..724a9e477b95 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -767,6 +767,19 @@ public abstract class Connection extends Conferenceable { "android.telecom.extra.AUDIO_CODEC"; /** + * Float connection extra key used to store the audio codec bitrate in kbps for the current + * {@link Connection}. + */ + public static final String EXTRA_AUDIO_CODEC_BITRATE_KBPS = + "android.telecom.extra.AUDIO_CODEC_BITRATE_KBPS"; + + /** + * Float connection extra key used to store the audio codec bandwidth in khz for the current + * {@link Connection}. + */ + public static final String EXTRA_AUDIO_CODEC_BANDWIDTH_KHZ = + "android.telecom.extra.AUDIO_CODEC_BANDWIDTH_KHZ"; + /** * Connection event used to inform Telecom that it should play the on hold tone. This is used * to play a tone when the peer puts the current call on hold. Sent to Telecom via * {@link #sendConnectionEvent(String, Bundle)}. diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 982e5f30e28c..07de61759d59 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -680,6 +680,7 @@ public abstract class InCallService extends Service { public static abstract class VideoCall { /** @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void destroy(); /** diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 724c17177f76..da2d4d82481b 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -32,6 +32,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -1227,7 +1228,7 @@ public class TelecomManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public List<PhoneAccountHandle> getPhoneAccountsForPackage() { try { if (isServiceConnected()) { @@ -1355,7 +1356,7 @@ public class TelecomManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void clearPhoneAccounts() { clearAccounts(); } @@ -1365,7 +1366,7 @@ public class TelecomManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void clearAccounts() { try { if (isServiceConnected()) { @@ -1397,7 +1398,7 @@ public class TelecomManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public ComponentName getDefaultPhoneApp() { try { if (isServiceConnected()) { @@ -1589,6 +1590,30 @@ public class TelecomManager { } /** + * Returns whether the caller has {@link InCallService} access for companion apps. + * + * A companion app is an app associated with a physical wearable device via the + * {@link android.companion.CompanionDeviceManager} API. + * + * @return {@code true} if the caller has {@link InCallService} access for + * companion app; {@code false} otherwise. + */ + public boolean hasCompanionInCallServiceAccess() { + try { + if (isServiceConnected()) { + return getTelecomService().hasCompanionInCallServiceAccess( + mContext.getOpPackageName()); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling hasCompanionInCallServiceAccess().", e); + if (!isSystemProcess()) { + e.rethrowAsRuntimeException(); + } + } + return false; + } + + /** * Returns whether there is an ongoing call originating from a managed * {@link ConnectionService}. An ongoing call can be in dialing, ringing, active or holding * states. @@ -2384,6 +2409,10 @@ public class TelecomManager { } } + private boolean isSystemProcess() { + return Process.myUid() == Process.SYSTEM_UID; + } + private ITelecomService getTelecomService() { if (mTelecomServiceOverride != null) { return mTelecomServiceOverride; diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index a28a999e8d19..6dc096daf4ea 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -179,6 +179,11 @@ interface ITelecomService { boolean isInCall(String callingPackage, String callingFeatureId); /** + * @see TelecomServiceImpl#hasCompanionInCallServiceAccess + */ + boolean hasCompanionInCallServiceAccess(String callingPackage); + + /** * @see TelecomServiceImpl#isInManagedCall */ boolean isInManagedCall(String callingPackage, String callingFeatureId); diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 7aecfdde71bc..d1412b772eef 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -32,6 +32,7 @@ import android.util.ArrayMap; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.util.TelephonyUtils; import java.util.ArrayList; import java.util.List; @@ -314,7 +315,7 @@ public final class CarrierAppUtils { String[] packageNames = new String[enabledCarrierPackages.size()]; enabledCarrierPackages.toArray(packageNames); permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, - UserHandle.of(userId), Runnable::run, isSuccess -> { }); + UserHandle.of(userId), TelephonyUtils.DIRECT_EXECUTOR, isSuccess -> { }); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, "Could not reach PackageManager", e); diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index 7736473feafb..02d741011220 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -34,6 +34,7 @@ import java.io.PrintWriter; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -44,6 +45,8 @@ public final class TelephonyUtils { public static boolean IS_USER = "user".equals(android.os.Build.TYPE); public static boolean IS_DEBUGGABLE = SystemProperties.getInt("ro.debuggable", 0) == 1; + public static final Executor DIRECT_EXECUTOR = Runnable::run; + /** * Verify that caller holds {@link android.Manifest.permission#DUMP}. * diff --git a/telephony/java/android/telephony/CarrierBandwidth.aidl b/telephony/java/android/telephony/CarrierBandwidth.aidl new file mode 100644 index 000000000000..d0861b88e737 --- /dev/null +++ b/telephony/java/android/telephony/CarrierBandwidth.aidl @@ -0,0 +1,17 @@ +/* + * 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.telephony; +parcelable CarrierBandwidth;
\ No newline at end of file diff --git a/telephony/java/android/telephony/CarrierBandwidth.java b/telephony/java/android/telephony/CarrierBandwidth.java new file mode 100644 index 000000000000..17747a3919ee --- /dev/null +++ b/telephony/java/android/telephony/CarrierBandwidth.java @@ -0,0 +1,208 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Defines downlink and uplink capacity of a network in kbps + * @hide + */ +@SystemApi +public final class CarrierBandwidth implements Parcelable { + /** + * Any field that is not reported shall be set to INVALID + */ + public static final int INVALID = -1; + + /** + * Estimated downlink capacity in kbps of the primary carrier. + * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth. + * This will be {@link #INVALID} if the network is not connected + */ + private int mPrimaryDownlinkCapacityKbps; + + /** + * Estimated uplink capacity in kbps of the primary carrier. + * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth. + * This will be {@link #INVALID} if the network is not connected + */ + private int mPrimaryUplinkCapacityKbps; + + /** + * Estimated downlink capacity in kbps of the secondary carrier in a dual connected network. + * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth. + * This will be {@link #INVALID} if the network is not connected + */ + private int mSecondaryDownlinkCapacityKbps; + + /** + * Estimated uplink capacity in kbps of the secondary carrier in a dual connected network. + * This bandwidth estimate shall be the estimated maximum sustainable link bandwidth. + * This will be {@link #INVALID} if the network is not connected + */ + private int mSecondaryUplinkCapacityKbps; + + /** @hide **/ + public CarrierBandwidth(Parcel in) { + mPrimaryDownlinkCapacityKbps = in.readInt(); + mPrimaryUplinkCapacityKbps = in.readInt(); + mSecondaryDownlinkCapacityKbps = in.readInt(); + mSecondaryUplinkCapacityKbps = in.readInt(); + } + + /** @hide **/ + public CarrierBandwidth() { + mPrimaryDownlinkCapacityKbps = INVALID; + mPrimaryUplinkCapacityKbps = INVALID; + mSecondaryDownlinkCapacityKbps = INVALID; + mSecondaryUplinkCapacityKbps = INVALID; + } + + /** + * Constructor. + * + * @param primaryDownlinkCapacityKbps Estimated downlink capacity in kbps of + * the primary carrier. + * @param primaryUplinkCapacityKbps Estimated uplink capacity in kbps of + * the primary carrier. + * @param secondaryDownlinkCapacityKbps Estimated downlink capacity in kbps of + * the secondary carrier + * @param secondaryUplinkCapacityKbps Estimated uplink capacity in kbps of + * the secondary carrier + */ + public CarrierBandwidth(int primaryDownlinkCapacityKbps, int primaryUplinkCapacityKbps, + int secondaryDownlinkCapacityKbps, int secondaryUplinkCapacityKbps) { + mPrimaryDownlinkCapacityKbps = primaryDownlinkCapacityKbps; + mPrimaryUplinkCapacityKbps = primaryUplinkCapacityKbps; + mSecondaryDownlinkCapacityKbps = secondaryDownlinkCapacityKbps; + mSecondaryUplinkCapacityKbps = secondaryUplinkCapacityKbps; + } + + /** + * Retrieves the upstream bandwidth for the primary network in Kbps. This always only refers to + * the estimated first hop transport bandwidth. + * This will be INVALID if the network is not connected + * + * @return The estimated first hop upstream (device to network) bandwidth. + */ + public int getPrimaryDownlinkCapacityKbps() { + return mPrimaryDownlinkCapacityKbps; + } + + /** + * Retrieves the downstream bandwidth for the primary network in Kbps. This always only refers + * to the estimated first hop transport bandwidth. + * This will be INVALID if the network is not connected + * + * @return The estimated first hop downstream (network to device) bandwidth. + */ + public int getPrimaryUplinkCapacityKbps() { + return mPrimaryUplinkCapacityKbps; + } + + /** + * Retrieves the upstream bandwidth for the secondary network in Kbps. This always only refers + * to the estimated first hop transport bandwidth. + * This will be INVALID if the network is not connected + * + * @return The estimated first hop upstream (device to network) bandwidth. + */ + public int getSecondaryDownlinkCapacityKbps() { + return mSecondaryDownlinkCapacityKbps; + } + + /** + * Retrieves the downstream bandwidth for the secondary network in Kbps. This always only + * refers to the estimated first hop transport bandwidth. + * This will be INVALID if the network is not connected + * + * @return The estimated first hop downstream (network to device) bandwidth. + */ + public int getSecondaryUplinkCapacityKbps() { + return mSecondaryUplinkCapacityKbps; + } + + @NonNull + @Override + public String toString() { + return "CarrierBandwidth: {primaryDownlinkCapacityKbps=" + mPrimaryDownlinkCapacityKbps + + " primaryUplinkCapacityKbps=" + mPrimaryUplinkCapacityKbps + + " secondaryDownlinkCapacityKbps=" + mSecondaryDownlinkCapacityKbps + + " secondaryUplinkCapacityKbps=" + mSecondaryUplinkCapacityKbps + + "}"; + } + + @Override + public int hashCode() { + return Objects.hash( + mPrimaryDownlinkCapacityKbps, + mPrimaryUplinkCapacityKbps, + mSecondaryDownlinkCapacityKbps, + mSecondaryUplinkCapacityKbps); + } + + @Override + public boolean equals(@Nullable Object o) { + if (o == null || !(o instanceof CallQuality) || hashCode() != o.hashCode()) { + return false; + } + if (this == o) { + return true; + } + CarrierBandwidth s = (CarrierBandwidth) o; + return (mPrimaryDownlinkCapacityKbps == s.mPrimaryDownlinkCapacityKbps + && mPrimaryUplinkCapacityKbps == s.mPrimaryUplinkCapacityKbps + && mSecondaryDownlinkCapacityKbps == s.mSecondaryDownlinkCapacityKbps + && mSecondaryDownlinkCapacityKbps == s.mSecondaryDownlinkCapacityKbps); + } + + /** + * {@link Parcelable#describeContents} + */ + public int describeContents() { + return 0; + } + + /** + * {@link Parcelable#writeToParcel} + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mPrimaryDownlinkCapacityKbps); + dest.writeInt(mPrimaryUplinkCapacityKbps); + dest.writeInt(mSecondaryDownlinkCapacityKbps); + dest.writeInt(mSecondaryUplinkCapacityKbps); + } + + public static final @android.annotation.NonNull Parcelable.Creator<CarrierBandwidth> CREATOR = + new Parcelable.Creator() { + public CarrierBandwidth createFromParcel(Parcel in) { + return new CarrierBandwidth(in); + } + + public CarrierBandwidth[] newArray(int size) { + return new CarrierBandwidth[size]; + } + }; +} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 0d557067f84e..0c0943d795f5 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -79,6 +79,30 @@ public class CarrierConfigManager { */ public static final int SERVICE_CLASS_VOICE = ImsSsData.SERVICE_CLASS_VOICE; + /** + * Only send USSD over IMS while CS is out of service, otherwise send USSD over CS. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_CS_PREFERRED = 0; + + /** + * Send USSD over IMS or CS while IMS is out of service or silent redial over CS if needed. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_IMS_PREFERRED = 1; + + /** + * Only send USSD over CS. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_CS_ONLY = 2; + + /** + * Only send USSD over IMS and disallow silent redial over CS. + * {@link #KEY_CARRIER_USSD_METHOD_INT} + */ + public static final int USSD_OVER_IMS_ONLY = 3; + private final Context mContext; /** @@ -584,6 +608,20 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_VT_AVAILABLE_BOOL = "carrier_vt_available_bool"; /** + * Specify the method of selection for UE sending USSD requests. The default value is + * {@link #USSD_OVER_CS_PREFERRED}. + * <p> Available options: + * <ul> + * <li>0: {@link #USSD_OVER_CS_PREFERRED} </li> + * <li>1: {@link #USSD_OVER_IMS_PREFERRED} </li> + * <li>2: {@link #USSD_OVER_CS_ONLY} </li> + * <li>3: {@link #USSD_OVER_IMS_ONLY} </li> + * </ul> + */ + public static final String KEY_CARRIER_USSD_METHOD_INT = + "carrier_ussd_method_int"; + + /** * Flag specifying whether to show an alert dialog for 5G disable when the user disables VoLTE. * By default this value is {@code false}. * @@ -3969,6 +4007,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_SETTINGS_ENABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_AVAILABLE_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VT_AVAILABLE_BOOL, false); + sDefaults.putInt(KEY_CARRIER_USSD_METHOD_INT, USSD_OVER_CS_PREFERRED); sDefaults.putBoolean(KEY_VOLTE_5G_LIMITED_ALERT_DIALOG_BOOL, false); sDefaults.putBoolean(KEY_NOTIFY_HANDOVER_VIDEO_FROM_WIFI_TO_LTE_BOOL, false); sDefaults.putBoolean(KEY_ALLOW_MERGING_RTT_CALLS_BOOL, false); @@ -4743,7 +4782,7 @@ public class CarrierConfigManager { */ @NonNull @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public static PersistableBundle getDefaultConfig() { return new PersistableBundle(sDefaults); } diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java index 8f5ec365e65c..e595002d175b 100644 --- a/telephony/java/android/telephony/CellLocation.java +++ b/telephony/java/android/telephony/CellLocation.java @@ -98,12 +98,14 @@ public abstract class CellLocation { /** * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract void fillInNotifierBundle(Bundle bundle); /** * @hide */ + @SuppressWarnings("HiddenAbstractMethod") @UnsupportedAppUsage public abstract boolean isEmpty(); @@ -111,6 +113,7 @@ public abstract class CellLocation { * Invalidate this object. The location area code and the cell id are set to -1. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract void setStateInvalid(); /** diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java index 28feab27a794..42d7707c97d9 100644 --- a/telephony/java/android/telephony/ImsManager.java +++ b/telephony/java/android/telephony/ImsManager.java @@ -22,7 +22,12 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.telephony.BinderCacheManager; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyFrameworkInitializer; +import android.telephony.ims.aidl.IImsRcsController; + +import com.android.internal.telephony.ITelephony; /** * Provides access to information about Telephony IMS services on the device. @@ -30,8 +35,6 @@ import android.telephony.SubscriptionManager; @SystemService(Context.TELEPHONY_IMS_SERVICE) public class ImsManager { - private Context mContext; - /** * <p>Broadcast Action: Indicates that a previously allowed IMS operation was rejected by the * network due to the network returning a "forbidden" response. This may be due to a @@ -87,6 +90,14 @@ public class ImsManager { public static final String EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE = "android.telephony.ims.extra.WFC_REGISTRATION_FAILURE_MESSAGE"; + // Cache Telephony Binder interfaces, one cache per process. + private static final BinderCacheManager<ITelephony> sTelephonyCache = + new BinderCacheManager<>(ImsManager::getITelephonyInterface); + private static final BinderCacheManager<IImsRcsController> sRcsCache = + new BinderCacheManager<>(ImsManager::getIImsRcsControllerInterface); + + private final Context mContext; + /** * Use {@link Context#getSystemService(String)} to get an instance of this class. * @hide @@ -108,7 +119,7 @@ public class ImsManager { throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId); } - return new ImsRcsManager(mContext, subscriptionId); + return new ImsRcsManager(mContext, subscriptionId, sRcsCache); } /** @@ -124,17 +135,19 @@ public class ImsManager { throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId); } - return new ImsMmTelManager(subscriptionId); + return new ImsMmTelManager(subscriptionId, sTelephonyCache); } /** - * Create an instance of SipDelegateManager for the subscription id specified. + * Create an instance of {@link SipDelegateManager} for the subscription id specified. * <p> - * Used for RCS single registration cases, where an IMS application needs to forward SIP - * traffic through the device's IMS service. - * @param subscriptionId The ID of the subscription that this SipDelegateManager will use. + * Allows an IMS application to forward SIP traffic through the device's IMS service, + * which is used for cellular carriers that require the device to share a single IMS + * registration for both MMTEL and RCS features. + * @param subscriptionId The ID of the subscription that this {@link SipDelegateManager} will + * be bound to. * @throws IllegalArgumentException if the subscription is invalid. - * @return a SipDelegateManager instance for the specified subscription ID. + * @return a {@link SipDelegateManager} instance for the specified subscription ID. * @hide */ @SystemApi @@ -144,6 +157,22 @@ public class ImsManager { throw new IllegalArgumentException("Invalid subscription ID: " + subscriptionId); } - return new SipDelegateManager(mContext, subscriptionId); + return new SipDelegateManager(mContext, subscriptionId, sRcsCache); + } + + private static IImsRcsController getIImsRcsControllerInterface() { + return IImsRcsController.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyImsServiceRegisterer() + .get()); + } + + private static ITelephony getITelephonyInterface() { + return ITelephony.Stub.asInterface( + TelephonyFrameworkInitializer + .getTelephonyServiceManager() + .getTelephonyServiceRegisterer() + .get()); } } diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index f8a200a5f8d3..8507d8512a5c 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.TransportType; @@ -635,7 +636,8 @@ public final class NetworkRegistrationInfo implements Parcelable { .append(" cellIdentity=").append(mCellIdentity) .append(" voiceSpecificInfo=").append(mVoiceSpecificInfo) .append(" dataSpecificInfo=").append(mDataSpecificInfo) - .append(" nrState=").append(nrStateToString(mNrState)) + .append(" nrState=").append(Build.IS_DEBUGGABLE + ? nrStateToString(mNrState) : "****") .append(" rRplmn=").append(mRplmn) .append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation) .append("}").toString(); diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 41b3ee672f46..dedb1afa2495 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1102,7 +1102,8 @@ public class ServiceState implements Parcelable { .append(", isUsingCarrierAggregation=").append(isUsingCarrierAggregation()) .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost) .append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos) - .append(", mNrFrequencyRange=").append(mNrFrequencyRange) + .append(", mNrFrequencyRange=").append(Build.IS_DEBUGGABLE + ? mNrFrequencyRange : FREQUENCY_RANGE_UNKNOWN) .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw) .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw) .append(", mIsDataRoamingFromRegistration=") diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 88aec5184548..6f88cbdbff05 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4656,7 +4656,7 @@ public class TelephonyManager { * be implemented instead. */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void setVisualVoicemailEnabled(PhoneAccountHandle phoneAccountHandle, boolean enabled){ } @@ -4671,7 +4671,7 @@ public class TelephonyManager { */ @SystemApi @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public boolean isVisualVoicemailEnabled(PhoneAccountHandle phoneAccountHandle){ return false; } @@ -4690,7 +4690,7 @@ public class TelephonyManager { * @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") @Nullable public Bundle getVisualVoicemailSettings(){ try { @@ -8524,7 +8524,7 @@ public class TelephonyManager { /** @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public int checkCarrierPrivilegesForPackage(String pkgName) { try { ITelephony telephony = getITelephony(); @@ -8540,7 +8540,7 @@ public class TelephonyManager { /** @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public int checkCarrierPrivilegesForPackageAnyPhone(String pkgName) { try { ITelephony telephony = getITelephony(); @@ -8616,7 +8616,7 @@ public class TelephonyManager { /** @hide */ @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void dial(String number) { try { ITelephony telephony = getITelephony(); @@ -8675,7 +8675,7 @@ public class TelephonyManager { */ @Deprecated @SystemApi - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void silenceRinger() { // No-op } @@ -13440,6 +13440,33 @@ public class TelephonyManager { } /** + * Get carrier bandwidth. In case of Dual connected network this will report + * bandwidth per primary and secondary network. + * @return CarrierBandwidth with bandwidth of both primary and secondary carrier. + * @throws IllegalStateException if the Telephony process is not currently available. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @NonNull + public CarrierBandwidth getCarrierBandwidth() { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.getCarrierBandwidth(getSubId()); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Log.e(TAG, "getCarrierBandwidth RemoteException", ex); + ex.rethrowFromSystemServer(); + } + + //Should not reach. Adding return statement to make compiler happy + return null; + } + + /** * Called when userActivity is signalled in the power manager. * This should only be called from system Uid. * @hide @@ -13756,6 +13783,15 @@ public class TelephonyManager { } /** + * Setup sITelephony for testing. + * @hide + */ + @VisibleForTesting + public static void setupITelephonyForTest(ITelephony telephony) { + sITelephony = telephony; + } + + /** * Whether device can connect to 5G network when two SIMs are active. * @hide * TODO b/153669716: remove or make system API. diff --git a/telephony/java/android/telephony/ims/DelegateMessageCallback.java b/telephony/java/android/telephony/ims/DelegateMessageCallback.java new file mode 100644 index 000000000000..beec4a680d78 --- /dev/null +++ b/telephony/java/android/telephony/ims/DelegateMessageCallback.java @@ -0,0 +1,59 @@ +/* + * 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.telephony.ims.stub.SipDelegate; + +/** + * Callback interface provided to the SipTransport implementation to notify a remote application of + * the following: + * <ul> + * <li>A new incoming SIP message associated with the feature tags the SipDelegate registered + * with has been received or an in-dialog request to this SipDelegate has been received.</li> + * <li>Acknowledge that an outgoing SIP message from the RCS application has been sent + * successfully or notify the application of the reason why it was not sent</li> + * </ul> + * @hide + */ +public interface DelegateMessageCallback { + + /** + * Send a new incoming SIP message to the remote application for processing. + */ + void onMessageReceived(@NonNull SipMessage message); + + /** + * Notify the remote application that a previous request to send a SIP message using + * {@link SipDelegate#sendMessage} has succeeded. + * + * @param viaTransactionId The transaction ID found in the via header field of the + * previously sent {@link SipMessage}. + */ + void onMessageSent(@NonNull String viaTransactionId); + + /** + * Notify the remote application that a previous request to send a SIP message using + * {@link SipDelegate#sendMessage} has failed. + * + * @param viaTransactionId The Transaction ID found in the via header field of the previously + * sent {@link SipMessage}. + * @param reason The reason for the failure. + */ + void onMessageSendFailure(@NonNull String viaTransactionId, + @SipDelegateManager.MessageFailureReason int reason); +} diff --git a/telephony/java/android/telephony/ims/DelegateRegistrationState.aidl b/telephony/java/android/telephony/ims/DelegateRegistrationState.aidl new file mode 100644 index 000000000000..756ea920d06a --- /dev/null +++ b/telephony/java/android/telephony/ims/DelegateRegistrationState.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 DelegateRegistrationState; diff --git a/telephony/java/android/telephony/ims/DelegateRegistrationState.java b/telephony/java/android/telephony/ims/DelegateRegistrationState.java new file mode 100644 index 000000000000..4facfa77de21 --- /dev/null +++ b/telephony/java/android/telephony/ims/DelegateRegistrationState.java @@ -0,0 +1,327 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArraySet; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +/** + * Contains the full state of the IMS feature tags associated with a SipDelegate and managed by the + * ImsService. + * @hide + */ +public final class DelegateRegistrationState implements Parcelable { + + /** + * This feature tag has been deregistered for an unknown reason. Outgoing out-of-dialog SIP + * messages associated with feature tags that are not registered will fail. + */ + public static final int DEREGISTERED_REASON_UNKNOWN = 0; + + /** + * This feature tag has been deregistered because it is not provisioned to be used on this radio + * access technology or PDN. Outgoing out-of-dialog SIP messages associated with feature tags + * that are not registered will fail. + * <p> + * There may be new incoming SIP dialog requests on a feature that that is not provisioned. It + * is still expected that the SipDelegateConnection responds to the request. + */ + public static final int DEREGISTERED_REASON_NOT_PROVISIONED = 1; + + /** + * This feature tag has been deregistered because IMS has been deregistered. All outgoing SIP + * messages will fail until IMS registration occurs. + */ + public static final int DEREGISTERED_REASON_NOT_REGISTERED = 2; + + /** + * This feature tag is being deregistered because the PDN that the IMS registration is on is + *changing. + * All open SIP dialogs need to be closed before the PDN change can proceed. + */ + public static final int DEREGISTERING_REASON_PDN_CHANGE = 3; + + /** + * This feature tag is being deregistered due to a provisioning change. This can be triggered by + * many things, such as a provisioning change triggered by the carrier network, a radio access + * technology change by the modem causing a different set of feature tags to be provisioned, or + * a user triggered hange, such as data being enabled/disabled. + * <p> + * All open SIP dialogs associated with the new deprovisioned feature tag need to be closed + * before the IMS registration modification can proceed. + */ + public static final int DEREGISTERING_REASON_PROVISIONING_CHANGE = 4; + + /** + * This feature tag is deregistering because the SipDelegate associated with this feature tag + * needs to change its supported feature set. + * <p> + * All open SIP Dialogs associated with this feature tag must be closed before this operation + * can proceed. + */ + public static final int DEREGISTERING_REASON_FEATURE_TAGS_CHANGING = 5; + + /** + * This feature tag is deregistering because the SipDelegate is in the process of being + * destroyed. + * <p> + * All open SIP Dialogs associated with this feature tag must be closed before this operation + * can proceed. + */ + public static final int DEREGISTERING_REASON_DESTROY_PENDING = 6; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "DEREGISTERED_REASON_", value = { + DEREGISTERED_REASON_UNKNOWN, + DEREGISTERED_REASON_NOT_PROVISIONED, + DEREGISTERED_REASON_NOT_REGISTERED + }) + public @interface DeregisteredReason {} + + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "DEREGISTERING_REASON_", value = { + DEREGISTERING_REASON_PDN_CHANGE, + DEREGISTERING_REASON_PROVISIONING_CHANGE, + DEREGISTERING_REASON_FEATURE_TAGS_CHANGING, + DEREGISTERING_REASON_DESTROY_PENDING + }) + public @interface DeregisteringReason {} + + private final ArrayList<String> mRegisteredTags = new ArrayList<>(); + private final ArrayList<FeatureTagState> mDeregisteringTags = new ArrayList<>(); + private final ArrayList<FeatureTagState> mDeregisteredTags = new ArrayList<>(); + + /** + * Builder used to create new instances of {@link DelegateRegistrationState}. + */ + public static class Builder { + + private final DelegateRegistrationState mState; + + /* Create a new instance of {@link Builder} */ + public Builder() { + mState = new DelegateRegistrationState(); + } + + /** + * Add a feature tag that is currently included in the current network IMS Registration. + * @param featureTag The IMS media feature tag included in the current IMS registration. + * @return The in-progress Builder instance for RegistrationState. + */ + public Builder addRegisteredFeatureTag(@NonNull String featureTag) { + if (!mState.mRegisteredTags.contains(featureTag)) { + mState.mRegisteredTags.add(featureTag); + } + return this; + } + + /** + * Add a list of feature tags that are currently included in the current network IMS + * Registration. + * @param featureTags The IMS media feature tags included in the current IMS registration. + * @return The in-progress Builder instance for RegistrationState. + */ + public Builder addRegisteredFeatureTags(@NonNull Set<String> featureTags) { + mState.mRegisteredTags.addAll(featureTags); + return this; + } + + /** + * Add a feature tag that is in the current network IMS Registration, but is in the progress + * of being deregistered and requires action from the RCS application before the IMS + * registration can be modified. + * + * See {@link DeregisteringReason} for more information regarding what is required by the + * RCS application to proceed. + * + * @param featureTag The media feature tag that has limited or no availability due to its + * current deregistering state. + * @param reason The reason why the media feature tag has moved to the deregistering state. + * The availability of the feature tag depends on the {@link DeregisteringReason}. + * @return The in-progress Builder instance for RegistrationState. + */ + public Builder addDeregisteringFeatureTag(@NonNull String featureTag, + @DeregisteringReason int reason) { + boolean ftExists = mState.mDeregisteringTags.stream().anyMatch( + f -> f.getFeatureTag().equals(featureTag)); + if (!ftExists) { + mState.mDeregisteringTags.add(new FeatureTagState(featureTag, reason)); + } + return this; + } + + /** + * Add a feature tag that is currently not included in the network RCS registration. See + * {@link DeregisteredReason} for more information regarding the reason for why the feature + * tag is not registered. + * @param featureTag The media feature tag that is not registered. + * @param reason The reason why the media feature tag has been deregistered. + * @return The in-progress Builder instance for RegistrationState. + */ + public Builder addDeregisteredFeatureTag(@NonNull String featureTag, + @DeregisteredReason int reason) { + boolean ftExists = mState.mDeregisteredTags.stream().anyMatch( + f -> f.getFeatureTag().equals(featureTag)); + if (!ftExists) { + mState.mDeregisteredTags.add(new FeatureTagState(featureTag, reason)); + } + return this; + } + + /** + * @return the finalized instance. + */ + public DelegateRegistrationState build() { + return mState; + } + } + + /** + * The builder should be used to construct a new instance of this class. + */ + private DelegateRegistrationState() {} + + /** + * Used for unparcelling only. + */ + private DelegateRegistrationState(Parcel source) { + source.readList(mRegisteredTags, null /*classloader*/); + readStateFromParcel(source, mDeregisteringTags); + readStateFromParcel(source, mDeregisteredTags); + } + + /** + * Get the feature tags that this SipDelegate is associated with that are currently part of the + * network IMS registration. SIP Messages both in and out of a SIP Dialog may be sent and + * received using these feature tags. + * @return A Set of feature tags that the SipDelegate has associated with that are included in + * the network IMS registration. + */ + public @NonNull Set<String> getRegisteredFeatureTags() { + return new ArraySet<>(mRegisteredTags); + } + + /** + * Get the feature tags that this SipDelegate is associated with that are currently part of the + * network IMS registration but are in the process of being deregistered. + * <p> + * Any incoming SIP messages associated with a feature tag included in this list will still be + * delivered. Outgoing SIP messages that are still in-dialog will be delivered to the + * SipDelegate, but outgoing out-of-dialog SIP messages with a feature tag that is included in + * this list will fail. + * <p> + * The SipDelegate will stay in this state for a limited period of time while it waits for the + * RCS application to perform a specific action. More details on the actions that can cause this + * state as well as the expected response are included in the reason codes and can be found in + * {@link DeregisteringReason}. + * @return A Set of feature tags that the SipDelegate has associated with that are included in + * the network IMS registration but are in the process of deregistering. + */ + public @NonNull Set<FeatureTagState> getDeregisteringFeatureTags() { + return new ArraySet<>(mDeregisteringTags); + } + + /** + * Get the list of feature tags that are associated with this SipDelegate but are not currently + * included in the network IMS registration. + * <p> + * See {@link DeregisteredReason} codes for more information related to the reasons why this may + * occur. + * <p> + * Due to network race conditions, there may still be onditions where an incoming out-of-dialog + * SIP message is delivered for a feature tag that is considered deregistered. Due to this + * condition, in-dialog outgoing SIP messages for deregistered feature tags will still be + * allowed as long as they are in response to a dialog started by a remote party. Any outgoing + * out-of-dialog SIP messages associated with feature tags included in this list will fail to be + * sent. + * @return A list of feature tags that the SipDelegate has associated with that not included in + * the network IMS registration. + */ + public @NonNull Set<FeatureTagState> getDeregisteredFeatureTags() { + return new ArraySet<>(mDeregisteredTags); + } + + public static final Creator<DelegateRegistrationState> CREATOR = + new Creator<DelegateRegistrationState>() { + @Override + public DelegateRegistrationState createFromParcel(Parcel source) { + return new DelegateRegistrationState(source); + } + + @Override + public DelegateRegistrationState[] newArray(int size) { + return new DelegateRegistrationState[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeList(mRegisteredTags); + writeStateToParcel(dest, mDeregisteringTags); + writeStateToParcel(dest, mDeregisteredTags); + } + + private void writeStateToParcel(Parcel dest, List<FeatureTagState> state) { + dest.writeInt(state.size()); + for (FeatureTagState s : state) { + dest.writeString(s.getFeatureTag()); + dest.writeInt(s.getState()); + } + } + + private void readStateFromParcel(Parcel source, List<FeatureTagState> emptyState) { + int len = source.readInt(); + for (int i = 0; i < len; i++) { + String ft = source.readString(); + int reason = source.readInt(); + emptyState.add(new FeatureTagState(ft, reason)); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DelegateRegistrationState that = (DelegateRegistrationState) o; + return mRegisteredTags.equals(that.mRegisteredTags) + && mDeregisteringTags.equals(that.mDeregisteringTags) + && mDeregisteredTags.equals(that.mDeregisteredTags); + } + + @Override + public int hashCode() { + return Objects.hash(mRegisteredTags, mDeregisteringTags, mDeregisteredTags); + } +} diff --git a/telephony/java/android/telephony/ims/DelegateRequest.aidl b/telephony/java/android/telephony/ims/DelegateRequest.aidl new file mode 100644 index 000000000000..60c990f8258f --- /dev/null +++ b/telephony/java/android/telephony/ims/DelegateRequest.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 DelegateRequest; diff --git a/telephony/java/android/telephony/ims/DelegateRequest.java b/telephony/java/android/telephony/ims/DelegateRequest.java new file mode 100644 index 000000000000..f384901d58bd --- /dev/null +++ b/telephony/java/android/telephony/ims/DelegateRequest.java @@ -0,0 +1,101 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; +import android.telephony.ims.stub.SipDelegate; +import android.util.ArraySet; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.Set; + +/** + * Contains information required for the creation of a {@link SipDelegate} and the associated + * SipDelegateConnection given back to the requesting application. + * @hide + */ +public final class DelegateRequest implements Parcelable { + + private final ArrayList<String> mFeatureTags; + + /** + * Create a new DelegateRequest, which will be used to create a SipDelegate by the ImsService. + * @param featureTags The list of IMS feature tags that will be associated with the SipDelegate + * created using this DelegateRequest. All feature tags are expected to be in + * the format defined in RCC.07 section 2.6.1.3. + */ + public DelegateRequest(@NonNull Set<String> featureTags) { + if (featureTags == null) { + throw new IllegalStateException("Invalid arguments, featureTags List can not be null"); + } + mFeatureTags = new ArrayList<>(featureTags); + } + + /** + * @return the list of IMS feature tag associated with this DelegateRequest in the format + * defined in RCC.07 section 2.6.1.3. + */ + public Set<String> getFeatureTags() { + return new ArraySet<>(mFeatureTags); + } + + /** + * Internal constructor used only for unparcelling. + */ + private DelegateRequest(Parcel in) { + mFeatureTags = new ArrayList<>(); + in.readList(mFeatureTags, null /*classLoader*/); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeList(mFeatureTags); + } + + public static final @NonNull Creator<DelegateRequest> CREATOR = new Creator<DelegateRequest>() { + @Override + public DelegateRequest createFromParcel(Parcel source) { + return new DelegateRequest(source); + } + + @Override + public DelegateRequest[] newArray(int size) { + return new DelegateRequest[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DelegateRequest that = (DelegateRequest) o; + return mFeatureTags.equals(that.mFeatureTags); + } + + @Override + public int hashCode() { + return Objects.hash(mFeatureTags); + } +} diff --git a/telephony/java/android/telephony/ims/DelegateStateCallback.java b/telephony/java/android/telephony/ims/DelegateStateCallback.java new file mode 100644 index 000000000000..0f1afc42249e --- /dev/null +++ b/telephony/java/android/telephony/ims/DelegateStateCallback.java @@ -0,0 +1,101 @@ +/* + * 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.telephony.ims.stub.SipDelegate; +import android.telephony.ims.stub.SipTransportImplBase; + +import java.util.List; + +/** + * Callback interface to notify a remote application of the following: + * <ul> + * <li>the {@link SipDelegate} associated with this callback has been created or destroyed in + * response to a creation or destruction request from the framework</li> + * <li>the SIP IMS configuration associated with this {@link SipDelegate} has changed</li> + * <li>the IMS registration of the feature tags associated with this {@link SipDelegate} have + * changed.</li> + * </ul> + * @hide + */ +public interface DelegateStateCallback { + + /** + * This must be called by the ImsService after {@link SipTransportImplBase#createSipDelegate} is + * called by the framework to notify the framework and remote application that the + * {@link SipDelegate} has been successfully created. + * + * @param delegate The SipDelegate created to service the DelegateRequest. + * @param deniedTags A List of {@link FeatureTagState}, which contains the feature tags + * associated with this {@link SipDelegate} that have no access to send/receive SIP messages + * as well as a reason for why the feature tag is denied. For more information on the reason + * why the feature tag was denied access, see the + * {@link SipDelegateManager.DeniedReason} reasons. This is considered a permanent denial due + * to this {@link SipDelegate} not supporting a feature or this ImsService already + * implementing this feature elsewhere. If all features of this {@link SipDelegate} are + * denied, {@link #onCreated(SipDelegate, List)} should still be called as the framework will + * later call {@link SipTransportImplBase#destroySipDelegate(SipDelegate, int)} to clean the + * delegate up. + */ + void onCreated(@NonNull SipDelegate delegate, @Nullable List<FeatureTagState> deniedTags); + + /** + * This must be called by the ImsService after the framework calls + * {@link SipTransportImplBase#destroySipDelegate} to notify the framework and remote + * application that the procedure to destroy the {@link SipDelegate} has been completed. + * @param reasonCode The reason for closing this delegate. + */ + void onDestroyed(@SipDelegateManager.SipDelegateDestroyReason int reasonCode); + + /** + * Call to notify the remote application of a configuration change associated with this + * {@link SipDelegate}. + * <p> + * The remote application will not be able to proceed sending SIP messages until after this + * configuration is sent the first time, so this configuration should be sent as soon as the + * {@link SipDelegate} has access to these configuration parameters. + * <p> + * Incoming SIP messages should not be routed to the remote application until AFTER this + * configuration change is sent to ensure that the remote application can respond correctly. + * Similarly, if there is an event that triggers the IMS configuration to change, incoming SIP + * messages routing should be delayed until the {@link SipDelegate} sends the IMS configuration + * change event to reduce conditions where the remote application is using a stale IMS + * configuration. + */ + void onImsConfigurationChanged(@NonNull SipDelegateImsConfiguration config); + + /** + * Call to notify the remote application that the {@link SipDelegate} has modified the IMS + * registration state of the RCS feature tags that were requested as part of the initial + * {@link DelegateRequest}. + * <p> + * See {@link DelegateRegistrationState} for more information about how IMS Registration state + * should be communicated the associated SipDelegateConnection in cases such as + * IMS deregistration, handover, PDN change, provisioning changes, etc… + * <p> + * Note: Even after the status of the feature tags are updated here to deregistered, the + * SipDelegate must still be able to handle these messages and call + * {@link DelegateMessageCallback#onMessageSendFailure} to notify the RCS application that the + * message was not sent. + * + * @param registrationState The current network IMS registration state for all feature tags + * associated with this SipDelegate. + */ + void onFeatureTagRegistrationChanged(@NonNull DelegateRegistrationState registrationState); +} diff --git a/telephony/java/android/telephony/ims/FeatureTagState.aidl b/telephony/java/android/telephony/ims/FeatureTagState.aidl new file mode 100644 index 000000000000..bce55742461c --- /dev/null +++ b/telephony/java/android/telephony/ims/FeatureTagState.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 FeatureTagState; diff --git a/telephony/java/android/telephony/ims/FeatureTagState.java b/telephony/java/android/telephony/ims/FeatureTagState.java new file mode 100644 index 000000000000..060be6f2510d --- /dev/null +++ b/telephony/java/android/telephony/ims/FeatureTagState.java @@ -0,0 +1,131 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; +import android.telephony.ims.stub.DelegateConnectionStateCallback; +import android.telephony.ims.stub.SipDelegate; + +import java.util.List; +import java.util.Objects; + +/** + * Maps an IMS media feature tag 3gpp universal resource name (URN) previously mapped to a + * {@link SipDelegate} in the associated {@link DelegateRequest} to its current availability + * state as set by the ImsService managing the related IMS registration. + * + * This class is only used to report more information about a IMS feature tag that is not fully + * available at this time. + * <p> + * Please see {@link DelegateRegistrationState}, {@link DelegateStateCallback}, and + * {@link DelegateConnectionStateCallback} for more information about how this class is used to + * convey the state of IMS feature tags that were requested by {@link DelegateRequest} but are not + * currently available. + * @hide + */ +public final class FeatureTagState implements Parcelable { + + private final String mFeatureTag; + private final int mState; + + /** + * Associate an IMS feature tag with its current state. See {@link DelegateRegistrationState} + * and {@link DelegateConnectionStateCallback#onFeatureTagStatusChanged( + * DelegateRegistrationState, List)} and + * {@link DelegateStateCallback#onCreated(SipDelegate, List)} for examples on how and when this + * is used. + * + * @param featureTag The IMS feature tag that is deregistered, in the process of + * deregistering, or denied. + * @param state The {@link DelegateRegistrationState.DeregisteredReason}, + * {@link DelegateRegistrationState.DeregisteringReason}, or + * {@link SipDelegateManager.DeniedReason} associated with this feature tag. + */ + public FeatureTagState(@NonNull String featureTag, int state) { + mFeatureTag = featureTag; + mState = state; + } + + /** + * Used for constructing instances during un-parcelling. + */ + private FeatureTagState(Parcel source) { + mFeatureTag = source.readString(); + mState = source.readInt(); + } + + /** + * @return The IMS feature tag string that is in the process of deregistering, + * deregistered, or denied. + */ + public @NonNull String getFeatureTag() { + return mFeatureTag; + } + + /** + * @return The reason for why the feature tag is currently in the process of deregistering, + * has been deregistered, or has been denied. See {@link DelegateRegistrationState} and + * {@link DelegateConnectionStateCallback} for more information. + */ + public int getState() { + return mState; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mFeatureTag); + dest.writeInt(mState); + } + + public static final Creator<FeatureTagState> CREATOR = new Creator<FeatureTagState>() { + @Override + public FeatureTagState createFromParcel(Parcel source) { + return new FeatureTagState(source); + } + + @Override + public FeatureTagState[] newArray(int size) { + return new FeatureTagState[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FeatureTagState that = (FeatureTagState) o; + return mState == that.mState + && mFeatureTag.equals(that.mFeatureTag); + } + + @Override + public int hashCode() { + return Objects.hash(mFeatureTag, mState); + } + + @Override + public String toString() { + return "FeatureTagState{" + "mFeatureTag='" + mFeatureTag + ", mState=" + mState + '}'; + } +} diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 9a55cec80816..1b51936e873b 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -450,8 +450,6 @@ public final class ImsCallProfile implements Parcelable { /** Indicates if we have known the intent of the user for the call is emergency */ private boolean mHasKnownUserIntentEmergency = false; - private Set<RtpHeaderExtensionType> mOfferedRtpHeaderExtensionTypes = new ArraySet<>(); - private Set<RtpHeaderExtensionType> mAcceptedRtpHeaderExtensionTypes = new ArraySet<>(); /** @@ -692,7 +690,6 @@ public final class ImsCallProfile implements Parcelable { out.writeBoolean(mHasKnownUserIntentEmergency); out.writeInt(mRestrictCause); out.writeInt(mCallerNumberVerificationStatus); - out.writeArray(mOfferedRtpHeaderExtensionTypes.toArray()); out.writeArray(mAcceptedRtpHeaderExtensionTypes.toArray()); } @@ -708,9 +705,6 @@ public final class ImsCallProfile implements Parcelable { mHasKnownUserIntentEmergency = in.readBoolean(); mRestrictCause = in.readInt(); mCallerNumberVerificationStatus = in.readInt(); - Object[] offered = in.readArray(RtpHeaderExtensionType.class.getClassLoader()); - mOfferedRtpHeaderExtensionTypes = Arrays.stream(offered) - .map(o -> (RtpHeaderExtensionType) o).collect(Collectors.toSet()); Object[] accepted = in.readArray(RtpHeaderExtensionType.class.getClassLoader()); mAcceptedRtpHeaderExtensionTypes = Arrays.stream(accepted) .map(o -> (RtpHeaderExtensionType) o).collect(Collectors.toSet()); @@ -1106,46 +1100,13 @@ public final class ImsCallProfile implements Parcelable { } /** - * For an incoming or outgoing call, indicates the {@link RtpHeaderExtensionType}s which the - * caller is offering to make available. - * <p> - * For outgoing calls, an {@link ImsService} will reserve - * {@link RtpHeaderExtensionType#getLocalIdentifier()} identifiers the telephony stack has - * proposed to use and not use these same local identifiers. The offered header extension - * types for an outgoing call can be found in the - * {@link ImsCallProfile#getOfferedRtpHeaderExtensionTypes()} and will be available to the - * {@link ImsService} in {@link MmTelFeature#createCallSession(ImsCallProfile)}. - * The {@link ImsService} sets the accepted {@link #setAcceptedRtpHeaderExtensionTypes(Set)} - * when the SDP offer/accept process has completed. - * <p> - * According to RFC8285, RTP header extensions available to a call are determined using the - * offer/accept phase of the SDP protocol (see RFC4566). - * - * @return the {@link RtpHeaderExtensionType}s which were offered by other end of the call. - */ - public @NonNull Set<RtpHeaderExtensionType> getOfferedRtpHeaderExtensionTypes() { - return mOfferedRtpHeaderExtensionTypes; - } - - /** - * Sets the offered {@link RtpHeaderExtensionType}s for this call. - * <p> - * According to RFC8285, RTP header extensions available to a call are determined using the - * offer/accept phase of the SDP protocol (see RFC4566). - * - * @param rtpHeaderExtensions the {@link RtpHeaderExtensionType}s which are offered. - */ - public void setOfferedRtpHeaderExtensionTypes(@NonNull Set<RtpHeaderExtensionType> - rtpHeaderExtensions) { - mOfferedRtpHeaderExtensionTypes.clear(); - mOfferedRtpHeaderExtensionTypes.addAll(rtpHeaderExtensions); - } - - /** * Gets the {@link RtpHeaderExtensionType}s which have been accepted by both ends of the call. * <p> * According to RFC8285, RTP header extensions available to a call are determined using the * offer/accept phase of the SDP protocol (see RFC4566). + * <p> + * The offered header extension types supported by the framework and exposed to the + * {@link ImsService} via {@link MmTelFeature#changeOfferedRtpHeaderExtensionTypes(Set)}. * * @return the {@link RtpHeaderExtensionType}s which were accepted by the other end of the call. */ diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index a4f2a316c99d..d1a893f61e00 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -29,6 +29,7 @@ import android.os.Binder; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.telephony.AccessNetworkConstants; +import android.telephony.BinderCacheManager; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; @@ -213,6 +214,7 @@ public class ImsMmTelManager implements RegistrationManager { } private final int mSubId; + private final BinderCacheManager<ITelephony> mBinderCache; /** * Create an instance of {@link ImsMmTelManager} for the subscription id specified. @@ -242,7 +244,8 @@ public class ImsMmTelManager implements RegistrationManager { throw new IllegalArgumentException("Invalid subscription ID"); } - return new ImsMmTelManager(subId); + return new ImsMmTelManager(subId, new BinderCacheManager<>( + ImsMmTelManager::getITelephonyInterface)); } /** @@ -250,8 +253,9 @@ public class ImsMmTelManager implements RegistrationManager { * @hide */ @VisibleForTesting - public ImsMmTelManager(int subId) { + public ImsMmTelManager(int subId, BinderCacheManager<ITelephony> binderCache) { mSubId = subId; + mBinderCache = binderCache; } /** @@ -1367,7 +1371,11 @@ public class ImsMmTelManager implements RegistrationManager { } } - private static ITelephony getITelephony() { + private ITelephony getITelephony() { + return mBinderCache.getBinder(); + } + + private static ITelephony getITelephonyInterface() { ITelephony binder = ITelephony.Stub.asInterface( TelephonyFrameworkInitializer .getTelephonyServiceManager() diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index 8b6dac82b0eb..4292aae59515 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -28,6 +28,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.provider.Settings; import android.telephony.AccessNetworkConstants; +import android.telephony.BinderCacheManager; import android.telephony.CarrierConfigManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsCapabilityCallback; @@ -149,14 +150,17 @@ public class ImsRcsManager { private final int mSubId; private final Context mContext; + private final BinderCacheManager<IImsRcsController> mBinderCache; /** * Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this class. * @hide */ - public ImsRcsManager(Context context, int subId) { + public ImsRcsManager(Context context, int subId, + BinderCacheManager<IImsRcsController> binderCache) { mSubId = subId; mContext = context; + mBinderCache = binderCache; } /** diff --git a/telephony/java/android/telephony/ims/SipDelegateConnection.java b/telephony/java/android/telephony/ims/SipDelegateConnection.java new file mode 100644 index 000000000000..6bfdc2c6d48a --- /dev/null +++ b/telephony/java/android/telephony/ims/SipDelegateConnection.java @@ -0,0 +1,73 @@ +/* + * 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.telephony.ims.stub.SipDelegate; + +/** + * Represents a connection to the remote {@link SipDelegate} that is managed by the + * {@link ImsService} implementing IMS for the subscription that is associated with it. + * <p> + * The remote delegate will handle messages sent by this {@link SipDelegateConnection}, notifying + * the associated {@link DelegateMessageCallback} when the message was either sent successfully or + * failed to be sent. + * <p> + * It is also the responsibility of this {@link SipDelegateConnection} to acknowledge when incoming + * SIP messages have been received successfully via + * {@link DelegateMessageCallback#onMessageReceived(SipMessage)} or when there was an error + * receiving the message using {@link #notifyMessageReceived(String)} and + * {@link #notifyMessageReceiveError(String, int)}. + * + * @see SipDelegateManager#createSipDelegate + * @hide + */ +public interface SipDelegateConnection { + + /** + * Send a SIP message to the SIP delegate to be sent over the carrier’s network. The + * {@link SipMessage} will either be acknowledged with + * {@link DelegateMessageCallback#onMessageSent(String)} upon successful sending of this message + * or {@link DelegateMessageCallback#onMessageSendFailure(String, int)} if there was an error + * sending the message. + * @param sipMessage The SipMessage to be sent. + * @param configVersion The SipDelegateImsConfiguration version used to construct the + * SipMessage. See {@link SipDelegateImsConfiguration#getVersion} for more + * information on this parameter and why it is used. + */ + void sendMessage(@NonNull SipMessage sipMessage, int configVersion); + + /** + * Notify the {@link SipDelegate} that a SIP message received from + * {@link DelegateMessageCallback#onMessageReceived(SipMessage)} has been received successfully + * and is being processed. + * @param viaTransactionId Per RFC3261 Sec 8.1.1.7 the transaction ID associated with the Via + * branch parameter. + */ + void notifyMessageReceived(@NonNull String viaTransactionId); + + /** + * Notify the SIP delegate that the SIP message has been received from + * {@link DelegateMessageCallback#onMessageReceived(SipMessage)}, however there was an error + * processing it. + * @param viaTransactionId Per RFC3261 Sec 8.1.1.7 the transaction ID associated with the Via + * branch parameter. + * @param reason The reason why the error occurred. + */ + void notifyMessageReceiveError(@NonNull String viaTransactionId, + @SipDelegateManager.MessageFailureReason int reason); +} diff --git a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.aidl b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.aidl new file mode 100644 index 000000000000..44ae1b130046 --- /dev/null +++ b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.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 SipDelegateImsConfiguration; diff --git a/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java new file mode 100644 index 000000000000..8abd0ee94865 --- /dev/null +++ b/telephony/java/android/telephony/ims/SipDelegateImsConfiguration.java @@ -0,0 +1,499 @@ +/* + * 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.StringDef; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.telephony.ims.stub.SipDelegate; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The IMS registration and other attributes that the {@link SipDelegateConnection} used by the + * IMS application will need to be aware of to correctly generate outgoing {@link SipMessage}s. + * <p> + * The IMS service must generate new instances of this configuration as the IMS configuration + * managed by the IMS service changes. Along with each {@link SipDelegateImsConfiguration} instance + * containing the configuration is the "version", which should be incremented every time a new + * {@link SipDelegateImsConfiguration} instance is created. The {@link SipDelegateConnection} will + * include the version of the {@link SipDelegateImsConfiguration} instance that it used in order for + * the {@link SipDelegate} to easily identify if the IMS application used a now stale configuration + * to generate the {@link SipMessage} and return + * {@link SipDelegateManager#MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION} in + * {@link DelegateMessageCallback#onMessageSendFailure(String, int)} so that the IMS application can + * regenerate that {@link SipMessage} using the correct {@link SipDelegateImsConfiguration} + * instance. + * <p> + * Every time the IMS configuration state changes in the IMS service, a full configuration should + * be generated. The new {@link SipDelegateImsConfiguration} instance should not be an incremental + * update. + * @hide + */ +public class SipDelegateImsConfiguration implements Parcelable { + + /** + * IPV4 Address type. + * <p> + * Used as a potential value for {@link #KEY_SIP_CONFIG_IPTYPE_STRING}. + */ + public static final String IPTYPE_IPV4 = "IPV4"; + + /** + * IPV6 Address type. + * <p> + * Used as a potential value for {@link #KEY_SIP_CONFIG_IPTYPE_STRING}. + */ + public static final String IPTYPE_IPV6 = "IPV6"; + + /** + * The SIP transport uses UDP. + * <p> + * Used as a potential value for {@link #KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING}. + */ + public static final String SIP_TRANSPORT_UDP = "UDP"; + + /** + * The SIP transport uses TCP. + * <p> + * Used as a potential value for {@link #KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING}. + */ + public static final String SIP_TRANSPORT_TCP = "TCP"; + + /** + * Flag specifying if SIP compact form is enabled + */ + public static final String KEY_SIP_CONFIG_IS_COMPACT_FORM_ENABLED_BOOL = + "sip_config_is_compact_form_enabled_bool"; + + /** + * Flag specifying if SIP keepalives are enabled + */ + public static final String KEY_SIP_CONFIG_IS_KEEPALIVE_ENABLED_BOOL = + "sip_config_is_keepalive_enabled_bool"; + + /** + * Maximum SIP payload to be sent on UDP. If the SIP message payload is greater than max udp + * payload size, then TCP must be used + */ + public static final String KEY_SIP_CONFIG_MAX_PAYLOAD_SIZE_ON_UDP_INT = + "sip_config_udp_max_payload_size_int"; + + /** + * Transport protocol used for SIP signaling. + * Available options are: {@link #SIP_TRANSPORT_UDP }, {@link #SIP_TRANSPORT_TCP } + */ + public static final String KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING = + "sip_config_protocol_type_string"; + + /** + * IMS public user identifier string + */ + public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = + "sip_config_ue_public_user_id_string"; + + /** + * IMS private user identifier string + */ + public static final String KEY_SIP_CONFIG_UE_PRIVATE_USER_ID_STRING = + "sip_config_ue_private_user_id_string"; + + /** + * IMS home domain string + */ + public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string"; + + /** + * IMEI string. Application can include the Instance-ID feature tag " +sip.instance" in the + * Contact header with a value of the device IMEI in the form "urn:gsma:imei:<device IMEI>". + */ + public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string"; + + /** + * IP address type for SIP signaling. + * Available options are: {@link #IPTYPE_IPV6}, {@link #IPTYPE_IPV4} + */ + public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string"; + + /** + * Local IPaddress used for SIP signaling. + */ + public static final String KEY_SIP_CONFIG_UE_DEFAULT_IPADDRESS_STRING = + "sip_config_ue_default_ipaddress_string"; + + /** + * Local port used for sending SIP traffic + */ + public static final String KEY_SIP_CONFIG_UE_DEFAULT_PORT_INT = + "sip_config_ue_default_port_int"; + + /** + * SIP server / PCSCF default ip address + */ + public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_IPADDRESS_STRING = + "sip_config_server_default_ipaddress_string"; + + /** + * SIP server / PCSCF port used for sending SIP traffic + */ + public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_PORT_INT = + "sip_config_server_default_port_int"; + + /** + * Flag specifying if Network Address Translation is enabled and UE is behind a NAT. + */ + public static final String KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL = + "sip_config_is_nat_enabled_bool"; + + /** + * UE's public IPaddress when UE is behind a NAT. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_UE_PUBLIC_IPADDRESS_WITH_NAT_STRING = + "sip_config_ue_public_ipaddress_with_nat_string"; + + /** + * UE's public SIP port when UE is behind a NAT. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = + "sip_config_ue_public_port_with_nat_int"; + + /** + * Flag specifying if Globally routable user-agent uri (GRUU) is enabled as per TS 23.808 + */ + public static final String KEY_SIP_CONFIG_IS_GRUU_ENABLED_BOOL = + "sip_config_is_gruu_enabled_bool"; + + /** + * UE's Globally routable user-agent uri if this feature is enabled. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_GRUU_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_UE_PUBLIC_GRUU_STRING = + "sip_config_ue_public_gruu_string"; + + /** + * Flag specifying if SIP over IPSec is enabled. + */ + public static final String KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL = + "sip_config_is_ipsec_enabled_bool"; + /** + * UE's SIP port used to send traffic when IPSec is enabled. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_UE_IPSEC_CLIENT_PORT_INT = + "sip_config_ue_ipsec_client_port_int"; + + /** + * UE's SIP port used to receive traffic when IPSec is enabled. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_UE_IPSEC_SERVER_PORT_INT = + "sip_config_ue_ipsec_server_port_int"; + + /** + * UE's SIP port used for the previous IPsec security association if IPSec is enabled. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_UE_IPSEC_OLD_CLIENT_PORT_INT = + "sip_config_ue_ipsec_old_client_port_int"; + + /** + * Port number used by the SIP server to send SIP traffic when IPSec is enabled. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_SERVER_IPSEC_CLIENT_PORT_INT = + "sip_config_server_ipsec_client_port_int"; + + /** + * Port number used by the SIP server to receive incoming SIP traffic when IPSec is enabled. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_SERVER_IPSEC_SERVER_PORT_INT = + "sip_config_server_ipsec_server_port_int"; + + /** + * Port number used by the SIP server to send SIP traffic on the previous IPSec security + * association when IPSec is enabled. + * <p> + * This key will not exist if {@link #KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL} is {@code false}. + */ + public static final String KEY_SIP_CONFIG_SERVER_IPSEC_OLD_CLIENT_PORT_INT = + "sip_config_server_ipsec_old_client_port_int"; + /** + * SIP Authentication header string + */ + public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = + "sip_config_auhentication_header_string"; + + /** + * SIP Authentication nonce string + */ + public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = + "sip_config_authentication_nonce_string"; + + /** + * SIP service route header string + */ + public static final String KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING = + "sip_config_service_route_header_string"; + + /** + * SIP security verify header string + */ + public static final String KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING = + "sip_config_security_verify_header_string"; + + /** + * SIP Path header string + */ + public static final String KEY_SIP_CONFIG_PATH_HEADER_STRING = + "sip_config_path_header_string"; + + /** + * SIP User part string in contact header + */ + public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = + "sip_config_uri_user_part_string"; + + /** + * SIP P-access-network-info header string + */ + public static final String KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING = + "sip_config_p_access_network_info_header_string"; + + /** + * SIP P-last-access-network-info header string + */ + public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING = + "sip_config_p_last_access_network_info_header_string"; + + /** + * SIP P-associated-uri header string + */ + public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING = + "sip_config_p_associated_uri_header_string"; + + /**@hide*/ + @StringDef(prefix = "KEY_SIP_CONFIG", suffix = "_STRING", value = { + KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING, + KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING, + KEY_SIP_CONFIG_UE_PRIVATE_USER_ID_STRING, + KEY_SIP_CONFIG_HOME_DOMAIN_STRING, + KEY_SIP_CONFIG_IMEI_STRING, + KEY_SIP_CONFIG_IPTYPE_STRING, + KEY_SIP_CONFIG_UE_DEFAULT_IPADDRESS_STRING, + KEY_SIP_CONFIG_SERVER_DEFAULT_IPADDRESS_STRING, + KEY_SIP_CONFIG_UE_PUBLIC_IPADDRESS_WITH_NAT_STRING, + KEY_SIP_CONFIG_UE_PUBLIC_GRUU_STRING, + KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING, + KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING, + KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING, + KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING, + KEY_SIP_CONFIG_PATH_HEADER_STRING, + KEY_SIP_CONFIG_URI_USER_PART_STRING, + KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING, + KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING, + KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StringConfigKey {} + + /**@hide*/ + @StringDef(prefix = "KEY_SIP_CONFIG", suffix = "_INT", value = { + KEY_SIP_CONFIG_MAX_PAYLOAD_SIZE_ON_UDP_INT, + KEY_SIP_CONFIG_UE_DEFAULT_PORT_INT, + KEY_SIP_CONFIG_SERVER_DEFAULT_PORT_INT, + KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT, + KEY_SIP_CONFIG_UE_IPSEC_CLIENT_PORT_INT, + KEY_SIP_CONFIG_UE_IPSEC_SERVER_PORT_INT, + KEY_SIP_CONFIG_UE_IPSEC_OLD_CLIENT_PORT_INT, + KEY_SIP_CONFIG_SERVER_IPSEC_CLIENT_PORT_INT, + KEY_SIP_CONFIG_SERVER_IPSEC_SERVER_PORT_INT, + KEY_SIP_CONFIG_SERVER_IPSEC_OLD_CLIENT_PORT_INT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface IntConfigKey {} + + /**@hide*/ + @StringDef(prefix = "KEY_SIP_CONFIG", suffix = "_BOOL", value = { + KEY_SIP_CONFIG_IS_COMPACT_FORM_ENABLED_BOOL, + KEY_SIP_CONFIG_IS_KEEPALIVE_ENABLED_BOOL, + KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL, + KEY_SIP_CONFIG_IS_GRUU_ENABLED_BOOL, + KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface BooleanConfigKey {} + + /** + * Builder class to be used when constructing a new SipDelegateImsConfiguration. + */ + public static class Builder { + private final long mVersion; + private final PersistableBundle mBundle; + + /** + * Creates an empty implementation of SipDelegateImsConfiguration. + * @param version The version associated with the SipDelegateImsConfiguration being built. + * See {@link #getVersion} for more information. + */ + public Builder(int version) { + mVersion = version; + mBundle = new PersistableBundle(); + } + /** + * Clones an existing implementation of SipDelegateImsConfiguration to handle situations + * where only a small number of parameters have changed from the previous configuration. + * <p> + * Automatically increments the version of this configuration by 1. See {@link #getVersion} + * for more information. + */ + public Builder(@NonNull SipDelegateImsConfiguration config) { + mVersion = config.getVersion() + 1; + mBundle = config.copyBundle(); + } + /** + * Put a string value into this configuration bundle for the given key. + */ + public Builder putString(@StringConfigKey String key, String value) { + mBundle.putString(key, value); + return this; + } + + /** + * Replace the existing default value with a new value for a given key. + */ + public Builder putInt(@IntConfigKey String key, int value) { + mBundle.putInt(key, value); + return this; + } + + /** + * Replace the existing default value with a new value for a given key. + */ + public Builder putBoolean(@BooleanConfigKey String key, boolean value) { + mBundle.putBoolean(key, value); + return this; + } + + /** + * @return a new SipDelegateImsConfiguration from this Builder. + */ + public SipDelegateImsConfiguration build() { + return new SipDelegateImsConfiguration(mVersion, mBundle); + } + } + + private final long mVersion; + private final PersistableBundle mBundle; + + private SipDelegateImsConfiguration(long version, PersistableBundle bundle) { + mVersion = version; + mBundle = bundle; + } + + private SipDelegateImsConfiguration(Parcel source) { + mVersion = source.readLong(); + mBundle = source.readPersistableBundle(); + } + + /** + * @return the string value associated with a given key or {@code null} if it doesn't exist. + */ + public @StringConfigKey String getString(String key) { + return mBundle.getString(key); + } + + /** + * @return the Integer value associated with a given key or {@code null} if the value doesn't + * exist. + */ + public @IntConfigKey Integer getInt(String key) { + if (!mBundle.containsKey(key)) { + return null; + } + return mBundle.getInt(key); + } + + /** + * @return the Integer value associated with a given key or {@code null} if the value doesn't + * exist. + */ + public @BooleanConfigKey Boolean getBoolen(String key) { + if (!mBundle.containsKey(key)) { + return null; + } + return mBundle.getBoolean(key); + } + + /** + * @return a shallow copy of the full configuration. + */ + public PersistableBundle copyBundle() { + return new PersistableBundle(mBundle); + } + + /** + * An integer representing the version number of this SipDelegateImsConfiguration. + * {@link SipMessage}s that are created using this configuration will also have a this + * version number associated with them, which will allow the IMS service to validate that the + * {@link SipMessage} was using the latest configuration during creation and not a stale + * configuration due to race conditions between the configuration being updated and the RCS + * application not receiving the updated configuration before generating a new message. + * + * @return the version number associated with this {@link SipDelegateImsConfiguration}. + */ + public long getVersion() { + return mVersion; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mVersion); + dest.writePersistableBundle(mBundle); + } + + public static final Creator<SipDelegateImsConfiguration> CREATOR = + new Creator<SipDelegateImsConfiguration>() { + @Override + public SipDelegateImsConfiguration createFromParcel(Parcel source) { + return new SipDelegateImsConfiguration(source); + } + + @Override + public SipDelegateImsConfiguration[] newArray(int size) { + return new SipDelegateImsConfiguration[size]; + } + }; +} diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java index 82c8a9cd58f4..337b7d49323d 100644 --- a/telephony/java/android/telephony/ims/SipDelegateManager.java +++ b/telephony/java/android/telephony/ims/SipDelegateManager.java @@ -17,29 +17,251 @@ package android.telephony.ims; import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; -import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; +import android.telephony.BinderCacheManager; import android.telephony.CarrierConfigManager; -import android.telephony.TelephonyFrameworkInitializer; import android.telephony.ims.aidl.IImsRcsController; +import android.telephony.ims.aidl.SipDelegateConnectionAidlWrapper; +import android.telephony.ims.stub.DelegateConnectionMessageCallback; +import android.telephony.ims.stub.DelegateConnectionStateCallback; +import android.telephony.ims.stub.SipDelegate; import com.android.internal.annotations.VisibleForTesting; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + /** - * Manages the creation and destruction of SipDelegates, which allow an IMS application to forward - * SIP messages for the purposes of providing a single IMS registration to the carrier's IMS network - * from multiple sources. + * Manages the creation and destruction of SipDelegates for the {@link ImsService} managing IMS + * for the subscription ID that this SipDelegateManager has been created for. + * + * This allows multiple IMS applications to forward SIP messages to/from their application for the + * purposes of providing a single IMS registration to the carrier's IMS network from potentially + * many IMS stacks implementing a subset of the supported MMTEL/RCS features. * @hide */ @SystemApi public class SipDelegateManager { + /** + * The SIP message has failed being sent or received for an unknown reason. + * <p> + * The caller should retry a message that failed with this response. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_UNKNOWN = 0; + + /** + * The remote service associated with this connection has died and the message was not + * properly sent/received. + * <p> + * This is considered a permanent error and the system will automatically begin the teardown and + * destruction of the SipDelegate. No further messages should be sent on this transport. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_DELEGATE_DEAD = 1; + + /** + * The message has not been sent/received because the delegate is in the process of closing and + * has become unavailable. No further messages should be sent/received on this delegate. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_DELEGATE_CLOSED = 2; + + /** + * The SIP message has an invalid start line and the message can not be sent. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_INVALID_START_LINE = 3; + + /** + * One or more of the header fields in the header section of the outgoing SIP message is invalid + * and the SIP message can not be sent. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS = 4; + + /** + * The body content of the SIP message is invalid and the message can not be sent. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_INVALID_BODY_CONTENT = 5; + + /** + * The feature tag associated with the outgoing message does not match any known feature tags + * and this message can not be sent. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG = 6; + + /** + * The feature tag associated with the outgoing message is not enabled for the associated + * SipDelegateConnection and can not be sent. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_TAG_NOT_ENABLED_FOR_DELEGATE = 7; + + /** + * The link to the network has been lost and the outgoing message has failed to send. + * <p> + * This message should be retried when connectivity to the network is re-established. See + * {@link android.net.ConnectivityManager.NetworkCallback} for how this can be determined. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE = 8; + + /** + * The outgoing SIP message has not been sent due to the SipDelegate not being registered for + * IMS at this time. + * <p> + * This is considered a temporary failure, the message should not be retried until an IMS + * registration change callback is received via + * {@link DelegateConnectionStateCallback#onFeatureTagStatusChanged} + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_NOT_REGISTERED = 9; + + /** + * The outgoing SIP message has not been sent because the {@link SipDelegateImsConfiguration} + * version associated with the outgoing {@link SipMessage} is now stale and has failed + * validation checks. + * <p> + * The @link SipMessage} should be recreated using the newest + * {@link SipDelegateImsConfiguration} and sent again. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION = 10; + + /** + * The outgoing SIP message has not been sent because the internal state of the associated + * {@link SipDelegate} is changing and has temporarily brought the transport down. + * <p> + * This is considered a temporary error and the {@link SipDelegateConnection} should resend the + * message once {@link DelegateRegistrationState#DEREGISTERING_REASON_FEATURE_TAGS_CHANGING} is + * no longer reported. + * @hide + */ + public static final int MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION = 11; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "MESSAGE_FAILURE_REASON_", value = { + MESSAGE_FAILURE_REASON_UNKNOWN, + MESSAGE_FAILURE_REASON_DELEGATE_DEAD, + MESSAGE_FAILURE_REASON_DELEGATE_CLOSED, + MESSAGE_FAILURE_REASON_INVALID_START_LINE, + MESSAGE_FAILURE_REASON_INVALID_HEADER_FIELDS, + MESSAGE_FAILURE_REASON_INVALID_BODY_CONTENT, + MESSAGE_FAILURE_REASON_INVALID_FEATURE_TAG, + MESSAGE_FAILURE_REASON_TAG_NOT_ENABLED_FOR_DELEGATE, + MESSAGE_FAILURE_REASON_NETWORK_NOT_AVAILABLE, + MESSAGE_FAILURE_REASON_NOT_REGISTERED, + MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION, + MESSAGE_FAILURE_REASON_INTERNAL_DELEGATE_STATE_TRANSITION + }) + public @interface MessageFailureReason {} + + + /** + * Access to use this feature tag has been denied for an unknown reason. + * @hide + */ + public static final int DENIED_REASON_UNKNOWN = 0; + + /** + * This feature tag is allowed to be used by this SipDelegateConnection, but it is in use by + * another SipDelegateConnection and can not be associated with this delegate. The feature tag + * will stay in this state until the feature tag is release by the other application. + * @hide + */ + public static final int DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE = 1; + + /** + * Access to use this feature tag has been denied because this application does not have the + * permissions required to access this feature tag. + * @hide + */ + public static final int DENIED_REASON_NOT_ALLOWED = 2; + + /** + * Access to use this feature tag has been denied because single registration is not allowed by + * the carrier at this time. The application should fall back to dual registration if + * applicable. + * @hide + */ + public static final int DENIED_REASON_SINGLE_REGISTRATION_NOT_ALLOWED = 3; + + /** + * This feature tag is not recognized as a valid feature tag by the SipDelegate and has been + * denied. + * @hide + */ + public static final int DENIED_REASON_INVALID = 4; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "DENIED_REASON_", value = { + DENIED_REASON_UNKNOWN, + DENIED_REASON_IN_USE_BY_ANOTHER_DELEGATE, + DENIED_REASON_NOT_ALLOWED, + DENIED_REASON_SINGLE_REGISTRATION_NOT_ALLOWED, + DENIED_REASON_INVALID + }) + public @interface DeniedReason {} + + /** + * The SipDelegate has closed due to an unknown reason. + * @hide + */ + public static final int SIP_DELEGATE_DESTROY_REASON_UNKNOWN = 0; + + /** + * The SipDelegate has closed because the IMS service has died unexpectedly. + * @hide + */ + public static final int SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD = 1; + + /** + * The SipDelegate has closed because the IMS application has requested that the connection be + * destroyed. + * @hide + */ + public static final int SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP = 2; + + /** + * The SipDelegate has closed because the IMS service does not support the creation of + * SipDelegates. + * @hide + */ + public static final int SIP_DELEGATE_DESTROY_REASON_SERVICE_NOT_SUPPORTED = 3; + + /** + * The SipDelegate has been closed due to the user disabling RCS. + * @hide + */ + public static final int SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS = 4; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "SIP_DELEGATE_DESTROY_REASON", value = { + SIP_DELEGATE_DESTROY_REASON_UNKNOWN, + SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD, + SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP, + SIP_DELEGATE_DESTROY_REASON_SERVICE_NOT_SUPPORTED, + SIP_DELEGATE_DESTROY_REASON_USER_DISABLED_RCS + }) + public @interface SipDelegateDestroyReason {} + private final Context mContext; private final int mSubId; + private final BinderCacheManager<IImsRcsController> mBinderCache; /** * Only visible for testing. To instantiate an instance of this class, please use @@ -47,9 +269,11 @@ public class SipDelegateManager { * @hide */ @VisibleForTesting - public SipDelegateManager(Context context, int subId) { + public SipDelegateManager(Context context, int subId, + BinderCacheManager<IImsRcsController> binderCache) { mContext = context; mSubId = subId; + mBinderCache = binderCache; } /** @@ -69,7 +293,7 @@ public class SipDelegateManager { @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSupported() throws ImsException { try { - IImsRcsController controller = getIImsRcsController(); + IImsRcsController controller = mBinderCache.getBinder(); if (controller == null) { throw new ImsException("Telephony server is down", ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); @@ -83,11 +307,90 @@ public class SipDelegateManager { } } - private IImsRcsController getIImsRcsController() { - IBinder binder = TelephonyFrameworkInitializer - .getTelephonyServiceManager() - .getTelephonyImsServiceRegisterer() - .get(); - return IImsRcsController.Stub.asInterface(binder); + /** + * Request that the ImsService implementation create a SipDelegate, which will configure the + * ImsService to forward SIP traffic that matches the filtering criteria set in supplied + * {@link DelegateRequest} to the application that the supplied callbacks are registered for. + * <p> + * This API requires that the caller is running as part of a long-running process and will + * always be available to handle incoming messages. One mechanism that can be used for this is + * the {@link android.service.carrier.CarrierMessagingClientService}, which the framework keeps + * a persistent binding to when the app is the default SMS application. + * @param request The parameters that are associated with the SipDelegate creation request that + * will be used to create the SipDelegate connection. + * @param executor The executor that will be used to call the callbacks associated with this + * SipDelegate. + * @param dc The callback that will be used to notify the listener of the creation/destruction + * of the remote SipDelegate as well as changes to the state of the remote SipDelegate + * connection. + * @param mc The callback that will be used to notify the listener of new incoming SIP messages + * as well as the status of messages that were sent by the associated + * SipDelegateConnection. + * @throws ImsException Thrown if there was a problem communicating with the ImsService + * associated with this SipDelegateManager. See {@link ImsException#getCode()}. + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void createSipDelegate(@NonNull DelegateRequest request, @NonNull Executor executor, + @NonNull DelegateConnectionStateCallback dc, + @NonNull DelegateConnectionMessageCallback mc) throws ImsException { + if (request == null || executor == null || dc == null || mc == null) { + throw new IllegalArgumentException("Invalid arguments passed into createSipDelegate"); + } + try { + SipDelegateConnectionAidlWrapper wrapper = + new SipDelegateConnectionAidlWrapper(executor, dc, mc); + IImsRcsController controller = mBinderCache.listenOnBinder(wrapper, + wrapper::binderDied); + if (controller == null) { + throw new ImsException("Telephony server is down", + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + controller.createSipDelegate(mSubId, request, wrapper.getStateCallbackBinder(), + wrapper.getMessageCallbackBinder()); + } catch (ServiceSpecificException e) { + throw new ImsException(e.getMessage(), e.errorCode); + } catch (RemoteException e) { + throw new ImsException(e.getMessage(), + ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + /** + * Destroy a previously created {@link SipDelegateConnection} that was created using + * {@link #createSipDelegate}. + * <p> + * This will also clean up all related callbacks in the associated ImsService. + * @param delegateConnection The SipDelegateConnection to destroy. + * @param reason The reason for why this SipDelegateConnection was destroyed. + * @hide + */ + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void destroySipDelegate(@NonNull SipDelegateConnection delegateConnection, + @SipDelegateDestroyReason int reason) { + + if (delegateConnection == null) { + throw new IllegalArgumentException("invalid argument passed into destroySipDelegate"); + } + if (delegateConnection instanceof SipDelegateConnectionAidlWrapper) { + SipDelegateConnectionAidlWrapper w = + (SipDelegateConnectionAidlWrapper) delegateConnection; + try { + IImsRcsController c = mBinderCache.removeRunnable(w); + c.destroySipDelegate(mSubId, w.getSipDelegateBinder(), reason); + } catch (RemoteException e) { + // Connection to telephony died, but this will signal destruction of SipDelegate + // eventually anyway, so return normally. + try { + w.getStateCallbackBinder().onDestroyed( + SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_REQUESTED_BY_APP); + } catch (RemoteException ignore) { + // Local to process. + } + } + } else { + throw new IllegalArgumentException("Unknown SipDelegateConnection implementation passed" + + " into this method"); + } } } diff --git a/telephony/java/android/telephony/ims/SipMessage.aidl b/telephony/java/android/telephony/ims/SipMessage.aidl new file mode 100644 index 000000000000..5140f8ac357f --- /dev/null +++ b/telephony/java/android/telephony/ims/SipMessage.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 SipMessage; diff --git a/telephony/java/android/telephony/ims/SipMessage.java b/telephony/java/android/telephony/ims/SipMessage.java new file mode 100644 index 000000000000..c3b1be2d7fc8 --- /dev/null +++ b/telephony/java/android/telephony/ims/SipMessage.java @@ -0,0 +1,155 @@ +/* + * 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.os.Build; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Represents a partially encoded SIP message. See RFC 3261 for more information on how SIP + * messages are structured and used. + * <p> + * The SIP message is represented in a partially encoded form in order to allow for easier + * verification and should not be used as a generic SIP message container. + * @hide + */ +public final class SipMessage implements Parcelable { + // Should not be set to true for production! + private static final boolean IS_DEBUGGING = Build.IS_ENG; + + private static final String[] SIP_REQUEST_METHODS = new String[] {"INVITE", "ACK", "OPTIONS", + "BYE", "CANCEL", "REGISTER"}; + + private final String mStartLine; + private final String mHeaderSection; + private final byte[] mContent; + + /** + * Represents a partially encoded SIP message. + * + * @param startLine The start line of the message, containing either the request-line or + * status-line. + * @param headerSection A String containing the full unencoded SIP message header. + * @param content UTF-8 encoded SIP message body. + */ + public SipMessage(@NonNull String startLine, @NonNull String headerSection, + @NonNull byte[] content) { + if (startLine == null || headerSection == null || content == null) { + throw new IllegalArgumentException("One or more null parameters entered"); + } + mStartLine = startLine; + mHeaderSection = headerSection; + mContent = content; + } + + /** + * Private constructor used only for unparcelling. + */ + private SipMessage(Parcel source) { + mStartLine = source.readString(); + mHeaderSection = source.readString(); + mContent = new byte[source.readInt()]; + source.readByteArray(mContent); + } + /** + * @return The start line of the SIP message, which contains either the request-line or + * status-line. + */ + public @NonNull String getStartLine() { + return mStartLine; + } + + /** + * @return The full, unencoded header section of the SIP message. + */ + public @NonNull String getHeaderSection() { + return mHeaderSection; + } + + /** + * @return only the UTF-8 encoded SIP message body. + */ + public @NonNull byte[] getContent() { + return mContent; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mStartLine); + dest.writeString(mHeaderSection); + dest.writeInt(mContent.length); + dest.writeByteArray(mContent); + } + + public static final Creator<SipMessage> CREATOR = new Creator<SipMessage>() { + @Override + public SipMessage createFromParcel(Parcel source) { + return new SipMessage(source); + } + + @Override + public SipMessage[] newArray(int size) { + return new SipMessage[size]; + } + }; + + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("StartLine: ["); + if (IS_DEBUGGING) { + b.append(mStartLine); + } else { + b.append(sanitizeStartLineRequest(mStartLine)); + } + b.append("], ["); + b.append("Header: ["); + if (IS_DEBUGGING) { + b.append(mHeaderSection); + } else { + // only identify transaction id/call ID when it is available. + b.append("***"); + } + b.append("], "); + b.append("Content: [NOT SHOWN]"); + return b.toString(); + } + + /** + * Start lines containing requests are formatted: METHOD SP Request-URI SP SIP-Version CRLF. + * Detect if this is a REQUEST and redact Request-URI portion here, as it contains PII. + */ + private String sanitizeStartLineRequest(String startLine) { + String[] splitLine = startLine.split(" "); + if (splitLine == null || splitLine.length == 0) { + return "(INVALID STARTLINE)"; + } + for (String method : SIP_REQUEST_METHODS) { + if (splitLine[0].contains(method)) { + return splitLine[0] + " <Request-URI> " + splitLine[2]; + } + } + return startLine; + } +} diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl index b9a6b3c38a92..37fec7a73634 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl @@ -21,6 +21,7 @@ import android.telephony.ims.aidl.IImsMmTelListener; import android.telephony.ims.aidl.IImsSmsListener; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.feature.CapabilityChangeRequest; +import android.telephony.ims.RtpHeaderExtensionType; import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; @@ -29,6 +30,8 @@ import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; import com.android.ims.internal.IImsUt; +import java.util.List; + /** * See MmTelFeature for more information. * {@hide} @@ -37,6 +40,7 @@ interface IImsMmTelFeature { void setListener(IImsMmTelListener l); int getFeatureState(); ImsCallProfile createCallProfile(int callSessionType, int callType); + void changeOfferedRtpHeaderExtensionTypes(in List<RtpHeaderExtensionType> types); IImsCallSession createCallSession(in ImsCallProfile profile); int shouldProcessCall(in String[] uris); IImsUt getUtInterface(); diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl index 8e84e9373f65..f218e35a5a9b 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl @@ -17,10 +17,15 @@ package android.telephony.ims.aidl; import android.net.Uri; +import android.telephony.ims.DelegateRequest; import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.aidl.IImsRegistrationCallback; import android.telephony.ims.aidl.IRcsUceControllerCallback; import android.telephony.ims.aidl.IRcsUcePublishStateCallback; -import android.telephony.ims.aidl.IImsRegistrationCallback; +import android.telephony.ims.aidl.IRcsUcePublishStateCallback; +import android.telephony.ims.aidl.ISipDelegate; +import android.telephony.ims.aidl.ISipDelegateMessageCallback; +import android.telephony.ims.aidl.ISipDelegateConnectionStateCallback; import com.android.ims.ImsFeatureContainer; import com.android.ims.internal.IImsServiceFeatureCallback; @@ -58,6 +63,10 @@ interface IImsRcsController { // SipDelegateManager boolean isSipDelegateSupported(int subId); + void createSipDelegate(int subId, in DelegateRequest request, + ISipDelegateConnectionStateCallback delegateState, + ISipDelegateMessageCallback delegateMessage); + void destroySipDelegate(int subId, ISipDelegate connection, int reason); // Internal commands that should not be made public void registerRcsFeatureCallback(int slotId, in IImsServiceFeatureCallback callback); diff --git a/telephony/java/android/telephony/ims/aidl/ISipDelegate.aidl b/telephony/java/android/telephony/ims/aidl/ISipDelegate.aidl new file mode 100644 index 000000000000..477ee958e1e8 --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/ISipDelegate.aidl @@ -0,0 +1,32 @@ +/* + * 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; + +import android.telephony.ims.SipMessage; + +/** + * See {@link SipDelegate} and {@link SipDelegateConnection} for docs regarding this callback. + * {@hide} + */ +oneway interface ISipDelegate { + void sendMessage(in SipMessage sipMessage, int configVersion); + void notifyMessageReceived(in String viaTransactionId); + void notifyMessageReceiveError(in String viaTransactionId, int reason); + + // only used by SipDelegate. + void closeDialog(in String callId); +} diff --git a/telephony/java/android/telephony/ims/aidl/ISipDelegateConnectionStateCallback.aidl b/telephony/java/android/telephony/ims/aidl/ISipDelegateConnectionStateCallback.aidl new file mode 100644 index 000000000000..ddfcb9994cb8 --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/ISipDelegateConnectionStateCallback.aidl @@ -0,0 +1,34 @@ +/* + * 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; + +import android.telephony.ims.DelegateRegistrationState; +import android.telephony.ims.FeatureTagState; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.aidl.ISipDelegate; + +/** + * See {@link SipDelegateConnectionStateCallback} for docs regarding this callback. + * {@hide} + */ +oneway interface ISipDelegateConnectionStateCallback { + void onCreated(ISipDelegate c); + void onFeatureTagStatusChanged(in DelegateRegistrationState registrationState, + in List<FeatureTagState> deniedFeatureTags); + void onImsConfigurationChanged(in SipDelegateImsConfiguration registeredSipConfig); + void onDestroyed(int reason); +} diff --git a/telephony/java/android/telephony/ims/aidl/ISipDelegateMessageCallback.aidl b/telephony/java/android/telephony/ims/aidl/ISipDelegateMessageCallback.aidl new file mode 100644 index 000000000000..30b7d6c70647 --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/ISipDelegateMessageCallback.aidl @@ -0,0 +1,30 @@ +/* + * 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; + +import android.telephony.ims.SipMessage; + +/** + * See {@link DelegateMessageCallback} and {@link DelegateConnectionMessageCallback} for docs + * regarding this callback. + * {@hide} + */ +oneway interface ISipDelegateMessageCallback { + void onMessageReceived(in SipMessage message); + void onMessageSent(in String viaTransactionId); + void onMessageSendFailure(in String viaTransactionId, int reason); +} diff --git a/telephony/java/android/telephony/ims/aidl/ISipDelegateStateCallback.aidl b/telephony/java/android/telephony/ims/aidl/ISipDelegateStateCallback.aidl new file mode 100644 index 000000000000..609ee260cbab --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/ISipDelegateStateCallback.aidl @@ -0,0 +1,33 @@ +/* + * 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; + +import android.telephony.ims.DelegateRegistrationState; +import android.telephony.ims.FeatureTagState; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.aidl.ISipDelegate; + +/** + * See {@link SipDelegateStateCallback} for docs regarding this callback. + * {@hide} + */ +oneway interface ISipDelegateStateCallback { + void onCreated(ISipDelegate c, in List<FeatureTagState> deniedFeatureTags); + void onFeatureTagRegistrationChanged(in DelegateRegistrationState registrationState); + void onImsConfigurationChanged(in SipDelegateImsConfiguration registeredSipConfig); + void onDestroyed(int reason); +} diff --git a/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl b/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl index fe233430ffad..cd888391c584 100644 --- a/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl +++ b/telephony/java/android/telephony/ims/aidl/ISipTransport.aidl @@ -16,9 +16,17 @@ package android.telephony.ims.aidl; +import android.telephony.ims.DelegateRequest; +import android.telephony.ims.aidl.ISipDelegate; +import android.telephony.ims.aidl.ISipDelegateMessageCallback; +import android.telephony.ims.aidl.ISipDelegateStateCallback; + /** * Interface for commands to the SIP Transport implementation. * {@hide} */ -interface ISipTransport { +oneway interface ISipTransport { + void createSipDelegate(in DelegateRequest request, ISipDelegateStateCallback dc, + ISipDelegateMessageCallback mc); + void destroySipDelegate(ISipDelegate delegate, int reason); } diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java new file mode 100644 index 000000000000..a7f62cc32be1 --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/SipDelegateAidlWrapper.java @@ -0,0 +1,192 @@ +/* + * 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; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Binder; +import android.os.RemoteException; +import android.telephony.ims.DelegateMessageCallback; +import android.telephony.ims.DelegateRegistrationState; +import android.telephony.ims.DelegateStateCallback; +import android.telephony.ims.FeatureTagState; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.SipMessage; +import android.telephony.ims.stub.SipDelegate; + +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Implementation of callbacks by wrapping the internal AIDL from telephony. Also implements + * ISipDelegate internally when {@link DelegateStateCallback#onCreated(SipDelegate, List)} is called + * in order to trampoline events back to telephony. + * @hide + */ +public class SipDelegateAidlWrapper implements DelegateStateCallback, DelegateMessageCallback { + + private final ISipDelegate.Stub mDelegateBinder = new ISipDelegate.Stub() { + @Override + public void sendMessage(SipMessage sipMessage, int configVersion) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.sendMessage(sipMessage, configVersion)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void notifyMessageReceived(String viaTransactionId) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.notifyMessageReceived(viaTransactionId)); + } finally { + Binder.restoreCallingIdentity(token); + } + + } + + @Override + public void notifyMessageReceiveError(String viaTransactionId, int reason) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.notifyMessageReceiveError(viaTransactionId, reason)); + } finally { + Binder.restoreCallingIdentity(token); + } + + } + + @Override + public void closeDialog(String callId) { + SipDelegate d = mDelegate; + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> d.closeDialog(callId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + private final ISipDelegateMessageCallback mMessageBinder; + private final ISipDelegateStateCallback mStateBinder; + private final Executor mExecutor; + + private volatile SipDelegate mDelegate; + + public SipDelegateAidlWrapper(Executor executor, ISipDelegateStateCallback stateBinder, + ISipDelegateMessageCallback messageBinder) { + mExecutor = executor; + mStateBinder = stateBinder; + mMessageBinder = messageBinder; + } + + @Override + public void onMessageReceived(SipMessage message) { + try { + mMessageBinder.onMessageReceived(message); + } catch (RemoteException e) { + // BinderDied will be called on SipTransport instance to trigger destruction. Notify + // failure message failure locally for now. + SipDelegate d = mDelegate; + if (d != null) { + notifyLocalMessageFailedToBeReceived(message, + SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD); + } + } + } + + @Override + public void onMessageSent(String viaTransactionId) { + try { + mMessageBinder.onMessageSent(viaTransactionId); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onMessageSendFailure(String viaTransactionId, int reason) { + try { + mMessageBinder.onMessageSendFailure(viaTransactionId, reason); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onCreated(@NonNull SipDelegate delegate, + @Nullable List<FeatureTagState> deniedTags) { + mDelegate = delegate; + try { + mStateBinder.onCreated(mDelegateBinder, deniedTags); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onFeatureTagRegistrationChanged(DelegateRegistrationState registrationState) { + try { + mStateBinder.onFeatureTagRegistrationChanged(registrationState); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onImsConfigurationChanged(@NonNull SipDelegateImsConfiguration config) { + try { + mStateBinder.onImsConfigurationChanged(config); + } catch (RemoteException e) { + // BinderDied will trigger destroySipDelegate, so just ignore this locally. + } + } + + @Override + public void onDestroyed(int reasonCode) { + mDelegate = null; + try { + mStateBinder.onDestroyed(reasonCode); + } catch (RemoteException e) { + // Do not worry about this if the remote side is already dead. + } + } + + public SipDelegate getDelegate() { + return mDelegate; + } + + public ISipDelegate getDelegateBinder() { + return mDelegateBinder; + } + + private void notifyLocalMessageFailedToBeReceived(SipMessage m, int reason) { + //TODO: parse transaction ID or throw IllegalArgumentException if the SipMessage + // transaction ID can not be parsed. + SipDelegate d = mDelegate; + if (d != null) { + mExecutor.execute(() -> d.notifyMessageReceiveError(null, reason)); + } + } +} diff --git a/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java new file mode 100644 index 000000000000..3bd1a462b31a --- /dev/null +++ b/telephony/java/android/telephony/ims/aidl/SipDelegateConnectionAidlWrapper.java @@ -0,0 +1,260 @@ +/* + * 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; + +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.ims.DelegateRegistrationState; +import android.telephony.ims.FeatureTagState; +import android.telephony.ims.SipDelegateConnection; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.SipMessage; +import android.telephony.ims.stub.DelegateConnectionMessageCallback; +import android.telephony.ims.stub.DelegateConnectionStateCallback; +import android.telephony.ims.stub.SipDelegate; +import android.util.ArraySet; +import android.util.Log; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Wrapper class implementing {@link SipDelegateConnection} using AIDL, which is returned to the + * local process. Also holds a reference to incoming connection message and state AIDL impl to + * trampoline events to callbacks as well as notify the local process in the event that the remote + * process becomes unavailable. + * <p> + * When the remote {@link SipDelegate} is created, this instance tracks the + * {@link ISipDelegate} associated with it and implements the + * {@link SipDelegateConnection} sent back to the local callback. + * @hide + */ +public class SipDelegateConnectionAidlWrapper implements SipDelegateConnection, + IBinder.DeathRecipient { + private static final String LOG_TAG = "SipDelegateCAW"; + + private final ISipDelegateConnectionStateCallback.Stub mStateBinder = + new ISipDelegateConnectionStateCallback.Stub() { + @Override + public void onCreated(ISipDelegate c) { + associateSipDelegate(c); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mStateCallback.onCreated(SipDelegateConnectionAidlWrapper.this)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onFeatureTagStatusChanged(DelegateRegistrationState registrationState, + List<FeatureTagState> deniedFeatureTags) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mStateCallback.onFeatureTagStatusChanged(registrationState, + new ArraySet<>(deniedFeatureTags))); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onImsConfigurationChanged(SipDelegateImsConfiguration registeredSipConfig) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mStateCallback.onImsConfigurationChanged(registeredSipConfig)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onDestroyed(int reason) { + invalidateSipDelegateBinder(); + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mStateCallback.onDestroyed(reason)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + private final ISipDelegateMessageCallback.Stub mMessageBinder = + new ISipDelegateMessageCallback.Stub() { + @Override + public void onMessageReceived(SipMessage message) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mMessageCallback.onMessageReceived(message)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onMessageSent(String viaTransactionId) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mMessageCallback.onMessageSent(viaTransactionId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void onMessageSendFailure(String viaTransactionId, int reason) { + final long token = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> + mMessageCallback.onMessageSendFailure(viaTransactionId, reason)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }; + + + private final Executor mExecutor; + private final DelegateConnectionStateCallback mStateCallback; + private final DelegateConnectionMessageCallback mMessageCallback; + private final AtomicReference<ISipDelegate> mDelegateBinder = + new AtomicReference<>(); + + /** + * Wrap the local state and message callbacks, calling the implementation of these interfaces + * when the remote process calls these methods. + */ + public SipDelegateConnectionAidlWrapper(Executor executor, + DelegateConnectionStateCallback stateCallback, + DelegateConnectionMessageCallback messageCallback) { + mExecutor = executor; + mStateCallback = stateCallback; + mMessageCallback = messageCallback; + } + + @Override + public void sendMessage(SipMessage sipMessage, int configVersion) { + try { + ISipDelegate conn = getSipDelegateBinder(); + if (conn == null) { + notifyLocalMessageFailedToSend(sipMessage, + SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_CLOSED); + return; + } + conn.sendMessage(sipMessage, configVersion); + } catch (RemoteException e) { + notifyLocalMessageFailedToSend(sipMessage, + SipDelegateManager.MESSAGE_FAILURE_REASON_DELEGATE_DEAD); + } + } + + @Override + public void notifyMessageReceived(String viaTransactionId) { + try { + ISipDelegate conn = getSipDelegateBinder(); + if (conn == null) { + return; + } + conn.notifyMessageReceived(viaTransactionId); + } catch (RemoteException e) { + // Nothing to do here, app will eventually get remote death callback. + } + } + + @Override + public void notifyMessageReceiveError(String viaTransactionId, int reason) { + try { + ISipDelegate conn = getSipDelegateBinder(); + if (conn == null) { + return; + } + conn.notifyMessageReceiveError(viaTransactionId, reason); + } catch (RemoteException e) { + // Nothing to do here, app will eventually get remote death callback. + } + } + + // Also called upon IImsRcsController death (telephony process dies). + @Override + public void binderDied() { + invalidateSipDelegateBinder(); + mExecutor.execute(() -> mStateCallback.onDestroyed( + SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD)); + } + + /** + * @return Implementation of state binder. + */ + public ISipDelegateConnectionStateCallback getStateCallbackBinder() { + return mStateBinder; + } + + /** + * @return Implementation of message binder. + */ + public ISipDelegateMessageCallback getMessageCallbackBinder() { + return mMessageBinder; + } + + /** + * @return The ISipDelegateConnection associated with this wrapper. + */ + public ISipDelegate getSipDelegateBinder() { + return mDelegateBinder.get(); + } + + private void associateSipDelegate(ISipDelegate c) { + if (c != null) { + try { + c.asBinder().linkToDeath(this, 0 /*flags*/); + } catch (RemoteException e) { + // already dead. + c = null; + } + } + mDelegateBinder.set(c); + } + + private void invalidateSipDelegateBinder() { + ISipDelegate oldVal = mDelegateBinder.getAndUpdate((unused) -> null); + if (oldVal != null) { + try { + oldVal.asBinder().unlinkToDeath(this, 0 /*flags*/); + } catch (NoSuchElementException e) { + Log.i(LOG_TAG, "invalidateSipDelegateBinder: " + e); + } + } + } + + private void notifyLocalMessageFailedToSend(SipMessage m, int reason) { + //TODO: parse transaction ID or throw IllegalArgumentException if the SipMessage + // transaction ID can not be parsed. + mExecutor.execute(() -> + mMessageCallback.onMessageSendFailure(null, reason)); + } +} diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index b0a7b62c88ab..96ca0225040f 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -509,6 +509,7 @@ public abstract class ImsFeature { * @return true if the capability is enabled, false otherwise. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") public abstract boolean queryCapabilityConfiguration(int capability, int radioTech); /** @@ -547,5 +548,6 @@ public abstract class ImsFeature { * @return Binder instance that the framework will use to communicate with this feature. * @hide */ + @SuppressWarnings("HiddenAbstractMethod") protected abstract IInterface getBinder(); } diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index d7b0e0f030ab..e570fb6f5612 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -27,6 +27,8 @@ import android.telecom.TelecomManager; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsCallSession; import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsService; +import android.telephony.ims.RtpHeaderExtensionType; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsMmTelListener; @@ -37,6 +39,7 @@ import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsSmsImplBase; import android.telephony.ims.stub.ImsUtImplBase; +import android.util.ArraySet; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; @@ -45,6 +48,8 @@ import com.android.ims.internal.IImsUt; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; +import java.util.Set; /** * Base implementation for Voice and SMS (IR-92) and Video (IR-94) IMS support. @@ -93,6 +98,18 @@ public class MmTelFeature extends ImsFeature { } @Override + public void changeOfferedRtpHeaderExtensionTypes(List<RtpHeaderExtensionType> types) + throws RemoteException { + synchronized (mLock) { + try { + MmTelFeature.this.changeOfferedRtpHeaderExtensionTypes(new ArraySet<>(types)); + } catch (Exception e) { + throw new RemoteException(e.getMessage()); + } + } + } + + @Override public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException { synchronized (mLock) { return createCallSessionInterface(profile); @@ -623,6 +640,24 @@ public class MmTelFeature extends ImsFeature { } /** + * Called by the framework to report a change to the RTP header extension types which should be + * offered during SDP negotiation (see RFC8285 for more information). + * <p> + * The {@link ImsService} should report the RTP header extensions which were accepted during + * SDP negotiation using {@link ImsCallProfile#setAcceptedRtpHeaderExtensionTypes(Set)}. + * + * @param extensionTypes The RTP header extensions the framework wishes to offer during + * outgoing and incoming call setup. An empty list indicates that there + * are no framework defined RTP header extension types to offer. + * @hide + */ + @SystemApi + public void changeOfferedRtpHeaderExtensionTypes( + @NonNull Set<RtpHeaderExtensionType> extensionTypes) { + // Base implementation - should be overridden if RTP header extension handling is supported. + } + + /** * @hide */ public IImsCallSession createCallSessionInterface(ImsCallProfile profile) diff --git a/telephony/java/android/telephony/ims/stub/DelegateConnectionMessageCallback.java b/telephony/java/android/telephony/ims/stub/DelegateConnectionMessageCallback.java new file mode 100644 index 000000000000..59f9601299b2 --- /dev/null +++ b/telephony/java/android/telephony/ims/stub/DelegateConnectionMessageCallback.java @@ -0,0 +1,54 @@ +/* + * 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.stub; + +import android.annotation.NonNull; +import android.telephony.ims.SipDelegateConnection; +import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.SipMessage; + +/** + * The callback associated with a {@link SipDelegateConnection}, which handles newly received + * messages as well as the result of sending a SIP message. + * @hide + */ +public interface DelegateConnectionMessageCallback { + + /** + * A new {@link SipMessage} has been received from the delegate. + * @param message the {@link SipMessage} routed to this RCS application. + */ + void onMessageReceived(@NonNull SipMessage message); + + /** + * A message previously sent to the SIP delegate using + * {@link SipDelegateConnection#sendMessage} has been successfully sent. + * @param viaTransactionId The transaction ID found in the via header field of the + * previously sent {@link SipMessage}. + */ + void onMessageSent(@NonNull String viaTransactionId); + + /** + * A message previously sent to the SIP delegate using + * {@link SipDelegateConnection#sendMessage} has failed to be sent. + * @param viaTransactionId The Transaction ID found in the via header field of the + * previously sent {@link SipMessage}. + * @param reason The reason for the failure. + */ + void onMessageSendFailure(String viaTransactionId, + @SipDelegateManager.MessageFailureReason int reason); +} diff --git a/telephony/java/android/telephony/ims/stub/DelegateConnectionStateCallback.java b/telephony/java/android/telephony/ims/stub/DelegateConnectionStateCallback.java new file mode 100644 index 000000000000..976180538b18 --- /dev/null +++ b/telephony/java/android/telephony/ims/stub/DelegateConnectionStateCallback.java @@ -0,0 +1,147 @@ +/* + * 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.stub; + +import android.annotation.NonNull; +import android.telephony.ims.DelegateRegistrationState; +import android.telephony.ims.DelegateRequest; +import android.telephony.ims.FeatureTagState; +import android.telephony.ims.SipDelegateConnection; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.SipDelegateManager; + +import java.util.Set; + +/** + * The callback associated with a {@link SipDelegateConnection} that manages the state of the + * SipDelegateConnection. + * <p> + * After {@link SipDelegateManager#createSipDelegate} is used to request a new + * {@link SipDelegateConnection} be created, {@link #onCreated} will be called with the + * {@link SipDelegateConnection} instance that must be used to communicate with the remote + * {@link SipDelegate}. + * <p> + * After, {@link #onFeatureTagStatusChanged} will always be called at least once with the current + * status of the feature tags that have been requested. The application may receive multiple + * {@link #onFeatureTagStatusChanged} callbacks over the lifetime of the associated + * {@link SipDelegateConnection}, which will signal changes to how SIP messages associated with + * those feature tags will be handled. + * <p> + * In order to start sending SIP messages, the SIP configuration parameters will need to be + * received, so the messaging application should make no assumptions about these parameters and wait + * until {@link #onImsConfigurationChanged(SipDelegateImsConfiguration)} has been called. This is + * guaranteed to happen after the first {@link #onFeatureTagStatusChanged} if there is at least one + * feature tag that has been successfully associated with the {@link SipDelegateConnection}. If all + * feature tags were denied, no IMS configuration will be sent. + * <p> + * The {@link SipDelegateConnection} will stay associated with this RCS application until either the + * RCS application calls {@link SipDelegateManager#destroySipDelegate} or telephony destroys the + * {@link SipDelegateConnection}. In both cases, {@link #onDestroyed(int)} will be called. + * Telephony destroying the {@link SipDelegateConnection} instance is rare and will only happen in + * rare cases, such as if telephony itself or IMS service dies unexpectedly. See + * {@link SipDelegateManager.SipDelegateDestroyReason} reasons for more information on all of the + * cases that will trigger the {@link SipDelegateConnection} to be destroyed. + * + * @hide + */ +public interface DelegateConnectionStateCallback { + + /** + * A {@link SipDelegateConnection} has been successfully created for the + * {@link DelegateRequest} used when calling {@link SipDelegateManager#createSipDelegate}. + */ + void onCreated(@NonNull SipDelegateConnection c); + + /** + * The status of the RCS feature tags that were requested as part of the initial + * {@link DelegateRequest}. + * <p> + * There are four states that each RCS feature tag can be in: registered, deregistering, + * deregistered, and denied. + * <p> + * When a feature tag is considered registered, SIP messages associated with that feature tag + * may be sent and received freely. + * <p> + * When a feature tag is deregistering, the network IMS registration still contains the feature + * tag, however the IMS service and associated {@link SipDelegate} is in the progress of + * modifying the IMS registration to remove this feature tag and requires the application to + * perform an action before the IMS registration can change. The specific action required for + * the SipDelegate to continue modifying the IMS registration can be found in the definition of + * each {@link DelegateRegistrationState.DeregisteringReason}. + * <p> + * When a feature tag is in the deregistered state, new out-of-dialog SIP messages for that + * feature tag will be rejected, however due to network race conditions, the RCS application + * should still be able to handle new out-of-dialog SIP requests from the network. This may not + * be possible, however, if the IMS registration itself was lost. See the + * {@link DelegateRegistrationState.DeregisteredReason} reasons for more information on how SIP + * messages are handled in each of these cases. + * <p> + * If a feature tag is denied, no incoming messages will be routed to the associated + * {@link DelegateConnectionMessageCallback} and all outgoing SIP messages related to this + * feature tag will be rejected. See {@link SipDelegateManager.DeniedReason} + * reasons for more information about the conditions when this will happen. + * <p> + * The set of feature tags contained in the registered, deregistering, deregistered, and denied + * lists will always equal the set of feature tags requested in the initial + * {@link DelegateRequest}. + * <p> + * Transitions of feature tags from registered, deregistering, and deregistered and vice-versa + * may happen quite often, however transitions to/from denied are rare and only occur if the + * user has changed the role of your application to add/remove support for one or more requested + * feature tags or carrier provisioning has enabled or disabled single registration entirely. + * Please see {@link SipDelegateManager.DeniedReason} reasons for an explanation of each of + * these cases as well as what may cause them to change. + * + * @param registrationState The new IMS registration state of each of the feature tags + * associated with the {@link SipDelegate}. + * @param deniedFeatureTags A list of {@link FeatureTagState} objects, each containing a feature + * tag associated with this {@link SipDelegateConnection} that has no access to + * send/receive SIP messages as well as a reason for why the feature tag is denied. For more + * information on the reason why the feature tag was denied access, see the + * {@link SipDelegateManager.DeniedReason} reasons. + */ + void onFeatureTagStatusChanged(@NonNull DelegateRegistrationState registrationState, + @NonNull Set<FeatureTagState> deniedFeatureTags); + + + /** + * IMS configuration of the underlying IMS stack used by this IMS application for construction + * of the SIP messages that will be sent over the carrier's network. + * <p> + * There should never be assumptions made about the configuration of the underling IMS stack and + * the IMS application should wait for this indication before sending out any outgoing SIP + * messages. + * <p> + * Configuration may change due to IMS registration changes as well as + * other optional events on the carrier network. If IMS stack is already registered at the time + * of callback registration, then this method shall be invoked with the current configuration. + * Otherwise, there may be a delay in this method being called if initial IMS registration has + * not compleed yet. + * + * @param registeredSipConfig The configuration of the IMS stack registered on the IMS network. + */ + void onImsConfigurationChanged(@NonNull SipDelegateImsConfiguration registeredSipConfig); + + /** + * The previously created {@link SipDelegateConnection} instance delivered via + * {@link #onCreated(SipDelegateConnection)} has been destroyed. This interface should no longer + * be used for any SIP message handling. + * + * @param reason The reason for the failure. + */ + void onDestroyed(@SipDelegateManager.SipDelegateDestroyReason int reason); +} diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 12abdd1d7e11..a6f5c45445f5 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -17,6 +17,8 @@ package android.telephony.ims.stub; import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.Uri; import android.os.RemoteException; @@ -126,6 +128,57 @@ public class ImsRegistrationImplBase { } /** + * Called by the framework to request that the ImsService perform the network registration + * of all SIP delegates associated with this ImsService. + * <p> + * 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 + } + + + /** + * Called by the framework to request that the ImsService perform the network deregistration of + * all SIP delegates associated with this ImsService. + * <p> + * This is typically called in situations where the user has changed the configuration of the + * device (for example, the default messaging application) and the framework is reconfiguring + * the tags associated with each IMS application. + * <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 + } + + /** + * Called by the framework to notify the ImsService that a SIP delegate connection has received + * a SIP message containing a permanent failure response (such as a 403) or an indication that a + * SIP response timer has timed out in response to an outgoing SIP message. This method will be + * called when this condition occurs to trigger the ImsService to tear down the full IMS + * registration and re-register again. + * + * @param sipCode The SIP error code that represents a permanent failure that was received in + * response to a request generated by the IMS application. See RFC3261 7.2 for the general + * classes of responses available here, however the codes that generate this condition may + * 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, + @Nullable String sipReason) { + // Stub implementation, ImsService should implement this + } + + + /** * Notify the framework that the device is connected to the IMS network. * * @param imsRadioTech the radio access technology. Valid values are defined as diff --git a/telephony/java/android/telephony/ims/stub/SipDelegate.java b/telephony/java/android/telephony/ims/stub/SipDelegate.java new file mode 100644 index 000000000000..3ec97095eb00 --- /dev/null +++ b/telephony/java/android/telephony/ims/stub/SipDelegate.java @@ -0,0 +1,91 @@ +/* + * 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.stub; + +import android.annotation.NonNull; +import android.telephony.ims.DelegateMessageCallback; +import android.telephony.ims.ImsService; +import android.telephony.ims.SipDelegateImsConfiguration; +import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.SipMessage; + +/** + * The {@link SipDelegate} is implemented by the {@link ImsService} and allows a privileged + * IMS application to use this delegate to send SIP messages as well as acknowledge the receipt of + * incoming SIP messages delivered to the application over the existing IMS registration, allowing + * for a single IMS registration for multiple IMS applications. + * <p> + * Once the SIP delegate is created for that application, + * {@link ImsRegistrationImplBase#updateSipDelegateRegistration()} will be called, indicating that + * the application is finished setting up SipDelegates and the existing IMS registration may be + * modified to include the features managed by these SipDelegates. + * <p> + * This SipDelegate will need to notify the remote application of the registration of these features + * as well as the associated {@link SipDelegateImsConfiguration} before the application can start + * sending/receiving SIP messages via the transport. See + * {@link android.telephony.ims.DelegateStateCallback} for more information. + * @hide + */ +public interface SipDelegate { + + /** + * The framework calls this method when a remote RCS application wishes to send a new outgoing + * SIP message. + * <p> + * Once sent, this SIP delegate should notify the remote application of the success or + * failure using {@link DelegateMessageCallback#onMessageSent(String)} or + * {@link DelegateMessageCallback#onMessageSendFailure(String, int)}. + * @param message The SIP message to be sent over the operator’s network. + * @param configVersion The SipDelegateImsConfiguration version used to construct the + * SipMessage. See {@link SipDelegateImsConfiguration} for more information. If the + * version specified here does not match the most recently constructed + * {@link SipDelegateImsConfiguration}, this message should fail validation checks and + * {@link DelegateMessageCallback#onMessageSendFailure} should be called with code + * {@link SipDelegateManager#MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION}. + */ + void sendMessage(@NonNull SipMessage message, int configVersion); + + /** + * The framework is requesting that routing resources associated with the SIP dialog using the + * provided Call-ID to be cleaned up. + * <p> + * Typically a SIP Dialog close event will be signalled by that dialog receiving a BYE or 200 OK + * message, however, in some cases, the framework will request that the ImsService close the + * dialog due to the open dialog holding up an event such as applying a provisioning change or + * handing over to another transport type. + * @param callId The call-ID header value associated with the ongoing SIP Dialog that the + * framework is requesting be closed. + */ + void closeDialog(@NonNull String callId); + + /** + * The remote application has received the SIP message and is processing it. + * @param viaTransactionId The Transaction ID found in the via header field of the + * previously sent {@link SipMessage}. + */ + void notifyMessageReceived(@NonNull String viaTransactionId); + + /** + * The remote application has either not received the SIP message or there was an error + * processing it. + * @param viaTransactionId The Transaction ID found in the via header field of the + * previously sent {@link SipMessage}. + * @param reason The reason why the message was not correctly received. + */ + void notifyMessageReceiveError(@NonNull String viaTransactionId, + @SipDelegateManager.MessageFailureReason int reason); +} diff --git a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java index b2b2914b3739..b48f6317e413 100644 --- a/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java +++ b/telephony/java/android/telephony/ims/stub/SipTransportImplBase.java @@ -18,27 +18,75 @@ package android.telephony.ims.stub; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.os.Binder; +import android.os.IBinder; +import android.telephony.ims.DelegateMessageCallback; +import android.telephony.ims.DelegateRequest; +import android.telephony.ims.DelegateStateCallback; +import android.telephony.ims.SipDelegateManager; +import android.telephony.ims.aidl.ISipDelegate; +import android.telephony.ims.aidl.ISipDelegateMessageCallback; +import android.telephony.ims.aidl.ISipDelegateStateCallback; import android.telephony.ims.aidl.ISipTransport; +import android.telephony.ims.aidl.SipDelegateAidlWrapper; +import android.util.Log; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** - * Manages the creation and destruction of SipDelegates in order to proxy SIP traffic to other - * IMS applications in order to support IMS single registration. + * The ImsService implements this class to manage the creation and destruction of + * {@link SipDelegate}s. + * + * {@link SipDelegate}s allow the ImsService to forward SIP traffic generated and consumed by IMS + * applications as a delegate to the associated carrier's IMS Network in order to support using a + * single IMS registration for all MMTEL and RCS signalling traffic. * @hide */ @SystemApi public class SipTransportImplBase { + private static final String LOG_TAG = "SipTransportIB"; - private final Executor mBinderExecutor; - private final ISipTransport mSipTransportImpl = new ISipTransport.Stub() { + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + mBinderExecutor.execute(() -> binderDiedInternal()); + } + }; + private final ISipTransport.Stub mSipTransportImpl = new ISipTransport.Stub() { + @Override + public void createSipDelegate(DelegateRequest request, ISipDelegateStateCallback dc, + ISipDelegateMessageCallback mc) { + final long token = Binder.clearCallingIdentity(); + try { + mBinderExecutor.execute(() -> createSipDelegateInternal(request, dc, mc)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void destroySipDelegate(ISipDelegate delegate, int reason) { + final long token = Binder.clearCallingIdentity(); + try { + mBinderExecutor.execute(() -> destroySipDelegateInternal(delegate, reason)); + } finally { + Binder.restoreCallingIdentity(token); + } + } }; + private final Executor mBinderExecutor; + private final ArrayList<SipDelegateAidlWrapper> mDelegates = new ArrayList<>(); + /** * Create an implementation of SipTransportImplBase. * - * @param executor The executor that remote calls from the framework should be called on. + * @param executor The executor that remote calls from the framework will be called on. This + * includes the methods here as well as the methods in {@link SipDelegate}. */ public SipTransportImplBase(@NonNull Executor executor) { if (executor == null) { @@ -49,6 +97,79 @@ public class SipTransportImplBase { } /** + * Called by the Telephony framework to request the creation of a new {@link SipDelegate}. + * <p> + * The implementation must call {@link DelegateStateCallback#onCreated(SipDelegate, List)} with + * the {@link SipDelegate} that is associated with the {@link DelegateRequest}. + * <p> + * This method will be called on the Executor specified in + * {@link SipTransportImplBase#SipTransportImplBase(Executor)}. + * + * @param request A SIP delegate request containing the parameters that the remote RCS + * application wishes to use. + * @param dc A callback back to the remote application to be used to communicate state callbacks + * for the SipDelegate. + * @param mc A callback back to the remote application to be used to send SIP messages to the + * remote application and acknowledge the sending of outgoing SIP messages. + * @hide + */ + public void createSipDelegate(@NonNull DelegateRequest request, + @NonNull DelegateStateCallback dc, @NonNull DelegateMessageCallback mc) { + throw new UnsupportedOperationException("destroySipDelegate not implemented!"); + } + + /** + * Destroys the SipDelegate associated with a remote IMS application. + * <p> + * After the delegate is destroyed, {@link DelegateStateCallback#onDestroyed(int)} must be + * called to notify listeners of its destruction to release associated resources. + * <p> + * This method will be called on the Executor specified in + * {@link SipTransportImplBase#SipTransportImplBase(Executor)}. + * @param delegate The delegate to be destroyed. + * @param reason The reason the remote connection to this {@link SipDelegate} is being + * destroyed. + * @hide + */ + public void destroySipDelegate(@NonNull SipDelegate delegate, + @SipDelegateManager.SipDelegateDestroyReason int reason) { + throw new UnsupportedOperationException("destroySipDelegate not implemented!"); + } + + private void createSipDelegateInternal(DelegateRequest r, ISipDelegateStateCallback cb, + ISipDelegateMessageCallback mc) { + SipDelegateAidlWrapper wrapper = new SipDelegateAidlWrapper(mBinderExecutor, cb, mc); + mDelegates.add(wrapper); + createSipDelegate(r, wrapper, wrapper); + } + + private void destroySipDelegateInternal(ISipDelegate d, int reason) { + SipDelegateAidlWrapper result = null; + for (SipDelegateAidlWrapper w : mDelegates) { + if (Objects.equals(d, w.getDelegateBinder())) { + result = w; + break; + } + } + + if (result != null) { + mDelegates.remove(result); + destroySipDelegate(result.getDelegate(), reason); + } else { + Log.w(LOG_TAG, "destroySipDelegateInternal, could not findSipDelegate corresponding to " + + d); + } + } + + private void binderDiedInternal() { + for (SipDelegateAidlWrapper w : mDelegates) { + destroySipDelegate(w.getDelegate(), + SipDelegateManager.SIP_DELEGATE_DESTROY_REASON_SERVICE_DEAD); + } + mDelegates.clear(); + } + + /** * @return The IInterface used by the framework. * @hide */ diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 36d01f459fb8..d16cb16a290c 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -31,6 +31,7 @@ import android.service.carrier.CarrierIdentifier; import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telephony.CallForwardingInfo; +import android.telephony.CarrierBandwidth; import android.telephony.CarrierRestrictionRules; import android.telephony.CellIdentity; import android.telephony.CellInfo; @@ -2232,4 +2233,10 @@ interface ITelephony { * @return true if dual connectivity is enabled else false */ boolean isNrDualConnectivityEnabled(int subId); + + /** + * Get carrier bandwidth per primary and secondary carrier + * @return CarrierBandwidth with bandwidth of both primary and secondary carrier. + */ + CarrierBandwidth getCarrierBandwidth(int subId); } diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java index 7abe1893dd9e..cd9406cf3481 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java @@ -35,7 +35,6 @@ import static android.net.NetworkStats.ROAMING_YES; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.SET_DEFAULT; import static android.net.NetworkStats.SET_FOREGROUND; -import static android.net.NetworkStats.STATS_PER_IFACE; import static android.net.NetworkStats.STATS_PER_UID; import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkStats.TAG_NONE; @@ -994,7 +993,7 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { public void testTethering() throws Exception { // pretend first mobile network comes online expectDefaultSettings(); - NetworkState[] states = new NetworkState[] {buildMobile3gState(IMSI_1)}; + final NetworkState[] states = new NetworkState[]{buildMobile3gState(IMSI_1)}; expectNetworkStatsSummary(buildEmptyStats()); expectNetworkStatsUidDetail(buildEmptyStats()); @@ -1004,23 +1003,39 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { incrementCurrentTime(HOUR_IN_MILLIS); expectDefaultSettings(); + // Register custom provider and retrieve callback. + final TestableNetworkStatsProviderBinder provider = + new TestableNetworkStatsProviderBinder(); + final INetworkStatsProviderCallback cb = + mService.registerNetworkStatsProvider("TEST-TETHERING-OFFLOAD", provider); + assertNotNull(cb); + final long now = getElapsedRealtime(); + // Traffic seen by kernel counters (includes software tethering). - final NetworkStats ifaceStats = new NetworkStats(getElapsedRealtime(), 1) + final NetworkStats swIfaceStats = new NetworkStats(now, 1) .insertEntry(TEST_IFACE, 1536L, 12L, 384L, 3L); // Hardware tethering traffic, not seen by kernel counters. - final NetworkStats tetherStatsHardware = new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, 512L, 4L, 128L, 1L); + final NetworkStats tetherHwIfaceStats = new NetworkStats(now, 1) + .insertEntry(new NetworkStats.Entry(TEST_IFACE, UID_ALL, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 512L, 4L, 128L, 1L, 0L)); + final NetworkStats tetherHwUidStats = new NetworkStats(now, 1) + .insertEntry(new NetworkStats.Entry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, + TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES, + 512L, 4L, 128L, 1L, 0L)); + cb.notifyStatsUpdated(0 /* unused */, tetherHwIfaceStats, tetherHwUidStats); - // Traffic for UID_RED. - final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1) + // Fake some traffic done by apps on the device (as opposed to tethering), and record it + // into UID stats (as opposed to iface stats). + final NetworkStats localUidStats = new NetworkStats(now, 1) .insertEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L); - // All tethering traffic, both hardware and software. - final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1) - .insertEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L, + // Software per-uid tethering traffic. + final NetworkStats tetherSwUidStats = new NetworkStats(now, 1) + .insertEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1408L, 10L, 256L, 1L, 0L); - expectNetworkStatsSummary(ifaceStats, tetherStatsHardware); - expectNetworkStatsUidDetail(uidStats, tetherStats); + expectNetworkStatsSummary(swIfaceStats); + expectNetworkStatsUidDetail(localUidStats, tetherSwUidStats); forcePollAndWaitForIdle(); // verify service recorded history @@ -1362,12 +1377,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { } private void expectNetworkStatsSummary(NetworkStats summary) throws Exception { - expectNetworkStatsSummary(summary, new NetworkStats(0L, 0)); - } - - private void expectNetworkStatsSummary(NetworkStats summary, NetworkStats tetherStats) - throws Exception { - expectNetworkStatsTethering(STATS_PER_IFACE, tetherStats); expectNetworkStatsSummaryDev(summary.clone()); expectNetworkStatsSummaryXt(summary.clone()); } @@ -1380,11 +1389,6 @@ public class NetworkStatsServiceTest extends NetworkStatsBaseTest { when(mStatsFactory.readNetworkStatsSummaryXt()).thenReturn(summary); } - private void expectNetworkStatsTethering(int how, NetworkStats stats) - throws Exception { - when(mNetManager.getNetworkStatsTethering(how)).thenReturn(stats); - } - private void expectNetworkStatsUidDetail(NetworkStats detail) throws Exception { expectNetworkStatsUidDetail(detail, new NetworkStats(0L, 0)); } diff --git a/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java new file mode 100644 index 000000000000..a44a734a2dce --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/util/PersistableBundleUtilsTest.java @@ -0,0 +1,214 @@ +/* + * 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.util; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import android.os.PersistableBundle; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class PersistableBundleUtilsTest { + private static final String TEST_KEY = "testKey"; + private static final String TEST_STRING_PREFIX = "testString"; + private static final int[] TEST_INT_ARRAY = new int[] {0, 1, 2, 3, 4}; + + private static final int NUM_COLLECTION_ENTRIES = 10; + + private static class TestKey { + private static final String TEST_INTEGER_KEY = + "mTestInteger"; // Purposely colliding with keys of test class to ensure namespacing + private final int mTestInteger; + + TestKey(int testInteger) { + mTestInteger = testInteger; + } + + TestKey(PersistableBundle in) { + mTestInteger = in.getInt(TEST_INTEGER_KEY); + } + + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(TEST_INTEGER_KEY, mTestInteger); + + return result; + } + + @Override + public int hashCode() { + return Objects.hash(mTestInteger); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TestKey)) { + return false; + } + + final TestKey other = (TestKey) o; + return mTestInteger == other.mTestInteger; + } + } + + private static class TestClass { + private static final String TEST_INTEGER_KEY = "mTestInteger"; + private final int mTestInteger; + + private static final String TEST_INT_ARRAY_KEY = "mTestIntArray"; + private final int[] mTestIntArray; + + private static final String TEST_STRING_KEY = "mTestString"; + private final String mTestString; + + private static final String TEST_PERSISTABLE_BUNDLE_KEY = "mTestPersistableBundle"; + private final PersistableBundle mTestPersistableBundle; + + TestClass( + int testInteger, + int[] testIntArray, + String testString, + PersistableBundle testPersistableBundle) { + mTestInteger = testInteger; + mTestIntArray = testIntArray; + mTestString = testString; + mTestPersistableBundle = testPersistableBundle; + } + + TestClass(PersistableBundle in) { + mTestInteger = in.getInt(TEST_INTEGER_KEY); + mTestIntArray = in.getIntArray(TEST_INT_ARRAY_KEY); + mTestString = in.getString(TEST_STRING_KEY); + mTestPersistableBundle = in.getPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY); + } + + public PersistableBundle toPersistableBundle() { + final PersistableBundle result = new PersistableBundle(); + + result.putInt(TEST_INTEGER_KEY, mTestInteger); + result.putIntArray(TEST_INT_ARRAY_KEY, mTestIntArray); + result.putString(TEST_STRING_KEY, mTestString); + result.putPersistableBundle(TEST_PERSISTABLE_BUNDLE_KEY, mTestPersistableBundle); + + return result; + } + + @Override + public int hashCode() { + return Objects.hash( + mTestInteger, + Arrays.hashCode(mTestIntArray), + mTestString, + mTestPersistableBundle); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof TestClass)) { + return false; + } + + final TestClass other = (TestClass) o; + + // TODO: Add a proper equals() to PersistableBundle. But in the meantime, force + // TODO: unparcelling in order to allow test comparison. + if (mTestPersistableBundle.size() != other.mTestPersistableBundle.size()) { + return false; + } + + return mTestInteger == other.mTestInteger + && Arrays.equals(mTestIntArray, other.mTestIntArray) + && mTestString.equals(other.mTestString) + && mTestPersistableBundle.kindofEquals(other.mTestPersistableBundle); + } + } + + @Test + public void testListConversionLossless() throws Exception { + final List<TestClass> sourceList = new ArrayList<>(); + for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) { + final PersistableBundle innerBundle = new PersistableBundle(); + innerBundle.putInt(TEST_KEY, i); + + sourceList.add(new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle)); + } + + final PersistableBundle bundled = + PersistableBundleUtils.fromList(sourceList, TestClass::toPersistableBundle); + final List<TestClass> resultList = PersistableBundleUtils.toList(bundled, TestClass::new); + + assertEquals(sourceList, resultList); + } + + @Test + public void testMapConversionLossless() throws Exception { + final LinkedHashMap<TestKey, TestClass> sourceMap = new LinkedHashMap<>(); + for (int i = 0; i < NUM_COLLECTION_ENTRIES; i++) { + final TestKey key = new TestKey(i * i); + + final PersistableBundle innerBundle = new PersistableBundle(); + innerBundle.putInt(TEST_KEY, i); + final TestClass value = + new TestClass(i, TEST_INT_ARRAY, TEST_STRING_PREFIX + i, innerBundle); + + sourceMap.put(key, value); + } + + final PersistableBundle bundled = + PersistableBundleUtils.fromMap( + sourceMap, TestKey::toPersistableBundle, TestClass::toPersistableBundle); + final LinkedHashMap<TestKey, TestClass> resultList = + PersistableBundleUtils.toMap(bundled, TestKey::new, TestClass::new); + + assertEquals(sourceMap, resultList); + } + + @Test + public void testByteArrayConversionLossless() { + final byte[] byteArray = "testByteArrayConversionLossless".getBytes(); + + PersistableBundle bundle = PersistableBundleUtils.fromByteArray(byteArray); + byte[] result = PersistableBundleUtils.toByteArray(bundle); + + assertArrayEquals(byteArray, result); + } + + @Test + public void testIntegerConversionLossless() throws Exception { + final int testInt = 1; + final PersistableBundle integerBundle = + PersistableBundleUtils.INTEGER_SERIALIZER.toPersistableBundle(testInt); + final int result = + PersistableBundleUtils.INTEGER_DESERIALIZER.fromPersistableBundle(integerBundle); + + assertEquals(testInt, result); + } +} diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp index 3e49034310c3..52949da1b64f 100644 --- a/tools/aapt2/DominatorTree_test.cpp +++ b/tools/aapt2/DominatorTree_test.cpp @@ -198,5 +198,33 @@ TEST(DominatorTreeTest, NonZeroDensitiesMatch) { EXPECT_EQ(expected, printer.ToString(&tree)); } +TEST(DominatorTreeTest, MccMncIsPeertoLocale) { + const ConfigDescription default_config = {}; + const ConfigDescription de_config = test::ParseConfigOrDie("de"); + const ConfigDescription fr_config = test::ParseConfigOrDie("fr"); + const ConfigDescription mcc_config = test::ParseConfigOrDie("mcc262"); + const ConfigDescription mcc_fr_config = test::ParseConfigOrDie("mcc262-fr"); + const ConfigDescription mnc_config = test::ParseConfigOrDie("mnc2"); + const ConfigDescription mnc_fr_config = test::ParseConfigOrDie("mnc2-fr"); + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(de_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(fr_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(mcc_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(mcc_fr_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(mnc_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(mnc_fr_config, "")); + DominatorTree tree(configs); + PrettyPrinter printer; + std::string expected = + "<default>\n" + "de\n" + "fr\n" + "mcc262\n" + "mcc262-fr\n" + "mnc2\n" + "mnc2-fr\n"; + EXPECT_EQ(expected, printer.ToString(&tree)); +} } // namespace aapt diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp index 048e318d2802..888de40be268 100644 --- a/tools/aapt2/optimize/ResourceDeduper_test.cpp +++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp @@ -52,9 +52,11 @@ TEST(ResourceDeduperTest, SameValuesAreDeduped) { .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/dedupe", default_config)); EXPECT_THAT(table, Not(HasValue("android:string/dedupe", ldrtl_config))); EXPECT_THAT(table, Not(HasValue("android:string/dedupe", land_config))); + EXPECT_THAT(table, HasValue("android:string/dedupe2", default_config)); EXPECT_THAT(table, HasValue("android:string/dedupe2", ldrtl_v21_config)); EXPECT_THAT(table, Not(HasValue("android:string/dedupe2", ldrtl_config))); @@ -151,4 +153,24 @@ TEST(ResourceDeduperTest, LocalesValuesAreKept) { EXPECT_THAT(table, HasValue("android:string/keep", fr_rCA_config)); } +TEST(ResourceDeduperTest, MccMncValuesAreKept) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription mcc_config = test::ParseConfigOrDie("mcc262"); + const ConfigDescription mnc_config = test::ParseConfigOrDie("mnc2"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, mcc_config, "keep") + .AddString("android:string/keep", ResourceId{}, mnc_config, "keep") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", mcc_config)); + EXPECT_THAT(table, HasValue("android:string/keep", mnc_config)); +} + + } // namespace aapt diff --git a/wifi/Android.bp b/wifi/Android.bp index 1cc5073c5f71..8b89959a4f05 100644 --- a/wifi/Android.bp +++ b/wifi/Android.bp @@ -76,25 +76,34 @@ test_access_hidden_api_whitelist = [ "//packages/apps/Settings/tests/robotests", // TODO(b/161767237): remove ] -// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility -// classes before they are renamed. -java_library { - name: "framework-wifi-pre-jarjar", +// defaults shared between `framework-wifi` & `framework-wifi-pre-jarjar` +// java_sdk_library `framework-wifi` needs sources to generate stubs, so it cannot reuse +// `framework-wifi-pre-jarjar` +java_defaults { + name: "framework-wifi-defaults", defaults: ["wifi-module-sdk-version-defaults"], - sdk_version: "module_current", static_libs: [ "framework-wifi-util-lib", "android.hardware.wifi-V1.0-java-constants", + "modules-utils-build", ], libs: [ - "framework-annotations-lib", "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage ], srcs: [ ":framework-wifi-updatable-sources", ":framework-wifi-util-lib-aidls", ], - // java_api_finder must accompany `srcs` +} + +// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility +// classes before they are renamed. +java_library { + name: "framework-wifi-pre-jarjar", + defaults: ["framework-wifi-defaults"], + sdk_version: "module_current", + libs: ["framework-annotations-lib"], + // java_api_finder must accompany `srcs` (`srcs` defined in `framework-wifi-defaults`) plugins: ["java_api_finder"], installable: false, visibility: [ @@ -108,18 +117,7 @@ java_sdk_library { name: "framework-wifi", defaults: [ "framework-module-defaults", - "wifi-module-sdk-version-defaults", - ], - static_libs: [ - "framework-wifi-util-lib", - "android.hardware.wifi-V1.0-java-constants", - ], - libs: [ - "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage - ], - srcs: [ - ":framework-wifi-updatable-sources", - ":framework-wifi-util-lib-aidls", + "framework-wifi-defaults", ], jarjar_rules: ":wifi-jarjar-rules", diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt index b489be23b737..ff06a180b8c1 100644 --- a/wifi/jarjar-rules.txt +++ b/wifi/jarjar-rules.txt @@ -124,3 +124,4 @@ rule com.android.internal.util.Preconditions* com.android.wifi.x.@0 rule com.android.internal.util.Protocol* com.android.wifi.x.@0 rule com.android.net.module.util.** com.android.wifi.x.@0 +rule com.android.modules.utils.** com.android.wifi.x.@0 diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java index 73c52ab0ab1b..034defb083de 100644 --- a/wifi/java/android/net/wifi/RttManager.java +++ b/wifi/java/android/net/wifi/RttManager.java @@ -173,7 +173,7 @@ public class RttManager { /** @deprecated Use the new {@link android.net.wifi.RttManager#getRttCapabilities()} API.*/ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public Capabilities getCapabilities() { throw new UnsupportedOperationException( "getCapabilities is not supported in the adaptation layer"); diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 94771ac4ad78..4163c88c0418 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -1239,7 +1239,7 @@ public class WifiScanner { * @param bssidInfos access points to watch */ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void configureWifiChange( int rssiSampleSize, /* sample size for RSSI averaging */ int lostApSampleSize, /* samples to confirm AP's loss */ @@ -1273,7 +1273,7 @@ public class WifiScanner { * provided on {@link #stopTrackingWifiChange} */ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void startTrackingWifiChange(WifiChangeListener listener) { throw new UnsupportedOperationException(); } @@ -1284,7 +1284,7 @@ public class WifiScanner { * #stopTrackingWifiChange} */ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void stopTrackingWifiChange(WifiChangeListener listener) { throw new UnsupportedOperationException(); } @@ -1292,7 +1292,7 @@ public class WifiScanner { /** @hide */ @SystemApi @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void configureWifiChange(WifiChangeSettings settings) { throw new UnsupportedOperationException(); } @@ -1348,7 +1348,7 @@ public class WifiScanner { * also be provided on {@link #stopTrackingBssids} */ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void startTrackingBssids(BssidInfo[] bssidInfos, int apLostThreshold, BssidListener listener) { throw new UnsupportedOperationException(); @@ -1359,7 +1359,7 @@ public class WifiScanner { * @param listener same object provided in {@link #startTrackingBssids} */ @Deprecated - @SuppressLint("Doclava125") + @SuppressLint("RequiresPermission") public void stopTrackingBssids(BssidListener listener) { throw new UnsupportedOperationException(); } diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp index 6a39959e8cfd..b710a1492d8c 100644 --- a/wifi/tests/Android.bp +++ b/wifi/tests/Android.bp @@ -31,10 +31,11 @@ android_test { static_libs: [ "androidx.test.rules", "core-test-rules", + "frameworks-base-testutils", "guava", "mockito-target-minus-junit4", + "modules-utils-build", "net-tests-utils", - "frameworks-base-testutils", "truth-prebuilt", ], @@ -47,4 +48,8 @@ android_test { "device-tests", "mts", ], + + // static libs used by both framework-wifi & FrameworksWifiApiTests. Need to rename test usage + // to a different package name to prevent conflict with the copy in production code. + jarjar_rules: "test-jarjar-rules.txt", } diff --git a/wifi/tests/test-jarjar-rules.txt b/wifi/tests/test-jarjar-rules.txt new file mode 100644 index 000000000000..41b97abb87b5 --- /dev/null +++ b/wifi/tests/test-jarjar-rules.txt @@ -0,0 +1 @@ +rule com.android.modules.utils.** com.android.wifi.test.x.@0 |