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