diff --git a/api/current.txt b/api/current.txt index 2b8f3a7102d9c..14130f4c32d06 100644 --- a/api/current.txt +++ b/api/current.txt @@ -44683,6 +44683,7 @@ package android.telephony { method public String getNumber(); method public int getSimSlotIndex(); method public int getSubscriptionId(); + method public int getSubscriptionType(); method public boolean isEmbedded(); method public boolean isOpportunistic(); method public void writeToParcel(android.os.Parcel, int); @@ -44733,6 +44734,8 @@ 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 SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0 + field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1 } public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener { diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index fc47f67f174ca..ee2fe8f5750fd 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -3518,7 +3518,7 @@ Lcom/android/internal/telephony/SubscriptionController;->mDefaultPhoneId:I Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object; Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V -Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V +Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 1378bb004696a..d777bf123b67d 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -22,6 +22,10 @@ import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; import android.app.ActivityThread; import android.app.PendingIntent; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothMapClient; +import android.bluetooth.BluetoothProfile; import android.content.ActivityNotFoundException; import android.content.ContentValues; import android.content.Context; @@ -32,6 +36,7 @@ import android.os.Build; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; +import android.telecom.PhoneAccount; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; @@ -61,6 +66,8 @@ import java.util.Map; */ public final class SmsManager { private static final String TAG = "SmsManager"; + private static final boolean DBG = false; + /** * A psuedo-subId that represents the default subId at any given time. The actual subId it * represents changes as the default subId is changed. @@ -339,6 +346,44 @@ public final class SmsManager { throw new IllegalArgumentException("Invalid message body"); } + // A Manager code accessing another manager is *not* acceptable, in Android. + // In this particular case, it is unavoidable because of the following: + // If the subscription for this SmsManager instance belongs to a remote SIM + // then a listener to get BluetoothMapClient proxy needs to be started up. + // Doing that is possible only in a foreground thread or as a system user. + // i.e., Can't be done in ISms service. + // For that reason, SubscriptionManager needs to be accessed here to determine + // if the subscription belongs to a remote SIM. + // Ideally, there should be another API in ISms to service messages going thru + // remote SIM subscriptions (and ISms should be tweaked to be able to access + // BluetoothMapClient proxy) + Context context = ActivityThread.currentApplication().getApplicationContext(); + SubscriptionManager manager = (SubscriptionManager) context + .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + int subId = getSubscriptionId(); + SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId); + if (DBG) { + Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info); + } + if (info == null) { + // There is no subscription for the given subId. That can only mean one thing: + // the caller is using a SmsManager instance with an obsolete subscription id. + // That is most probably because caller didn't invalidate SmsManager instance + // for an already deleted subscription id. + Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete."); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + } + + /* If the Subscription associated with this SmsManager instance belongs to a remote-sim, + * then send the message thru the remote-sim subscription. + */ + if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) { + if (DBG) Log.d(TAG, "sending message thru bluetooth"); + sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent, + deliveryIntent, info); + return; + } + try { ISms iccISms = getISmsServiceOrThrow(); iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(), @@ -350,6 +395,79 @@ public final class SmsManager { } } + private void sendTextMessageBluetooth(String destAddr, String scAddress, + String text, PendingIntent sentIntent, PendingIntent deliveryIntent, + SubscriptionInfo info) { + BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); + if (btAdapter == null) { + // No bluetooth service on this platform? + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId()); + if (device == null) { + if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId()); + sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + return; + } + btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(), + new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent), + BluetoothProfile.MAP_CLIENT); + } + + private class MapMessageSender implements BluetoothProfile.ServiceListener { + final Uri[] mDestAddr; + private String mMessage; + final BluetoothDevice mDevice; + final PendingIntent mSentIntent; + final PendingIntent mDeliveryIntent; + MapMessageSender(final String destAddr, final String message, final BluetoothDevice device, + final PendingIntent sentIntent, final PendingIntent deliveryIntent) { + super(); + mDestAddr = new Uri[] {new Uri.Builder() + .appendPath(destAddr) + .scheme(PhoneAccount.SCHEME_TEL) + .build()}; + mMessage = message; + mDevice = device; + mSentIntent = sentIntent; + mDeliveryIntent = deliveryIntent; + } + + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + if (DBG) Log.d(TAG, "Service connected"); + if (profile != BluetoothProfile.MAP_CLIENT) return; + BluetoothMapClient mapProfile = (BluetoothMapClient) proxy; + if (mMessage != null) { + if (DBG) Log.d(TAG, "Sending message thru bluetooth"); + mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent); + mMessage = null; + } + BluetoothAdapter.getDefaultAdapter() + .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile); + } + + @Override + public void onServiceDisconnected(int profile) { + if (mMessage != null) { + if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message"); + sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE); + mMessage = null; + } + } + } + + private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) { + try { + intent.send(errorCode); + } catch (PendingIntent.CanceledException e) { + // PendingIntent is cancelled. ignore sending this error code back to + // caller. + if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage()); + } + } + /** * Send a text based SMS without writing it into the SMS Provider. * @@ -888,8 +1006,6 @@ public final class SmsManager { } } - - /** * Get the SmsManager associated with the default subscription id. The instance will always be * associated with the default subscription id, even if the default subscription id is changed. diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index 51d5ab17ee16c..a3b3374b183cd 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -183,6 +183,11 @@ public class SubscriptionInfo implements Parcelable { */ private int mProfileClass; + /** + * Type of subscription + */ + private int mSubscriptionType; + /** * @hide */ @@ -206,7 +211,8 @@ public class SubscriptionInfo implements Parcelable { @Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) { this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1, - isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass); + isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass, + SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM); } /** @@ -217,7 +223,7 @@ public class SubscriptionInfo implements Parcelable { Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded, @Nullable UiccAccessRule[] accessRules, String cardString, int cardId, boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered, - boolean isGroupDisabled, int carrierid, int profileClass) { + boolean isGroupDisabled, int carrierId, int profileClass, int subType) { this.mId = id; this.mIccId = iccId; this.mSimSlotIndex = simSlotIndex; @@ -239,11 +245,11 @@ public class SubscriptionInfo implements Parcelable { this.mGroupUUID = groupUUID; this.mIsMetered = isMetered; this.mIsGroupDisabled = isGroupDisabled; - this.mCarrierId = carrierid; + this.mCarrierId = carrierId; this.mProfileClass = profileClass; + this.mSubscriptionType = subType; } - /** * @return the subscription ID. */ @@ -486,6 +492,16 @@ public class SubscriptionInfo implements Parcelable { return this.mProfileClass; } + /** + * This method returns the type of a subscription. It can be + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or + * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @return the type of subscription + */ + public @SubscriptionManager.SubscriptionType int getSubscriptionType() { + return mSubscriptionType; + } + /** * Checks whether the app with the given context is authorized to manage this subscription * according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded} @@ -612,11 +628,12 @@ public class SubscriptionInfo implements Parcelable { boolean isGroupDisabled = source.readBoolean(); int carrierid = source.readInt(); int profileClass = source.readInt(); + int subType = source.readInt(); return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID, - isMetered, isGroupDisabled, carrierid, profileClass); + isMetered, isGroupDisabled, carrierid, profileClass, subType); } @Override @@ -650,6 +667,7 @@ public class SubscriptionInfo implements Parcelable { dest.writeBoolean(mIsGroupDisabled); dest.writeInt(mCarrierId); dest.writeInt(mProfileClass); + dest.writeInt(mSubscriptionType); } @Override @@ -686,7 +704,8 @@ public class SubscriptionInfo implements Parcelable { + " cardString=" + cardStringToPrint + " cardId=" + mCardId + " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID + " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled - + " profileClass=" + mProfileClass + "}"; + + " profileClass=" + mProfileClass + + " subscriptionType=" + mSubscriptionType + "}"; } @Override diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 9fa4c3ce899fd..a4b9ec4bab29a 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -247,7 +247,9 @@ public class SubscriptionManager { public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id"; /** - * TelephonyProvider column name for SIM ICC Identifier + * TelephonyProvider column name for a unique identifier for the subscription within the + * specific subscription type. For example, it contains SIM ICC Identifier subscriptions + * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices. *
Type: TEXT (String)
*/ /** @hide */ @@ -264,6 +266,63 @@ public class SubscriptionManager { /** @hide */ public static final int SIM_NOT_INSERTED = -1; + /** + * The slot-index for Bluetooth Remote-SIM subscriptions + * @hide + */ + public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX; + + /** + * TelephonyProvider column name Subscription-type. + *Type: INTEGER (int)
{@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions, + * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions. + * Default value is 0. + */ + /** @hide */ + public static final String SUBSCRIPTION_TYPE = "subscription_type"; + + /** + * This constant is to designate a subscription as a Local-SIM Subscription. + *A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the + * device. + *
+ */ + public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; + + /** + * This constant is to designate a subscription as a Remote-SIM Subscription. + *+ * A Remote-SIM subscription is for a SIM on a phone connected to this device via some + * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can + * be used for SMS, Voice and data by proxying data through the connected device. + * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs. + *
+ * + *+ * A Remote-SIM is available only as long the phone stays connected to this device. + * When the phone disconnects, Remote-SIM subscription is removed from this device and is + * no longer known. All data associated with the subscription, such as stored SMS, call logs, + * contacts etc, are removed from this device. + *
+ * + *+ * If the phone re-connects to this device, a new Remote-SIM subscription is created for + * the phone. The Subscription Id associated with the new subscription is different from + * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the + * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that + * was never seen before. + *
+ */ + public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SUBSCRIPTION_TYPE_"}, + value = { + SUBSCRIPTION_TYPE_LOCAL_SIM, + SUBSCRIPTION_TYPE_REMOTE_SIM}) + public @interface SubscriptionType {} + /** * TelephonyProvider column name for user displayed name. *Type: TEXT (String)
@@ -1145,7 +1204,7 @@ public class SubscriptionManager { } /** - * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted + * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}. * *Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} @@ -1427,23 +1486,86 @@ public class SubscriptionManager { logd("[addSubscriptionInfoRecord]- invalid slotIndex"); } - try { - ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); - if (iSub != null) { - // FIXME: This returns 1 on success, 0 on error should should we return it? - iSub.addSubInfoRecord(iccId, slotIndex); - } else { - logd("[addSubscriptionInfoRecord]- ISub service is null"); - } - } catch (RemoteException ex) { - // ignore it - } + addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM); // FIXME: Always returns null? return null; } + /** + * Add a new SubscriptionInfo to SubscriptionInfo database if needed + * @param uniqueId This is the unique identifier for the subscription within the + * specific subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType + * of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex, + int subscriptionType) { + if (VDBG) { + logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", displayName:" + displayName + ", slotIndex:" + slotIndex + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result); + } else { + logd("successfully added new subscription"); + } + } catch (RemoteException ex) { + // ignore it + } + } + + /** + * Remove SubscriptionInfo record from the SubscriptionInfo database + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the {@link #SUBSCRIPTION_TYPE} + * @hide + */ + public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) { + if (VDBG) { + logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId + + ", subscriptionType: " + subscriptionType); + } + if (uniqueId == null) { + Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null"); + return; + } + + try { + ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub")); + if (iSub == null) { + Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null"); + return; + } + int result = iSub.removeSubInfo(uniqueId, subscriptionType); + if (result < 0) { + Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result); + } else { + logd("successfully removed subscription"); + } + } catch (RemoteException ex) { + // ignore it + } + } + /** * Set SIM icon tint color by simInfo index * @param tint the RGB value of icon tint color of the SIM diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl index 577ddbda50faa..04ec3d1a3df3f 100755 --- a/telephony/java/com/android/internal/telephony/ISub.aidl +++ b/telephony/java/com/android/internal/telephony/ISub.aidl @@ -52,8 +52,8 @@ interface ISub { /** * Get the active SubscriptionInfo associated with the slotIndex * @param slotIndex the slot which the subscription is inserted - * @param callingPackage The package maing the call. - * @return SubscriptionInfo, maybe null if its not active + * @param callingPackage The package making the call. + * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex. */ SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage); @@ -114,6 +114,26 @@ interface ISub { */ int addSubInfoRecord(String iccId, int slotIndex); + /** + * Add a new subscription info record, if needed + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param displayName human-readable name of the device the subscription corresponds to. + * @param slotIndex the slot assigned to this device + * @param subscriptionType the type of subscription to be added. + * @return 0 if success, < 0 on error. + */ + int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType); + + /** + * Remove subscription info record for the given device. + * @param uniqueId This is the unique identifier for the subscription within the specific + * subscription type. + * @param subscriptionType the type of subscription to be removed + * @return 0 if success, < 0 on error. + */ + int removeSubInfo(String uniqueId, int subscriptionType); + /** * Set SIM icon tint color by simInfo index * @param tint the icon tint color of the SIM