Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aea0391c authored by Vasu Nori's avatar Vasu Nori Committed by Scott Randolph
Browse files

Implementation of Remote-SIM.

Design doc http://shortn/_GG690j15js

1. Modified SmsManager to send message thru Bluetooth if the subscription
   is for a Remote-SIM.
2. Added new column 'subscriptionType' to SubscriptionInfo.
3. SubscriptionManager:
  a. Added new constants for Local SIM and Remote SIM
  b. Added hidden APIs to add/remove Remote SIM subscriptions

Bug: 112321331
Test: tested manually and w/ unittests
Change-Id: Ib15553a806691a258fc00f7aaf812b8a9d49c2e9
parent bc40469e
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -42854,6 +42854,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);
@@ -42904,6 +42905,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 {
+1 −1
Original line number Diff line number Diff line
@@ -3624,7 +3624,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
+118 −2
Original line number Diff line number Diff line
@@ -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.
+25 −6
Original line number Diff line number Diff line
@@ -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}
@@ -611,11 +627,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
@@ -649,6 +666,7 @@ public class SubscriptionInfo implements Parcelable {
        dest.writeBoolean(mIsGroupDisabled);
        dest.writeInt(mCarrierId);
        dest.writeInt(mProfileClass);
        dest.writeInt(mSubscriptionType);
    }

    @Override
@@ -685,7 +703,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
+130 −8
Original line number Diff line number Diff line
@@ -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.
     * <P>Type: TEXT (String)</P>
     */
    /** @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.
     * <P>Type: INTEGER (int)</P> {@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.
     * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the
     * device.
     * </p>
     */
    public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;

    /**
     * This constant is to designate a subscription as a Remote-SIM Subscription.
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     *
     * <p>
     * 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.
     * </p>
     */
    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.
     * <P>Type: TEXT (String)</P>
@@ -1146,7 +1205,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}.
     *
     * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1428,21 +1487,84 @@ public class SubscriptionManager {
            logd("[addSubscriptionInfoRecord]- invalid slotIndex");
        }

        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) {
                // FIXME: This returns 1 on success, 0 on error should should we return it?
                iSub.addSubInfoRecord(iccId, slotIndex);
            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("[addSubscriptionInfoRecord]- ISub service is null");
                logd("successfully added new subscription");
            }
        } catch (RemoteException ex) {
            // ignore it
        }
    }

        // FIXME: Always returns null?
        return null;
    /**
     * 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
        }
    }

    /**
Loading