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