diff options
48 files changed, 3231 insertions, 221 deletions
diff --git a/MULTIUSER_OWNERS b/MULTIUSER_OWNERS index fbc611a39d7d..9d92e0fc50f7 100644 --- a/MULTIUSER_OWNERS +++ b/MULTIUSER_OWNERS @@ -1,4 +1,5 @@ # OWNERS of Multiuser related files bookatz@google.com +olilan@google.com omakoto@google.com yamasani@google.com diff --git a/StubLibraries.bp b/StubLibraries.bp index d090296523ed..31fecd1c45c3 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -59,19 +59,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -115,19 +109,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -151,37 +139,25 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "removed.txt", tag: ".removed-api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -210,19 +186,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", @@ -282,10 +252,7 @@ java_defaults { java_version: "1.8", compile_dex: true, dist: { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], tag: ".jar", dest: "android-non-updatable.jar", }, @@ -341,10 +308,7 @@ java_library { java_defaults { name: "android_stubs_dists_default", dist: { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], tag: ".jar", dest: "android.jar", }, @@ -376,10 +340,7 @@ java_library { dists: [ { // Legacy dist path - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], tag: ".jar", dest: "android_system.jar", }, @@ -412,6 +373,7 @@ java_library { static_libs: [ "android-non-updatable.stubs.module_lib", "art.module.public.api.stubs.module_lib", + "i18n.module.public.api.stubs", ], dist: { dir: "apistubs/android/module-lib", diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java index c251529ae0b1..e5b07429a5c6 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobService.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java @@ -125,7 +125,7 @@ public abstract class JobService extends Service { * will not be invoked. * * @param params Parameters specifying info about this job, including the optional - * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle). + * extras configured with {@link JobInfo.Builder#setExtras(android.os.PersistableBundle)}. * This object serves to identify this specific running job instance when calling * {@link #jobFinished(JobParameters, boolean)}. * @return {@code true} if your service will continue running, using a separate thread diff --git a/api/Android.bp b/api/Android.bp index ed2247b6eaa4..c6ea175954c9 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -120,10 +120,7 @@ genrule { dest: "current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android.txt", }, @@ -205,10 +202,7 @@ genrule { dest: "removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/public/api", dest: "removed.txt", }, @@ -244,10 +238,7 @@ genrule { dest: "system-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android.txt", }, @@ -302,10 +293,7 @@ genrule { dest: "system-removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system/api", dest: "removed.txt", }, @@ -342,10 +330,7 @@ genrule { dest: "module-lib-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android.txt", }, @@ -402,10 +387,7 @@ genrule { dest: "module-lib-removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "removed.txt", }, @@ -446,10 +428,7 @@ genrule { dest: "system-server-current.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android.txt", }, @@ -473,10 +452,7 @@ genrule { dest: "system-server-removed.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "removed.txt", }, @@ -514,9 +490,6 @@ genrule { tools: ["api_versions_trimmer"], cmd: "$(location api_versions_trimmer) $(out) $(in)", dist: { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], }, } diff --git a/core/api/current.txt b/core/api/current.txt index 36565da633e4..14a9a23ba0d8 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -42581,6 +42581,7 @@ package android.telephony { method @Deprecated public int getMnc(); method @Nullable public String getMncString(); method public String getNumber(); + method public int getPortIndex(); method public int getSimSlotIndex(); method public int getSubscriptionId(); method public int getSubscriptionType(); @@ -42613,6 +42614,8 @@ package android.telephony { method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int); method public int getDeviceToDeviceStatusSharingPreference(int); method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions(); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public String getPhoneNumber(int, int); + method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public String getPhoneNumber(int); method public static int getSlotIndex(int); method @Nullable public int[] getSubscriptionIds(int); method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int); @@ -42624,6 +42627,7 @@ package android.telephony { method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid); + method public void setCarrierPhoneNumber(int, @NonNull String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(int, @NonNull java.util.List<android.net.Uri>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int); @@ -42650,6 +42654,9 @@ package android.telephony { field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX"; field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff + field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2 + field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3 + field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1 field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 } @@ -43127,14 +43134,28 @@ package android.telephony { method public int describeContents(); method public int getCardId(); method @Nullable public String getEid(); - method @Nullable public String getIccId(); - method public int getSlotIndex(); + method @Deprecated @Nullable public String getIccId(); + method public int getPhysicalSlotIndex(); + method @NonNull public java.util.Collection<android.telephony.UiccPortInfo> getPorts(); + method @Deprecated public int getSlotIndex(); method public boolean isEuicc(); + method public boolean isMultipleEnabledProfilesSupported(); method public boolean isRemovable(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccCardInfo> CREATOR; } + public final class UiccPortInfo implements android.os.Parcelable { + method public int describeContents(); + method @Nullable public String getIccId(); + method @IntRange(from=0) public int getLogicalSlotIndex(); + method @IntRange(from=0) public int getPortIndex(); + method public boolean isActive(); + method public void writeToParcel(@Nullable android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccPortInfo> CREATOR; + field public static final String ICCID_REDACTED = "FFFFFFFFFFFFFFFFFFFF"; + } + public abstract class VisualVoicemailService extends android.app.Service { ctor public VisualVoicemailService(); method public android.os.IBinder onBind(android.content.Intent); @@ -43435,6 +43456,7 @@ package android.telephony.euicc { method @Nullable public String getEid(); method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo(); method public boolean isEnabled(); + method public boolean isSimPortAvailable(int); method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException; method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8ce48ab8ce24..444f6568c11b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -5252,6 +5252,7 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups(); method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List<android.media.AudioDeviceAttributes> getDevicesForAttributes(@NonNull android.media.AudioAttributes); + method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int); method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); @@ -12144,6 +12145,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerState(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimPowerStateForSlot(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSimSlotMapping(@NonNull java.util.Collection<android.telephony.UiccSlotMapping>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>); method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean); @@ -12155,7 +12157,7 @@ package android.telephony { method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPinReportResult(String); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean supplyPuk(String, String); method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int[] supplyPukReportResult(String, String); - method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]); + method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[]); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void toggleRadioOnOff(); method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateOtaEmergencyNumberDbFilePath(@NonNull android.os.ParcelFileDescriptor); method public void updateServiceLocation(); @@ -12329,10 +12331,11 @@ package android.telephony { method public int describeContents(); method public String getCardId(); method public int getCardStateInfo(); - method public boolean getIsActive(); + method @Deprecated public boolean getIsActive(); method public boolean getIsEuicc(); method public boolean getIsExtendedApduSupported(); - method public int getLogicalSlotIdx(); + method @Deprecated public int getLogicalSlotIdx(); + method @NonNull public java.util.Collection<android.telephony.UiccPortInfo> getPorts(); method public boolean isRemovable(); method public void writeToParcel(android.os.Parcel, int); field public static final int CARD_STATE_INFO_ABSENT = 1; // 0x1 @@ -12342,6 +12345,15 @@ package android.telephony { field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccSlotInfo> CREATOR; } + public final class UiccSlotMapping implements android.os.Parcelable { + method public int describeContents(); + method @IntRange(from=0) public int getLogicalSlotIndex(); + method @IntRange(from=0) public int getPhysicalSlotIndex(); + method @IntRange(from=0) public int getPortIndex(); + method public void writeToParcel(@Nullable android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccSlotMapping> CREATOR; + } + public abstract class VisualVoicemailService extends android.app.Service { method public static final void sendVisualVoicemailSms(android.content.Context, android.telecom.PhoneAccountHandle, String, short, String, android.app.PendingIntent); method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings); @@ -12524,6 +12536,7 @@ package android.telephony.data { method public final int getSlotIndex(); method public final void notifyApnUnthrottled(@NonNull String); method public final void notifyDataCallListChanged(java.util.List<android.telephony.data.DataCallResponse>); + method public final void notifyDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile); method public void requestDataCallList(@NonNull android.telephony.data.DataServiceCallback); method public void setDataProfile(@NonNull java.util.List<android.telephony.data.DataProfile>, boolean, @NonNull android.telephony.data.DataServiceCallback); method public void setInitialAttachApn(@NonNull android.telephony.data.DataProfile, boolean, @NonNull android.telephony.data.DataServiceCallback); @@ -12534,6 +12547,7 @@ package android.telephony.data { public class DataServiceCallback { method public void onApnUnthrottled(@NonNull String); method public void onDataCallListChanged(@NonNull java.util.List<android.telephony.data.DataCallResponse>); + method public void onDataProfileUnthrottled(@NonNull android.telephony.data.DataProfile); method public void onDeactivateDataCallComplete(int); method public void onRequestDataCallListComplete(int, @NonNull java.util.List<android.telephony.data.DataCallResponse>); method public void onSetDataProfileComplete(int); @@ -12633,7 +12647,8 @@ package android.telephony.euicc { method public void authenticateServer(String, String, byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); method public void cancelSession(String, byte[], @android.telephony.euicc.EuiccCardManager.CancelReason int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); method public void deleteProfile(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); - method public void disableProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); + method @Deprecated public void disableProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); + method public void disableProfile(@Nullable String, @Nullable String, int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); method public void listNotifications(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>); method public void loadBoundProfilePackage(String, byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); method public void prepareDownload(String, @Nullable byte[], byte[], byte[], byte[], java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<byte[]>); @@ -12651,7 +12666,8 @@ package android.telephony.euicc { method public void retrieveNotificationList(String, @android.telephony.euicc.EuiccNotification.Event int, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.telephony.euicc.EuiccNotification[]>); method public void setDefaultSmdpAddress(String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); method public void setNickname(String, String, String, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<java.lang.Void>); - method public void switchToProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>); + method @Deprecated public void switchToProfile(String, String, boolean, java.util.concurrent.Executor, android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>); + method public void switchToProfile(@Nullable String, @Nullable String, int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccCardManager.ResultCallback<android.service.euicc.EuiccProfileInfo>); field public static final int CANCEL_REASON_END_USER_REJECTED = 0; // 0x0 field public static final int CANCEL_REASON_POSTPONED = 1; // 0x1 field public static final int CANCEL_REASON_PPR_NOT_ALLOWED = 3; // 0x3 diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 9b37457c9907..c6c64b034b60 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -3086,6 +3086,9 @@ public final class BluetoothAdapter { BluetoothCsipSetCoordinator csipSetCoordinator = new BluetoothCsipSetCoordinator(context, listener, this); return true; + } else if (profile == BluetoothProfile.LE_CALL_CONTROL) { + BluetoothLeCallControl tbs = new BluetoothLeCallControl(context, listener); + return true; } else { return false; } @@ -3188,6 +3191,10 @@ public final class BluetoothAdapter { (BluetoothCsipSetCoordinator) proxy; csipSetCoordinator.close(); break; + case BluetoothProfile.LE_CALL_CONTROL: + BluetoothLeCallControl tbs = (BluetoothLeCallControl) proxy; + tbs.close(); + break; } } diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java index fe8d1ba80e33..b531829d2940 100644 --- a/core/java/android/bluetooth/BluetoothGatt.java +++ b/core/java/android/bluetooth/BluetoothGatt.java @@ -87,7 +87,7 @@ public final class BluetoothGatt implements BluetoothProfile { private static final int CONN_STATE_CLOSED = 4; private static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 5; - private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 1000; // milliseconds + private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 10; // milliseconds private List<BluetoothGattService> mServices; diff --git a/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java b/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java new file mode 100644 index 000000000000..cb47280acc7e --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeBroadcastSourceInfo.java @@ -0,0 +1,788 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This class represents an LE Audio Broadcast Source and the associated information that is needed + * by Broadcast Audio Scan Service (BASS) residing on a Scan Delegator. + * + * <p>For example, the Scan Delegator on an LE Audio Broadcast Sink can use the information + * contained within an instance of this class to synchronize with an LE Audio Broadcast Source in + * order to listen to a Broadcast Audio Stream. + * + * <p>BroadcastAssistant has a BASS client which facilitates scanning and discovery of Broadcast + * Sources on behalf of say a Broadcast Sink. Upon successful discovery of one or more Broadcast + * sources, this information needs to be communicated to the BASS Server residing within the Scan + * Delegator on a Broadcast Sink. This is achieved using the Periodic Advertising Synchronization + * Transfer (PAST) procedure. This procedure uses information contained within an instance of this + * class. + * + * @hide + */ +public final class BluetoothLeBroadcastSourceInfo implements Parcelable { + private static final String TAG = "BluetoothLeBroadcastSourceInfo"; + private static final boolean DBG = true; + + /** + * Constants representing Broadcast Source address types + * + * @hide + */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_", + value = { + LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC, + LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM, + LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSourceAddressType {} + + /** + * Represents a public address used by an LE Audio Broadcast Source + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC = 0; + + /** + * Represents a random address used by an LE Audio Broadcast Source + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM = 1; + + /** + * Represents an invalid address used by an LE Audio Broadcast Seurce + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID = 0xFFFF; + + /** + * Periodic Advertising Synchronization state + * + * <p>Periodic Advertising (PA) enables the LE Audio Broadcast Assistant to discover broadcast + * audio streams as well as the audio stream configuration on behalf of an LE Audio Broadcast + * Sink. This information can then be transferred to the LE Audio Broadcast Sink using the + * Periodic Advertising Synchronizaton Transfer (PAST) procedure. + * + * @hide + */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_", + value = { + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL, + LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSinkPaSyncState {} + + /** + * Indicates that the Broadcast Sink is not synchronized with the Periodic Advertisements (PA) + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE = 0; + + /** + * Indicates that the Broadcast Sink requested the Broadcast Assistant to synchronize with the + * Periodic Advertisements (PA). + * + * <p>This is also known as scan delegation or scan offloading. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ = 1; + + /** + * Indicates that the Broadcast Sink is synchronized with the Periodic Advertisements (PA). + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC = 2; + + /** + * Indicates that the Broadcast Sink was unable to synchronize with the Periodic Advertisements + * (PA). + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL = 3; + + /** + * Indicates that the Broadcast Sink should be synchronized with the Periodic Advertisements + * (PA) using the Periodic Advertisements Synchronization Transfert (PAST) procedure. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST = 4; + + /** + * Indicates that the Broadcast Sink synchornization state is invalid. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_", + value = { + LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED, + LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSinkAudioSyncState {} + + /** + * Indicates that the Broadcast Sink is not synchronized with a Broadcast Audio Stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0; + + /** + * Indicates that the Broadcast Sink is synchronized with a Broadcast Audio Stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED = 1; + + /** + * Indicates that the Broadcast Sink audio synchronization state is invalid. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID = 0xFFFF; + + /** @hide */ + @IntDef( + prefix = "LE_AUDIO_BROADCAST_SINK_ENC_STATE_", + value = { + LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED, + LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED, + LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING, + LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface LeAudioBroadcastSinkEncryptionState {} + + /** + * Indicates that the Broadcast Sink is synchronized with an unencrypted audio stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED = 0; + + /** + * Indicates that the Broadcast Sink needs a Broadcast Code to synchronize with the audio + * stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED = 1; + + /** + * Indicates that the Broadcast Sink is synchronized with an encrypted audio stream. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING = 2; + + /** + * Indicates that the Broadcast Sink is unable to decrypt an audio stream due to an incorrect + * Broadcast Code + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE = 3; + + /** + * Indicates that the Broadcast Sink encryption state is invalid. + * + * @hide + */ + public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID = 0xFF; + + /** + * Represents an invalid LE Audio Broadcast Source ID + * + * @hide + */ + public static final byte LE_AUDIO_BROADCAST_SINK_INVALID_SOURCE_ID = (byte) 0x00; + + /** + * Represents an invalid Broadcast ID of a Broadcast Source + * + * @hide + */ + public static final int INVALID_BROADCAST_ID = 0xFFFFFF; + + private byte mSourceId; + private @LeAudioBroadcastSourceAddressType int mSourceAddressType; + private BluetoothDevice mSourceDevice; + private byte mSourceAdvSid; + private int mBroadcastId; + private @LeAudioBroadcastSinkPaSyncState int mPaSyncState; + private @LeAudioBroadcastSinkEncryptionState int mEncryptionStatus; + private @LeAudioBroadcastSinkAudioSyncState int mAudioSyncState; + private byte[] mBadBroadcastCode; + private byte mNumSubGroups; + private Map<Integer, Integer> mSubgroupBisSyncState = new HashMap<Integer, Integer>(); + private Map<Integer, byte[]> mSubgroupMetadata = new HashMap<Integer, byte[]>(); + + private String mBroadcastCode; + private static final int BIS_NO_PREF = 0xFFFFFFFF; + private static final int BROADCAST_CODE_SIZE = 16; + + /** + * Constructor to create an Empty object of {@link BluetoothLeBroadcastSourceInfo } with the + * given Source Id. + * + * <p>This is mainly used to represent the Empty Broadcast Source entries + * + * @param sourceId Source Id for this Broadcast Source info object + * @hide + */ + public BluetoothLeBroadcastSourceInfo(byte sourceId) { + mSourceId = sourceId; + mSourceAddressType = LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID; + mSourceDevice = null; + mSourceAdvSid = (byte) 0x00; + mBroadcastId = INVALID_BROADCAST_ID; + mPaSyncState = LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID; + mAudioSyncState = LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID; + mEncryptionStatus = LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID; + mBadBroadcastCode = null; + mNumSubGroups = 0; + mBroadcastCode = null; + } + + /*package*/ BluetoothLeBroadcastSourceInfo( + byte sourceId, + @LeAudioBroadcastSourceAddressType int addressType, + @NonNull BluetoothDevice device, + byte advSid, + int broadcastId, + @LeAudioBroadcastSinkPaSyncState int paSyncstate, + @LeAudioBroadcastSinkEncryptionState int encryptionStatus, + @LeAudioBroadcastSinkAudioSyncState int audioSyncstate, + @Nullable byte[] badCode, + byte numSubGroups, + @NonNull Map<Integer, Integer> bisSyncState, + @Nullable Map<Integer, byte[]> subgroupMetadata, + @NonNull String broadcastCode) { + mSourceId = sourceId; + mSourceAddressType = addressType; + mSourceDevice = device; + mSourceAdvSid = advSid; + mBroadcastId = broadcastId; + mPaSyncState = paSyncstate; + mEncryptionStatus = encryptionStatus; + mAudioSyncState = audioSyncstate; + + if (badCode != null && badCode.length != 0) { + mBadBroadcastCode = new byte[badCode.length]; + System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length); + } + mNumSubGroups = numSubGroups; + mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); + mSubgroupMetadata = new HashMap<Integer, byte[]>(subgroupMetadata); + mBroadcastCode = broadcastCode; + } + + @Override + public boolean equals(Object o) { + if (o instanceof BluetoothLeBroadcastSourceInfo) { + BluetoothLeBroadcastSourceInfo other = (BluetoothLeBroadcastSourceInfo) o; + return (other.mSourceId == mSourceId + && other.mSourceAddressType == mSourceAddressType + && other.mSourceDevice == mSourceDevice + && other.mSourceAdvSid == mSourceAdvSid + && other.mBroadcastId == mBroadcastId + && other.mPaSyncState == mPaSyncState + && other.mEncryptionStatus == mEncryptionStatus + && other.mAudioSyncState == mAudioSyncState + && Arrays.equals(other.mBadBroadcastCode, mBadBroadcastCode) + && other.mNumSubGroups == mNumSubGroups + && mSubgroupBisSyncState.equals(other.mSubgroupBisSyncState) + && mSubgroupMetadata.equals(other.mSubgroupMetadata) + && other.mBroadcastCode == mBroadcastCode); + } + return false; + } + + /** + * Checks if an instance of {@link BluetoothLeBroadcastSourceInfo} is empty. + * + * @hide + */ + public boolean isEmpty() { + boolean ret = false; + if (mSourceAddressType == LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID + && mSourceDevice == null + && mSourceAdvSid == (byte) 0 + && mPaSyncState == LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID + && mEncryptionStatus == LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID + && mAudioSyncState == LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID + && mBadBroadcastCode == null + && mNumSubGroups == 0 + && mSubgroupBisSyncState.size() == 0 + && mSubgroupMetadata.size() == 0 + && mBroadcastCode == null) { + ret = true; + } + return ret; + } + + /** + * Compares an instance of {@link BluetoothLeBroadcastSourceInfo} with the provided instance. + * + * @hide + */ + public boolean matches(BluetoothLeBroadcastSourceInfo srcInfo) { + boolean ret = false; + if (srcInfo == null) { + ret = false; + } else { + if (mSourceDevice == null) { + if (mSourceAdvSid == srcInfo.getAdvertisingSid() + && mSourceAddressType == srcInfo.getAdvAddressType()) { + ret = true; + } + } else { + if (mSourceDevice.equals(srcInfo.getSourceDevice()) + && mSourceAdvSid == srcInfo.getAdvertisingSid() + && mSourceAddressType == srcInfo.getAdvAddressType() + && mBroadcastId == srcInfo.getBroadcastId()) { + ret = true; + } + } + } + return ret; + } + + @Override + public int hashCode() { + return Objects.hash( + mSourceId, + mSourceAddressType, + mSourceDevice, + mSourceAdvSid, + mBroadcastId, + mPaSyncState, + mEncryptionStatus, + mAudioSyncState, + mBadBroadcastCode, + mNumSubGroups, + mSubgroupBisSyncState, + mSubgroupMetadata, + mBroadcastCode); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + return "{BluetoothLeBroadcastSourceInfo : mSourceId" + + mSourceId + + " addressType: " + + mSourceAddressType + + " sourceDevice: " + + mSourceDevice + + " mSourceAdvSid:" + + mSourceAdvSid + + " mBroadcastId:" + + mBroadcastId + + " mPaSyncState:" + + mPaSyncState + + " mEncryptionStatus:" + + mEncryptionStatus + + " mAudioSyncState:" + + mAudioSyncState + + " mBadBroadcastCode:" + + mBadBroadcastCode + + " mNumSubGroups:" + + mNumSubGroups + + " mSubgroupBisSyncState:" + + mSubgroupBisSyncState + + " mSubgroupMetadata:" + + mSubgroupMetadata + + " mBroadcastCode:" + + mBroadcastCode + + "}"; + } + + /** + * Get the Source Id + * + * @return byte representing the Source Id, {@link + * #LE_AUDIO_BROADCAST_ASSISTANT_INVALID_SOURCE_ID} if invalid + * @hide + */ + public byte getSourceId() { + return mSourceId; + } + + /** + * Set the Source Id + * + * @param sourceId source Id + * @hide + */ + public void setSourceId(byte sourceId) { + mSourceId = sourceId; + } + + /** + * Set the Broadcast Source device + * + * @param sourceDevice the Broadcast Source BluetoothDevice + * @hide + */ + public void setSourceDevice(@NonNull BluetoothDevice sourceDevice) { + mSourceDevice = sourceDevice; + } + + /** + * Get the Broadcast Source BluetoothDevice + * + * @return Broadcast Source BluetoothDevice + * @hide + */ + public @NonNull BluetoothDevice getSourceDevice() { + return mSourceDevice; + } + + /** + * Set the address type of the Broadcast Source advertisements + * + * @hide + */ + public void setAdvAddressType(@LeAudioBroadcastSourceAddressType int addressType) { + mSourceAddressType = addressType; + } + + /** + * Get the address type used by advertisements from the Broadcast Source. + * BluetoothLeBroadcastSourceInfo Object + * + * @hide + */ + @LeAudioBroadcastSourceAddressType + public int getAdvAddressType() { + return mSourceAddressType; + } + + /** + * Set the advertising SID of the Broadcast Source advertisement. + * + * @param advSid advertising SID of the Broadcast Source + * @hide + */ + public void setAdvertisingSid(byte advSid) { + mSourceAdvSid = advSid; + } + + /** + * Get the advertising SID of the Broadcast Source advertisement. + * + * @return advertising SID of the Broadcast Source + * @hide + */ + public byte getAdvertisingSid() { + return mSourceAdvSid; + } + + /** + * Get the Broadcast ID of the Broadcast Source. + * + * @return broadcast ID + * @hide + */ + public int getBroadcastId() { + return mBroadcastId; + } + + /** + * Set the Periodic Advertising (PA) Sync State. + * + * @hide + */ + /*package*/ void setPaSyncState(@LeAudioBroadcastSinkPaSyncState int paSyncState) { + mPaSyncState = paSyncState; + } + + /** + * Get the Periodic Advertising (PA) Sync State + * + * @hide + */ + public @LeAudioBroadcastSinkPaSyncState int getMetadataSyncState() { + return mPaSyncState; + } + + /** + * Set the audio sync state + * + * @hide + */ + /*package*/ void setAudioSyncState(@LeAudioBroadcastSinkAudioSyncState int audioSyncState) { + mAudioSyncState = audioSyncState; + } + + /** + * Get the audio sync state + * + * @hide + */ + public @LeAudioBroadcastSinkAudioSyncState int getAudioSyncState() { + return mAudioSyncState; + } + + /** + * Set the encryption status + * + * @hide + */ + /*package*/ void setEncryptionStatus( + @LeAudioBroadcastSinkEncryptionState int encryptionStatus) { + mEncryptionStatus = encryptionStatus; + } + + /** + * Get the encryption status + * + * @hide + */ + public @LeAudioBroadcastSinkEncryptionState int getEncryptionStatus() { + return mEncryptionStatus; + } + + /** + * Get the incorrect broadcast code that the Scan delegator used to decrypt the Broadcast Audio + * Stream and failed. + * + * <p>This code is valid only if {@link #getEncryptionStatus} returns {@link + * #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} + * + * @return byte array containing bad broadcast value, null if the current encryption status is + * not {@link #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE} + * @hide + */ + public @Nullable byte[] getBadBroadcastCode() { + return mBadBroadcastCode; + } + + /** + * Get the number of subgroups. + * + * @return number of subgroups + * @hide + */ + public byte getNumberOfSubGroups() { + return mNumSubGroups; + } + + public @NonNull Map<Integer, Integer> getSubgroupBisSyncState() { + return mSubgroupBisSyncState; + } + + public void setSubgroupBisSyncState(@NonNull Map<Integer, Integer> bisSyncState) { + mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState); + } + + /*package*/ void setBroadcastCode(@NonNull String broadcastCode) { + mBroadcastCode = broadcastCode; + } + + /** + * Get the broadcast code + * + * @return + * @hide + */ + public @NonNull String getBroadcastCode() { + return mBroadcastCode; + } + + /** + * Set the broadcast ID + * + * @param broadcastId broadcast ID of the Broadcast Source + * @hide + */ + public void setBroadcastId(int broadcastId) { + mBroadcastId = broadcastId; + } + + private void writeSubgroupBisSyncStateToParcel( + @NonNull Parcel dest, @NonNull Map<Integer, Integer> subgroupBisSyncState) { + dest.writeInt(subgroupBisSyncState.size()); + for (Map.Entry<Integer, Integer> entry : subgroupBisSyncState.entrySet()) { + dest.writeInt(entry.getKey()); + dest.writeInt(entry.getValue()); + } + } + + private static void readSubgroupBisSyncStateFromParcel( + @NonNull Parcel in, @NonNull Map<Integer, Integer> subgroupBisSyncState) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer value = in.readInt(); + subgroupBisSyncState.put(key, value); + } + } + + private void writeSubgroupMetadataToParcel( + @NonNull Parcel dest, @Nullable Map<Integer, byte[]> subgroupMetadata) { + if (subgroupMetadata == null) { + dest.writeInt(0); + return; + } + + dest.writeInt(subgroupMetadata.size()); + for (Map.Entry<Integer, byte[]> entry : subgroupMetadata.entrySet()) { + dest.writeInt(entry.getKey()); + byte[] metadata = entry.getValue(); + if (metadata != null) { + dest.writeInt(metadata.length); + dest.writeByteArray(metadata); + } + } + } + + private static void readSubgroupMetadataFromParcel( + @NonNull Parcel in, @NonNull Map<Integer, byte[]> subgroupMetadata) { + int size = in.readInt(); + + for (int i = 0; i < size; i++) { + Integer key = in.readInt(); + Integer metaDataLen = in.readInt(); + byte[] metadata = null; + if (metaDataLen != 0) { + metadata = new byte[metaDataLen]; + in.readByteArray(metadata); + } + subgroupMetadata.put(key, metadata); + } + } + + public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastSourceInfo> CREATOR = + new Parcelable.Creator<BluetoothLeBroadcastSourceInfo>() { + public @NonNull BluetoothLeBroadcastSourceInfo createFromParcel( + @NonNull Parcel in) { + final byte sourceId = in.readByte(); + final int sourceAddressType = in.readInt(); + final BluetoothDevice sourceDevice = + in.readTypedObject(BluetoothDevice.CREATOR); + final byte sourceAdvSid = in.readByte(); + final int broadcastId = in.readInt(); + final int paSyncState = in.readInt(); + final int audioSyncState = in.readInt(); + final int encryptionStatus = in.readInt(); + final int badBroadcastLen = in.readInt(); + byte[] badBroadcastCode = null; + + if (badBroadcastLen > 0) { + badBroadcastCode = new byte[badBroadcastLen]; + in.readByteArray(badBroadcastCode); + } + final byte numSubGroups = in.readByte(); + final String broadcastCode = in.readString(); + Map<Integer, Integer> subgroupBisSyncState = new HashMap<Integer, Integer>(); + readSubgroupBisSyncStateFromParcel(in, subgroupBisSyncState); + Map<Integer, byte[]> subgroupMetadata = new HashMap<Integer, byte[]>(); + readSubgroupMetadataFromParcel(in, subgroupMetadata); + + BluetoothLeBroadcastSourceInfo srcInfo = + new BluetoothLeBroadcastSourceInfo( + sourceId, + sourceAddressType, + sourceDevice, + sourceAdvSid, + broadcastId, + paSyncState, + encryptionStatus, + audioSyncState, + badBroadcastCode, + numSubGroups, + subgroupBisSyncState, + subgroupMetadata, + broadcastCode); + return srcInfo; + } + + public @NonNull BluetoothLeBroadcastSourceInfo[] newArray(int size) { + return new BluetoothLeBroadcastSourceInfo[size]; + } + }; + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeByte(mSourceId); + out.writeInt(mSourceAddressType); + out.writeTypedObject(mSourceDevice, 0); + out.writeByte(mSourceAdvSid); + out.writeInt(mBroadcastId); + out.writeInt(mPaSyncState); + out.writeInt(mAudioSyncState); + out.writeInt(mEncryptionStatus); + + if (mBadBroadcastCode != null) { + out.writeInt(mBadBroadcastCode.length); + out.writeByteArray(mBadBroadcastCode); + } else { + // zero indicates that there is no "bad broadcast code" + out.writeInt(0); + } + out.writeByte(mNumSubGroups); + out.writeString(mBroadcastCode); + writeSubgroupBisSyncStateToParcel(out, mSubgroupBisSyncState); + writeSubgroupMetadataToParcel(out, mSubgroupMetadata); + } + + private static void log(@NonNull String msg) { + if (DBG) { + Log.d(TAG, msg); + } + } +} +; diff --git a/core/java/android/bluetooth/BluetoothLeCall.java b/core/java/android/bluetooth/BluetoothLeCall.java new file mode 100644 index 000000000000..fb7789db25c7 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeCall.java @@ -0,0 +1,285 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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.bluetooth; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelUuid; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.UUID; + +/** + * Representation of Call + * + * @hide + */ +public final class BluetoothLeCall implements Parcelable { + + /** @hide */ + @IntDef(prefix = "STATE_", value = { + STATE_INCOMING, + STATE_DIALING, + STATE_ALERTING, + STATE_ACTIVE, + STATE_LOCALLY_HELD, + STATE_REMOTELY_HELD, + STATE_LOCALLY_AND_REMOTELY_HELD + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State { + } + + /** + * A remote party is calling (incoming call). + * + * @hide + */ + public static final int STATE_INCOMING = 0x00; + + /** + * The process to call the remote party has started but the remote party is not + * being alerted (outgoing call). + * + * @hide + */ + public static final int STATE_DIALING = 0x01; + + /** + * A remote party is being alerted (outgoing call). + * + * @hide + */ + public static final int STATE_ALERTING = 0x02; + + /** + * The call is in an active conversation. + * + * @hide + */ + public static final int STATE_ACTIVE = 0x03; + + /** + * The call is connected but held locally. “Locally Held” implies that either + * the server or the client can affect the state. + * + * @hide + */ + public static final int STATE_LOCALLY_HELD = 0x04; + + /** + * The call is connected but held remotely. “Remotely Held” means that the state + * is controlled by the remote party of a call. + * + * @hide + */ + public static final int STATE_REMOTELY_HELD = 0x05; + + /** + * The call is connected but held both locally and remotely. + * + * @hide + */ + public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06; + + /** + * Whether the call direction is outgoing. + * + * @hide + */ + public static final int FLAG_OUTGOING_CALL = 0x00000001; + + /** + * Whether the call URI and Friendly Name are withheld by server. + * + * @hide + */ + public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002; + + /** + * Whether the call URI and Friendly Name are withheld by network. + * + * @hide + */ + public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004; + + /** Unique UUID that identifies this call */ + private UUID mUuid; + + /** Remote Caller URI */ + private String mUri; + + /** Caller friendly name */ + private String mFriendlyName; + + /** Call state */ + private @State int mState; + + /** Call flags */ + private int mCallFlags; + + /** @hide */ + public BluetoothLeCall(@NonNull BluetoothLeCall that) { + mUuid = new UUID(that.getUuid().getMostSignificantBits(), + that.getUuid().getLeastSignificantBits()); + mUri = that.mUri; + mFriendlyName = that.mFriendlyName; + mState = that.mState; + mCallFlags = that.mCallFlags; + } + + /** @hide */ + public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName, + @State int state, int callFlags) { + mUuid = uuid; + mUri = uri; + mFriendlyName = friendlyName; + mState = state; + mCallFlags = callFlags; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + BluetoothLeCall that = (BluetoothLeCall) o; + return mUuid.equals(that.mUuid) && mUri.equals(that.mUri) + && mFriendlyName.equals(that.mFriendlyName) && mState == that.mState + && mCallFlags == that.mCallFlags; + } + + @Override + public int hashCode() { + return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags); + } + + /** + * Returns a string representation of this BluetoothLeCall. + * + * <p> + * Currently this is the UUID. + * + * @return string representation of this BluetoothLeCall + */ + @Override + public String toString() { + return mUuid.toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(new ParcelUuid(mUuid), 0); + out.writeString(mUri); + out.writeString(mFriendlyName); + out.writeInt(mState); + out.writeInt(mCallFlags); + } + + public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR = + new Parcelable.Creator<BluetoothLeCall>() { + public BluetoothLeCall createFromParcel(Parcel in) { + return new BluetoothLeCall(in); + } + + public BluetoothLeCall[] newArray(int size) { + return new BluetoothLeCall[size]; + } + }; + + private BluetoothLeCall(Parcel in) { + mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid(); + mUri = in.readString(); + mFriendlyName = in.readString(); + mState = in.readInt(); + mCallFlags = in.readInt(); + } + + /** + * Returns an UUID of this BluetoothLeCall. + * + * <p> + * An UUID is unique identifier of a BluetoothLeCall. + * + * @return UUID of this BluetoothLeCall + * @hide + */ + public @NonNull UUID getUuid() { + return mUuid; + } + + /** + * Returns a URI of the remote party of this BluetoothLeCall. + * + * @return string representation of this BluetoothLeCall + * @hide + */ + public @NonNull String getUri() { + return mUri; + } + + /** + * Returns a friendly name of the call. + * + * @return friendly name representation of this BluetoothLeCall + * @hide + */ + public @NonNull String getFriendlyName() { + return mFriendlyName; + } + + /** + * Returns the call state. + * + * @return the state of this BluetoothLeCall + * @hide + */ + public @State int getState() { + return mState; + } + + /** + * Returns the call flags. + * + * @return call flags + * @hide + */ + public int getCallFlags() { + return mCallFlags; + } + + /** + * Whether the call direction is incoming. + * + * @return true if incoming call, false otherwise + * @hide + */ + public boolean isIncomingCall() { + return (mCallFlags & FLAG_OUTGOING_CALL) == 0; + } +} diff --git a/core/java/android/bluetooth/BluetoothLeCallControl.java b/core/java/android/bluetooth/BluetoothLeCallControl.java new file mode 100644 index 000000000000..5283e0804252 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothLeCallControl.java @@ -0,0 +1,911 @@ +/* + * Copyright 2019 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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.bluetooth; + +import android.Manifest; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.content.ComponentName; +import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; +import android.annotation.SuppressLint; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Executor; + +/** + * This class provides the APIs to control the Call Control profile. + * + * <p> + * This class provides Bluetooth Telephone Bearer Service functionality, + * allowing applications to expose a GATT Service based interface to control the + * state of the calls by remote devices such as LE audio devices. + * + * <p> + * BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the + * BluetoothLeCallControl proxy object. + * + * @hide + */ +public final class BluetoothLeCallControl implements BluetoothProfile { + private static final String TAG = "BluetoothLeCallControl"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** @hide */ + @IntDef(prefix = "RESULT_", value = { + RESULT_SUCCESS, + RESULT_ERROR_UNKNOWN_CALL_ID, + RESULT_ERROR_INVALID_URI, + RESULT_ERROR_APPLICATION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Result { + } + + /** + * Opcode write was successful. + * + * @hide + */ + public static final int RESULT_SUCCESS = 0; + + /** + * Unknown call Id has been used in the operation. + * + * @hide + */ + public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1; + + /** + * The URI provided in {@link Callback#onPlaceCallRequest} is invalid. + * + * @hide + */ + public static final int RESULT_ERROR_INVALID_URI = 2; + + /** + * Application internal error. + * + * @hide + */ + public static final int RESULT_ERROR_APPLICATION = 3; + + /** @hide */ + @IntDef(prefix = "TERMINATION_REASON_", value = { + TERMINATION_REASON_INVALID_URI, + TERMINATION_REASON_FAIL, + TERMINATION_REASON_REMOTE_HANGUP, + TERMINATION_REASON_SERVER_HANGUP, + TERMINATION_REASON_LINE_BUSY, + TERMINATION_REASON_NETWORK_CONGESTION, + TERMINATION_REASON_CLIENT_HANGUP, + TERMINATION_REASON_NO_SERVICE, + TERMINATION_REASON_NO_ANSWER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TerminationReason { + } + + /** + * Remote Caller ID value used to place a call was formed improperly. + * + * @hide + */ + public static final int TERMINATION_REASON_INVALID_URI = 0x00; + + /** + * Call fail. + * + * @hide + */ + public static final int TERMINATION_REASON_FAIL = 0x01; + + /** + * Remote party ended call. + * + * @hide + */ + public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02; + + /** + * Call ended from the server. + * + * @hide + */ + public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03; + + /** + * Line busy. + * + * @hide + */ + public static final int TERMINATION_REASON_LINE_BUSY = 0x04; + + /** + * Network congestion. + * + * @hide + */ + public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05; + + /** + * Client terminated. + * + * @hide + */ + public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06; + + /** + * No service. + * + * @hide + */ + public static final int TERMINATION_REASON_NO_SERVICE = 0x07; + + /** + * No answer. + * + * @hide + */ + public static final int TERMINATION_REASON_NO_ANSWER = 0x08; + + /* + * Flag indicating support for hold/unhold call feature. + * + * @hide + */ + public static final int CAPABILITY_HOLD_CALL = 0x00000001; + + /** + * Flag indicating support for joining calls feature. + * + * @hide + */ + public static final int CAPABILITY_JOIN_CALLS = 0x00000002; + + private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102; + private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103; + + private static final int REG_TIMEOUT = 10000; + + /** + * The template class is used to call callback functions on events from the TBS + * server. Callback functions are wrapped in this class and registered to the + * Android system during app registration. + * + * @hide + */ + public abstract static class Callback { + + private static final String TAG = "BluetoothLeCallControl.Callback"; + + /** + * Called when a remote client requested to accept the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to be accepted + * @hide + */ + public abstract void onAcceptCall(int requestId, @NonNull UUID callId); + + /** + * A remote client has requested to terminate the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to terminate + * @hide + */ + public abstract void onTerminateCall(int requestId, @NonNull UUID callId); + + /** + * A remote client has requested to hold the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to be put on hold + * @hide + */ + public void onHoldCall(int requestId, @NonNull UUID callId) { + Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); + } + + /** + * A remote client has requested to unhold the call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The call Id requested to unhold + * @hide + */ + public void onUnholdCall(int requestId, @NonNull UUID callId) { + Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!"); + } + + /** + * A remote client has requested to place a call. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callId The Id to be assigned for the new call + * @param uri The caller URI requested + * @hide + */ + public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri); + + /** + * A remote client has requested to join the calls. + * + * <p> + * An application must call {@link BluetoothLeCallControl#requestResult} to complete the + * request. + * + * @param requestId The Id of the request + * @param callIds The call Id list requested to join + * @hide + */ + public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) { + Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!"); + } + } + + private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub { + + private final Executor mExecutor; + private final Callback mCallback; + + CallbackWrapper(Executor executor, Callback callback) { + mExecutor = executor; + mCallback = callback; + } + + @Override + public void onBearerRegistered(int ccid) { + synchronized (mServerIfLock) { + if (mCallback != null) { + mCcid = ccid; + mServerIfLock.notifyAll(); + } else { + // registration timeout + Log.e(TAG, "onBearerRegistered: mCallback is null"); + } + } + } + + @Override + public void onAcceptCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onTerminateCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onHoldCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onUnholdCall(int requestId, ParcelUuid uuid) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid())); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) { + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri)); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + + @Override + public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) { + List<UUID> uuids = new ArrayList<>(); + for (ParcelUuid parcelUuid : parcelUuids) { + uuids.add(parcelUuid.getUuid()); + } + + final long identityToken = Binder.clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids)); + } finally { + Binder.restoreCallingIdentity(identityToken); + } + } + }; + + private Context mContext; + private ServiceListener mServiceListener; + private volatile IBluetoothLeCallControl mService; + private BluetoothAdapter mAdapter; + private int mCcid = 0; + private String mToken; + private Callback mCallback = null; + private Object mServerIfLock = new Object(); + + private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + public void onBluetoothStateChange(boolean up) { + if (DBG) + Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + doUnbind(); + } else { + doBind(); + } + } + }; + + /** + * Create a BluetoothLeCallControl proxy object for interacting with the local Bluetooth + * telephone bearer service. + */ + /* package */ BluetoothLeCallControl(Context context, ServiceListener listener) { + mContext = context; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + mServiceListener = listener; + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + doBind(); + } + + private boolean doBind() { + synchronized (mConnection) { + if (mService == null) { + if (VDBG) + Log.d(TAG, "Binding service..."); + try { + return mAdapter.getBluetoothManager(). + bindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, + mConnection); + } catch (RemoteException e) { + Log.e(TAG, "Unable to bind TelephoneBearerService", e); + } + } + } + return false; + } + + private void doUnbind() { + synchronized (mConnection) { + if (mService != null) { + if (VDBG) + Log.d(TAG, "Unbinding service..."); + try { + mAdapter.getBluetoothManager(). + unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL, + mConnection); + } catch (RemoteException e) { + Log.e(TAG, "Unable to unbind TelephoneBearerService", e); + } finally { + mService = null; + } + } + } + } + + /* package */ void close() { + if (VDBG) + log("close()"); + unregisterBearer(); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException re) { + Log.e(TAG, "", re); + } + } + mServiceListener = null; + doUnbind(); + } + + private IBluetoothLeCallControl getService() { + return mService; + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public int getConnectionState(@Nullable BluetoothDevice device) { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public @NonNull List<BluetoothDevice> getConnectedDevices() { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Not supported + * + * @throws UnsupportedOperationException + */ + @Override + public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( + @NonNull int[] states) { + throw new UnsupportedOperationException("not supported"); + } + + /** + * Register Telephone Bearer exposing the interface that allows remote devices + * to track and control the call states. + * + * <p> + * This is an asynchronous call. The callback is used to notify success or + * failure if the function returns true. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * <!-- The UCI is a String identifier of the telephone bearer as defined at + * https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers + * (login required). --> + * + * <!-- The examples of common URI schemes can be found in + * https://iana.org/assignments/uri-schemes/uri-schemes.xhtml --> + * + * <!-- The Technology is an integer value. The possible values are defined at + * https://www.bluetooth.com/specifications/assigned-numbers (login required). + * --> + * + * @param uci Bearer Unique Client Identifier + * @param uriSchemes URI Schemes supported list + * @param capabilities bearer capabilities + * @param provider Network provider name + * @param technology Network technology + * @param executor {@link Executor} object on which callback will be + * executed. The Executor object is required. + * @param callback {@link Callback} object to which callback messages will + * be sent. The Callback object is required. + * @return true on success, false otherwise + * @hide + */ + @SuppressLint("ExecutorRegistration") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean registerBearer(@Nullable String uci, + @NonNull List<String> uriSchemes, int capabilities, + @NonNull String provider, int technology, + @NonNull Executor executor, @NonNull Callback callback) { + if (DBG) { + Log.d(TAG, "registerBearer"); + } + if (callback == null) { + throw new IllegalArgumentException("null parameter: " + callback); + } + if (mCcid != 0) { + return false; + } + + mToken = uci; + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + synchronized (mServerIfLock) { + if (mCallback != null) { + Log.e(TAG, "Bearer can be opened only once"); + return false; + } + + mCallback = callback; + try { + CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); + service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities, + provider, technology); + } catch (RemoteException e) { + Log.e(TAG, "", e); + mCallback = null; + return false; + } + + try { + mServerIfLock.wait(REG_TIMEOUT); + } catch (InterruptedException e) { + Log.e(TAG, "" + e); + mCallback = null; + } + + if (mCcid == 0) { + mCallback = null; + return false; + } + + return true; + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + + return false; + } + + /** + * Unregister Telephone Bearer Service and destroy all the associated data. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void unregisterBearer() { + if (DBG) { + Log.d(TAG, "unregisterBearer"); + } + if (mCcid == 0) { + return; + } + + int ccid = mCcid; + mCcid = 0; + mCallback = null; + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.unregisterBearer(mToken); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Get the Content Control ID (CCID) value. + * + * @return ccid Content Control ID value + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public int getContentControlId() { + return mCcid; + } + + /** + * Notify about the newly added call. + * + * <p> + * This shall be called as early as possible after the call has been added. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param call Newly added call + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallAdded(@NonNull BluetoothLeCall call) { + if (DBG) { + Log.d(TAG, "onCallAdded: call=" + call); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callAdded(mCcid, call); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Notify about the removed call. + * + * <p> + * This shall be called as early as possible after the call has been removed. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callId The Id of a call that has been removed + * @param reason Call termination reason + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) { + if (DBG) { + Log.d(TAG, "callRemoved: callId=" + callId); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callRemoved(mCcid, new ParcelUuid(callId), reason); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Notify the call state change + * + * <p> + * This shall be called as early as possible after the state of the call has + * changed. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * @param callId The call Id that state has been changed + * @param state Call state + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) { + if (DBG) { + Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.callStateChanged(mCcid, new ParcelUuid(callId), state); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Provide the current calls list + * + * <p> + * This function must be invoked after registration if application has any + * calls. + * + * @param calls current calls list + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void currentCallsList(@NonNull List<BluetoothLeCall> calls) { + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.currentCallsList(mCcid, calls); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + } + + /** + * Provide the network current status + * + * <p> + * This function must be invoked on change of network state. + * + * <p> + * Requires {@link android.Manifest.permission#BLUETOOTH} permission. + * + * <!-- The Technology is an integer value. The possible values are defined at + * https://www.bluetooth.com/specifications/assigned-numbers (login required). + * --> + * + * @param provider Network provider name + * @param technology Network technology + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void networkStateChanged(@NonNull String provider, int technology) { + if (DBG) { + Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.networkStateChanged(mCcid, provider, technology); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + if (service == null) { + Log.w(TAG, "Proxy not attached to service"); + } + } + + /** + * Send a response to a call control request to a remote device. + * + * <p> + * This function must be invoked in when a request is received by one of these + * callback methods: + * + * <ul> + * <li>{@link Callback#onAcceptCall} + * <li>{@link Callback#onTerminateCall} + * <li>{@link Callback#onHoldCall} + * <li>{@link Callback#onUnholdCall} + * <li>{@link Callback#onPlaceCall} + * <li>{@link Callback#onJoinCalls} + * </ul> + * + * @param requestId The ID of the request that was received with the callback + * @param result The result of the request to be sent to the remote devices + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void requestResult(int requestId, @Result int result) { + if (DBG) { + Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result); + } + if (mCcid == 0) { + return; + } + + final IBluetoothLeCallControl service = getService(); + if (service != null) { + try { + service.requestResult(mCcid, requestId, result); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + } + + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + private static boolean isValidDevice(@Nullable BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); + } + + private static void log(String msg) { + Log.d(TAG, msg); + } + + private final IBluetoothProfileServiceConnection mConnection = + new IBluetoothProfileServiceConnection.Stub() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) { + Log.d(TAG, "Proxy object connected"); + } + mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service)); + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED)); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + if (DBG) { + Log.d(TAG, "Proxy object disconnected"); + } + doUnbind(); + mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED)); + } + }; + + private final Handler mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MESSAGE_TBS_SERVICE_CONNECTED: { + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL, + BluetoothLeCallControl.this); + } + break; + } + case MESSAGE_TBS_SERVICE_DISCONNECTED: { + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL); + } + break; + } + } + } + }; +} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index e047e5d81a9d..d0f74e985729 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -240,12 +240,19 @@ public interface BluetoothProfile { int LE_AUDIO_BROADCAST = 26; /** + * @hide + * Telephone Bearer Service from Call Control Profile + * + */ + int LE_CALL_CONTROL = 27; + + /** * Max profile ID. This value should be updated whenever a new profile is added to match * the largest value assigned to a profile. * * @hide */ - int MAX_PROFILE_ID = 26; + int MAX_PROFILE_ID = 27; /** * Default priority for devices that we try to auto-connect to and diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 6c3936569c28..07babb1a33b8 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -913,7 +913,7 @@ public final class DisplayManagerGlobal { private static final class DisplayListenerDelegate extends Handler { public final DisplayListener mListener; - public long mEventsMask; + public volatile long mEventsMask; private final DisplayInfo mDisplayInfo = new DisplayInfo(); @@ -933,12 +933,12 @@ public final class DisplayManagerGlobal { removeCallbacksAndMessages(null); } - public synchronized void setEventsMask(@EventsMask long newEventsMask) { + public void setEventsMask(@EventsMask long newEventsMask) { mEventsMask = newEventsMask; } @Override - public synchronized void handleMessage(Message msg) { + public void handleMessage(Message msg) { switch (msg.what) { case EVENT_DISPLAY_ADDED: if ((mEventsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) { diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 1e424d1194ae..5d9f2189df1b 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -10,9 +10,8 @@ per-file PowerManagerInternal.java = michaelwr@google.com, santoscordon@google.c # BatteryStats per-file *BatteryConsumer* = file:/BATTERY_STATS_OWNERS per-file BatteryManager* = file:/BATTERY_STATS_OWNERS -per-file BatteryStats* = file:/BATTERY_STATS_OWNERS -per-file BatteryUsageStats* = file:/BATTERY_STATS_OWNERS per-file PowerComponents.java = file:/BATTERY_STATS_OWNERS +per-file *Stats* = file:/BATTERY_STATS_OWNERS # Multiuser per-file IUser* = file:/MULTIUSER_OWNERS diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 982cf07da358..b7e8c8cb0a7e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1172,7 +1172,8 @@ public class AudioManager { * * @hide */ - @UnsupportedAppUsage + @SystemApi + @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int streamType) { final IAudioService service = getService(); try { diff --git a/omapi/aidl/Android.bp b/omapi/aidl/Android.bp index 2b81200f0079..d80317bb8c60 100644 --- a/omapi/aidl/Android.bp +++ b/omapi/aidl/Android.bp @@ -28,8 +28,5 @@ aidl_interface { rust: { enabled: true, }, - ndk: { - separate_platform_variant: false, - }, }, } diff --git a/packages/ConnectivityT/service/src/com/android/server/IpSecService.java b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java index d1e432e80f51..179d9459fd84 100644 --- a/packages/ConnectivityT/service/src/com/android/server/IpSecService.java +++ b/packages/ConnectivityT/service/src/com/android/server/IpSecService.java @@ -1236,37 +1236,53 @@ public class IpSecService extends IIpSecService.Stub { int callingUid = Binder.getCallingUid(); UserRecord userRecord = mUserResourceTracker.getUserRecord(callingUid); final int resourceId = mNextResourceId++; - FileDescriptor sockFd = null; + + ParcelFileDescriptor pFd = null; try { if (!userRecord.mSocketQuotaTracker.isAvailable()) { return new IpSecUdpEncapResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE); } - sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - mUidFdTagger.tag(sockFd, callingUid); + FileDescriptor sockFd = null; + try { + sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + pFd = ParcelFileDescriptor.dup(sockFd); + } finally { + IoUtils.closeQuietly(sockFd); + } + mUidFdTagger.tag(pFd.getFileDescriptor(), callingUid); // This code is common to both the unspecified and specified port cases Os.setsockoptInt( - sockFd, + pFd.getFileDescriptor(), OsConstants.IPPROTO_UDP, OsConstants.UDP_ENCAP, OsConstants.UDP_ENCAP_ESPINUDP); - mNetd.ipSecSetEncapSocketOwner(new ParcelFileDescriptor(sockFd), callingUid); + mNetd.ipSecSetEncapSocketOwner(pFd, callingUid); if (port != 0) { Log.v(TAG, "Binding to port " + port); - Os.bind(sockFd, INADDR_ANY, port); + Os.bind(pFd.getFileDescriptor(), INADDR_ANY, port); } else { - port = bindToRandomPort(sockFd); + port = bindToRandomPort(pFd.getFileDescriptor()); } userRecord.mEncapSocketRecords.put( resourceId, new RefcountedResource<EncapSocketRecord>( - new EncapSocketRecord(resourceId, sockFd, port), binder)); - return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, sockFd); + new EncapSocketRecord(resourceId, pFd.getFileDescriptor(), port), + binder)); + return new IpSecUdpEncapResponse(IpSecManager.Status.OK, resourceId, port, + pFd.getFileDescriptor()); } catch (IOException | ErrnoException e) { - IoUtils.closeQuietly(sockFd); + try { + if (pFd != null) { + pFd.close(); + } + } catch (IOException ex) { + // Nothing can be done at this point + Log.e(TAG, "Failed to close pFd."); + } } // If we make it to here, then something has gone wrong and we couldn't open a socket. // The only reasonable condition that would cause that is resource unavailable. diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 9de1c5ea1a3d..8dc18c01348a 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -251,6 +251,8 @@ <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" /> <!-- For handling silent audio recordings --> <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" /> + <!-- For asking AudioManager audio information --> + <uses-permission android:name="android.permission.QUERY_AUDIO_STATE"/> <!-- to read and change hvac values in a car --> <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" /> diff --git a/services/Android.bp b/services/Android.bp index e11b0442377e..841edc7ed509 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -221,19 +221,13 @@ droidstubs { }, dists: [ { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android-non-updatable.txt", tag: ".api.txt", }, { - targets: [ - "sdk", - "win_sdk", - ], + targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android-non-updatable-removed.txt", tag: ".removed-api.txt", diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 450e9881bef2..c8b4f1109b5e 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -46,6 +46,7 @@ import android.bluetooth.IBluetoothManager; import android.bluetooth.IBluetoothManagerCallback; import android.bluetooth.IBluetoothProfileServiceConnection; import android.bluetooth.IBluetoothStateChangeCallback; +import android.bluetooth.IBluetoothLeCallControl; import android.content.ActivityNotFoundException; import android.content.AttributionSource; import android.content.BroadcastReceiver; @@ -1328,11 +1329,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { + bluetoothProfile); } - if (bluetoothProfile != BluetoothProfile.HEADSET) { + Intent intent; + if (bluetoothProfile == BluetoothProfile.HEADSET) { + intent = new Intent(IBluetoothHeadset.class.getName()); + } else if (bluetoothProfile== BluetoothProfile.LE_CALL_CONTROL) { + intent = new Intent(IBluetoothLeCallControl.class.getName()); + } else { return false; } - Intent intent = new Intent(IBluetoothHeadset.class.getName()); psc = new ProfileServiceConnections(intent); if (!psc.bindService()) { return false; diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index df5d60c3a74d..e00c8a39074a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3193,6 +3193,13 @@ public class AudioService extends IAudioService.Stub } } + private void enforceQueryStatePermission() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Missing QUERY_AUDIO_STATE permissions"); + } + } + private void enforceQueryStateOrModifyRoutingPermission() { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED @@ -4094,6 +4101,7 @@ public class AudioService extends IAudioService.Stub /** Get last audible volume before stream was muted. */ public int getLastAudibleStreamVolume(int streamType) { + enforceQueryStatePermission(); ensureValidStreamType(streamType); int device = getDeviceForStream(streamType); return (mStreamStates[streamType].getIndex(device) + 5) / 10; diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index bf4ef4879c9a..2c666b5bb7c7 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1446,7 +1446,10 @@ public class Vpn { // parameters. If that fails, disconnect. if (oldConfig != null && updateLinkPropertiesInPlaceIfPossible(mNetworkAgent, oldConfig)) { - // Keep mNetworkAgent unchanged + // Update underlying networks if it is changed. + if (!Arrays.equals(oldConfig.underlyingNetworks, config.underlyingNetworks)) { + setUnderlyingNetworks(config.underlyingNetworks); + } } else { // Initialize the state for a new agent, while keeping the old one connected // in case this new connection fails. diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 08a72151aa37..97c168d4fb0b 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -1255,9 +1255,7 @@ public class StagingManager { info.diskImagePath = ai.modulePath; info.versionCode = ai.versionCode; info.versionName = ai.versionName; - info.hasBootClassPathJars = ai.hasBootClassPathJars; - info.hasDex2OatBootClassPathJars = ai.hasDex2OatBootClassPathJars; - info.hasSystemServerClassPathJars = ai.hasSystemServerClassPathJars; + info.hasClassPathJars = ai.hasClassPathJars; return info; } } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 46cb720cb2c0..186b2b5c7c50 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -435,7 +435,8 @@ final class DefaultPermissionGrantPolicy { || !pm.isGranted(Manifest.permission.READ_PRIVILEGED_PHONE_STATE, pkg, UserHandle.of(userId)) || !pm.isGranted(Manifest.permission.READ_PHONE_STATE, pkg, - UserHandle.of(userId))) { + UserHandle.of(userId)) + || pm.isSysComponentOrPersistentPlatformSignedPrivApp(pkg)) { continue; } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index bb9740b60f78..7066d5680f66 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -1585,7 +1585,7 @@ static jobject nativeCreateInputChannel(JNIEnv* env, jclass /* clazz */, jlong p if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); - message += StringPrintf(" Status=%d", inputChannel.error().code()); + message += StringPrintf(" Status=%d", static_cast<int>(inputChannel.error().code())); jniThrowRuntimeException(env, message.c_str()); return nullptr; } @@ -1619,7 +1619,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong p if (!inputChannel.ok()) { std::string message = inputChannel.error().message(); - message += StringPrintf(" Status=%d", inputChannel.error().code()); + message += StringPrintf(" Status=%d", static_cast<int>(inputChannel.error().code())); jniThrowRuntimeException(env, message.c_str()); return nullptr; } diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 63a7acfb5142..d6d6775cb40f 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -222,6 +222,11 @@ public class SubscriptionInfo implements Parcelable { private boolean mAreUiccApplicationsEnabled = true; /** + * The port index of the Uicc card. + */ + private final int mPortIndex; + + /** * Public copy constructor. * @hide */ @@ -274,6 +279,22 @@ public class SubscriptionInfo implements Parcelable { int carrierId, int profileClass, int subType, @Nullable String groupOwner, @Nullable UiccAccessRule[] carrierConfigAccessRules, boolean areUiccApplicationsEnabled) { + this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, + roaming, icon, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, + cardId, isOpportunistic, groupUUID, isGroupDisabled, carrierId, profileClass, + subType, groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, 0); + } + /** + * @hide + */ + public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName, + CharSequence carrierName, int nameSource, int iconTint, String number, int roaming, + Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, + @Nullable UiccAccessRule[] nativeAccessRules, String cardString, int cardId, + boolean isOpportunistic, @Nullable String groupUUID, boolean isGroupDisabled, + int carrierId, int profileClass, int subType, @Nullable String groupOwner, + @Nullable UiccAccessRule[] carrierConfigAccessRules, + boolean areUiccApplicationsEnabled, int portIndex) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -300,8 +321,8 @@ public class SubscriptionInfo implements Parcelable { this.mGroupOwner = groupOwner; this.mCarrierConfigAccessRules = carrierConfigAccessRules; this.mAreUiccApplicationsEnabled = areUiccApplicationsEnabled; + this.mPortIndex = portIndex; } - /** * @return the subscription ID. */ @@ -737,6 +758,14 @@ public class SubscriptionInfo implements Parcelable { public int getCardId() { return this.mCardId; } + /** + * Returns the port index of the SIM card which contains the subscription. + * + * @return the portIndex + */ + public int getPortIndex() { + return this.mPortIndex; + } /** * Set whether the subscription's group is disabled. @@ -783,6 +812,7 @@ public class SubscriptionInfo implements Parcelable { UiccAccessRule[] nativeAccessRules = source.createTypedArray(UiccAccessRule.CREATOR); String cardString = source.readString(); int cardId = source.readInt(); + int portId = source.readInt(); boolean isOpportunistic = source.readBoolean(); String groupUUID = source.readString(); boolean isGroupDisabled = source.readBoolean(); @@ -800,7 +830,7 @@ public class SubscriptionInfo implements Parcelable { carrierName, nameSource, iconTint, number, dataRoaming, /* icon= */ null, mcc, mnc, countryIso, isEmbedded, nativeAccessRules, cardString, cardId, isOpportunistic, groupUUID, isGroupDisabled, carrierid, profileClass, subType, - groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled); + groupOwner, carrierConfigAccessRules, areUiccApplicationsEnabled, portId); info.setAssociatedPlmns(ehplmns, hplmns); return info; } @@ -830,6 +860,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeTypedArray(mNativeAccessRules, flags); dest.writeString(mCardString); dest.writeInt(mCardId); + dest.writeInt(mPortIndex); dest.writeBoolean(mIsOpportunistic); dest.writeString(mGroupUUID == null ? null : mGroupUUID.toString()); dest.writeBoolean(mIsGroupDisabled); @@ -876,6 +907,7 @@ public class SubscriptionInfo implements Parcelable { + " mnc=" + mMnc + " countryIso=" + mCountryIso + " isEmbedded=" + mIsEmbedded + " nativeAccessRules=" + Arrays.toString(mNativeAccessRules) + " cardString=" + cardStringToPrint + " cardId=" + mCardId + + " portIndex=" + mPortIndex + " isOpportunistic=" + mIsOpportunistic + " groupUUID=" + mGroupUUID + " isGroupDisabled=" + mIsGroupDisabled + " profileClass=" + mProfileClass @@ -892,7 +924,7 @@ public class SubscriptionInfo implements Parcelable { return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded, mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso, mCardString, mCardId, mDisplayName, mCarrierName, mNativeAccessRules, mIsGroupDisabled, - mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled); + mCarrierId, mProfileClass, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex); } @Override @@ -925,6 +957,7 @@ public class SubscriptionInfo implements Parcelable { && Objects.equals(mCountryIso, toCompare.mCountryIso) && Objects.equals(mCardString, toCompare.mCardString) && Objects.equals(mCardId, toCompare.mCardId) + && mPortIndex == toCompare.mPortIndex && Objects.equals(mGroupOwner, toCompare.mGroupOwner) && TextUtils.equals(mDisplayName, toCompare.mDisplayName) && TextUtils.equals(mCarrierName, toCompare.mCarrierName) diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index d5315acf60d5..1fab89e51416 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1128,6 +1128,52 @@ public class SubscriptionManager { */ public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX"; + /** + * A source of phone number: the EF-MSISDN (see 3GPP TS 31.102), + * or EF-MDN for CDMA (see 3GPP2 C.P0065-B), from UICC application. + * + * <p>The availability and a of the number depends on the carrier. + * The number may be updated by over-the-air update to UICC applications + * from the carrier, or by other means with physical access to the SIM. + */ + public static final int PHONE_NUMBER_SOURCE_UICC = 1; + + /** + * A source of phone number: provided by an app that has carrier privilege. + * + * <p>The number is intended to be set by a carrier app knowing the correct number + * which is, for example, different from the number in {@link #PHONE_NUMBER_SOURCE_UICC UICC} + * for some reason. + * The number is not available until a carrier app sets one via + * {@link #setCarrierPhoneNumber(int, String)}. + * The app can update the number with the same API should the number change. + */ + public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; + + /** + * A source of phone number: provided by IMS (IP Multimedia Subsystem) implementation. + * When IMS service is registered (as indicated by + * {@link android.telephony.ims.RegistrationManager.RegistrationCallback#onRegistered(int)}) + * the IMS implementation may return P-Associated-Uri SIP headers (RFC 3455). The URIs + * are the user’s public user identities known to the network (see 3GPP TS 24.229 5.4.1.2), + * and the phone number is typically one of them (see “global number” in 3GPP TS 23.003 13.4). + * + * <p>This source provides the phone number from the last IMS registration. + * IMS registration may happen on every device reboot or other network condition changes. + * The number will be updated should the associated URI change after an IMS registration. + */ + public static final int PHONE_NUMBER_SOURCE_IMS = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PHONE_NUMBER_SOURCE"}, + value = { + PHONE_NUMBER_SOURCE_UICC, + PHONE_NUMBER_SOURCE_CARRIER, + PHONE_NUMBER_SOURCE_IMS, + }) + public @interface PhoneNumberSource {} + private final Context mContext; // Cache of Resource that has been created in getResourcesForSubId. Key is a Pair containing @@ -3763,4 +3809,132 @@ public class SubscriptionManager { RESTORE_SIM_SPECIFIC_SETTINGS_METHOD_NAME, null, bundle); } + + /** + * Returns the phone number for the given {@code subId} and {@code source}, + * or an empty string if not available. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, or + * READ_PRIVILEGED_PHONE_STATE permission (can only be granted to apps preloaded on device), + * or that the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} + * for the default one. + * @param source the source of the phone number, one of the PHONE_NUMBER_SOURCE_* constants. + * @return the phone number, or an empty string if not available. + * @throws IllegalArgumentException if {@code source} is invalid. + * @throws IllegalStateException if the telephony process is not currently available. + * @throws SecurityException if the caller doesn't have permissions required. + * @see #PHONE_NUMBER_SOURCE_UICC + * @see #PHONE_NUMBER_SOURCE_CARRIER + * @see #PHONE_NUMBER_SOURCE_IMS + */ + @SuppressAutoDoc // No support for carrier privileges (b/72967236) + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PHONE_NUMBERS, + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + }) + @NonNull + public String getPhoneNumber(int subscriptionId, @PhoneNumberSource int source) { + if (subscriptionId == DEFAULT_SUBSCRIPTION_ID) { + subscriptionId = getDefaultSubscriptionId(); + } + if (source != PHONE_NUMBER_SOURCE_UICC + && source != PHONE_NUMBER_SOURCE_CARRIER + && source != PHONE_NUMBER_SOURCE_IMS) { + throw new IllegalArgumentException("invalid source " + source); + } + try { + ISub iSub = TelephonyManager.getSubscriptionService(); + if (iSub != null) { + return iSub.getPhoneNumber(subscriptionId, source, + mContext.getOpPackageName(), mContext.getAttributionTag()); + } else { + throw new IllegalStateException("subscription service unavailable."); + } + } catch (RemoteException ex) { + throw ex.rethrowAsRuntimeException(); + } + } + + /** + * Returns the phone number for the given {@code subId}, or an empty string if + * not available. + * + * <p>This API is built up on {@link #getPhoneNumber(int, int)}, but picks + * from available sources in the following order: {@link #PHONE_NUMBER_SOURCE_CARRIER} + * > {@link #PHONE_NUMBER_SOURCE_UICC} > {@link #PHONE_NUMBER_SOURCE_IMS}. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS}, or + * READ_PRIVILEGED_PHONE_STATE permission (can only be granted to apps preloaded on device), + * or that the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} + * for the default one. + * @return the phone number, or an empty string if not available. + * @throws IllegalStateException if the telephony process is not currently available. + * @throws SecurityException if the caller doesn't have permissions required. + * @see #getPhoneNumber(int, int) + */ + @SuppressAutoDoc // No support for carrier privileges (b/72967236) + @RequiresPermission(anyOf = { + android.Manifest.permission.READ_PHONE_NUMBERS, + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, + }) + @NonNull + public String getPhoneNumber(int subscriptionId) { + if (subscriptionId == DEFAULT_SUBSCRIPTION_ID) { + subscriptionId = getDefaultSubscriptionId(); + } + try { + ISub iSub = TelephonyManager.getSubscriptionService(); + if (iSub != null) { + return iSub.getPhoneNumberFromFirstAvailableSource(subscriptionId, + mContext.getOpPackageName(), mContext.getAttributionTag()); + } else { + throw new IllegalStateException("subscription service unavailable."); + } + } catch (RemoteException ex) { + throw ex.rethrowAsRuntimeException(); + } + } + + /** + * Sets the phone number for the given {@code subId} for source + * {@link #PHONE_NUMBER_SOURCE_CARRIER carrier}. + * Sets an empty string to remove the previously set phone number. + * + * <p>Requires Permission: the calling app has carrier privileges + * (see {@link TelephonyManager#hasCarrierPrivileges}). + * + * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID} + * for the default one. + * @param number the phone number, or an empty string to remove the previously set number. + * @throws IllegalStateException if the telephony process is not currently available. + * @throws NullPointerException if {@code number} is {@code null}. + * @throws SecurityException if the caller doesn't have permissions required. + */ + public void setCarrierPhoneNumber(int subscriptionId, @NonNull String number) { + if (subscriptionId == DEFAULT_SUBSCRIPTION_ID) { + subscriptionId = getDefaultSubscriptionId(); + } + if (number == null) { + throw new NullPointerException("invalid number null"); + } + try { + ISub iSub = TelephonyManager.getSubscriptionService(); + if (iSub != null) { + iSub.setPhoneNumber(subscriptionId, PHONE_NUMBER_SOURCE_CARRIER, number, + mContext.getOpPackageName(), mContext.getAttributionTag()); + } else { + throw new IllegalStateException("subscription service unavailable."); + } + } catch (RemoteException ex) { + throw ex.rethrowAsRuntimeException(); + } + } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 30cb8ca64ad3..fd9247cdfda9 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -132,6 +132,8 @@ import java.lang.annotation.RetentionPolicy; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -145,6 +147,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.IntStream; + /** * Provides access to information about the telephony services on * the device. Applications can use the methods in this class to @@ -3941,8 +3944,8 @@ public class TelephonyManager { * <p> * If the caller has carrier priviliges on any active subscription, then they have permission to * get simple information like the card ID ({@link UiccCardInfo#getCardId()}), whether the card - * is an eUICC ({@link UiccCardInfo#isEuicc()}), and the slot index where the card is inserted - * ({@link UiccCardInfo#getSlotIndex()}). + * is an eUICC ({@link UiccCardInfo#isEuicc()}), and the physical slot index where the card is + * inserted ({@link UiccCardInfo#getPhysicalSlotIndex()}. * <p> * To get private information such as the EID ({@link UiccCardInfo#getEid()}) or ICCID * ({@link UiccCardInfo#getIccId()}), the caller must have carrier priviliges on that specific @@ -3986,7 +3989,7 @@ public class TelephonyManager { if (telephony == null) { return null; } - return telephony.getUiccSlotsInfo(); + return telephony.getUiccSlotsInfo(mContext.getOpPackageName()); } catch (RemoteException e) { return null; } @@ -4019,8 +4022,13 @@ public class TelephonyManager { * size should be same as {@link #getUiccSlotsInfo()}. * @return boolean Return true if the switch succeeds, false if the switch fails. * @hide + * @deprecated {@link #setSimSlotMapping(Collection, Executor, Consumer)} */ + // TODO: once integrating the HAL changes we can convert int[] to List<UiccSlotMapping> and + // converge API's in ITelephony.aidl and PhoneInterfaceManager + @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean switchSlots(int[] physicalSlots) { try { @@ -4035,6 +4043,109 @@ public class TelephonyManager { } /** + * @param slotMapping Logical to physical slot and port mapping. + * @return {@code true} if slotMapping is valid. + * @return {@code false} if slotMapping is invalid. + * + * slotMapping is invalid if there are different entries (physical slot + port) mapping to the + * same logical slot or if there are same {physical slot + port} mapping to the different + * logical slot + * @hide + */ + private static boolean isSlotMappingValid(@NonNull Collection<UiccSlotMapping> slotMapping) { + // Grouping the collection by logicalSlotIndex, finding different entries mapping to the + // same logical slot + Map<Integer, List<UiccSlotMapping>> slotMappingInfo = slotMapping.stream().collect( + Collectors.groupingBy(UiccSlotMapping::getLogicalSlotIndex)); + for (Map.Entry<Integer, List<UiccSlotMapping>> entry : slotMappingInfo.entrySet()) { + List<UiccSlotMapping> logicalSlotMap = entry.getValue(); + if (logicalSlotMap.size() > 1) { + // duplicate logicalSlotIndex found + return false; + } + } + + // Grouping the collection by physical slot and port, finding same entries mapping to the + // different logical slot + Map<List<Integer>, List<UiccSlotMapping>> slotMapInfos = slotMapping.stream().collect( + Collectors.groupingBy( + slot -> Arrays.asList(slot.getPhysicalSlotIndex(), slot.getPortIndex()))); + for (Map.Entry<List<Integer>, List<UiccSlotMapping>> entry : slotMapInfos.entrySet()) { + List<UiccSlotMapping> portAndPhysicalSlotList = entry.getValue(); + if (portAndPhysicalSlotList.size() > 1) { + // duplicate pair of portIndex and physicalSlotIndex found + return false; + } + } + return true; + } + /** + * Maps the logical slots to physical slots and ports. Mapping is specified from + * {@link UiccSlotMapping} which consist of both physical slot index and port index. + * Logical slot is the slot that is seen by modem. Physical slot is the actual physical slot. + * Port index is the index (enumerated value) for the associated port available on the SIM. + * Each physical slot can have multiple ports if multi-enabled profile(MEP) is supported. + * + * Example: no. of logical slots 1 and physical slots 2 do not support MEP, each physical slot + * has one port: + * The only logical slot (index 0) can be mapped to first physical slot (value 0), port(index + * 0) or + * second physical slot(value 1), port (index 0), while the other physical slot remains unmapped + * and inactive. + * slotMapping[0] = UiccSlotMapping{0 //logical slot, 0 //physical slot//, 0 //port//} + * slotMapping[0] = UiccSlotMapping{1 // logical slot, 1 //physical slot//, 0 //port//} + * + * Example no. of logical slots 2 and physical slots 2 supports MEP with 2 ports available: + * Each logical slot must be mapped to a port (physical slot and port combination). + * First logical slot (index 0) can be mapped to physical slot 1 and the second logical slot + * can be mapped to either port from physical slot 2. + * + * slotMapping[0] = UiccSlotMapping{0, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 0, 0} or + * slotMapping[0] = UiccSlotMapping{0, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 1, 1} + * + * or the other way around, the second logical slot(index 1) can be mapped to physical slot 1 + * and the first logical slot can be mapped to either port from physical slot 2. + * + * slotMapping[1] = UiccSlotMapping{0, 0, 0} and slotMapping[0] = UiccSlotMapping{1, 0, 0} or + * slotMapping[1] = UiccSlotMapping{0, 0, 0} and slotMapping[0] = UiccSlotMapping{1, 1, 1} + * + * another possible mapping is each logical slot maps to each port of physical slot 2 and there + * is no active logical modem mapped to physical slot 1. + * + * slotMapping[0] = UiccSlotMapping{1, 0, 0} and slotMapping[1] = UiccSlotMapping{1, 1, 1} or + * slotMapping[0] = UiccSlotMapping{1, 1, 1} and slotMapping[1] = UiccSlotMapping{1, 0, 0} + * + * @param slotMapping Logical to physical slot and port mapping. + * @throws IllegalStateException if telephony service is null or slot mapping was sent when the + * radio in middle of a silent restart or other invalid states to handle the command + * @throws IllegalArgumentException if the caller passes in an invalid collection of + * UiccSlotMapping like duplicate data, etc + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setSimSlotMapping(@NonNull Collection<UiccSlotMapping> slotMapping) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + if (isSlotMappingValid(slotMapping)) { + boolean result = telephony.setSimSlotMapping(new ArrayList(slotMapping)); + if (!result) { + throw new IllegalStateException("setSimSlotMapping has failed"); + } + } else { + throw new IllegalArgumentException("Duplicate UiccSlotMapping data found"); + } + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * Get the mapping from logical slots to physical slots. The key of the map is the logical slot * id and the value is the physical slots id mapped to this logical slot id. * @@ -4051,7 +4162,7 @@ public class TelephonyManager { try { ITelephony telephony = getITelephony(); if (telephony != null) { - int[] slotMappingArray = telephony.getSlotsMapping(); + int[] slotMappingArray = telephony.getSlotsMapping(mContext.getOpPackageName()); for (int i = 0; i < slotMappingArray.length; i++) { slotMapping.put(i, slotMappingArray[i]); } diff --git a/telephony/java/android/telephony/UiccCardInfo.java b/telephony/java/android/telephony/UiccCardInfo.java index b438920362ff..7dfe450fdb1a 100644 --- a/telephony/java/android/telephony/UiccCardInfo.java +++ b/telephony/java/android/telephony/UiccCardInfo.java @@ -20,21 +20,27 @@ import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** * The UiccCardInfo represents information about a currently inserted UICC or embedded eUICC. */ public final class UiccCardInfo implements Parcelable { - private final boolean mIsEuicc; private final int mCardId; private final String mEid; private final String mIccId; - private final int mSlotIndex; + private final int mPhysicalSlotIndex; private final boolean mIsRemovable; + private final boolean mIsMultipleEnabledProfilesSupported; + private final List<UiccPortInfo> mPortList; + private boolean mIccIdAccessRestricted = false; - public static final @android.annotation.NonNull Creator<UiccCardInfo> CREATOR = new Creator<UiccCardInfo>() { + public static final @NonNull Creator<UiccCardInfo> CREATOR = new Creator<UiccCardInfo>() { @Override public UiccCardInfo createFromParcel(Parcel in) { return new UiccCardInfo(in); @@ -47,22 +53,29 @@ public final class UiccCardInfo implements Parcelable { }; private UiccCardInfo(Parcel in) { - mIsEuicc = in.readByte() != 0; + mIsEuicc = in.readBoolean(); mCardId = in.readInt(); - mEid = in.readString(); - mIccId = in.readString(); - mSlotIndex = in.readInt(); - mIsRemovable = in.readByte() != 0; + mEid = in.readString8(); + mIccId = in.readString8(); + mPhysicalSlotIndex = in.readInt(); + mIsRemovable = in.readBoolean(); + mIsMultipleEnabledProfilesSupported = in.readBoolean(); + mPortList = new ArrayList<UiccPortInfo>(); + in.readTypedList(mPortList, UiccPortInfo.CREATOR); + mIccIdAccessRestricted = in.readBoolean(); } @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeByte((byte) (mIsEuicc ? 1 : 0)); + dest.writeBoolean(mIsEuicc); dest.writeInt(mCardId); - dest.writeString(mEid); - dest.writeString(mIccId); - dest.writeInt(mSlotIndex); - dest.writeByte((byte) (mIsRemovable ? 1 : 0)); + dest.writeString8(mEid); + dest.writeString8(mIccId); + dest.writeInt(mPhysicalSlotIndex); + dest.writeBoolean(mIsRemovable); + dest.writeBoolean(mIsMultipleEnabledProfilesSupported); + dest.writeTypedList(mPortList, flags); + dest.writeBoolean(mIccIdAccessRestricted); } @Override @@ -71,20 +84,34 @@ public final class UiccCardInfo implements Parcelable { } /** + * Construct a UiccCardInfo. + * + * @param isEuicc is a flag to check is eUICC or not + * @param cardId is unique ID used to identify a UiccCard. + * @param eid is unique eUICC Identifier + * @param physicalSlotIndex is unique index referring to a physical SIM slot. + * @param isRemovable is a flag to check is removable or embedded + * @param isMultipleEnabledProfilesSupported is a flag to check is MEP enabled or not + * @param portList has the information regarding port, ICCID and its active status + * * @hide */ - public UiccCardInfo(boolean isEuicc, int cardId, String eid, String iccId, int slotIndex, - boolean isRemovable) { + public UiccCardInfo(boolean isEuicc, int cardId, String eid, int physicalSlotIndex, + boolean isRemovable, boolean isMultipleEnabledProfilesSupported, + @NonNull List<UiccPortInfo> portList) { this.mIsEuicc = isEuicc; this.mCardId = cardId; this.mEid = eid; - this.mIccId = iccId; - this.mSlotIndex = slotIndex; + this.mIccId = null; + this.mPhysicalSlotIndex = physicalSlotIndex; this.mIsRemovable = isRemovable; + this.mIsMultipleEnabledProfilesSupported = isMultipleEnabledProfilesSupported; + this.mPortList = portList; } /** * Return whether the UICC is an eUICC. + * * @return true if the UICC is an eUICC. */ public boolean isEuicc() { @@ -119,40 +146,83 @@ public final class UiccCardInfo implements Parcelable { * <p> * Note that this field may be omitted if the caller does not have the correct permissions * (see {@link TelephonyManager#getUiccCardsInfo()}). + * + * @deprecated with support for MEP(multiple enabled profile), a SIM card can have more than one + * ICCID active at the same time.Instead use {@link UiccPortInfo#getIccId()} to retrieve ICCID. + * To find {@link UiccPortInfo} use {@link UiccCardInfo#getPorts()} + * + * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond. */ @Nullable + @Deprecated public String getIccId() { - return mIccId; + if (mIccIdAccessRestricted) { + throw new UnsupportedOperationException("getIccId from UiccPortInfo"); + } + //always return ICCID from first port. + return getPorts().stream().findFirst().get().getIccId(); } /** * Gets the slot index for the slot that the UICC is currently inserted in. + * + * @deprecated use {@link #getPhysicalSlotIndex()} */ + @Deprecated public int getSlotIndex() { - return mSlotIndex; + return mPhysicalSlotIndex; } /** - * Returns a copy of the UiccCardinfo with the EID and ICCID set to null. These values are - * generally private and require carrier privileges to view. - * - * @hide + * Gets the physical slot index for the slot that the UICC is currently inserted in. */ - @NonNull - public UiccCardInfo getUnprivileged() { - return new UiccCardInfo(mIsEuicc, mCardId, null, null, mSlotIndex, mIsRemovable); + public int getPhysicalSlotIndex() { + return mPhysicalSlotIndex; } /** * Return whether the UICC or eUICC is removable. * <p> * UICCs are generally removable, but eUICCs may be removable or built in to the device. + * * @return true if the UICC or eUICC is removable */ public boolean isRemovable() { return mIsRemovable; } + /* + * Whether the UICC card supports multiple enable profile(MEP) + * UICCs are generally MEP disabled, there can be only one active profile on the physical + * sim card. + * + * @return {@code true} if the eUICC is supporting multiple enabled profile(MEP). + */ + public boolean isMultipleEnabledProfilesSupported() { + return mIsMultipleEnabledProfilesSupported; + } + + /** + * Get information regarding port, ICCID and its active status. + * + * @return Collection of {@link UiccPortInfo} + */ + public @NonNull Collection<UiccPortInfo> getPorts() { + return Collections.unmodifiableList(mPortList); + } + + /** + * if the flag is set to {@code true} the calling app is not allowed to access deprecated + * {@link #getIccId()} + * @param iccIdAccessRestricted is the flag to check if app is allowed to access ICCID + * + * @hide + */ + public void setIccIdAccessRestricted(boolean iccIdAccessRestricted) { + this.mIccIdAccessRestricted = iccIdAccessRestricted; + } + + @Override public boolean equals(Object obj) { if (this == obj) { @@ -167,13 +237,16 @@ public final class UiccCardInfo implements Parcelable { && (mCardId == that.mCardId) && (Objects.equals(mEid, that.mEid)) && (Objects.equals(mIccId, that.mIccId)) - && (mSlotIndex == that.mSlotIndex) - && (mIsRemovable == that.mIsRemovable)); + && (mPhysicalSlotIndex == that.mPhysicalSlotIndex) + && (mIsRemovable == that.mIsRemovable) + && (mIsMultipleEnabledProfilesSupported == that.mIsMultipleEnabledProfilesSupported) + && (Objects.equals(mPortList, that.mPortList))); } @Override public int hashCode() { - return Objects.hash(mIsEuicc, mCardId, mEid, mIccId, mSlotIndex, mIsRemovable); + return Objects.hash(mIsEuicc, mCardId, mEid, mIccId, mPhysicalSlotIndex, mIsRemovable, + mIsMultipleEnabledProfilesSupported, mPortList); } @Override @@ -185,11 +258,17 @@ public final class UiccCardInfo implements Parcelable { + ", mEid=" + mEid + ", mIccId=" - + mIccId - + ", mSlotIndex=" - + mSlotIndex + + SubscriptionInfo.givePrintableIccid(mIccId) + + ", mPhysicalSlotIndex=" + + mPhysicalSlotIndex + ", mIsRemovable=" + mIsRemovable + + ", mIsMultipleEnabledProfilesSupported=" + + mIsMultipleEnabledProfilesSupported + + ", mPortList=" + + mPortList + + ", mIccIdAccessRestricted=" + + mIccIdAccessRestricted + ")"; } -} +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/UiccPortInfo.aidl b/telephony/java/android/telephony/UiccPortInfo.aidl new file mode 100644 index 000000000000..7fff4ba5c651 --- /dev/null +++ b/telephony/java/android/telephony/UiccPortInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable UiccPortInfo; diff --git a/telephony/java/android/telephony/UiccPortInfo.java b/telephony/java/android/telephony/UiccPortInfo.java new file mode 100644 index 000000000000..d1838c0b91f4 --- /dev/null +++ b/telephony/java/android/telephony/UiccPortInfo.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * UiccPortInfo class represents information about a single port contained on {@link UiccCardInfo}. + * Per GSMA SGP.22 V3.0, a port is a logical entity to which an active UICC profile can be bound on + * a UICC card. If UICC supports 2 ports, then the port index is numbered 0,1. + * Each port index is unique within an UICC, but not necessarily unique across UICC’s. + * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0. + */ +public final class UiccPortInfo implements Parcelable{ + private final String mIccId; + private final int mPortIndex; + private final int mLogicalSlotIndex; + private final boolean mIsActive; + + /** + * A redacted String if caller does not have permission to read ICCID. + */ + public static final String ICCID_REDACTED = "FFFFFFFFFFFFFFFFFFFF"; + + public static final @NonNull Creator<UiccPortInfo> CREATOR = + new Creator<UiccPortInfo>() { + @Override + public UiccPortInfo createFromParcel(Parcel in) { + return new UiccPortInfo(in); + } + @Override + public UiccPortInfo[] newArray(int size) { + return new UiccPortInfo[size]; + } + }; + + private UiccPortInfo(Parcel in) { + mIccId = in.readString8(); + mPortIndex = in.readInt(); + mLogicalSlotIndex = in.readInt(); + mIsActive = in.readBoolean(); + } + + @Override + public void writeToParcel(@Nullable Parcel dest, int flags) { + dest.writeString8(mIccId); + dest.writeInt(mPortIndex); + dest.writeInt(mLogicalSlotIndex); + dest.writeBoolean(mIsActive); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Construct a UiccPortInfo. + * + * @param iccId The ICCID of the profile. + * @param portIndex The port index is an enumeration of the ports available on the UICC. + * @param logicalSlotIndex is unique index referring to a logical SIM slot. + * @param isActive is flag to check if port was tied to a modem stack. + * + * @hide + */ + public UiccPortInfo(String iccId, int portIndex, int logicalSlotIndex, boolean isActive) { + this.mIccId = iccId; + this.mPortIndex = portIndex; + this.mLogicalSlotIndex = logicalSlotIndex; + this.mIsActive = isActive; + } + + /** + * Get the ICCID of the profile associated with this port. + * If this port is not {@link #isActive()}, returns {@code null}. + * If the caller does not have access to the ICCID for this port, it will be redacted and + * {@link #ICCID_REDACTED} will be returned. + */ + public @Nullable String getIccId() { + return mIccId; + } + + /** + * The port index is an enumeration of the ports available on the UICC. + * Example: if eUICC1 supports 2 ports, then the port index is numbered 0,1. + * Each port index is unique within an UICC, but not necessarily unique across UICC’s. + * For UICC's does not support MEP(Multi-enabled profile), just return the default port index 0. + */ + @IntRange(from = 0) + public int getPortIndex() { + return mPortIndex; + } + + /** + * @return {@code true} if port was tied to a modem stack. + */ + public boolean isActive() { + return mIsActive; + } + + /** + * Gets logical slot index for the slot that the UICC is currently attached. + * Logical slot index or ID: unique index referring to a logical SIM slot. + * Logical slot IDs start at 0 and go up depending on the number of supported active slots on + * a device. + * For example, a dual-SIM device typically has slot 0 and slot 1. + * If a device has multiple physical slots but only supports one active slot, + * it will have only the logical slot ID 0. + * + * @return the logical slot index for UICC port, if there is no logical slot index it returns + * {@link SubscriptionManager#INVALID_SIM_SLOT_INDEX} + */ + @IntRange(from = 0) + public int getLogicalSlotIndex() { + return mLogicalSlotIndex; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + UiccPortInfo that = (UiccPortInfo) obj; + return (Objects.equals(mIccId, that.mIccId)) + && (mPortIndex == that.mPortIndex) + && (mLogicalSlotIndex == that.mLogicalSlotIndex) + && (mIsActive == that.mIsActive); + } + + @Override + public int hashCode() { + return Objects.hash(mIccId, mPortIndex, mLogicalSlotIndex, mIsActive); + } + + @NonNull + @Override + public String toString() { + return "UiccPortInfo (isActive=" + + mIsActive + + ", iccId=" + + SubscriptionInfo.givePrintableIccid(mIccId) + + ", portIndex=" + + mPortIndex + + ", mLogicalSlotIndex=" + + mLogicalSlotIndex + + ")"; + } +} diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java index a0e949aa7fc5..2b1c8c863eb6 100644 --- a/telephony/java/android/telephony/UiccSlotInfo.java +++ b/telephony/java/android/telephony/UiccSlotInfo.java @@ -24,6 +24,10 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Objects; /** @@ -64,8 +68,10 @@ public class UiccSlotInfo implements Parcelable { private final int mLogicalSlotIdx; private final boolean mIsExtendedApduSupported; private final boolean mIsRemovable; + private final List<UiccPortInfo> mPortList; + private boolean mLogicalSlotAccessRestricted = false; - public static final @android.annotation.NonNull Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() { + public static final @NonNull Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() { @Override public UiccSlotInfo createFromParcel(Parcel in) { return new UiccSlotInfo(in); @@ -78,24 +84,29 @@ public class UiccSlotInfo implements Parcelable { }; private UiccSlotInfo(Parcel in) { - mIsActive = in.readByte() != 0; - mIsEuicc = in.readByte() != 0; - mCardId = in.readString(); + mIsActive = in.readBoolean(); + mIsEuicc = in.readBoolean(); + mCardId = in.readString8(); mCardStateInfo = in.readInt(); mLogicalSlotIdx = in.readInt(); - mIsExtendedApduSupported = in.readByte() != 0; - mIsRemovable = in.readByte() != 0; + mIsExtendedApduSupported = in.readBoolean(); + mIsRemovable = in.readBoolean(); + mPortList = new ArrayList<UiccPortInfo>(); + in.readTypedList(mPortList, UiccPortInfo.CREATOR); + mLogicalSlotAccessRestricted = in.readBoolean(); } @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeByte((byte) (mIsActive ? 1 : 0)); - dest.writeByte((byte) (mIsEuicc ? 1 : 0)); - dest.writeString(mCardId); + dest.writeBoolean(mIsActive); + dest.writeBoolean(mIsEuicc); + dest.writeString8(mCardId); dest.writeInt(mCardStateInfo); dest.writeInt(mLogicalSlotIdx); - dest.writeByte((byte) (mIsExtendedApduSupported ? 1 : 0)); - dest.writeByte((byte) (mIsRemovable ? 1 : 0)); + dest.writeBoolean(mIsExtendedApduSupported); + dest.writeBoolean(mIsRemovable); + dest.writeTypedList(mPortList, flags); + dest.writeBoolean(mLogicalSlotAccessRestricted); } @Override @@ -117,25 +128,42 @@ public class UiccSlotInfo implements Parcelable { this.mLogicalSlotIdx = logicalSlotIdx; this.mIsExtendedApduSupported = isExtendedApduSupported; this.mIsRemovable = false; + this.mPortList = null; } /** + * Construct a UiccSlotInfo. * @hide */ - public UiccSlotInfo(boolean isActive, boolean isEuicc, String cardId, - @CardStateInfo int cardStateInfo, int logicalSlotIdx, boolean isExtendedApduSupported, - boolean isRemovable) { - this.mIsActive = isActive; + public UiccSlotInfo(boolean isEuicc, String cardId, + @CardStateInfo int cardStateInfo, boolean isExtendedApduSupported, + boolean isRemovable, @NonNull List<UiccPortInfo> portList) { + this.mIsActive = portList.get(0).isActive(); this.mIsEuicc = isEuicc; this.mCardId = cardId; this.mCardStateInfo = cardStateInfo; - this.mLogicalSlotIdx = logicalSlotIdx; + this.mLogicalSlotIdx = portList.get(0).getLogicalSlotIndex(); this.mIsExtendedApduSupported = isExtendedApduSupported; this.mIsRemovable = isRemovable; + this.mPortList = portList; } + /** + * @deprecated There is no longer isActive state for each slot because ports belonging + * to the physical slot could have different states + * we instead use {@link UiccPortInfo#isActive()} + * To get UiccPortInfo use {@link UiccSlotInfo#getPorts()} + * + * @return {@code true} if status is active. + * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond. + */ + @Deprecated public boolean getIsActive() { - return mIsActive; + if (mLogicalSlotAccessRestricted) { + throw new UnsupportedOperationException("get port status from UiccPortInfo"); + } + //always return status from first port. + return getPorts().stream().findFirst().get().isActive(); } public boolean getIsEuicc() { @@ -159,8 +187,21 @@ public class UiccSlotInfo implements Parcelable { return mCardStateInfo; } + /** + * @deprecated There is no longer getLogicalSlotIndex + * There is no longer getLogicalSlotIdx as each port belonging to this physical slot could have + * different logical slot index. Use {@link UiccPortInfo#getLogicalSlotIndex()} instead + * + * @throws UnsupportedOperationException if the calling app's target SDK is T and beyond. + */ + @Deprecated public int getLogicalSlotIdx() { - return mLogicalSlotIdx; + if (mLogicalSlotAccessRestricted) { + throw new UnsupportedOperationException("get logical slot index from UiccPortInfo"); + } + //always return logical slot index from first port. + //portList always have at least one element. + return getPorts().stream().findFirst().get().getLogicalSlotIndex(); } /** @@ -170,16 +211,37 @@ public class UiccSlotInfo implements Parcelable { return mIsExtendedApduSupported; } - /** + /** * Return whether the UICC slot is for a removable UICC. * <p> * UICCs are generally removable, but eUICCs may be removable or built in to the device. + * * @return true if the slot is for removable UICCs */ public boolean isRemovable() { return mIsRemovable; } + /** + * Get Information regarding port, iccid and its active status. + * + * @return Collection of {@link UiccPortInfo} + */ + public @NonNull Collection<UiccPortInfo> getPorts() { + return Collections.unmodifiableList(mPortList); + } + + /** + * Set the flag to check compatibility of the calling app's target SDK is T and beyond. + * + * @param logicalSlotAccessRestricted is the flag to check compatibility. + * + * @hide + */ + public void setLogicalSlotAccessRestricted(boolean logicalSlotAccessRestricted) { + this.mLogicalSlotAccessRestricted = logicalSlotAccessRestricted; + } + @Override public boolean equals(@Nullable Object obj) { if (this == obj) { @@ -196,20 +258,14 @@ public class UiccSlotInfo implements Parcelable { && (mCardStateInfo == that.mCardStateInfo) && (mLogicalSlotIdx == that.mLogicalSlotIdx) && (mIsExtendedApduSupported == that.mIsExtendedApduSupported) - && (mIsRemovable == that.mIsRemovable); + && (mIsRemovable == that.mIsRemovable) + && (Objects.equals(mPortList, that.mPortList)); } @Override public int hashCode() { - int result = 1; - result = 31 * result + (mIsActive ? 1 : 0); - result = 31 * result + (mIsEuicc ? 1 : 0); - result = 31 * result + Objects.hashCode(mCardId); - result = 31 * result + mCardStateInfo; - result = 31 * result + mLogicalSlotIdx; - result = 31 * result + (mIsExtendedApduSupported ? 1 : 0); - result = 31 * result + (mIsRemovable ? 1 : 0); - return result; + return Objects.hash(mIsActive, mIsEuicc, mCardId, mCardStateInfo, mLogicalSlotIdx, + mIsExtendedApduSupported, mIsRemovable, mPortList); } @NonNull @@ -229,6 +285,10 @@ public class UiccSlotInfo implements Parcelable { + mIsExtendedApduSupported + ", mIsRemovable=" + mIsRemovable + + ", mPortList=" + + mPortList + + ", mLogicalSlotAccessRestricted=" + + mLogicalSlotAccessRestricted + ")"; } } diff --git a/telephony/java/android/telephony/UiccSlotMapping.aidl b/telephony/java/android/telephony/UiccSlotMapping.aidl new file mode 100644 index 000000000000..3b1949958935 --- /dev/null +++ b/telephony/java/android/telephony/UiccSlotMapping.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +parcelable UiccSlotMapping; diff --git a/telephony/java/android/telephony/UiccSlotMapping.java b/telephony/java/android/telephony/UiccSlotMapping.java new file mode 100644 index 000000000000..87e7acdc792d --- /dev/null +++ b/telephony/java/android/telephony/UiccSlotMapping.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * <p>Provides information for a SIM slot mapping, which establishes a unique mapping between a + * logical SIM slot and a physical SIM slot and port index. A logical SIM slot represents a + * potentially active SIM slot, where a physical SIM slot and port index represent a hardware SIM + * slot and port (capable of having an active profile) which can be mapped to a logical sim slot. + * <p>It contains the following parameters: + * <ul> + * <li>Port index: unique index referring to a port belonging to the physical SIM slot. + * If the SIM does not support multiple enabled profiles, the port index is default index 0.</li> + * <li>Physical slot index: unique index referring to a physical SIM slot. Physical slot IDs start + * at 0 and go up depending on the number of physical slots on the device. + * This differs from the number of logical slots a device has, which corresponds to the number of + * active slots a device is capable of using. For example, a device which switches between dual-SIM + * and single-SIM mode may always have two physical slots, but in single-SIM mode it will have only + * one logical slot.</li> + * <li>Logical slot index: unique index referring to a logical SIM slot, Logical slot IDs start at 0 + * and go up depending on the number of supported active slots on a device. + * For example, a dual-SIM device typically has slot 0 and slot 1. If a device has multiple physical + * slots but only supports one active slot, it will have only the logical slot ID 0</li> + * </ul> + * + * <p> This configurations tells a specific logical slot is mapped to a port from an actual physical + * sim slot @see <a href="https://developer.android.com/guide/topics/connectivity/telecom/telephony-ids">the Android Developer Site</a> + * for more information. + * @hide + */ +@SystemApi +public final class UiccSlotMapping implements Parcelable { + private final int mPortIndex; + private final int mPhysicalSlotIndex; + private final int mLogicalSlotIndex; + + public static final @NonNull Creator<UiccSlotMapping> CREATOR = + new Creator<UiccSlotMapping>() { + @Override + public UiccSlotMapping createFromParcel(Parcel in) { + return new UiccSlotMapping(in); + } + + @Override + public UiccSlotMapping[] newArray(int size) { + return new UiccSlotMapping[size]; + } + }; + + private UiccSlotMapping(Parcel in) { + mPortIndex = in.readInt(); + mPhysicalSlotIndex = in.readInt(); + mLogicalSlotIndex = in.readInt(); + } + + @Override + public void writeToParcel(@Nullable Parcel dest, int flags) { + dest.writeInt(mPortIndex); + dest.writeInt(mPhysicalSlotIndex); + dest.writeInt(mLogicalSlotIndex); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * + * @param portIndex The port index is an enumeration of the ports available on the UICC. + * @param physicalSlotIndex is unique index referring to a physical SIM slot. + * @param logicalSlotIndex is unique index referring to a logical SIM slot. + * + * @hide + */ + public UiccSlotMapping(int portIndex, int physicalSlotIndex, int logicalSlotIndex) { + this.mPortIndex = portIndex; + this.mPhysicalSlotIndex = physicalSlotIndex; + this.mLogicalSlotIndex = logicalSlotIndex; + } + + /** + * Port index is the unique index referring to a port belonging to the physical SIM slot. + * If the SIM does not support multiple enabled profiles, the port index is default index 0. + * + * @return port index. + */ + @IntRange(from = 0) + public int getPortIndex() { + return mPortIndex; + } + + /** + * Gets the physical slot index for the slot that the UICC is currently inserted in. + * + * @return physical slot index which is the index of actual physical UICC slot. + */ + @IntRange(from = 0) + public int getPhysicalSlotIndex() { + return mPhysicalSlotIndex; + } + + /** + * Gets logical slot index for the slot that the UICC is currently attached. + * Logical slot index is the unique index referring to a logical slot(logical modem stack). + * + * @return logical slot index; + */ + @IntRange(from = 0) + public int getLogicalSlotIndex() { + return mLogicalSlotIndex; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + UiccSlotMapping that = (UiccSlotMapping) obj; + return (mPortIndex == that.mPortIndex) + && (mPhysicalSlotIndex == that.mPhysicalSlotIndex) + && (mLogicalSlotIndex == that.mLogicalSlotIndex); + } + + @Override + public int hashCode() { + return Objects.hash(mPortIndex, mPhysicalSlotIndex, mLogicalSlotIndex); + } + + @NonNull + @Override + public String toString() { + return "UiccSlotMapping (mPortIndex=" + + mPortIndex + + ", mPhysicalSlotIndex=" + + mPhysicalSlotIndex + + ", mLogicalSlotIndex=" + + mLogicalSlotIndex + + ")"; + } +} diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java index 2f034752ae5f..892eb2937491 100644 --- a/telephony/java/android/telephony/data/DataService.java +++ b/telephony/java/android/telephony/data/DataService.java @@ -402,6 +402,21 @@ public abstract class DataService extends Service { } /** + * Notify the system that a given DataProfile was unthrottled. + * + * @param dataProfile DataProfile associated with an APN returned from the modem + */ + public final void notifyDataProfileUnthrottled(@NonNull DataProfile dataProfile) { + synchronized (mApnUnthrottledCallbacks) { + for (IDataServiceCallback callback : mApnUnthrottledCallbacks) { + mHandler.obtainMessage(DATA_SERVICE_INDICATION_APN_UNTHROTTLED, + mSlotIndex, 0, new ApnUnthrottledIndication(dataProfile, + callback)).sendToTarget(); + } + } + } + + /** * Called when the instance of data service is destroyed (e.g. got unbind or binder died) * or when the data service provider is removed. The extended class should implement this * method to perform cleanup works. @@ -496,13 +511,20 @@ public abstract class DataService extends Service { } private static final class ApnUnthrottledIndication { + public final DataProfile dataProfile; public final String apn; public final IDataServiceCallback callback; ApnUnthrottledIndication(String apn, IDataServiceCallback callback) { + this.dataProfile = null; this.apn = apn; this.callback = callback; } + ApnUnthrottledIndication(DataProfile dataProfile, IDataServiceCallback callback) { + this.dataProfile = dataProfile; + this.apn = null; + this.callback = callback; + } } private class DataServiceHandler extends Handler { @@ -636,8 +658,13 @@ public abstract class DataService extends Service { ApnUnthrottledIndication apnUnthrottledIndication = (ApnUnthrottledIndication) message.obj; try { - apnUnthrottledIndication.callback - .onApnUnthrottled(apnUnthrottledIndication.apn); + if (apnUnthrottledIndication.dataProfile != null) { + apnUnthrottledIndication.callback + .onDataProfileUnthrottled(apnUnthrottledIndication.dataProfile); + } else { + apnUnthrottledIndication.callback + .onApnUnthrottled(apnUnthrottledIndication.apn); + } } catch (RemoteException e) { loge("Failed to call onApnUnthrottled. " + e); } diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java index d0827159b98d..051d6c5d5ec0 100644 --- a/telephony/java/android/telephony/data/DataServiceCallback.java +++ b/telephony/java/android/telephony/data/DataServiceCallback.java @@ -260,8 +260,8 @@ public class DataServiceCallback { /** * Unthrottles the APN on the current transport. There is no matching "APN throttle" method. - * Instead, the APN is throttled for the time specified in - * {@link DataCallResponse#getRetryDurationMillis}. + * Instead, the APN is throttled when {@link IDataService#setupDataCall} fails within + * the time specified by {@link DataCallResponse#getRetryDurationMillis}. * <p/> * see: {@link DataCallResponse#getRetryDurationMillis} * @@ -279,4 +279,27 @@ public class DataServiceCallback { Rlog.e(TAG, "onApnUnthrottled: callback is null!"); } } + + /** + * Unthrottles the DataProfile on the current transport. + * There is no matching "DataProfile throttle" method. + * Instead, the DataProfile is throttled when {@link IDataService#setupDataCall} fails within + * the time specified by {@link DataCallResponse#getRetryDurationMillis}. + * <p/> + * see: {@link DataCallResponse#getRetryDurationMillis} + * + * @param dataProfile DataProfile containing the APN to be throttled + */ + public void onDataProfileUnthrottled(final @NonNull DataProfile dataProfile) { + if (mCallback != null) { + try { + if (DBG) Rlog.d(TAG, "onDataProfileUnthrottled"); + mCallback.onDataProfileUnthrottled(dataProfile); + } catch (RemoteException e) { + Rlog.e(TAG, "onDataProfileUnthrottled: remote exception", e); + } + } else { + Rlog.e(TAG, "onDataProfileUnthrottled: callback is null!"); + } + } } diff --git a/telephony/java/android/telephony/data/IDataServiceCallback.aidl b/telephony/java/android/telephony/data/IDataServiceCallback.aidl index 9cc2feac331a..8205b5eb4d37 100644 --- a/telephony/java/android/telephony/data/IDataServiceCallback.aidl +++ b/telephony/java/android/telephony/data/IDataServiceCallback.aidl @@ -17,6 +17,7 @@ package android.telephony.data; import android.telephony.data.DataCallResponse; +import android.telephony.data.DataProfile; /** * The call back interface @@ -33,4 +34,5 @@ oneway interface IDataServiceCallback void onHandoverStarted(int result); void onHandoverCancelled(int result); void onApnUnthrottled(in String apn); + void onDataProfileUnthrottled(in DataProfile dp); } diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index e1aec0a593b4..ab35d77c0b4d 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -17,6 +17,7 @@ package android.telephony.euicc; import android.annotation.CallbackExecutor; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; @@ -24,6 +25,7 @@ import android.os.Binder; import android.os.RemoteException; import android.service.euicc.EuiccProfileInfo; import android.telephony.TelephonyFrameworkInitializer; +import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.telephony.euicc.IAuthenticateServerCallback; @@ -122,7 +124,6 @@ public class EuiccCardManager { /** Result code indicating the caller is not the active LPA. */ public static final int RESULT_CALLER_NOT_ALLOWED = -3; - /** * Callback to receive the result of an eUICC card API. * @@ -220,12 +221,48 @@ public class EuiccCardManager { * @param refresh Whether sending the REFRESH command to modem. * @param executor The executor through which the callback should be invoked. * @param callback The callback to get the result code. + * + * @deprecated instead use {@link #disableProfile(String, String, int, boolean, Executor, + * ResultCallback)} */ + @Deprecated public void disableProfile(String cardId, String iccid, boolean refresh, @CallbackExecutor Executor executor, ResultCallback<Void> callback) { try { getIEuiccCardController().disableProfile(mContext.getOpPackageName(), cardId, iccid, - refresh, new IDisableProfileCallback.Stub() { + TelephonyManager.DEFAULT_PORT_INDEX, refresh, + new IDisableProfileCallback.Stub() { + @Override + public void onComplete(int resultCode) { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, null)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling disableProfile", e); + throw e.rethrowFromSystemServer(); + } + } + /** + * Disables the profile of the given ICCID. + * + * @param cardId The Id of the eUICC. + * @param iccid The iccid of the profile. + * @param portIndex the Port index is the unique index referring to a port. + * @param refresh Whether sending the REFRESH command to modem. + * @param executor The executor through which the callback should be invoked. + * @param callback The callback to get the result code. + */ + public void disableProfile(@Nullable String cardId, @Nullable String iccid, int portIndex, + boolean refresh, @NonNull @CallbackExecutor Executor executor, + @NonNull ResultCallback<Void> callback) { + try { + getIEuiccCardController().disableProfile(mContext.getOpPackageName(), cardId, iccid, + portIndex, refresh, new IDisableProfileCallback.Stub() { @Override public void onComplete(int resultCode) { final long token = Binder.clearCallingIdentity(); @@ -251,12 +288,51 @@ public class EuiccCardManager { * @param refresh Whether sending the REFRESH command to modem. * @param executor The executor through which the callback should be invoked. * @param callback The callback to get the result code and the EuiccProfileInfo enabled. + * + * @deprecated instead use {@link #switchToProfile(String, String, int, boolean, Executor, + * ResultCallback)} */ + @Deprecated public void switchToProfile(String cardId, String iccid, boolean refresh, @CallbackExecutor Executor executor, ResultCallback<EuiccProfileInfo> callback) { try { getIEuiccCardController().switchToProfile(mContext.getOpPackageName(), cardId, iccid, - refresh, new ISwitchToProfileCallback.Stub() { + TelephonyManager.DEFAULT_PORT_INDEX, refresh, + new ISwitchToProfileCallback.Stub() { + @Override + public void onComplete(int resultCode, EuiccProfileInfo profile) { + final long token = Binder.clearCallingIdentity(); + try { + executor.execute(() -> callback.onComplete(resultCode, profile)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + }); + } catch (RemoteException e) { + Log.e(TAG, "Error calling switchToProfile", e); + throw e.rethrowFromSystemServer(); + } + } + + /** + * Switches from the current profile to another profile. The current profile will be disabled + * and the specified profile will be enabled. Here portIndex specifies on which port the + * profile is to be enabled. + * + * @param cardId The Id of the eUICC. + * @param iccid The iccid of the profile to switch to. + * @param portIndex The Port index is the unique index referring to a port. + * @param refresh Whether sending the REFRESH command to modem. + * @param executor The executor through which the callback should be invoked. + * @param callback The callback to get the result code and the EuiccProfileInfo enabled. + */ + public void switchToProfile(@Nullable String cardId, @Nullable String iccid, int portIndex, + boolean refresh, @NonNull @CallbackExecutor Executor executor, + @NonNull ResultCallback<EuiccProfileInfo> callback) { + try { + getIEuiccCardController().switchToProfile(mContext.getOpPackageName(), cardId, iccid, + portIndex, refresh, new ISwitchToProfileCallback.Stub() { @Override public void onComplete(int resultCode, EuiccProfileInfo profile) { final long token = Binder.clearCallingIdentity(); diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 2edb564cc950..45022a6e4d8c 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -1418,4 +1418,22 @@ public class EuiccManager { .getEuiccControllerService() .get()); } + + /** + * Returns whether the passing portIndex is available. + * A port is available if it has no profiles enabled on it or calling app has carrier privilege + * over the profile installed on the selected port. + * Always returns false if the cardId is a physical card. + * + * @param portIndex is an enumeration of the ports available on the UICC. + * @return {@code true} if port is available + */ + public boolean isSimPortAvailable(int portIndex) { + try { + return getIEuiccController().isSimPortAvailable(mCardId, portIndex, + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 6493772039e6..a900c84c1819 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -304,4 +304,13 @@ interface ISub { int setDeviceToDeviceStatusSharing(int sharing, int subId); int setDeviceToDeviceStatusSharingContacts(String contacts, int subscriptionId); + + String getPhoneNumber(int subId, int source, + String callingPackage, String callingFeatureId); + + String getPhoneNumberFromFirstAvailableSource(int subId, + String callingPackage, String callingFeatureId); + + void setPhoneNumber(int subId, int source, String number, + String callingPackage, String callingFeatureId); } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 55778883edde..167aa07f3ac2 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -77,6 +77,7 @@ import java.util.Map; import android.telephony.UiccCardInfo; import android.telephony.UiccSlotInfo; +import android.telephony.UiccSlotMapping; /** * Interface used to interact with the phone. Mostly this is used by the @@ -1742,17 +1743,35 @@ interface ITelephony { * @return UiccSlotInfo array. * @hide */ - UiccSlotInfo[] getUiccSlotsInfo(); + UiccSlotInfo[] getUiccSlotsInfo(String callingPackage); /** * Map logicalSlot to physicalSlot, and activate the physicalSlot if it is inactive. * @param physicalSlots Index i in the array representing physical slot for phone i. The array * size should be same as getPhoneCount(). + * @deprecated Use {@link #setSimSlotMapping(in List<UiccSlotMapping> slotMapping)} instead. * @return boolean Return true if the switch succeeds, false if the switch fails. */ boolean switchSlots(in int[] physicalSlots); /** + * Maps the logical slots to the SlotPortMapping which consist of both physical slot index and + * port index. Logical slot is the slot that is seen by modem. Physical slot is the actual + * physical slot. Port index is the index (enumerated value) for the associated port available + * on the SIM. Each physical slot can have multiple ports which enables multi-enabled profile + * (MEP). If eUICC physical slot supports 2 ports, then the port index is numbered 0,1 and if + * eUICC2 supports 4 ports then the port index is numbered 0,1,2,3. Each portId is unique within + * a UICC physical slot but not necessarily unique across UICC’s. SEP(Single enabled profile) + * eUICC and non-eUICC will only have port Index 0. + * + * Logical slots that are already mapped to the requested SlotPortMapping are not impacted. + * @param slotMapping Index i in the list representing slot mapping for phone i. + * + * @return {@code true} if the switch succeeds, {@code false} if the switch fails. + */ + boolean setSimSlotMapping(in List<UiccSlotMapping> slotMapping); + + /** * Returns whether mobile data roaming is enabled on the subscription with id {@code subId}. * * @param subId the subscription id @@ -2130,7 +2149,7 @@ interface ITelephony { /** * Get the mapping from logical slots to physical slots. */ - int[] getSlotsMapping(); + int[] getSlotsMapping(String callingPackage); /** * Get the IRadio HAL Version encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java index 3a99f0e010c6..f6502466e25e 100644 --- a/telephony/java/com/android/internal/telephony/PhoneConstants.java +++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java @@ -171,6 +171,8 @@ public class PhoneConstants { public static final String SLOT_KEY = "slot"; + public static final String PORT_KEY = "port"; + // FIXME: This is used to pass a subId via intents, we need to look at its usage, which is // FIXME: extensive, and see if this should be an array of all active subId's or ...? /** diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index 866fd2c7394e..ba9584112b75 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -530,6 +530,8 @@ public interface RILConstants { int RIL_REQUEST_GET_SLICING_CONFIG = 224; int RIL_REQUEST_ENABLE_VONR = 225; int RIL_REQUEST_IS_VONR_ENABLED = 226; + int RIL_REQUEST_SET_USAGE_SETTING = 227; + int RIL_REQUEST_GET_USAGE_SETTING = 228; /* Responses begin */ int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl index e33f44c7b7d9..c717c092cbed 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccCardController.aidl @@ -45,10 +45,10 @@ interface IEuiccCardController { in IGetAllProfilesCallback callback); oneway void getProfile(String callingPackage, String cardId, String iccid, in IGetProfileCallback callback); - oneway void disableProfile(String callingPackage, String cardId, String iccid, boolean refresh, - in IDisableProfileCallback callback); - oneway void switchToProfile(String callingPackage, String cardId, String iccid, boolean refresh, - in ISwitchToProfileCallback callback); + oneway void disableProfile(String callingPackage, String cardId, String iccid, int portIndex, + boolean refresh, in IDisableProfileCallback callback); + oneway void switchToProfile(String callingPackage, String cardId, String iccid, int portIndex, + boolean refresh, in ISwitchToProfileCallback callback); oneway void setNickname(String callingPackage, String cardId, String iccid, String nickname, in ISetNicknameCallback callback); oneway void deleteProfile(String callingPackage, String cardId, String iccid, diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index 35e8a12e898a..944ce3486ca6 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -51,4 +51,5 @@ interface IEuiccController { void setSupportedCountries(boolean isSupported, in List<String> countriesList); List<String> getSupportedCountries(boolean isSupported); boolean isSupportedCountry(String countryIso); + boolean isSimPortAvailable(int cardId, int portIndex, String callingPackage); } diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java index 5b44dba49929..21c3f760771c 100644 --- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java +++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java @@ -23,6 +23,7 @@ import android.content.res.Resources.NotFoundException; import android.graphics.Bitmap; import android.graphics.Color; import android.os.Build; +import android.telephony.UiccPortInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.GsmAlphabet; @@ -44,8 +45,7 @@ public class IccUtils { static final int FPLMN_BYTE_SIZE = 3; // ICCID used for tests by some OEMs - // TODO(b/159354974): Replace the constant here with UiccPortInfo.ICCID_REDACTED once ready - private static final String TEST_ICCID = "FFFFFFFFFFFFFFFFFFFF"; + public static final String TEST_ICCID = UiccPortInfo.ICCID_REDACTED; // A table mapping from a number to a hex character for fast encoding hex strings. private static final char[] HEX_CHARS = { diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk index ce5e4cf46695..284008c5be71 100644 --- a/test-legacy/Android.mk +++ b/test-legacy/Android.mk @@ -41,6 +41,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ include $(BUILD_STATIC_JAVA_LIBRARY) # Archive a copy of the classes.jar in SDK build. -$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) +$(call dist-for-goals,sdk,$(full_classes_jar):android.test.legacy.jar) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index f0ab63eb41b5..06200cd99b5b 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -449,8 +449,7 @@ public class StagedInstallInternalTest { // Query proper module name result = getPackageManagerNative().getStagedApexInfo(TEST_APEX_PACKAGE_NAME); assertThat(result.moduleName).isEqualTo(TEST_APEX_PACKAGE_NAME); - assertThat(result.hasBootClassPathJars).isTrue(); - assertThat(result.hasSystemServerClassPathJars).isTrue(); + assertThat(result.hasClassPathJars).isTrue(); InstallUtils.openPackageInstallerSession(sessionId).abandon(); } |