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