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