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 17package com.android.services.telephony; 18 19import android.content.BroadcastReceiver; 20import android.content.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.net.Uri; 25import android.telecom.PhoneAccount; 26import android.telecom.PhoneAccountHandle; 27import android.telecom.TelecomManager; 28import android.telephony.PhoneStateListener; 29import android.telephony.ServiceState; 30import android.telephony.SubInfoRecord; 31import android.telephony.SubscriptionManager; 32import android.telephony.TelephonyManager; 33import android.text.TextUtils; 34 35import com.android.internal.telephony.Phone; 36import com.android.internal.telephony.PhoneFactory; 37import com.android.internal.telephony.PhoneProxy; 38import com.android.internal.telephony.TelephonyIntents; 39import com.android.phone.R; 40 41import java.util.Arrays; 42import java.util.LinkedList; 43import java.util.List; 44 45/** 46 * Owns all data we have registered with Telecom including handling dynamic addition and 47 * removal of SIMs and SIP accounts. 48 */ 49final class TelecomAccountRegistry { 50 private static final boolean DBG = false; /* STOP SHIP if true */ 51 52 // Slot IDs are zero based indices but the numbered icons represent the first, second, 53 // etc... SIM in the device. So that means that index 0 is SIM 1, index 1 is SIM 2 and so on. 54 55 private final static int[] phoneAccountIcons = { 56 R.drawable.ic_multi_sim1, 57 R.drawable.ic_multi_sim2, 58 R.drawable.ic_multi_sim3, 59 R.drawable.ic_multi_sim4 60 }; 61 62 // This icon is the one that is used when the Slot ID that we have for a particular SIM 63 // is not supported, i.e. SubscriptionManager.INVALID_SLOT_ID or the 5th SIM in a phone. 64 private final static int defaultPhoneAccountIcon = R.drawable.ic_multi_sim; 65 66 private final class AccountEntry { 67 private final Phone mPhone; 68 private final PhoneAccount mAccount; 69 private final PstnIncomingCallNotifier mIncomingCallNotifier; 70 71 AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) { 72 mPhone = phone; 73 mAccount = registerPstnPhoneAccount(isEmergency, isDummy); 74 Log.d(this, "Registered phoneAccount: %s with handle: %s", 75 mAccount, mAccount.getAccountHandle()); 76 mIncomingCallNotifier = new PstnIncomingCallNotifier((PhoneProxy) mPhone); 77 } 78 79 void teardown() { 80 mIncomingCallNotifier.teardown(); 81 } 82 83 /** 84 * Registers the specified account with Telecom as a PhoneAccountHandle. 85 */ 86 private PhoneAccount registerPstnPhoneAccount(boolean isEmergency, boolean isDummyAccount) { 87 String dummyPrefix = isDummyAccount ? "Dummy " : ""; 88 89 // Build the Phone account handle. 90 PhoneAccountHandle phoneAccountHandle = 91 makePstnPhoneAccountHandleWithPrefix(mPhone, dummyPrefix, isEmergency); 92 93 // Populate the phone account data. 94 long subId = mPhone.getSubId(); 95 int slotId = SubscriptionManager.INVALID_SLOT_ID; 96 String line1Number = mTelephonyManager.getLine1NumberForSubscriber(subId); 97 if (line1Number == null) { 98 line1Number = ""; 99 } 100 String subNumber = mPhone.getPhoneSubInfo().getLine1Number(); 101 if (subNumber == null) { 102 subNumber = ""; 103 } 104 105 String label; 106 String description; 107 108 if (isEmergency) { 109 label = mContext.getResources().getString(R.string.sim_label_emergency_calls); 110 description = 111 mContext.getResources().getString(R.string.sim_description_emergency_calls); 112 } else if (mTelephonyManager.getPhoneCount() == 1) { 113 // For single-SIM devices, we show the label and description as whatever the name of 114 // the network is. 115 description = label = mTelephonyManager.getNetworkOperatorName(); 116 } else { 117 String subDisplayName = null; 118 // We can only get the real slotId from the SubInfoRecord, we can't calculate the 119 // slotId from the subId or the phoneId in all instances. 120 SubInfoRecord record = SubscriptionManager.getSubInfoForSubscriber(subId); 121 if (record != null) { 122 subDisplayName = record.displayName; 123 slotId = record.slotId; 124 } 125 126 String slotIdString; 127 if (SubscriptionManager.isValidSlotId(slotId)) { 128 slotIdString = Integer.toString(slotId); 129 } else { 130 slotIdString = mContext.getResources().getString(R.string.unknown); 131 } 132 133 if (TextUtils.isEmpty(subDisplayName)) { 134 // Either the sub record is not there or it has an empty display name. 135 Log.w(this, "Could not get a display name for subid: %d", subId); 136 subDisplayName = mContext.getResources().getString( 137 R.string.sim_description_default, slotIdString); 138 } 139 140 // The label is user-visible so let's use the display name that the user may 141 // have set in Settings->Sim cards. 142 label = dummyPrefix + subDisplayName; 143 description = dummyPrefix + mContext.getResources().getString( 144 R.string.sim_description_default, slotIdString); 145 } 146 147 // By default all SIM phone accounts can place emergency calls. 148 int capabilities = PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION | 149 PhoneAccount.CAPABILITY_CALL_PROVIDER | 150 PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS; 151 152 PhoneAccount account = PhoneAccount.builder(phoneAccountHandle, label) 153 .setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL, line1Number, null)) 154 .setSubscriptionAddress( 155 Uri.fromParts(PhoneAccount.SCHEME_TEL, subNumber, null)) 156 .setCapabilities(capabilities) 157 .setIconResId(getPhoneAccountIcon(slotId)) 158 .setShortDescription(description) 159 .setSupportedUriSchemes(Arrays.asList( 160 PhoneAccount.SCHEME_TEL, PhoneAccount.SCHEME_VOICEMAIL)) 161 .build(); 162 163 // Register with Telecom and put into the account entry. 164 mTelecomManager.registerPhoneAccount(account); 165 return account; 166 } 167 168 public PhoneAccountHandle getPhoneAccountHandle() { 169 return mAccount != null ? mAccount.getAccountHandle() : null; 170 } 171 } 172 173 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 174 @Override 175 public void onReceive(Context context, Intent intent) { 176 boolean rebuildAccounts = false; 177 String action = intent.getAction(); 178 if (TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED.equals(action)) { 179 int status = intent.getIntExtra( 180 SubscriptionManager.INTENT_KEY_DETECT_STATUS, 181 SubscriptionManager.EXTRA_VALUE_NOCHANGE); 182 Log.i(this, "SUBINFO_RECORD_UPDATED : %d.", status); 183 // Anytime the SIM state changes...rerun the setup 184 // We rely on this notification even when the status is EXTRA_VALUE_NOCHANGE, 185 // so we explicitly do not check for that here. 186 rebuildAccounts = true; 187 } else if (TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE.equals(action)) { 188 String columnName = intent.getStringExtra(TelephonyIntents.EXTRA_COLUMN_NAME); 189 String stringContent = intent.getStringExtra(TelephonyIntents.EXTRA_STRING_CONTENT); 190 Log.v(this, "SUBINFO_CONTENT_CHANGE: Column: %s Content: %s", 191 columnName, stringContent); 192 rebuildAccounts = true; 193 } 194 if (rebuildAccounts) { 195 tearDownAccounts(); 196 setupAccounts(); 197 } 198 } 199 }; 200 201 private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 202 @Override 203 public void onServiceStateChanged(ServiceState serviceState) { 204 int newState = serviceState.getState(); 205 if (newState == ServiceState.STATE_IN_SERVICE && mServiceState != newState) { 206 tearDownAccounts(); 207 setupAccounts(); 208 } 209 mServiceState = newState; 210 } 211 }; 212 213 private static TelecomAccountRegistry sInstance; 214 private final Context mContext; 215 private final TelecomManager mTelecomManager; 216 private final TelephonyManager mTelephonyManager; 217 private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>(); 218 private int mServiceState = ServiceState.STATE_POWER_OFF; 219 220 TelecomAccountRegistry(Context context) { 221 mContext = context; 222 mTelecomManager = TelecomManager.from(context); 223 mTelephonyManager = TelephonyManager.from(context); 224 } 225 226 static synchronized final TelecomAccountRegistry getInstance(Context context) { 227 if (sInstance == null) { 228 sInstance = new TelecomAccountRegistry(context); 229 } 230 return sInstance; 231 } 232 233 /** 234 * Sets up all the phone accounts for SIMs on first boot. 235 */ 236 void setupOnBoot() { 237 // We need to register for both types of intents if we want to see added/removed Subs 238 // along with changes to a given Sub. 239 IntentFilter intentFilter = new IntentFilter(); 240 intentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED); 241 intentFilter.addAction(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE); 242 mContext.registerReceiver(mReceiver, intentFilter); 243 244 // We also need to listen for changes to the service state (e.g. emergency -> in service) 245 // because this could signal a removal or addition of a SIM in a single SIM phone. 246 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE); 247 } 248 249 static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 250 return makePstnPhoneAccountHandleWithPrefix(phone, "", false); 251 } 252 253 private static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 254 Phone phone, String prefix, boolean isEmergency) { 255 ComponentName pstnConnectionServiceName = 256 new ComponentName(phone.getContext(), TelephonyConnectionService.class); 257 // TODO: Should use some sort of special hidden flag to decorate this account as 258 // an emergency-only account 259 String id = isEmergency ? "E" : prefix + String.valueOf(phone.getSubId()); 260 return new PhoneAccountHandle(pstnConnectionServiceName, id); 261 } 262 263 /** 264 * Determines if the list of {@link AccountEntry}(s) contains an {@link AccountEntry} with a 265 * specified {@link PhoneAccountHandle}. 266 * 267 * @param handle The {@link PhoneAccountHandle}. 268 * @return {@code True} if an entry exists. 269 */ 270 private boolean hasAccountEntryForPhoneAccount(PhoneAccountHandle handle) { 271 for (AccountEntry entry : mAccounts) { 272 if (entry.getPhoneAccountHandle().equals(handle)) { 273 return true; 274 } 275 } 276 return false; 277 } 278 279 /** 280 * Un-registers any {@link PhoneAccount}s which are no longer present in the list 281 * {@code AccountEntry}(s). 282 */ 283 private void cleanupPhoneAccounts() { 284 ComponentName telephonyComponentName = 285 new ComponentName(mContext, TelephonyConnectionService.class); 286 List<PhoneAccountHandle> accountHandles = mTelecomManager.getAllPhoneAccountHandles(); 287 for (PhoneAccountHandle handle : accountHandles) { 288 if (telephonyComponentName.equals(handle.getComponentName()) && 289 !hasAccountEntryForPhoneAccount(handle)) { 290 Log.d(this, "Unregistering phone account %s.", handle); 291 mTelecomManager.unregisterPhoneAccount(handle); 292 } 293 } 294 } 295 296 private void setupAccounts() { 297 // Go through SIM-based phones and register ourselves -- registering an existing account 298 // will cause the existing entry to be replaced. 299 Phone[] phones = PhoneFactory.getPhones(); 300 Log.d(this, "Found %d phones. Attempting to register.", phones.length); 301 for (Phone phone : phones) { 302 long subscriptionId = phone.getSubId(); 303 Log.d(this, "Phone with subscription id %d", subscriptionId); 304 if (subscriptionId >= 0) { 305 mAccounts.add(new AccountEntry(phone, false /* emergency */, false /* isDummy */)); 306 } 307 } 308 309 // If we did not list ANY accounts, we need to provide a "default" SIM account 310 // for emergency numbers since no actual SIM is needed for dialing emergency 311 // numbers but a phone account is. 312 if (mAccounts.isEmpty()) { 313 mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */, 314 false /* isDummy */)); 315 } 316 317 // Add a fake account entry. 318 if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) { 319 mAccounts.add(new AccountEntry(phones[0], false /* emergency */, true /* isDummy */)); 320 } 321 322 // Clean up any PhoneAccounts that are no longer relevant 323 cleanupPhoneAccounts(); 324 } 325 326 private int getPhoneAccountIcon(int index) { 327 // A valid slot id doesn't necessarily mean that we have an icon for it. 328 if (SubscriptionManager.isValidSlotId(index) && 329 index < TelecomAccountRegistry.phoneAccountIcons.length) { 330 return TelecomAccountRegistry.phoneAccountIcons[index]; 331 } 332 // Invalid indices get the default icon that has no number associated with it. 333 return defaultPhoneAccountIcon; 334 } 335 336 private void tearDownAccounts() { 337 for (AccountEntry entry : mAccounts) { 338 entry.teardown(); 339 } 340 mAccounts.clear(); 341 } 342} 343