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