PhoneAccountRegistrar.java revision 3b62312e0596321c7bd256b90ddce46ade57e1c0
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.server.telecom; 18 19import android.Manifest; 20import android.content.Intent; 21import android.content.pm.PackageManager; 22import android.content.pm.ResolveInfo; 23import android.content.pm.ServiceInfo; 24import android.os.Environment; 25import android.os.UserHandle; 26import android.provider.Settings; 27import android.telecom.ConnectionService; 28import android.telecom.PhoneAccount; 29import android.telecom.PhoneAccountHandle; 30import android.telecom.TelecomManager; 31import android.telephony.PhoneNumberUtils; 32import android.telephony.SubscriptionManager; 33import android.content.ComponentName; 34import android.content.Context; 35import android.net.Uri; 36import android.text.TextUtils; 37import android.util.AtomicFile; 38import android.util.Xml; 39 40// TODO: Needed for move to system service: import com.android.internal.R; 41import com.android.internal.annotations.VisibleForTesting; 42import com.android.internal.util.FastXmlSerializer; 43import com.android.internal.util.IndentingPrintWriter; 44import com.android.internal.util.XmlUtils; 45 46import org.xmlpull.v1.XmlPullParser; 47import org.xmlpull.v1.XmlPullParserException; 48import org.xmlpull.v1.XmlSerializer; 49 50import java.io.BufferedInputStream; 51import java.io.BufferedOutputStream; 52import java.io.File; 53import java.io.FileNotFoundException; 54import java.io.FileOutputStream; 55import java.io.IOException; 56import java.io.InputStream; 57import java.lang.Integer; 58import java.lang.SecurityException; 59import java.lang.String; 60import java.util.ArrayList; 61import java.util.Collections; 62import java.util.Iterator; 63import java.util.List; 64import java.util.Objects; 65import java.util.concurrent.CopyOnWriteArrayList; 66 67/** 68 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim 69 * delegate for all the account handling methods on {@link android.telecom.TelecomManager} as implemented in 70 * {@link TelecomServiceImpl}, with the notable exception that {@link TelecomServiceImpl} is 71 * responsible for security checking to make sure that the caller has proper authority over 72 * the {@code ComponentName}s they are declaring in their {@code PhoneAccountHandle}s. 73 */ 74public final class PhoneAccountRegistrar { 75 76 public static final PhoneAccountHandle NO_ACCOUNT_SELECTED = 77 new PhoneAccountHandle(new ComponentName("null", "null"), "NO_ACCOUNT_SELECTED"); 78 79 public abstract static class Listener { 80 public void onAccountsChanged(PhoneAccountRegistrar registrar) {} 81 public void onDefaultOutgoingChanged(PhoneAccountRegistrar registrar) {} 82 public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {} 83 } 84 85 private static final String FILE_NAME = "phone-account-registrar-state.xml"; 86 @VisibleForTesting 87 public static final int EXPECTED_STATE_VERSION = 3; 88 89 /** Keep in sync with the same in SipSettings.java */ 90 private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES"; 91 92 private final List<Listener> mListeners = new CopyOnWriteArrayList<>(); 93 private final AtomicFile mAtomicFile; 94 private final Context mContext; 95 private State mState; 96 97 @VisibleForTesting 98 public PhoneAccountRegistrar(Context context) { 99 this(context, FILE_NAME); 100 } 101 102 @VisibleForTesting 103 public PhoneAccountRegistrar(Context context, String fileName) { 104 // TODO: This file path is subject to change -- it is storing the phone account registry 105 // state file in the path /data/system/users/0/, which is likely not correct in a 106 // multi-user setting. 107 /** UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE 108 String filePath = Environment.getUserSystemDirectory(UserHandle.myUserId()). 109 getAbsolutePath(); 110 mAtomicFile = new AtomicFile(new File(filePath, fileName)); 111 UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE */ 112 mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName)); 113 114 mState = new State(); 115 mContext = context; 116 read(); 117 } 118 119 /** 120 * Retrieves the subscription id for a given phone account if it exists. Subscription ids 121 * apply only to PSTN/SIM card phone accounts so all other accounts should not have a 122 * subscription id. 123 * @param accountHandle The handle for the phone account for which to retrieve the 124 * subscription id. 125 * @return The value of the subscription id or -1 if it does not exist or is not valid. 126 */ 127 public int getSubscriptionIdForPhoneAccount(PhoneAccountHandle accountHandle) { 128 PhoneAccount account = getPhoneAccount(accountHandle); 129 if (account == null || !account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) || 130 !TextUtils.isDigitsOnly(accountHandle.getId())) { 131 // Since no decimals or negative numbers can be valid subscription ids, only a string of 132 // numbers can be subscription id 133 return -1; 134 } 135 return Integer.parseInt(accountHandle.getId()); 136 } 137 138 /** 139 * Retrieves the default outgoing phone account supporting the specified uriScheme. 140 * @param uriScheme The URI scheme for the outgoing call. 141 * @return The {@link PhoneAccountHandle} to use. 142 */ 143 public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) { 144 final PhoneAccountHandle userSelected = getUserSelectedOutgoingPhoneAccount(); 145 146 if (userSelected != null) { 147 // If there is a default PhoneAccount, ensure it supports calls to handles with the 148 // specified uriScheme. 149 final PhoneAccount userSelectedAccount = getPhoneAccount(userSelected); 150 if (userSelectedAccount.supportsUriScheme(uriScheme)) { 151 return userSelected; 152 } 153 } 154 155 List<PhoneAccountHandle> outgoing = getCallCapablePhoneAccounts(uriScheme); 156 switch (outgoing.size()) { 157 case 0: 158 // There are no accounts, so there can be no default 159 return null; 160 case 1: 161 // There is only one account, which is by definition the default 162 return outgoing.get(0); 163 default: 164 // There are multiple accounts with no selected default 165 return null; 166 } 167 } 168 169 PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { 170 if (mState.defaultOutgoing != null) { 171 // Return the registered outgoing default iff it still exists (we keep a sticky 172 // default to survive account deletion and re-addition) 173 for (int i = 0; i < mState.accounts.size(); i++) { 174 if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)) { 175 return mState.defaultOutgoing; 176 } 177 } 178 // At this point, there was a registered default but it has been deleted; proceed 179 // as though there were no default 180 } 181 return null; 182 } 183 184 public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { 185 if (accountHandle == null) { 186 // Asking to clear the default outgoing is a valid request 187 mState.defaultOutgoing = null; 188 } else { 189 boolean found = false; 190 for (PhoneAccount m : mState.accounts) { 191 if (Objects.equals(accountHandle, m.getAccountHandle())) { 192 found = true; 193 break; 194 } 195 } 196 197 if (!found) { 198 Log.w(this, "Trying to set nonexistent default outgoing %s", 199 accountHandle); 200 return; 201 } 202 203 if (!getPhoneAccount(accountHandle).hasCapabilities( 204 PhoneAccount.CAPABILITY_CALL_PROVIDER)) { 205 Log.w(this, "Trying to set non-call-provider default outgoing %s", 206 accountHandle); 207 return; 208 } 209 210 if (getPhoneAccount(accountHandle).hasCapabilities( 211 PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 212 // If the account selected is a SIM account, propagate down to the subscription 213 // record. 214 long subId = getSubscriptionIdForPhoneAccount(accountHandle); 215 SubscriptionManager.setDefaultVoiceSubId(subId); 216 } 217 218 mState.defaultOutgoing = accountHandle; 219 } 220 221 write(); 222 fireDefaultOutgoingChanged(); 223 } 224 225 public void setSimCallManager(PhoneAccountHandle callManager) { 226 if (callManager != null) { 227 PhoneAccount callManagerAccount = getPhoneAccount(callManager); 228 if (callManagerAccount == null) { 229 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager); 230 return; 231 } else if (!callManagerAccount.hasCapabilities( 232 PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) { 233 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount); 234 return; 235 } 236 } else { 237 callManager = NO_ACCOUNT_SELECTED; 238 } 239 mState.simCallManager = callManager; 240 241 write(); 242 fireSimCallManagerChanged(); 243 } 244 245 public PhoneAccountHandle getSimCallManager() { 246 if (mState.simCallManager != null) { 247 if (NO_ACCOUNT_SELECTED.equals(mState.simCallManager)) { 248 return null; 249 } 250 // Return the registered sim call manager iff it still exists (we keep a sticky 251 // setting to survive account deletion and re-addition) 252 for (int i = 0; i < mState.accounts.size(); i++) { 253 if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)) { 254 return mState.simCallManager; 255 } 256 } 257 } 258 259 // See if the OEM has specified a default one. 260 String defaultConnectionMgr = 261 mContext.getResources().getString(R.string.default_connection_manager_component); 262 if (!TextUtils.isEmpty(defaultConnectionMgr)) { 263 PackageManager pm = mContext.getPackageManager(); 264 265 ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr); 266 Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE); 267 intent.setComponent(componentName); 268 269 // Make sure that the component can be resolved. 270 List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0); 271 if (!resolveInfos.isEmpty()) { 272 // See if there is registered PhoneAccount by this component. 273 List<PhoneAccountHandle> handles = getAllPhoneAccountHandles(); 274 for (PhoneAccountHandle handle : handles) { 275 if (componentName.equals(handle.getComponentName())) { 276 return handle; 277 } 278 } 279 Log.d(this, "%s does not have a PhoneAccount; not using as default", componentName); 280 } else { 281 Log.d(this, "%s could not be resolved; not using as default", componentName); 282 } 283 } else { 284 Log.v(this, "No default connection manager specified"); 285 } 286 287 return null; 288 } 289 290 /** 291 * Retrieves a list of all {@link PhoneAccountHandle}s registered. 292 * 293 * @return The list of {@link PhoneAccountHandle}s. 294 */ 295 public List<PhoneAccountHandle> getAllPhoneAccountHandles() { 296 List<PhoneAccountHandle> accountHandles = new ArrayList<>(); 297 for (PhoneAccount m : mState.accounts) { 298 accountHandles.add(m.getAccountHandle()); 299 } 300 return accountHandles; 301 } 302 303 public List<PhoneAccount> getAllPhoneAccounts() { 304 return new ArrayList<>(mState.accounts); 305 } 306 307 /** 308 * Determines the number of all {@link PhoneAccount}s. 309 * 310 * @return The total number {@link PhoneAccount}s. 311 */ 312 public int getAllPhoneAccountsCount() { 313 return mState.accounts.size(); 314 } 315 316 /** 317 * Retrieves a list of all call provider phone accounts. 318 * 319 * @return The phone account handles. 320 */ 321 public List<PhoneAccountHandle> getCallCapablePhoneAccounts() { 322 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CALL_PROVIDER); 323 } 324 325 /** 326 * Retrieves a list of all phone account call provider phone accounts supporting the 327 * specified URI scheme. 328 * 329 * @param uriScheme The URI scheme. 330 * @return The phone account handles. 331 */ 332 public List<PhoneAccountHandle> getCallCapablePhoneAccounts(String uriScheme) { 333 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CALL_PROVIDER, uriScheme); 334 } 335 336 /** 337 * Retrieves a list of all phone accounts registered by a specified package. 338 * 339 * @param packageName The name of the package that registered the phone accounts. 340 * @return The phone account handles. 341 */ 342 public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) { 343 List<PhoneAccountHandle> accountHandles = new ArrayList<>(); 344 for (PhoneAccount m : mState.accounts) { 345 if (Objects.equals( 346 packageName, 347 m.getAccountHandle().getComponentName().getPackageName())) { 348 accountHandles.add(m.getAccountHandle()); 349 } 350 } 351 return accountHandles; 352 } 353 354 /** 355 * Retrieves a list of all phone account handles with the connection manager capability. 356 * 357 * @return The phone account handles. 358 */ 359 public List<PhoneAccountHandle> getConnectionManagerPhoneAccounts() { 360 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CONNECTION_MANAGER, 361 null /* supportedUriScheme */); 362 } 363 364 public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) { 365 for (PhoneAccount m : mState.accounts) { 366 if (Objects.equals(handle, m.getAccountHandle())) { 367 return m; 368 } 369 } 370 return null; 371 } 372 373 // TODO: Should we implement an artificial limit for # of accounts associated with a single 374 // ComponentName? 375 public void registerPhoneAccount(PhoneAccount account) { 376 // Enforce the requirement that a connection service for a phone account has the correct 377 // permission. 378 if (!phoneAccountHasPermission(account.getAccountHandle())) { 379 Log.w(this, "Phone account %s does not have BIND_CONNECTION_SERVICE permission.", 380 account.getAccountHandle()); 381 throw new SecurityException( 382 "PhoneAccount connection service requires BIND_CONNECTION_SERVICE permission."); 383 } 384 385 addOrReplacePhoneAccount(account); 386 } 387 388 /** 389 * Adds a {@code PhoneAccount}, replacing an existing one if found. 390 * 391 * @param account The {@code PhoneAccount} to add or replace. 392 */ 393 private void addOrReplacePhoneAccount(PhoneAccount account) { 394 mState.accounts.add(account); 395 // Search for duplicates and remove any that are found. 396 for (int i = 0; i < mState.accounts.size() - 1; i++) { 397 if (Objects.equals( 398 account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) { 399 // replace existing entry. 400 mState.accounts.remove(i); 401 break; 402 } 403 } 404 405 write(); 406 fireAccountsChanged(); 407 } 408 409 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { 410 for (int i = 0; i < mState.accounts.size(); i++) { 411 if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) { 412 mState.accounts.remove(i); 413 break; 414 } 415 } 416 417 write(); 418 fireAccountsChanged(); 419 } 420 421 /** 422 * Un-registers all phone accounts associated with a specified package. 423 * 424 * @param packageName The package for which phone accounts will be removed. 425 */ 426 public void clearAccounts(String packageName) { 427 boolean accountsRemoved = false; 428 Iterator<PhoneAccount> it = mState.accounts.iterator(); 429 while (it.hasNext()) { 430 PhoneAccount phoneAccount = it.next(); 431 if (Objects.equals( 432 packageName, 433 phoneAccount.getAccountHandle().getComponentName().getPackageName())) { 434 Log.i(this, "Removing phone account " + phoneAccount.getLabel()); 435 it.remove(); 436 accountsRemoved = true; 437 } 438 } 439 440 if (accountsRemoved) { 441 write(); 442 fireAccountsChanged(); 443 } 444 } 445 446 public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) { 447 int subId = getSubscriptionIdForPhoneAccount(accountHandle); 448 return PhoneNumberUtils.isVoiceMailNumber(subId, number); 449 } 450 451 public void addListener(Listener l) { 452 mListeners.add(l); 453 } 454 455 public void removeListener(Listener l) { 456 if (l != null) { 457 mListeners.remove(l); 458 } 459 } 460 461 private void fireAccountsChanged() { 462 for (Listener l : mListeners) { 463 l.onAccountsChanged(this); 464 } 465 } 466 467 private void fireDefaultOutgoingChanged() { 468 for (Listener l : mListeners) { 469 l.onDefaultOutgoingChanged(this); 470 } 471 } 472 473 private void fireSimCallManagerChanged() { 474 for (Listener l : mListeners) { 475 l.onSimCallManagerChanged(this); 476 } 477 } 478 479 /** 480 * Determines if the connection service specified by a {@link PhoneAccountHandle} has the 481 * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission. 482 * 483 * @param phoneAccountHandle The phone account to check. 484 * @return {@code True} if the phone account has permission. 485 */ 486 public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) { 487 PackageManager packageManager = mContext.getPackageManager(); 488 try { 489 ServiceInfo serviceInfo = packageManager.getServiceInfo( 490 phoneAccountHandle.getComponentName(), 0); 491 492 return serviceInfo.permission != null && 493 serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE); 494 } catch (PackageManager.NameNotFoundException e) { 495 Log.w(this, "Name not found %s", e); 496 return false; 497 } 498 } 499 500 //////////////////////////////////////////////////////////////////////////////////////////////// 501 502 /** 503 * Returns a list of phone account handles with the specified flag. 504 * 505 * @param flags Flags which the {@code PhoneAccount} must have. 506 */ 507 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags) { 508 return getPhoneAccountHandles(flags, null); 509 } 510 511 /** 512 * Returns a list of phone account handles with the specified flag, supporting the specified 513 * URI scheme. 514 * 515 * @param flags Flags which the {@code PhoneAccount} must have. 516 * @param uriScheme URI schemes the PhoneAccount must handle. {@code Null} bypasses the 517 * URI scheme check. 518 */ 519 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags, String uriScheme) { 520 List<PhoneAccountHandle> accountHandles = new ArrayList<>(); 521 for (PhoneAccount m : mState.accounts) { 522 if (m.hasCapabilities(flags) && (uriScheme == null || m.supportsUriScheme(uriScheme))) { 523 accountHandles.add(m.getAccountHandle()); 524 } 525 } 526 return accountHandles; 527 } 528 529 /** 530 * The state of this {@code PhoneAccountRegistrar}. 531 */ 532 @VisibleForTesting 533 public static class State { 534 /** 535 * The account selected by the user to be employed by default for making outgoing calls. 536 * If the user has not made such a selection, then this is null. 537 */ 538 public PhoneAccountHandle defaultOutgoing = null; 539 540 /** 541 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which 542 * manages and optimizes a user's PSTN SIM connections. 543 */ 544 public PhoneAccountHandle simCallManager; 545 546 /** 547 * The complete list of {@code PhoneAccount}s known to the Telecom subsystem. 548 */ 549 public final List<PhoneAccount> accounts = new ArrayList<>(); 550 551 /** 552 * The version number of the State data. 553 */ 554 public int versionNumber; 555 } 556 557 /** 558 * Dumps the state of the {@link CallsManager}. 559 * 560 * @param pw The {@code IndentingPrintWriter} to write the state to. 561 */ 562 public void dump(IndentingPrintWriter pw) { 563 if (mState != null) { 564 pw.println("xmlVersion: " + mState.versionNumber); 565 pw.println("defaultOutgoing: " + (mState.defaultOutgoing == null ? "none" : 566 mState.defaultOutgoing)); 567 pw.println("simCallManager: " + (mState.simCallManager == null ? "none" : 568 mState.simCallManager)); 569 pw.println("phoneAccounts:"); 570 pw.increaseIndent(); 571 for (PhoneAccount phoneAccount : mState.accounts) { 572 pw.println(phoneAccount); 573 } 574 pw.decreaseIndent(); 575 } 576 } 577 578 //////////////////////////////////////////////////////////////////////////////////////////////// 579 // 580 // State management 581 // 582 583 private void write() { 584 final FileOutputStream os; 585 try { 586 os = mAtomicFile.startWrite(); 587 boolean success = false; 588 try { 589 XmlSerializer serializer = new FastXmlSerializer(); 590 serializer.setOutput(new BufferedOutputStream(os), "utf-8"); 591 writeToXml(mState, serializer); 592 serializer.flush(); 593 success = true; 594 } finally { 595 if (success) { 596 mAtomicFile.finishWrite(os); 597 } else { 598 mAtomicFile.failWrite(os); 599 } 600 } 601 } catch (IOException e) { 602 Log.e(this, e, "Writing state to XML file"); 603 } 604 } 605 606 private void read() { 607 final InputStream is; 608 try { 609 is = mAtomicFile.openRead(); 610 } catch (FileNotFoundException ex) { 611 return; 612 } 613 614 boolean versionChanged = false; 615 616 XmlPullParser parser; 617 try { 618 parser = Xml.newPullParser(); 619 parser.setInput(new BufferedInputStream(is), null); 620 parser.nextTag(); 621 mState = readFromXml(parser, mContext); 622 versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION; 623 624 } catch (IOException | XmlPullParserException e) { 625 Log.e(this, e, "Reading state from XML file"); 626 mState = new State(); 627 } finally { 628 try { 629 is.close(); 630 } catch (IOException e) { 631 Log.e(this, e, "Closing InputStream"); 632 } 633 } 634 635 // If an upgrade occurred, write out the changed data. 636 if (versionChanged) { 637 write(); 638 } 639 } 640 641 private static void writeToXml(State state, XmlSerializer serializer) 642 throws IOException { 643 sStateXml.writeToXml(state, serializer); 644 } 645 646 private static State readFromXml(XmlPullParser parser, Context context) 647 throws IOException, XmlPullParserException { 648 State s = sStateXml.readFromXml(parser, 0, context); 649 return s != null ? s : new State(); 650 } 651 652 //////////////////////////////////////////////////////////////////////////////////////////////// 653 // 654 // XML serialization 655 // 656 657 @VisibleForTesting 658 public abstract static class XmlSerialization<T> { 659 private static final String LENGTH_ATTRIBUTE = "length"; 660 private static final String VALUE_TAG = "value"; 661 662 /** 663 * Write the supplied object to XML 664 */ 665 public abstract void writeToXml(T o, XmlSerializer serializer) 666 throws IOException; 667 668 /** 669 * Read from the supplied XML into a new object, returning null in case of an 670 * unrecoverable schema mismatch or other data error. 'parser' must be already 671 * positioned at the first tag that is expected to have been emitted by this 672 * object's writeToXml(). This object tries to fail early without modifying 673 * 'parser' if it does not recognize the data it sees. 674 */ 675 public abstract T readFromXml(XmlPullParser parser, int version, Context context) 676 throws IOException, XmlPullParserException; 677 678 protected void writeTextSafely(String tagName, Object value, XmlSerializer serializer) 679 throws IOException { 680 if (value != null) { 681 serializer.startTag(null, tagName); 682 serializer.text(Objects.toString(value)); 683 serializer.endTag(null, tagName); 684 } 685 } 686 687 /** 688 * Serializes a string array. 689 * 690 * @param tagName The tag name for the string array. 691 * @param values The string values to serialize. 692 * @param serializer The serializer. 693 * @throws IOException 694 */ 695 protected void writeStringList(String tagName, List<String> values, 696 XmlSerializer serializer) 697 throws IOException { 698 699 serializer.startTag(null, tagName); 700 if (values != null) { 701 serializer.attribute(null, LENGTH_ATTRIBUTE, Objects.toString(values.size())); 702 for (String toSerialize : values) { 703 serializer.startTag(null, VALUE_TAG); 704 if (toSerialize != null ){ 705 serializer.text(toSerialize); 706 } 707 serializer.endTag(null, VALUE_TAG); 708 } 709 } else { 710 serializer.attribute(null, LENGTH_ATTRIBUTE, "0"); 711 } 712 serializer.endTag(null, tagName); 713 714 } 715 716 /** 717 * Reads a string array from the XML parser. 718 * 719 * @param parser The XML parser. 720 * @return String array containing the parsed values. 721 * @throws IOException Exception related to IO. 722 * @throws XmlPullParserException Exception related to parsing. 723 */ 724 protected List<String> readStringList(XmlPullParser parser) 725 throws IOException, XmlPullParserException { 726 727 int length = Integer.parseInt(parser.getAttributeValue(null, LENGTH_ATTRIBUTE)); 728 List<String> arrayEntries = new ArrayList<String>(length); 729 String value = null; 730 731 if (length == 0) { 732 return arrayEntries; 733 } 734 735 int outerDepth = parser.getDepth(); 736 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 737 if (parser.getName().equals(VALUE_TAG)) { 738 parser.next(); 739 value = parser.getText(); 740 arrayEntries.add(value); 741 } 742 } 743 744 return arrayEntries; 745 } 746 } 747 748 @VisibleForTesting 749 public static final XmlSerialization<State> sStateXml = 750 new XmlSerialization<State>() { 751 private static final String CLASS_STATE = "phone_account_registrar_state"; 752 private static final String DEFAULT_OUTGOING = "default_outgoing"; 753 private static final String SIM_CALL_MANAGER = "sim_call_manager"; 754 private static final String ACCOUNTS = "accounts"; 755 private static final String VERSION = "version"; 756 757 @Override 758 public void writeToXml(State o, XmlSerializer serializer) 759 throws IOException { 760 if (o != null) { 761 serializer.startTag(null, CLASS_STATE); 762 serializer.attribute(null, VERSION, Objects.toString(EXPECTED_STATE_VERSION)); 763 764 if (o.defaultOutgoing != null) { 765 serializer.startTag(null, DEFAULT_OUTGOING); 766 sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer); 767 serializer.endTag(null, DEFAULT_OUTGOING); 768 } 769 770 if (o.simCallManager != null) { 771 serializer.startTag(null, SIM_CALL_MANAGER); 772 sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer); 773 serializer.endTag(null, SIM_CALL_MANAGER); 774 } 775 776 serializer.startTag(null, ACCOUNTS); 777 for (PhoneAccount m : o.accounts) { 778 sPhoneAccountXml.writeToXml(m, serializer); 779 } 780 serializer.endTag(null, ACCOUNTS); 781 782 serializer.endTag(null, CLASS_STATE); 783 } 784 } 785 786 @Override 787 public State readFromXml(XmlPullParser parser, int version, Context context) 788 throws IOException, XmlPullParserException { 789 if (parser.getName().equals(CLASS_STATE)) { 790 State s = new State(); 791 792 String rawVersion = parser.getAttributeValue(null, VERSION); 793 s.versionNumber = TextUtils.isEmpty(rawVersion) ? 1 : 794 Integer.parseInt(rawVersion); 795 796 int outerDepth = parser.getDepth(); 797 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 798 if (parser.getName().equals(DEFAULT_OUTGOING)) { 799 parser.nextTag(); 800 s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser, 801 s.versionNumber, context); 802 } else if (parser.getName().equals(SIM_CALL_MANAGER)) { 803 parser.nextTag(); 804 s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser, 805 s.versionNumber, context); 806 } else if (parser.getName().equals(ACCOUNTS)) { 807 int accountsDepth = parser.getDepth(); 808 while (XmlUtils.nextElementWithin(parser, accountsDepth)) { 809 PhoneAccount account = sPhoneAccountXml.readFromXml(parser, 810 s.versionNumber, context); 811 812 if (account != null && s.accounts != null) { 813 s.accounts.add(account); 814 } 815 } 816 } 817 } 818 return s; 819 } 820 return null; 821 } 822 }; 823 824 @VisibleForTesting 825 public static final XmlSerialization<PhoneAccount> sPhoneAccountXml = 826 new XmlSerialization<PhoneAccount>() { 827 private static final String CLASS_PHONE_ACCOUNT = "phone_account"; 828 private static final String ACCOUNT_HANDLE = "account_handle"; 829 private static final String ADDRESS = "handle"; 830 private static final String SUBSCRIPTION_ADDRESS = "subscription_number"; 831 private static final String CAPABILITIES = "capabilities"; 832 private static final String ICON_RES_ID = "icon_res_id"; 833 private static final String COLOR = "color"; 834 private static final String LABEL = "label"; 835 private static final String SHORT_DESCRIPTION = "short_description"; 836 private static final String SUPPORTED_URI_SCHEMES = "supported_uri_schemes"; 837 private static final String TRUE = "true"; 838 private static final String FALSE = "false"; 839 840 @Override 841 public void writeToXml(PhoneAccount o, XmlSerializer serializer) 842 throws IOException { 843 if (o != null) { 844 serializer.startTag(null, CLASS_PHONE_ACCOUNT); 845 846 if (o.getAccountHandle() != null) { 847 serializer.startTag(null, ACCOUNT_HANDLE); 848 sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer); 849 serializer.endTag(null, ACCOUNT_HANDLE); 850 } 851 852 writeTextSafely(ADDRESS, o.getAddress(), serializer); 853 writeTextSafely(SUBSCRIPTION_ADDRESS, o.getSubscriptionAddress(), serializer); 854 writeTextSafely(CAPABILITIES, Integer.toString(o.getCapabilities()), serializer); 855 writeTextSafely(ICON_RES_ID, Integer.toString(o.getIconResId()), serializer); 856 writeTextSafely(COLOR, Integer.toString(o.getColor()), serializer); 857 writeTextSafely(LABEL, o.getLabel(), serializer); 858 writeTextSafely(SHORT_DESCRIPTION, o.getShortDescription(), serializer); 859 writeStringList(SUPPORTED_URI_SCHEMES, o.getSupportedUriSchemes(), serializer); 860 861 serializer.endTag(null, CLASS_PHONE_ACCOUNT); 862 } 863 } 864 865 public PhoneAccount readFromXml(XmlPullParser parser, int version, Context context) 866 throws IOException, XmlPullParserException { 867 if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) { 868 int outerDepth = parser.getDepth(); 869 PhoneAccountHandle accountHandle = null; 870 Uri address = null; 871 Uri subscriptionAddress = null; 872 int capabilities = 0; 873 int iconResId = 0; 874 int color = 0; 875 String label = null; 876 String shortDescription = null; 877 List<String> supportedUriSchemes = null; 878 879 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 880 if (parser.getName().equals(ACCOUNT_HANDLE)) { 881 parser.nextTag(); 882 accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version, 883 context); 884 } else if (parser.getName().equals(ADDRESS)) { 885 parser.next(); 886 address = Uri.parse(parser.getText()); 887 } else if (parser.getName().equals(SUBSCRIPTION_ADDRESS)) { 888 parser.next(); 889 String nextText = parser.getText(); 890 subscriptionAddress = nextText == null ? null : Uri.parse(nextText); 891 } else if (parser.getName().equals(CAPABILITIES)) { 892 parser.next(); 893 capabilities = Integer.parseInt(parser.getText()); 894 } else if (parser.getName().equals(ICON_RES_ID)) { 895 parser.next(); 896 iconResId = Integer.parseInt(parser.getText()); 897 } else if (parser.getName().equals(COLOR)) { 898 parser.next(); 899 color = Integer.parseInt(parser.getText()); 900 } else if (parser.getName().equals(LABEL)) { 901 parser.next(); 902 label = parser.getText(); 903 } else if (parser.getName().equals(SHORT_DESCRIPTION)) { 904 parser.next(); 905 shortDescription = parser.getText(); 906 } else if (parser.getName().equals(SUPPORTED_URI_SCHEMES)) { 907 supportedUriSchemes = readStringList(parser); 908 } 909 } 910 911 // Upgrade older phone accounts to specify the supported URI schemes. 912 if (version < 2) { 913 ComponentName sipComponentName = new ComponentName("com.android.phone", 914 "com.android.services.telephony.sip.SipConnectionService"); 915 916 supportedUriSchemes = new ArrayList<>(); 917 918 // Handle the SIP connection service. 919 // Check the system settings to see if it also should handle "tel" calls. 920 if (accountHandle.getComponentName().equals(sipComponentName)) { 921 boolean useSipForPstn = useSipForPstnCalls(context); 922 supportedUriSchemes.add(PhoneAccount.SCHEME_SIP); 923 if (useSipForPstn) { 924 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL); 925 } 926 } else { 927 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL); 928 supportedUriSchemes.add(PhoneAccount.SCHEME_VOICEMAIL); 929 } 930 } 931 932 return PhoneAccount.builder(accountHandle, label) 933 .setAddress(address) 934 .setSubscriptionAddress(subscriptionAddress) 935 .setCapabilities(capabilities) 936 .setIconResId(iconResId) 937 .setColor(color) 938 .setShortDescription(shortDescription) 939 .setSupportedUriSchemes(supportedUriSchemes) 940 .build(); 941 } 942 return null; 943 } 944 945 /** 946 * Determines if the SIP call settings specify to use SIP for all calls, including PSTN calls. 947 * 948 * @param context The context. 949 * @return {@code True} if SIP should be used for all calls. 950 */ 951 private boolean useSipForPstnCalls(Context context) { 952 String option = Settings.System.getString(context.getContentResolver(), 953 Settings.System.SIP_CALL_OPTIONS); 954 option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY; 955 return option.equals(Settings.System.SIP_ALWAYS); 956 } 957 }; 958 959 @VisibleForTesting 960 public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml = 961 new XmlSerialization<PhoneAccountHandle>() { 962 private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle"; 963 private static final String COMPONENT_NAME = "component_name"; 964 private static final String ID = "id"; 965 966 @Override 967 public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer) 968 throws IOException { 969 if (o != null) { 970 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE); 971 972 if (o.getComponentName() != null) { 973 writeTextSafely( 974 COMPONENT_NAME, o.getComponentName().flattenToString(), serializer); 975 } 976 977 writeTextSafely(ID, o.getId(), serializer); 978 979 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE); 980 } 981 } 982 983 @Override 984 public PhoneAccountHandle readFromXml(XmlPullParser parser, int version, Context context) 985 throws IOException, XmlPullParserException { 986 if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) { 987 String componentNameString = null; 988 String idString = null; 989 int outerDepth = parser.getDepth(); 990 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 991 if (parser.getName().equals(COMPONENT_NAME)) { 992 parser.next(); 993 componentNameString = parser.getText(); 994 } else if (parser.getName().equals(ID)) { 995 parser.next(); 996 idString = parser.getText(); 997 } 998 } 999 if (componentNameString != null) { 1000 return new PhoneAccountHandle( 1001 ComponentName.unflattenFromString(componentNameString), 1002 idString); 1003 } 1004 } 1005 return null; 1006 } 1007 }; 1008} 1009