PhoneAccountRegistrar.java revision d45da14356db9e9ff0cba1d2bc75ccd749706aba
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 = 5; 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 Log.d(this, "addOrReplacePhoneAccount(%s -> %s)", 398 account.getAccountHandle(), account); 399 400 mState.accounts.add(account); 401 // Search for duplicates and remove any that are found. 402 for (int i = 0; i < mState.accounts.size() - 1; i++) { 403 if (Objects.equals( 404 account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) { 405 // replace existing entry. 406 mState.accounts.remove(i); 407 break; 408 } 409 } 410 411 write(); 412 fireAccountsChanged(); 413 } 414 415 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { 416 for (int i = 0; i < mState.accounts.size(); i++) { 417 if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) { 418 mState.accounts.remove(i); 419 break; 420 } 421 } 422 423 write(); 424 fireAccountsChanged(); 425 } 426 427 /** 428 * Un-registers all phone accounts associated with a specified package. 429 * 430 * @param packageName The package for which phone accounts will be removed. 431 */ 432 public void clearAccounts(String packageName) { 433 boolean accountsRemoved = false; 434 Iterator<PhoneAccount> it = mState.accounts.iterator(); 435 while (it.hasNext()) { 436 PhoneAccount phoneAccount = it.next(); 437 if (Objects.equals( 438 packageName, 439 phoneAccount.getAccountHandle().getComponentName().getPackageName())) { 440 Log.i(this, "Removing phone account " + phoneAccount.getLabel()); 441 it.remove(); 442 accountsRemoved = true; 443 } 444 } 445 446 if (accountsRemoved) { 447 write(); 448 fireAccountsChanged(); 449 } 450 } 451 452 public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) { 453 int subId = getSubscriptionIdForPhoneAccount(accountHandle); 454 return PhoneNumberUtils.isVoiceMailNumber(subId, number); 455 } 456 457 public void addListener(Listener l) { 458 mListeners.add(l); 459 } 460 461 public void removeListener(Listener l) { 462 if (l != null) { 463 mListeners.remove(l); 464 } 465 } 466 467 private void fireAccountsChanged() { 468 for (Listener l : mListeners) { 469 l.onAccountsChanged(this); 470 } 471 } 472 473 private void fireDefaultOutgoingChanged() { 474 for (Listener l : mListeners) { 475 l.onDefaultOutgoingChanged(this); 476 } 477 } 478 479 private void fireSimCallManagerChanged() { 480 for (Listener l : mListeners) { 481 l.onSimCallManagerChanged(this); 482 } 483 } 484 485 /** 486 * Determines if the connection service specified by a {@link PhoneAccountHandle} has the 487 * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission. 488 * 489 * @param phoneAccountHandle The phone account to check. 490 * @return {@code True} if the phone account has permission. 491 */ 492 public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) { 493 PackageManager packageManager = mContext.getPackageManager(); 494 try { 495 ServiceInfo serviceInfo = packageManager.getServiceInfo( 496 phoneAccountHandle.getComponentName(), 0); 497 498 return serviceInfo.permission != null && 499 serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE); 500 } catch (PackageManager.NameNotFoundException e) { 501 Log.w(this, "Name not found %s", e); 502 return false; 503 } 504 } 505 506 //////////////////////////////////////////////////////////////////////////////////////////////// 507 508 /** 509 * Returns a list of phone account handles with the specified flag. 510 * 511 * @param flags Flags which the {@code PhoneAccount} must have. 512 */ 513 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags) { 514 return getPhoneAccountHandles(flags, null); 515 } 516 517 /** 518 * Returns a list of phone account handles with the specified flag, supporting the specified 519 * URI scheme. 520 * 521 * @param flags Flags which the {@code PhoneAccount} must have. 522 * @param uriScheme URI schemes the PhoneAccount must handle. {@code Null} bypasses the 523 * URI scheme check. 524 */ 525 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags, String uriScheme) { 526 List<PhoneAccountHandle> accountHandles = new ArrayList<>(); 527 for (PhoneAccount m : mState.accounts) { 528 if (m.hasCapabilities(flags) && (uriScheme == null || m.supportsUriScheme(uriScheme))) { 529 // Also filter out unresolveable accounts 530 if (!resolveComponent(m.getAccountHandle().getComponentName()).isEmpty()) { 531 accountHandles.add(m.getAccountHandle()); 532 } 533 } 534 } 535 return accountHandles; 536 } 537 538 /** 539 * The state of this {@code PhoneAccountRegistrar}. 540 */ 541 @VisibleForTesting 542 public static class State { 543 /** 544 * The account selected by the user to be employed by default for making outgoing calls. 545 * If the user has not made such a selection, then this is null. 546 */ 547 public PhoneAccountHandle defaultOutgoing = null; 548 549 /** 550 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which 551 * manages and optimizes a user's PSTN SIM connections. 552 */ 553 public PhoneAccountHandle simCallManager; 554 555 /** 556 * The complete list of {@code PhoneAccount}s known to the Telecom subsystem. 557 */ 558 public final List<PhoneAccount> accounts = new ArrayList<>(); 559 560 /** 561 * The version number of the State data. 562 */ 563 public int versionNumber; 564 } 565 566 /** 567 * Dumps the state of the {@link CallsManager}. 568 * 569 * @param pw The {@code IndentingPrintWriter} to write the state to. 570 */ 571 public void dump(IndentingPrintWriter pw) { 572 if (mState != null) { 573 pw.println("xmlVersion: " + mState.versionNumber); 574 pw.println("defaultOutgoing: " + (mState.defaultOutgoing == null ? "none" : 575 mState.defaultOutgoing)); 576 pw.println("simCallManager: " + (mState.simCallManager == null ? "none" : 577 mState.simCallManager)); 578 pw.println("phoneAccounts:"); 579 pw.increaseIndent(); 580 for (PhoneAccount phoneAccount : mState.accounts) { 581 pw.println(phoneAccount); 582 } 583 pw.decreaseIndent(); 584 } 585 } 586 587 //////////////////////////////////////////////////////////////////////////////////////////////// 588 // 589 // State management 590 // 591 592 private void write() { 593 final FileOutputStream os; 594 try { 595 os = mAtomicFile.startWrite(); 596 boolean success = false; 597 try { 598 XmlSerializer serializer = new FastXmlSerializer(); 599 serializer.setOutput(new BufferedOutputStream(os), "utf-8"); 600 writeToXml(mState, serializer); 601 serializer.flush(); 602 success = true; 603 } finally { 604 if (success) { 605 mAtomicFile.finishWrite(os); 606 } else { 607 mAtomicFile.failWrite(os); 608 } 609 } 610 } catch (IOException e) { 611 Log.e(this, e, "Writing state to XML file"); 612 } 613 } 614 615 private void read() { 616 final InputStream is; 617 try { 618 is = mAtomicFile.openRead(); 619 } catch (FileNotFoundException ex) { 620 return; 621 } 622 623 boolean versionChanged = false; 624 625 XmlPullParser parser; 626 try { 627 parser = Xml.newPullParser(); 628 parser.setInput(new BufferedInputStream(is), null); 629 parser.nextTag(); 630 mState = readFromXml(parser, mContext); 631 versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION; 632 633 } catch (IOException | XmlPullParserException e) { 634 Log.e(this, e, "Reading state from XML file"); 635 mState = new State(); 636 } finally { 637 try { 638 is.close(); 639 } catch (IOException e) { 640 Log.e(this, e, "Closing InputStream"); 641 } 642 } 643 644 // If an upgrade occurred, write out the changed data. 645 if (versionChanged) { 646 write(); 647 } 648 } 649 650 private static void writeToXml(State state, XmlSerializer serializer) 651 throws IOException { 652 sStateXml.writeToXml(state, serializer); 653 } 654 655 private static State readFromXml(XmlPullParser parser, Context context) 656 throws IOException, XmlPullParserException { 657 State s = sStateXml.readFromXml(parser, 0, context); 658 return s != null ? s : new State(); 659 } 660 661 //////////////////////////////////////////////////////////////////////////////////////////////// 662 // 663 // XML serialization 664 // 665 666 @VisibleForTesting 667 public abstract static class XmlSerialization<T> { 668 private static final String LENGTH_ATTRIBUTE = "length"; 669 private static final String VALUE_TAG = "value"; 670 671 /** 672 * Write the supplied object to XML 673 */ 674 public abstract void writeToXml(T o, XmlSerializer serializer) 675 throws IOException; 676 677 /** 678 * Read from the supplied XML into a new object, returning null in case of an 679 * unrecoverable schema mismatch or other data error. 'parser' must be already 680 * positioned at the first tag that is expected to have been emitted by this 681 * object's writeToXml(). This object tries to fail early without modifying 682 * 'parser' if it does not recognize the data it sees. 683 */ 684 public abstract T readFromXml(XmlPullParser parser, int version, Context context) 685 throws IOException, XmlPullParserException; 686 687 protected void writeTextIfNonNull(String tagName, Object value, XmlSerializer serializer) 688 throws IOException { 689 if (value != null) { 690 serializer.startTag(null, tagName); 691 serializer.text(Objects.toString(value)); 692 serializer.endTag(null, tagName); 693 } 694 } 695 696 /** 697 * Serializes a string array. 698 * 699 * @param tagName The tag name for the string array. 700 * @param values The string values to serialize. 701 * @param serializer The serializer. 702 * @throws IOException 703 */ 704 protected void writeStringList(String tagName, List<String> values, 705 XmlSerializer serializer) 706 throws IOException { 707 708 serializer.startTag(null, tagName); 709 if (values != null) { 710 serializer.attribute(null, LENGTH_ATTRIBUTE, Objects.toString(values.size())); 711 for (String toSerialize : values) { 712 serializer.startTag(null, VALUE_TAG); 713 if (toSerialize != null ){ 714 serializer.text(toSerialize); 715 } 716 serializer.endTag(null, VALUE_TAG); 717 } 718 } else { 719 serializer.attribute(null, LENGTH_ATTRIBUTE, "0"); 720 } 721 serializer.endTag(null, tagName); 722 } 723 724 protected void writeBitmapIfNonNull(String tagName, Bitmap value, XmlSerializer serializer) 725 throws IOException { 726 if (value != null && value.getByteCount() > 0) { 727 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 728 value.compress(Bitmap.CompressFormat.PNG, 100, stream); 729 byte[] imageByteArray = stream.toByteArray(); 730 String text = Base64.encodeToString(imageByteArray, 0, imageByteArray.length, 0); 731 732 serializer.startTag(null, tagName); 733 serializer.text(text); 734 serializer.endTag(null, tagName); 735 } 736 } 737 738 /** 739 * Reads a string array from the XML parser. 740 * 741 * @param parser The XML parser. 742 * @return String array containing the parsed values. 743 * @throws IOException Exception related to IO. 744 * @throws XmlPullParserException Exception related to parsing. 745 */ 746 protected List<String> readStringList(XmlPullParser parser) 747 throws IOException, XmlPullParserException { 748 749 int length = Integer.parseInt(parser.getAttributeValue(null, LENGTH_ATTRIBUTE)); 750 List<String> arrayEntries = new ArrayList<String>(length); 751 String value = null; 752 753 if (length == 0) { 754 return arrayEntries; 755 } 756 757 int outerDepth = parser.getDepth(); 758 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 759 if (parser.getName().equals(VALUE_TAG)) { 760 parser.next(); 761 value = parser.getText(); 762 arrayEntries.add(value); 763 } 764 } 765 766 return arrayEntries; 767 } 768 769 protected Bitmap readBitmap(XmlPullParser parser) 770 throws IOException, XmlPullParserException { 771 byte[] imageByteArray = Base64.decode(parser.getText(), 0); 772 return BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length); 773 } 774 } 775 776 @VisibleForTesting 777 public static final XmlSerialization<State> sStateXml = 778 new XmlSerialization<State>() { 779 private static final String CLASS_STATE = "phone_account_registrar_state"; 780 private static final String DEFAULT_OUTGOING = "default_outgoing"; 781 private static final String SIM_CALL_MANAGER = "sim_call_manager"; 782 private static final String ACCOUNTS = "accounts"; 783 private static final String VERSION = "version"; 784 785 @Override 786 public void writeToXml(State o, XmlSerializer serializer) 787 throws IOException { 788 if (o != null) { 789 serializer.startTag(null, CLASS_STATE); 790 serializer.attribute(null, VERSION, Objects.toString(EXPECTED_STATE_VERSION)); 791 792 if (o.defaultOutgoing != null) { 793 serializer.startTag(null, DEFAULT_OUTGOING); 794 sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer); 795 serializer.endTag(null, DEFAULT_OUTGOING); 796 } 797 798 if (o.simCallManager != null) { 799 serializer.startTag(null, SIM_CALL_MANAGER); 800 sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer); 801 serializer.endTag(null, SIM_CALL_MANAGER); 802 } 803 804 serializer.startTag(null, ACCOUNTS); 805 for (PhoneAccount m : o.accounts) { 806 sPhoneAccountXml.writeToXml(m, serializer); 807 } 808 serializer.endTag(null, ACCOUNTS); 809 810 serializer.endTag(null, CLASS_STATE); 811 } 812 } 813 814 @Override 815 public State readFromXml(XmlPullParser parser, int version, Context context) 816 throws IOException, XmlPullParserException { 817 if (parser.getName().equals(CLASS_STATE)) { 818 State s = new State(); 819 820 String rawVersion = parser.getAttributeValue(null, VERSION); 821 s.versionNumber = TextUtils.isEmpty(rawVersion) ? 1 : 822 Integer.parseInt(rawVersion); 823 824 int outerDepth = parser.getDepth(); 825 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 826 if (parser.getName().equals(DEFAULT_OUTGOING)) { 827 parser.nextTag(); 828 s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser, 829 s.versionNumber, context); 830 } else if (parser.getName().equals(SIM_CALL_MANAGER)) { 831 parser.nextTag(); 832 s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser, 833 s.versionNumber, context); 834 } else if (parser.getName().equals(ACCOUNTS)) { 835 int accountsDepth = parser.getDepth(); 836 while (XmlUtils.nextElementWithin(parser, accountsDepth)) { 837 PhoneAccount account = sPhoneAccountXml.readFromXml(parser, 838 s.versionNumber, context); 839 840 if (account != null && s.accounts != null) { 841 s.accounts.add(account); 842 } 843 } 844 } 845 } 846 return s; 847 } 848 return null; 849 } 850 }; 851 852 @VisibleForTesting 853 public static final XmlSerialization<PhoneAccount> sPhoneAccountXml = 854 new XmlSerialization<PhoneAccount>() { 855 private static final String CLASS_PHONE_ACCOUNT = "phone_account"; 856 private static final String ACCOUNT_HANDLE = "account_handle"; 857 private static final String ADDRESS = "handle"; 858 private static final String SUBSCRIPTION_ADDRESS = "subscription_number"; 859 private static final String CAPABILITIES = "capabilities"; 860 private static final String ICON_RES_ID = "icon_res_id"; 861 private static final String ICON_PACKAGE_NAME = "icon_package_name"; 862 private static final String ICON_BITMAP = "icon_bitmap"; 863 private static final String ICON_TINT = "icon_tint"; 864 private static final String HIGHLIGHT_COLOR = "highlight_color"; 865 private static final String LABEL = "label"; 866 private static final String SHORT_DESCRIPTION = "short_description"; 867 private static final String SUPPORTED_URI_SCHEMES = "supported_uri_schemes"; 868 869 @Override 870 public void writeToXml(PhoneAccount o, XmlSerializer serializer) 871 throws IOException { 872 if (o != null) { 873 serializer.startTag(null, CLASS_PHONE_ACCOUNT); 874 875 if (o.getAccountHandle() != null) { 876 serializer.startTag(null, ACCOUNT_HANDLE); 877 sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer); 878 serializer.endTag(null, ACCOUNT_HANDLE); 879 } 880 881 writeTextIfNonNull(ADDRESS, o.getAddress(), serializer); 882 writeTextIfNonNull(SUBSCRIPTION_ADDRESS, o.getSubscriptionAddress(), serializer); 883 writeTextIfNonNull(CAPABILITIES, Integer.toString(o.getCapabilities()), serializer); 884 writeTextIfNonNull(ICON_RES_ID, Integer.toString(o.getIconResId()), serializer); 885 writeTextIfNonNull(ICON_PACKAGE_NAME, o.getIconPackageName(), serializer); 886 writeBitmapIfNonNull(ICON_BITMAP, o.getIconBitmap(), serializer); 887 writeTextIfNonNull(ICON_TINT, Integer.toString(o.getIconTint()), serializer); 888 writeTextIfNonNull(HIGHLIGHT_COLOR, 889 Integer.toString(o.getHighlightColor()), serializer); 890 writeTextIfNonNull(LABEL, o.getLabel(), serializer); 891 writeTextIfNonNull(SHORT_DESCRIPTION, o.getShortDescription(), serializer); 892 writeStringList(SUPPORTED_URI_SCHEMES, o.getSupportedUriSchemes(), serializer); 893 894 serializer.endTag(null, CLASS_PHONE_ACCOUNT); 895 } 896 } 897 898 public PhoneAccount readFromXml(XmlPullParser parser, int version, Context context) 899 throws IOException, XmlPullParserException { 900 if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) { 901 int outerDepth = parser.getDepth(); 902 PhoneAccountHandle accountHandle = null; 903 Uri address = null; 904 Uri subscriptionAddress = null; 905 int capabilities = 0; 906 int iconResId = PhoneAccount.NO_RESOURCE_ID; 907 String iconPackageName = null; 908 Bitmap iconBitmap = null; 909 int iconTint = PhoneAccount.NO_COLOR; 910 int highlightColor = PhoneAccount.NO_COLOR; 911 String label = null; 912 String shortDescription = null; 913 List<String> supportedUriSchemes = null; 914 915 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 916 if (parser.getName().equals(ACCOUNT_HANDLE)) { 917 parser.nextTag(); 918 accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version, 919 context); 920 } else if (parser.getName().equals(ADDRESS)) { 921 parser.next(); 922 address = Uri.parse(parser.getText()); 923 } else if (parser.getName().equals(SUBSCRIPTION_ADDRESS)) { 924 parser.next(); 925 String nextText = parser.getText(); 926 subscriptionAddress = nextText == null ? null : Uri.parse(nextText); 927 } else if (parser.getName().equals(CAPABILITIES)) { 928 parser.next(); 929 capabilities = Integer.parseInt(parser.getText()); 930 } else if (parser.getName().equals(ICON_RES_ID)) { 931 parser.next(); 932 iconResId = Integer.parseInt(parser.getText()); 933 } else if (parser.getName().equals(ICON_PACKAGE_NAME)) { 934 parser.next(); 935 iconPackageName = parser.getText(); 936 } else if (parser.getName().equals(ICON_BITMAP)) { 937 parser.next(); 938 iconBitmap = readBitmap(parser); 939 } else if (parser.getName().equals(ICON_TINT)) { 940 parser.next(); 941 iconTint = Integer.parseInt(parser.getText()); 942 } else if (parser.getName().equals(HIGHLIGHT_COLOR)) { 943 parser.next(); 944 highlightColor = Integer.parseInt(parser.getText()); 945 } else if (parser.getName().equals(LABEL)) { 946 parser.next(); 947 label = parser.getText(); 948 } else if (parser.getName().equals(SHORT_DESCRIPTION)) { 949 parser.next(); 950 shortDescription = parser.getText(); 951 } else if (parser.getName().equals(SUPPORTED_URI_SCHEMES)) { 952 supportedUriSchemes = readStringList(parser); 953 } 954 } 955 956 // Upgrade older phone accounts to specify the supported URI schemes. 957 if (version < 2) { 958 ComponentName sipComponentName = new ComponentName("com.android.phone", 959 "com.android.services.telephony.sip.SipConnectionService"); 960 961 supportedUriSchemes = new ArrayList<>(); 962 963 // Handle the SIP connection service. 964 // Check the system settings to see if it also should handle "tel" calls. 965 if (accountHandle.getComponentName().equals(sipComponentName)) { 966 boolean useSipForPstn = useSipForPstnCalls(context); 967 supportedUriSchemes.add(PhoneAccount.SCHEME_SIP); 968 if (useSipForPstn) { 969 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL); 970 } 971 } else { 972 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL); 973 supportedUriSchemes.add(PhoneAccount.SCHEME_VOICEMAIL); 974 } 975 } 976 977 // Upgrade older phone accounts with explicit package name 978 if (version < 5) { 979 if (iconBitmap == null) { 980 iconPackageName = accountHandle.getComponentName().getPackageName(); 981 } 982 } 983 984 PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, label) 985 .setAddress(address) 986 .setSubscriptionAddress(subscriptionAddress) 987 .setCapabilities(capabilities) 988 .setShortDescription(shortDescription) 989 .setSupportedUriSchemes(supportedUriSchemes) 990 .setHighlightColor(highlightColor); 991 992 if (iconBitmap == null) { 993 builder.setIcon(iconPackageName, iconResId, iconTint); 994 } else { 995 builder.setIcon(iconBitmap); 996 } 997 998 return builder.build(); 999 } 1000 return null; 1001 } 1002 1003 /** 1004 * Determines if the SIP call settings specify to use SIP for all calls, including PSTN calls. 1005 * 1006 * @param context The context. 1007 * @return {@code True} if SIP should be used for all calls. 1008 */ 1009 private boolean useSipForPstnCalls(Context context) { 1010 String option = Settings.System.getString(context.getContentResolver(), 1011 Settings.System.SIP_CALL_OPTIONS); 1012 option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY; 1013 return option.equals(Settings.System.SIP_ALWAYS); 1014 } 1015 }; 1016 1017 @VisibleForTesting 1018 public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml = 1019 new XmlSerialization<PhoneAccountHandle>() { 1020 private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle"; 1021 private static final String COMPONENT_NAME = "component_name"; 1022 private static final String ID = "id"; 1023 1024 @Override 1025 public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer) 1026 throws IOException { 1027 if (o != null) { 1028 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE); 1029 1030 if (o.getComponentName() != null) { 1031 writeTextIfNonNull( 1032 COMPONENT_NAME, o.getComponentName().flattenToString(), serializer); 1033 } 1034 1035 writeTextIfNonNull(ID, o.getId(), serializer); 1036 1037 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE); 1038 } 1039 } 1040 1041 @Override 1042 public PhoneAccountHandle readFromXml(XmlPullParser parser, int version, Context context) 1043 throws IOException, XmlPullParserException { 1044 if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) { 1045 String componentNameString = null; 1046 String idString = null; 1047 int outerDepth = parser.getDepth(); 1048 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1049 if (parser.getName().equals(COMPONENT_NAME)) { 1050 parser.next(); 1051 componentNameString = parser.getText(); 1052 } else if (parser.getName().equals(ID)) { 1053 parser.next(); 1054 idString = parser.getText(); 1055 } 1056 } 1057 if (componentNameString != null) { 1058 return new PhoneAccountHandle( 1059 ComponentName.unflattenFromString(componentNameString), 1060 idString); 1061 } 1062 } 1063 return null; 1064 } 1065 }; 1066} 1067