Home | History | Annotate | Download | only in telephony
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.services.telephony;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.res.Configuration;
     22 import android.content.res.Resources;
     23 import android.graphics.Bitmap;
     24 import android.graphics.BitmapFactory;
     25 import android.graphics.Canvas;
     26 import android.graphics.PorterDuff;
     27 import android.graphics.drawable.Drawable;
     28 import android.graphics.drawable.Icon;
     29 import android.net.Uri;
     30 import android.os.PersistableBundle;
     31 import android.telecom.PhoneAccount;
     32 import android.telecom.PhoneAccountHandle;
     33 import android.telecom.TelecomManager;
     34 import android.telephony.CarrierConfigManager;
     35 import android.telephony.PhoneStateListener;
     36 import android.telephony.ServiceState;
     37 import android.telephony.SubscriptionInfo;
     38 import android.telephony.SubscriptionManager;
     39 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
     40 import android.telephony.TelephonyManager;
     41 import android.text.TextUtils;
     42 
     43 import com.android.internal.telephony.Phone;
     44 import com.android.internal.telephony.PhoneFactory;
     45 import com.android.internal.telephony.PhoneProxy;
     46 import com.android.phone.PhoneGlobals;
     47 import com.android.phone.PhoneUtils;
     48 import com.android.phone.R;
     49 
     50 import java.util.Arrays;
     51 import java.util.LinkedList;
     52 import java.util.List;
     53 
     54 /**
     55  * Owns all data we have registered with Telecom including handling dynamic addition and
     56  * removal of SIMs and SIP accounts.
     57  */
     58 final class TelecomAccountRegistry {
     59     private static final boolean DBG = false; /* STOP SHIP if true */
     60 
     61     // This icon is the one that is used when the Slot ID that we have for a particular SIM
     62     // is not supported, i.e. SubscriptionManager.INVALID_SLOT_ID or the 5th SIM in a phone.
     63     private final static int DEFAULT_SIM_ICON =  R.drawable.ic_multi_sim;
     64 
     65     final class AccountEntry implements PstnPhoneCapabilitiesNotifier.Listener {
     66         private final Phone mPhone;
     67         private final PhoneAccount mAccount;
     68         private final PstnIncomingCallNotifier mIncomingCallNotifier;
     69         private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
     70         private boolean mIsVideoCapable;
     71         private boolean mIsVideoPauseSupported;
     72 
     73         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
     74             mPhone = phone;
     75             mAccount = registerPstnPhoneAccount(isEmergency, isDummy);
     76             Log.i(this, "Registered phoneAccount: %s with handle: %s",
     77                     mAccount, mAccount.getAccountHandle());
     78             mIncomingCallNotifier = new PstnIncomingCallNotifier((PhoneProxy) mPhone);
     79             mPhoneCapabilitiesNotifier = new PstnPhoneCapabilitiesNotifier((PhoneProxy) mPhone,
     80                     this);
     81         }
     82 
     83         void teardown() {
     84             mIncomingCallNotifier.teardown();
     85             mPhoneCapabilitiesNotifier.teardown();
     86         }
     87 
     88         /**
     89          * Registers the specified account with Telecom as a PhoneAccountHandle.
     90          */
     91         private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) {
     92             String dummyPrefix = isDummyAccount ? "Dummy " : "";
     93 
     94             // Build the Phone account handle.
     95             PhoneAccountHandle phoneAccountHandle =
     96                     PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
     97                             mPhone, dummyPrefix, isEmergency);
     98 
     99             // Populate the phone account data.
    100             int subId = mPhone.getSubId();
    101             int color = PhoneAccount.NO_HIGHLIGHT_COLOR;
    102             int slotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
    103             String line1Number = mTelephonyManager.getLine1NumberForSubscriber(subId);
    104             if (line1Number == null) {
    105                 line1Number = "";
    106             }
    107             String subNumber = mPhone.getPhoneSubInfo().getLine1Number(
    108                     mPhone.getContext().getOpPackageName());
    109             if (subNumber == null) {
    110                 subNumber = "";
    111             }
    112 
    113             String label;
    114             String description;
    115             Icon icon = null;
    116 
    117             // We can only get the real slotId from the SubInfoRecord, we can't calculate the
    118             // slotId from the subId or the phoneId in all instances.
    119             SubscriptionInfo record =
    120                     mSubscriptionManager.getActiveSubscriptionInfo(subId);
    121 
    122             if (isEmergency) {
    123                 label = mContext.getResources().getString(R.string.sim_label_emergency_calls);
    124                 description =
    125                         mContext.getResources().getString(R.string.sim_description_emergency_calls);
    126             } else if (mTelephonyManager.getPhoneCount() == 1) {
    127                 // For single-SIM devices, we show the label and description as whatever the name of
    128                 // the network is.
    129                 description = label = mTelephonyManager.getNetworkOperatorName();
    130             } else {
    131                 CharSequence subDisplayName = null;
    132 
    133                 if (record != null) {
    134                     subDisplayName = record.getDisplayName();
    135                     slotId = record.getSimSlotIndex();
    136                     color = record.getIconTint();
    137                     icon = Icon.createWithBitmap(record.createIconBitmap(mContext));
    138                 }
    139 
    140                 String slotIdString;
    141                 if (SubscriptionManager.isValidSlotId(slotId)) {
    142                     slotIdString = Integer.toString(slotId);
    143                 } else {
    144                     slotIdString = mContext.getResources().getString(R.string.unknown);
    145                 }
    146 
    147                 if (TextUtils.isEmpty(subDisplayName)) {
    148                     // Either the sub record is not there or it has an empty display name.
    149                     Log.w(this, "Could not get a display name for subid: %d", subId);
    150                     subDisplayName = mContext.getResources().getString(
    151                             R.string.sim_description_default, slotIdString);
    152                 }
    153 
    154                 // The label is user-visible so let's use the display name that the user may
    155                 // have set in Settings->Sim cards.
    156                 label = dummyPrefix + subDisplayName;
    157                 description = dummyPrefix + mContext.getResources().getString(
    158                                 R.string.sim_description_default, slotIdString);
    159             }
    160 
    161             // By default all SIM phone accounts can place emergency calls.
    162             int capabilities = PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
    163                     PhoneAccount.CAPABILITY_CALL_PROVIDER |
    164                     PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
    165                     PhoneAccount.CAPABILITY_MULTI_USER;
    166 
    167             mIsVideoCapable = mPhone.isVideoEnabled();
    168             if (mIsVideoCapable) {
    169                 capabilities |= PhoneAccount.CAPABILITY_VIDEO_CALLING;
    170             }
    171             if (record != null) {
    172                 updateVideoPauseSupport(record);
    173             }
    174 
    175             if (icon == null) {
    176                 // TODO: Switch to using Icon.createWithResource() once that supports tinting.
    177                 Resources res = mContext.getResources();
    178                 Drawable drawable = res.getDrawable(DEFAULT_SIM_ICON, null);
    179                 drawable.setTint(res.getColor(R.color.default_sim_icon_tint_color, null));
    180                 drawable.setTintMode(PorterDuff.Mode.SRC_ATOP);
    181 
    182                 int width = drawable.getIntrinsicWidth();
    183                 int height = drawable.getIntrinsicHeight();
    184                 Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    185                 Canvas canvas = new Canvas(bitmap);
    186                 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
    187                 drawable.draw(canvas);
    188 
    189                 icon = Icon.createWithBitmap(bitmap);
    190             }
    191 
    192             PhoneAccount account = PhoneAccount.builder(phoneAccountHandle, label)
    193                     .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, line1Number, null))
    194                     .setSubscriptionAddress(
    195                             Uri.fromParts(PhoneAccount.SCHEME_TEL, subNumber, null))
    196                     .setCapabilities(capabilities)
    197                     .setIcon(icon)
    198                     .setHighlightColor(color)
    199                     .setShortDescription(description)
    200                     .setSupportedUriSchemes(Arrays.asList(
    201                             PhoneAccount.SCHEME_TEL, PhoneAccount.SCHEME_VOICEMAIL))
    202                     .build();
    203 
    204             // Register with Telecom and put into the account entry.
    205             mTelecomManager.registerPhoneAccount(account);
    206             return account;
    207         }
    208 
    209         public PhoneAccountHandle getPhoneAccountHandle() {
    210             return mAccount != null ? mAccount.getAccountHandle() : null;
    211         }
    212 
    213         /**
    214          * Updates indicator for this {@link AccountEntry} to determine if the carrier supports
    215          * pause/resume signalling for IMS video calls.  The carrier setting is stored in MNC/MCC
    216          * configuration files.
    217          *
    218          * @param subscriptionInfo The subscription info.
    219          */
    220         private void updateVideoPauseSupport(SubscriptionInfo subscriptionInfo) {
    221             // Get the configuration for the MNC/MCC specified in the current subscription info.
    222             Configuration configuration = new Configuration();
    223             if (subscriptionInfo.getMcc() == 0 && subscriptionInfo.getMnc() == 0) {
    224                 Configuration config = mContext.getResources().getConfiguration();
    225                 configuration.mcc = config.mcc;
    226                 configuration.mnc = config.mnc;
    227                 Log.i(this, "updateVideoPauseSupport -- no mcc/mnc for sub: " + subscriptionInfo +
    228                         " using mcc/mnc from main context: " + configuration.mcc + "/" +
    229                         configuration.mnc);
    230             } else {
    231                 Log.i(this, "updateVideoPauseSupport -- mcc/mnc for sub: " + subscriptionInfo);
    232 
    233                 configuration.mcc = subscriptionInfo.getMcc();
    234                 configuration.mnc = subscriptionInfo.getMnc();
    235             }
    236 
    237             // Check if IMS video pause is supported.
    238             PersistableBundle b =
    239                     PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
    240             mIsVideoPauseSupported
    241                     = b.getBoolean(CarrierConfigManager.KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL);
    242         }
    243 
    244         /**
    245          * Receives callback from {@link PstnPhoneCapabilitiesNotifier} when the video capabilities
    246          * have changed.
    247          *
    248          * @param isVideoCapable {@code true} if video is capable.
    249          */
    250         @Override
    251         public void onVideoCapabilitiesChanged(boolean isVideoCapable) {
    252             mIsVideoCapable = isVideoCapable;
    253         }
    254 
    255         /**
    256          * Indicates whether this account supports pausing video calls.
    257          * @return {@code true} if the account supports pausing video calls, {@code false}
    258          * otherwise.
    259          */
    260         public boolean isVideoPauseSupported() {
    261             return mIsVideoCapable && mIsVideoPauseSupported;
    262         }
    263     }
    264 
    265     private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
    266             new OnSubscriptionsChangedListener() {
    267         @Override
    268         public void onSubscriptionsChanged() {
    269             // Any time the SubscriptionInfo changes...rerun the setup
    270             tearDownAccounts();
    271             setupAccounts();
    272         }
    273     };
    274 
    275     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    276         @Override
    277         public void onServiceStateChanged(ServiceState serviceState) {
    278             int newState = serviceState.getState();
    279             if (newState == ServiceState.STATE_IN_SERVICE && mServiceState != newState) {
    280                 tearDownAccounts();
    281                 setupAccounts();
    282             }
    283             mServiceState = newState;
    284         }
    285     };
    286 
    287     private static TelecomAccountRegistry sInstance;
    288     private final Context mContext;
    289     private final TelecomManager mTelecomManager;
    290     private final TelephonyManager mTelephonyManager;
    291     private final SubscriptionManager mSubscriptionManager;
    292     private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
    293     private int mServiceState = ServiceState.STATE_POWER_OFF;
    294 
    295     // TODO: Remove back-pointer from app singleton to Service, since this is not a preferred
    296     // pattern; redesign. This was added to fix a late release bug.
    297     private TelephonyConnectionService mTelephonyConnectionService;
    298 
    299     TelecomAccountRegistry(Context context) {
    300         mContext = context;
    301         mTelecomManager = TelecomManager.from(context);
    302         mTelephonyManager = TelephonyManager.from(context);
    303         mSubscriptionManager = SubscriptionManager.from(context);
    304     }
    305 
    306     static synchronized final TelecomAccountRegistry getInstance(Context context) {
    307         if (sInstance == null && context != null) {
    308             sInstance = new TelecomAccountRegistry(context);
    309         }
    310         return sInstance;
    311     }
    312 
    313     void setTelephonyConnectionService(TelephonyConnectionService telephonyConnectionService) {
    314         this.mTelephonyConnectionService = telephonyConnectionService;
    315     }
    316 
    317     TelephonyConnectionService getTelephonyConnectionService() {
    318         return mTelephonyConnectionService;
    319     }
    320 
    321     /**
    322      * Determines if the {@link AccountEntry} associated with a {@link PhoneAccountHandle} supports
    323      * pausing video calls.
    324      *
    325      * @param handle The {@link PhoneAccountHandle}.
    326      * @return {@code True} if video pausing is supported.
    327      */
    328     boolean isVideoPauseSupported(PhoneAccountHandle handle) {
    329         for (AccountEntry entry : mAccounts) {
    330             if (entry.getPhoneAccountHandle().equals(handle)) {
    331                 return entry.isVideoPauseSupported();
    332             }
    333         }
    334         return false;
    335     }
    336 
    337     /**
    338      * Sets up all the phone accounts for SIMs on first boot.
    339      */
    340     void setupOnBoot() {
    341         // TODO: When this object "finishes" we should unregister by invoking
    342         // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener);
    343         // This is not strictly necessary because it will be unregistered if the
    344         // notification fails but it is good form.
    345 
    346         // Register for SubscriptionInfo list changes which is guaranteed
    347         // to invoke onSubscriptionsChanged the first time.
    348         SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
    349                 mOnSubscriptionsChangedListener);
    350 
    351         // We also need to listen for changes to the service state (e.g. emergency -> in service)
    352         // because this could signal a removal or addition of a SIM in a single SIM phone.
    353         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
    354     }
    355 
    356     /**
    357      * Determines if the list of {@link AccountEntry}(s) contains an {@link AccountEntry} with a
    358      * specified {@link PhoneAccountHandle}.
    359      *
    360      * @param handle The {@link PhoneAccountHandle}.
    361      * @return {@code True} if an entry exists.
    362      */
    363     private boolean hasAccountEntryForPhoneAccount(PhoneAccountHandle handle) {
    364         for (AccountEntry entry : mAccounts) {
    365             if (entry.getPhoneAccountHandle().equals(handle)) {
    366                 return true;
    367             }
    368         }
    369         return false;
    370     }
    371 
    372     /**
    373      * Un-registers any {@link PhoneAccount}s which are no longer present in the list
    374      * {@code AccountEntry}(s).
    375      */
    376     private void cleanupPhoneAccounts() {
    377         ComponentName telephonyComponentName =
    378                 new ComponentName(mContext, TelephonyConnectionService.class);
    379         List<PhoneAccountHandle> accountHandles =
    380                 mTelecomManager.getCallCapablePhoneAccounts(true /* includeDisabled */);
    381         for (PhoneAccountHandle handle : accountHandles) {
    382             if (telephonyComponentName.equals(handle.getComponentName()) &&
    383                     !hasAccountEntryForPhoneAccount(handle)) {
    384                 Log.i(this, "Unregistering phone account %s.", handle);
    385                 mTelecomManager.unregisterPhoneAccount(handle);
    386             }
    387         }
    388     }
    389 
    390     private void setupAccounts() {
    391         // Go through SIM-based phones and register ourselves -- registering an existing account
    392         // will cause the existing entry to be replaced.
    393         Phone[] phones = PhoneFactory.getPhones();
    394         Log.d(this, "Found %d phones.  Attempting to register.", phones.length);
    395         for (Phone phone : phones) {
    396             long subscriptionId = phone.getSubId();
    397             Log.d(this, "Phone with subscription id %d", subscriptionId);
    398             if (subscriptionId >= 0) {
    399                 mAccounts.add(new AccountEntry(phone, false /* emergency */, false /* isDummy */));
    400             }
    401         }
    402 
    403         // If we did not list ANY accounts, we need to provide a "default" SIM account
    404         // for emergency numbers since no actual SIM is needed for dialing emergency
    405         // numbers but a phone account is.
    406         if (mAccounts.isEmpty()) {
    407             mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
    408                     false /* isDummy */));
    409         }
    410 
    411         // Add a fake account entry.
    412         if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
    413             mAccounts.add(new AccountEntry(phones[0], false /* emergency */, true /* isDummy */));
    414         }
    415 
    416         // Clean up any PhoneAccounts that are no longer relevant
    417         cleanupPhoneAccounts();
    418 
    419         // At some point, the phone account ID was switched from the subId to the iccId.
    420         // If there is a default account, check if this is the case, and upgrade the default account
    421         // from using the subId to iccId if so.
    422         PhoneAccountHandle defaultPhoneAccount =
    423                 mTelecomManager.getUserSelectedOutgoingPhoneAccount();
    424         ComponentName telephonyComponentName =
    425                 new ComponentName(mContext, TelephonyConnectionService.class);
    426 
    427         if (defaultPhoneAccount != null &&
    428                 telephonyComponentName.equals(defaultPhoneAccount.getComponentName()) &&
    429                 !hasAccountEntryForPhoneAccount(defaultPhoneAccount)) {
    430 
    431             String phoneAccountId = defaultPhoneAccount.getId();
    432             if (!TextUtils.isEmpty(phoneAccountId) && TextUtils.isDigitsOnly(phoneAccountId)) {
    433                 PhoneAccountHandle upgradedPhoneAccount =
    434                         PhoneUtils.makePstnPhoneAccountHandle(
    435                                 PhoneGlobals.getPhone(Integer.parseInt(phoneAccountId)));
    436 
    437                 if (hasAccountEntryForPhoneAccount(upgradedPhoneAccount)) {
    438                     mTelecomManager.setUserSelectedOutgoingPhoneAccount(upgradedPhoneAccount);
    439                 }
    440             }
    441         }
    442     }
    443 
    444     private void tearDownAccounts() {
    445         for (AccountEntry entry : mAccounts) {
    446             entry.teardown();
    447         }
    448         mAccounts.clear();
    449     }
    450 }
    451