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