PhoneAccountRegistrar.java revision 638f05cb620a17ae7334486d2f53e223ac65eef5
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.ComponentName; 21import android.content.Context; 22import android.content.Intent; 23import android.content.pm.PackageManager; 24import android.content.pm.ResolveInfo; 25import android.content.pm.ServiceInfo; 26import android.content.pm.UserInfo; 27import android.graphics.Bitmap; 28import android.graphics.BitmapFactory; 29import android.graphics.drawable.Icon; 30import android.net.Uri; 31import android.os.Binder; 32import android.os.Process; 33import android.os.UserHandle; 34import android.os.UserManager; 35import android.provider.Settings; 36import android.telecom.ConnectionService; 37import android.telecom.PhoneAccount; 38import android.telecom.PhoneAccountHandle; 39import android.telephony.PhoneNumberUtils; 40import android.telephony.SubscriptionManager; 41import android.telephony.TelephonyManager; 42import android.text.TextUtils; 43import android.util.AtomicFile; 44import android.util.Base64; 45import android.util.Xml; 46 47// TODO: Needed for move to system service: import com.android.internal.R; 48import com.android.internal.annotations.VisibleForTesting; 49import com.android.internal.util.FastXmlSerializer; 50import com.android.internal.util.IndentingPrintWriter; 51import com.android.internal.util.XmlUtils; 52 53import org.xmlpull.v1.XmlPullParser; 54import org.xmlpull.v1.XmlPullParserException; 55import org.xmlpull.v1.XmlSerializer; 56 57import java.io.BufferedInputStream; 58import java.io.BufferedOutputStream; 59import java.io.ByteArrayInputStream; 60import java.io.ByteArrayOutputStream; 61import java.io.File; 62import java.io.FileNotFoundException; 63import java.io.FileOutputStream; 64import java.io.IOException; 65import java.io.InputStream; 66import java.lang.Integer; 67import java.lang.SecurityException; 68import java.lang.String; 69import java.util.ArrayList; 70import java.util.Collections; 71import java.util.Iterator; 72import java.util.List; 73import java.util.Objects; 74import java.util.concurrent.CopyOnWriteArrayList; 75 76/** 77 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim 78 * delegate for all the account handling methods on {@link android.telecom.TelecomManager} as 79 * implemented in {@link TelecomServiceImpl}, with the notable exception that 80 * {@link TelecomServiceImpl} is responsible for security checking to make sure that the caller has 81 * proper authority over the {@code ComponentName}s they are declaring in their 82 * {@code PhoneAccountHandle}s. 83 * 84 * 85 * -- About Users and Phone Accounts -- 86 * 87 * We store all phone accounts for all users in a single place, which means that there are three 88 * users that we have to deal with in code: 89 * 1) The Android User that is currently active on the device. 90 * 2) The user which owns/registers the phone account. 91 * 3) The user running the app that is requesting the phone account information. 92 * 93 * For example, I have a device with 2 users, primary (A) and secondary (B), and the secondary user 94 * has a work profile running as another user (B2). Lets say that user B opens the phone settings 95 * (not currently supported, but theoretically speaking), and phone settings queries for a phone 96 * account list. Lets also say that an app running in the work profile has registered a phone 97 * account. This means that: 98 * 99 * Since phone settings always runs as the primary user, We have the following situation: 100 * User A (settings) is requesting a list of phone accounts while the active user is User B, and 101 * that list contains a phone account for profile User B2. 102 * 103 * In practice, (2) is stored with the phone account handle and is part of the handle's ID. (1) is 104 * saved in {@link #mCurrentUserHandle} and (3) we get from Binder.getCallingUser(). We check these 105 * users for visibility before returning any phone accounts. 106 */ 107public final class PhoneAccountRegistrar { 108 109 public static final PhoneAccountHandle NO_ACCOUNT_SELECTED = 110 new PhoneAccountHandle(new ComponentName("null", "null"), "NO_ACCOUNT_SELECTED"); 111 112 public abstract static class Listener { 113 public void onAccountsChanged(PhoneAccountRegistrar registrar) {} 114 public void onDefaultOutgoingChanged(PhoneAccountRegistrar registrar) {} 115 public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {} 116 } 117 118 private static final String FILE_NAME = "phone-account-registrar-state.xml"; 119 @VisibleForTesting 120 public static final int EXPECTED_STATE_VERSION = 5; 121 122 /** Keep in sync with the same in SipSettings.java */ 123 private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES"; 124 125 private final List<Listener> mListeners = new CopyOnWriteArrayList<>(); 126 private final AtomicFile mAtomicFile; 127 private final Context mContext; 128 private final UserManager mUserManager; 129 private final SubscriptionManager mSubscriptionManager; 130 private State mState; 131 private UserHandle mCurrentUserHandle; 132 133 @VisibleForTesting 134 public PhoneAccountRegistrar(Context context) { 135 this(context, FILE_NAME); 136 } 137 138 @VisibleForTesting 139 public PhoneAccountRegistrar(Context context, String fileName) { 140 // TODO: This file path is subject to change -- it is storing the phone account registry 141 // state file in the path /data/system/users/0/, which is likely not correct in a 142 // multi-user setting. 143 /** UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE 144 String filePath = Environment.getUserSystemDirectory(UserHandle.myUserId()). 145 getAbsolutePath(); 146 mAtomicFile = new AtomicFile(new File(filePath, fileName)); 147 UNCOMMENT_FOR_MOVE_TO_SYSTEM_SERVICE */ 148 mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName)); 149 150 mState = new State(); 151 mContext = context; 152 mUserManager = UserManager.get(context); 153 mSubscriptionManager = SubscriptionManager.from(mContext); 154 mCurrentUserHandle = Process.myUserHandle(); 155 read(); 156 } 157 158 /** 159 * Retrieves the subscription id for a given phone account if it exists. Subscription ids 160 * apply only to PSTN/SIM card phone accounts so all other accounts should not have a 161 * subscription id. 162 * @param accountHandle The handle for the phone account for which to retrieve the 163 * subscription id. 164 * @return The value of the subscription id or -1 if it does not exist or is not valid. 165 */ 166 public int getSubscriptionIdForPhoneAccount(PhoneAccountHandle accountHandle) { 167 PhoneAccount account = getPhoneAccountCheckCallingUser(accountHandle); 168 169 if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 170 TelephonyManager tm = 171 (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 172 return tm.getSubIdForPhoneAccount(account); 173 } 174 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 175 } 176 177 /** 178 * Retrieves the default outgoing phone account supporting the specified uriScheme. Note that if 179 * {@link #mCurrentUserHandle} does not have visibility into the current default, {@code null} 180 * will be returned. 181 * 182 * @param uriScheme The URI scheme for the outgoing call. 183 * @return The {@link PhoneAccountHandle} to use. 184 */ 185 public PhoneAccountHandle getOutgoingPhoneAccountForScheme(String uriScheme) { 186 final PhoneAccountHandle userSelected = getUserSelectedOutgoingPhoneAccount(); 187 188 if (userSelected != null) { 189 // If there is a default PhoneAccount, ensure it supports calls to handles with the 190 // specified uriScheme. 191 final PhoneAccount userSelectedAccount = getPhoneAccountCheckCallingUser(userSelected); 192 if (userSelectedAccount.supportsUriScheme(uriScheme)) { 193 return userSelected; 194 } 195 } 196 197 List<PhoneAccountHandle> outgoing = getCallCapablePhoneAccounts(uriScheme, false); 198 switch (outgoing.size()) { 199 case 0: 200 // There are no accounts, so there can be no default 201 return null; 202 case 1: 203 // There is only one account, which is by definition the default. 204 return outgoing.get(0); 205 default: 206 // There are multiple accounts with no selected default 207 return null; 208 } 209 } 210 211 /** 212 * @return The user-selected outgoing {@link PhoneAccount}, or null if it hasn't been set (or 213 * if it was set by another user). 214 */ 215 PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() { 216 PhoneAccount account = getPhoneAccountCheckCallingUser(mState.defaultOutgoing); 217 if (account != null) { 218 return mState.defaultOutgoing; 219 } 220 return null; 221 } 222 223 /** 224 * Sets the phone account with which to place all calls by default. Set by the user 225 * within phone settings. 226 */ 227 public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) { 228 if (accountHandle == null) { 229 // Asking to clear the default outgoing is a valid request 230 mState.defaultOutgoing = null; 231 } else { 232 // TODO: Do we really want to return for *any* user? 233 PhoneAccount account = getPhoneAccount(accountHandle); 234 if (account == null) { 235 Log.w(this, "Trying to set nonexistent default outgoing %s", 236 accountHandle); 237 return; 238 } 239 240 if (!account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) { 241 Log.w(this, "Trying to set non-call-provider default outgoing %s", 242 accountHandle); 243 return; 244 } 245 246 if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 247 // If the account selected is a SIM account, propagate down to the subscription 248 // record. 249 int subId = getSubscriptionIdForPhoneAccount(accountHandle); 250 mSubscriptionManager.setDefaultVoiceSubId(subId); 251 } 252 253 mState.defaultOutgoing = accountHandle; 254 } 255 256 write(); 257 fireDefaultOutgoingChanged(); 258 } 259 260 boolean isUserSelectedSmsPhoneAccount(PhoneAccountHandle accountHandle) { 261 return getSubscriptionIdForPhoneAccount(accountHandle) == 262 SubscriptionManager.getDefaultSmsSubId(); 263 } 264 265 public void setSimCallManager(PhoneAccountHandle callManager) { 266 if (callManager != null) { 267 // TODO: Do we really want to return for *any* user? 268 PhoneAccount callManagerAccount = getPhoneAccount(callManager); 269 if (callManagerAccount == null) { 270 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager); 271 return; 272 } else if (!callManagerAccount.hasCapabilities( 273 PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) { 274 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount); 275 return; 276 } 277 } else { 278 callManager = NO_ACCOUNT_SELECTED; 279 } 280 mState.simCallManager = callManager; 281 282 write(); 283 fireSimCallManagerChanged(); 284 } 285 286 /** 287 * @return The {@link PhoneAccount}s which are visible to {@link #mCurrentUserHandle}. 288 */ 289 public PhoneAccountHandle getSimCallManager() { 290 PhoneAccount account = getPhoneAccountCheckCallingUser(mState.simCallManager); 291 292 // Return the registered sim call manager iff it still exists (we keep a sticky 293 // setting to survive account deletion and re-addition) 294 if (account != null && !resolveComponent(mState.simCallManager).isEmpty()) { 295 return mState.simCallManager; 296 } 297 298 // We have no set call manager, but check to see if the OEM has specified a default one. 299 String defaultConnectionMgr = 300 mContext.getResources().getString(R.string.default_connection_manager_component); 301 if (!TextUtils.isEmpty(defaultConnectionMgr)) { 302 ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr); 303 if (componentName == null) { 304 return null; 305 } 306 307 // Make sure that the component can be resolved. 308 List<ResolveInfo> resolveInfos = resolveComponent(componentName, null); 309 if (resolveInfos.isEmpty()) { 310 resolveInfos = resolveComponent(componentName, Binder.getCallingUserHandle()); 311 } 312 313 if (!resolveInfos.isEmpty()) { 314 // See if there is registered PhoneAccount by this component. 315 List<PhoneAccountHandle> handles = getAllPhoneAccountHandles(); 316 for (PhoneAccountHandle handle : handles) { 317 if (componentName.equals(handle.getComponentName())) { 318 return handle; 319 } 320 } 321 Log.d(this, "%s does not have a PhoneAccount; not using as default", componentName); 322 } else { 323 Log.d(this, "%s could not be resolved; not using as default", componentName); 324 } 325 } else { 326 Log.v(this, "No default connection manager specified"); 327 } 328 329 return null; 330 } 331 332 /** 333 * Update the current UserHandle to track when users are switched. This will allow the 334 * PhoneAccountRegistar to self-filter the PhoneAccounts to make sure we don't leak anything 335 * across users. 336 * We cannot simply check the calling user because that would always return the primary user for 337 * all invocations originating with the system process. 338 * 339 * @param userHandle The {@link UserHandle}, as delivered by 340 * {@link Intent#ACTION_USER_SWITCHED}. 341 */ 342 public void setCurrentUserHandle(UserHandle userHandle) { 343 if (userHandle == null) { 344 Log.d(this, "setCurrentUserHandle, userHandle = null"); 345 userHandle = Process.myUserHandle(); 346 } 347 Log.d(this, "setCurrentUserHandle, %s", userHandle); 348 mCurrentUserHandle = userHandle; 349 } 350 351 public void enablePhoneAccount(PhoneAccountHandle accountHandle, boolean isEnabled) { 352 PhoneAccount account = getPhoneAccount(accountHandle); 353 if (account == null) { 354 Log.w(this, "Could not find account to enable: " + accountHandle); 355 return; 356 } else if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 357 // We never change the enabled state of SIM-based accounts. 358 Log.w(this, "Could not change enable state of SIM account: " + accountHandle); 359 return; 360 } 361 362 if (account.isEnabled() != isEnabled) { 363 account.setIsEnabled(isEnabled); 364 write(); 365 fireAccountsChanged(); 366 } 367 } 368 369 private boolean isVisibleForUser(PhoneAccount account) { 370 if (account == null) { 371 return false; 372 } 373 374 // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and 375 // all profiles. Only Telephony and SIP accounts should have this capability. 376 if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) { 377 return true; 378 } 379 380 UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle(); 381 if (phoneAccountUserHandle == null) { 382 return false; 383 } 384 385 if (mCurrentUserHandle == null) { 386 Log.d(this, "Current user is null; assuming true"); 387 return true; 388 } 389 390 if (phoneAccountUserHandle.equals(Binder.getCallingUserHandle())) { 391 return true; 392 } 393 394 // Special check for work profiles. 395 // Unlike in TelecomServiceImpl, we only care about *profiles* here. We want to make sure 396 // that we don't resolve PhoneAccount across *users*, but resolving across *profiles* is 397 // fine. 398 if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) { 399 List<UserInfo> profileUsers = 400 mUserManager.getProfiles(mCurrentUserHandle.getIdentifier()); 401 for (UserInfo profileInfo : profileUsers) { 402 if (profileInfo.getUserHandle().equals(phoneAccountUserHandle)) { 403 return true; 404 } 405 } 406 } 407 408 return false; 409 } 410 411 private List<ResolveInfo> resolveComponent(PhoneAccountHandle phoneAccountHandle) { 412 return resolveComponent(phoneAccountHandle.getComponentName(), 413 phoneAccountHandle.getUserHandle()); 414 } 415 416 private List<ResolveInfo> resolveComponent(ComponentName componentName, 417 UserHandle userHandle) { 418 PackageManager pm = mContext.getPackageManager(); 419 Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE); 420 intent.setComponent(componentName); 421 try { 422 if (userHandle != null) { 423 return pm.queryIntentServicesAsUser(intent, 0, userHandle.getIdentifier()); 424 } else { 425 return pm.queryIntentServices(intent, 0); 426 } 427 } catch (SecurityException e) { 428 Log.v(this, "%s is not visible for the calling user", componentName); 429 return Collections.EMPTY_LIST; 430 } 431 } 432 433 /** 434 * Retrieves a list of all {@link PhoneAccountHandle}s registered. 435 * Only returns accounts which are enabled. 436 * 437 * @return The list of {@link PhoneAccountHandle}s. 438 */ 439 public List<PhoneAccountHandle> getAllPhoneAccountHandles() { 440 return getPhoneAccountHandles(0, null, null, false); 441 } 442 443 public List<PhoneAccount> getAllPhoneAccounts() { 444 return getPhoneAccounts(0, null, null, false); 445 } 446 447 /** 448 * Retrieves a list of all phone account call provider phone accounts supporting the 449 * specified URI scheme. 450 * 451 * @param uriScheme The URI scheme. 452 * @return The phone account handles. 453 */ 454 public List<PhoneAccountHandle> getCallCapablePhoneAccounts( 455 String uriScheme, boolean includeDisabledAccounts) { 456 return getPhoneAccountHandles( 457 PhoneAccount.CAPABILITY_CALL_PROVIDER, uriScheme, null, includeDisabledAccounts); 458 } 459 460 /** 461 * Retrieves a list of all the SIM-based phone accounts. 462 */ 463 public List<PhoneAccountHandle> getSimPhoneAccounts() { 464 return getPhoneAccountHandles( 465 PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION, 466 null, null, false); 467 } 468 469 /** 470 * Retrieves a list of all phone accounts registered by a specified package. 471 * 472 * @param packageName The name of the package that registered the phone accounts. 473 * @return The phone account handles. 474 */ 475 public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) { 476 return getPhoneAccountHandles(0, null, packageName, false); 477 } 478 479 /** 480 * Retrieves a list of all phone account handles with the connection manager capability. 481 * 482 * @return The phone account handles. 483 */ 484 public List<PhoneAccountHandle> getConnectionManagerPhoneAccounts() { 485 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CONNECTION_MANAGER, null, null, true); 486 } 487 488 // TODO: Should we implement an artificial limit for # of accounts associated with a single 489 // ComponentName? 490 public void registerPhoneAccount(PhoneAccount account) { 491 // Enforce the requirement that a connection service for a phone account has the correct 492 // permission. 493 if (!phoneAccountRequiresBindPermission(account.getAccountHandle())) { 494 Log.w(this, 495 "Phone account %s does not have BIND_TELECOM_CONNECTION_SERVICE permission.", 496 account.getAccountHandle()); 497 throw new SecurityException("PhoneAccount connection service requires " 498 + "BIND_TELECOM_CONNECTION_SERVICE permission."); 499 } 500 501 addOrReplacePhoneAccount(account); 502 } 503 504 /** 505 * Adds a {@code PhoneAccount}, replacing an existing one if found. 506 * 507 * @param account The {@code PhoneAccount} to add or replace. 508 */ 509 private void addOrReplacePhoneAccount(PhoneAccount account) { 510 Log.d(this, "addOrReplacePhoneAccount(%s -> %s)", 511 account.getAccountHandle(), account); 512 513 // Start _enabled_ property as false. 514 // !!! IMPORTANT !!! It is important that we do not read the enabled state that the 515 // source app provides or else an third party app could enable itself. 516 boolean isEnabled = false; 517 518 PhoneAccount oldAccount = getPhoneAccount(account.getAccountHandle()); 519 if (oldAccount != null) { 520 mState.accounts.remove(oldAccount); 521 isEnabled = oldAccount.isEnabled(); 522 } 523 524 mState.accounts.add(account); 525 // Reset enabled state to whatever the value was if the account was already registered, 526 // or _true_ if this is a SIM-based account. All SIM-based accounts are always enabled. 527 account.setIsEnabled( 528 isEnabled || account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)); 529 530 write(); 531 fireAccountsChanged(); 532 } 533 534 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) { 535 PhoneAccount account = getPhoneAccount(accountHandle); 536 if (account != null) { 537 if (mState.accounts.remove(account)) { 538 write(); 539 fireAccountsChanged(); 540 } 541 } 542 } 543 544 /** 545 * Un-registers all phone accounts associated with a specified package. 546 * 547 * @param packageName The package for which phone accounts will be removed. 548 * @param userHandle The {@link UserHandle} the package is running under. 549 */ 550 public void clearAccounts(String packageName, UserHandle userHandle) { 551 boolean accountsRemoved = false; 552 Iterator<PhoneAccount> it = mState.accounts.iterator(); 553 while (it.hasNext()) { 554 PhoneAccount phoneAccount = it.next(); 555 PhoneAccountHandle handle = phoneAccount.getAccountHandle(); 556 if (Objects.equals(packageName, handle.getComponentName().getPackageName()) 557 && Objects.equals(userHandle, handle.getUserHandle())) { 558 Log.i(this, "Removing phone account " + phoneAccount.getLabel()); 559 it.remove(); 560 accountsRemoved = true; 561 } 562 } 563 564 if (accountsRemoved) { 565 write(); 566 fireAccountsChanged(); 567 } 568 } 569 570 public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number) { 571 int subId = getSubscriptionIdForPhoneAccount(accountHandle); 572 return PhoneNumberUtils.isVoiceMailNumber(subId, number); 573 } 574 575 public void addListener(Listener l) { 576 mListeners.add(l); 577 } 578 579 public void removeListener(Listener l) { 580 if (l != null) { 581 mListeners.remove(l); 582 } 583 } 584 585 private void fireAccountsChanged() { 586 for (Listener l : mListeners) { 587 l.onAccountsChanged(this); 588 } 589 } 590 591 private void fireDefaultOutgoingChanged() { 592 for (Listener l : mListeners) { 593 l.onDefaultOutgoingChanged(this); 594 } 595 } 596 597 private void fireSimCallManagerChanged() { 598 for (Listener l : mListeners) { 599 l.onSimCallManagerChanged(this); 600 } 601 } 602 603 /** 604 * Determines if the connection service specified by a {@link PhoneAccountHandle} requires the 605 * {@link Manifest.permission#BIND_TELECOM_CONNECTION_SERVICE} permission. 606 * 607 * @param phoneAccountHandle The phone account to check. 608 * @return {@code True} if the phone account has permission. 609 */ 610 public boolean phoneAccountRequiresBindPermission(PhoneAccountHandle phoneAccountHandle) { 611 List<ResolveInfo> resolveInfos = resolveComponent(phoneAccountHandle); 612 if (resolveInfos.isEmpty()) { 613 Log.w(this, "phoneAccount %s not found", phoneAccountHandle.getComponentName()); 614 return false; 615 } 616 for (ResolveInfo resolveInfo : resolveInfos) { 617 ServiceInfo serviceInfo = resolveInfo.serviceInfo; 618 if (serviceInfo == null) { 619 return false; 620 } 621 622 if (!Manifest.permission.BIND_CONNECTION_SERVICE.equals(serviceInfo.permission) && 623 !Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE.equals( 624 serviceInfo.permission)) { 625 // The ConnectionService must require either the deprecated BIND_CONNECTION_SERVICE, 626 // or the public BIND_TELECOM_CONNECTION_SERVICE permissions, both of which are 627 // system/signature only. 628 return false; 629 } 630 } 631 return true; 632 } 633 634 // 635 // Methods for retrieving PhoneAccounts and PhoneAccountHandles 636 // 637 638 /** 639 * Returns the PhoneAccount for the specified handle. Does no user checking. 640 * 641 * @param handle 642 * @return The corresponding phone account if one exists. 643 */ 644 PhoneAccount getPhoneAccount(PhoneAccountHandle handle) { 645 for (PhoneAccount m : mState.accounts) { 646 if (Objects.equals(handle, m.getAccountHandle())) { 647 return m; 648 } 649 } 650 return null; 651 } 652 653 /** 654 * Like getPhoneAccount, but checks to see if the current user is allowed to see the phone 655 * account before returning it. The current user is the active user on the actual android 656 * device. 657 */ 658 public PhoneAccount getPhoneAccountCheckCallingUser(PhoneAccountHandle handle) { 659 PhoneAccount account = getPhoneAccount(handle); 660 if (account != null && isVisibleForUser(account)) { 661 return account; 662 } 663 return null; 664 } 665 666 /** 667 * Returns a list of phone account handles with the specified capabilities, uri scheme, 668 * and package name. 669 */ 670 private List<PhoneAccountHandle> getPhoneAccountHandles( 671 int capabilities, 672 String uriScheme, 673 String packageName, 674 boolean includeDisabledAccounts) { 675 List<PhoneAccountHandle> handles = new ArrayList<>(); 676 677 for (PhoneAccount account : getPhoneAccounts( 678 capabilities, uriScheme, packageName, includeDisabledAccounts)) { 679 handles.add(account.getAccountHandle()); 680 } 681 return handles; 682 } 683 684 /** 685 * Returns a list of phone account handles with the specified flag, supporting the specified 686 * URI scheme, within the specified package name. 687 * 688 * @param capabilities Capabilities which the {@code PhoneAccount} must have. Ignored if 0. 689 * @param uriScheme URI schemes the PhoneAccount must handle. {@code null} bypasses the 690 * URI scheme check. 691 * @param packageName Package name of the PhoneAccount. {@code null} bypasses packageName check. 692 */ 693 private List<PhoneAccount> getPhoneAccounts( 694 int capabilities, 695 String uriScheme, 696 String packageName, 697 boolean includeDisabledAccounts) { 698 List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size()); 699 for (PhoneAccount m : mState.accounts) { 700 if (!(m.isEnabled() || includeDisabledAccounts)) { 701 // Do not include disabled accounts. 702 continue; 703 } 704 705 if (capabilities != 0 && !m.hasCapabilities(capabilities)) { 706 // Account doesn't have the right capabilities; skip this one. 707 continue; 708 } 709 if (uriScheme != null && !m.supportsUriScheme(uriScheme)) { 710 // Account doesn't support this URI scheme; skip this one. 711 continue; 712 } 713 PhoneAccountHandle handle = m.getAccountHandle(); 714 715 if (resolveComponent(handle).isEmpty()) { 716 // This component cannot be resolved anymore; skip this one. 717 continue; 718 } 719 if (packageName != null && 720 !packageName.equals(handle.getComponentName().getPackageName())) { 721 // Not the right package name; skip this one. 722 continue; 723 } 724 if (!isVisibleForUser(m)) { 725 // Account is not visible for the current user; skip this one. 726 continue; 727 } 728 accounts.add(m); 729 } 730 return accounts; 731 } 732 733 // 734 // State Implementation for PhoneAccountRegistrar 735 // 736 737 /** 738 * The state of this {@code PhoneAccountRegistrar}. 739 */ 740 @VisibleForTesting 741 public static class State { 742 /** 743 * The account selected by the user to be employed by default for making outgoing calls. 744 * If the user has not made such a selection, then this is null. 745 */ 746 public PhoneAccountHandle defaultOutgoing = null; 747 748 /** 749 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which 750 * manages and optimizes a user's PSTN SIM connections. 751 */ 752 public PhoneAccountHandle simCallManager; 753 754 /** 755 * The complete list of {@code PhoneAccount}s known to the Telecom subsystem. 756 */ 757 public final List<PhoneAccount> accounts = new ArrayList<>(); 758 759 /** 760 * The version number of the State data. 761 */ 762 public int versionNumber; 763 } 764 765 /** 766 * Dumps the state of the {@link CallsManager}. 767 * 768 * @param pw The {@code IndentingPrintWriter} to write the state to. 769 */ 770 public void dump(IndentingPrintWriter pw) { 771 if (mState != null) { 772 pw.println("xmlVersion: " + mState.versionNumber); 773 pw.println("defaultOutgoing: " + (mState.defaultOutgoing == null ? "none" : 774 mState.defaultOutgoing)); 775 pw.println("simCallManager: " + (mState.simCallManager == null ? "none" : 776 mState.simCallManager)); 777 pw.println("phoneAccounts:"); 778 pw.increaseIndent(); 779 for (PhoneAccount phoneAccount : mState.accounts) { 780 pw.println(phoneAccount); 781 } 782 pw.decreaseIndent(); 783 } 784 } 785 786 //////////////////////////////////////////////////////////////////////////////////////////////// 787 // 788 // State management 789 // 790 791 private void write() { 792 final FileOutputStream os; 793 try { 794 os = mAtomicFile.startWrite(); 795 boolean success = false; 796 try { 797 XmlSerializer serializer = new FastXmlSerializer(); 798 serializer.setOutput(new BufferedOutputStream(os), "utf-8"); 799 writeToXml(mState, serializer, mContext); 800 serializer.flush(); 801 success = true; 802 } finally { 803 if (success) { 804 mAtomicFile.finishWrite(os); 805 } else { 806 mAtomicFile.failWrite(os); 807 } 808 } 809 } catch (IOException e) { 810 Log.e(this, e, "Writing state to XML file"); 811 } 812 } 813 814 private void read() { 815 final InputStream is; 816 try { 817 is = mAtomicFile.openRead(); 818 } catch (FileNotFoundException ex) { 819 return; 820 } 821 822 boolean versionChanged = false; 823 824 XmlPullParser parser; 825 try { 826 parser = Xml.newPullParser(); 827 parser.setInput(new BufferedInputStream(is), null); 828 parser.nextTag(); 829 mState = readFromXml(parser, mContext); 830 versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION; 831 832 } catch (IOException | XmlPullParserException e) { 833 Log.e(this, e, "Reading state from XML file"); 834 mState = new State(); 835 } finally { 836 try { 837 is.close(); 838 } catch (IOException e) { 839 Log.e(this, e, "Closing InputStream"); 840 } 841 } 842 843 // Verify all of the UserHandles. 844 List<PhoneAccount> badAccounts = new ArrayList<>(); 845 for (PhoneAccount phoneAccount : mState.accounts) { 846 UserHandle userHandle = phoneAccount.getAccountHandle().getUserHandle(); 847 if (userHandle == null) { 848 Log.w(this, "Missing UserHandle for %s", phoneAccount); 849 badAccounts.add(phoneAccount); 850 } else if (mUserManager.getSerialNumberForUser(userHandle) == -1) { 851 Log.w(this, "User does not exist for %s", phoneAccount); 852 badAccounts.add(phoneAccount); 853 } 854 } 855 mState.accounts.removeAll(badAccounts); 856 857 // If an upgrade occurred, write out the changed data. 858 if (versionChanged || !badAccounts.isEmpty()) { 859 write(); 860 } 861 } 862 863 private static void writeToXml(State state, XmlSerializer serializer, Context context) 864 throws IOException { 865 sStateXml.writeToXml(state, serializer, context); 866 } 867 868 private static State readFromXml(XmlPullParser parser, Context context) 869 throws IOException, XmlPullParserException { 870 State s = sStateXml.readFromXml(parser, 0, context); 871 return s != null ? s : new State(); 872 } 873 874 //////////////////////////////////////////////////////////////////////////////////////////////// 875 // 876 // XML serialization 877 // 878 879 @VisibleForTesting 880 public abstract static class XmlSerialization<T> { 881 private static final String LENGTH_ATTRIBUTE = "length"; 882 private static final String VALUE_TAG = "value"; 883 884 /** 885 * Write the supplied object to XML 886 */ 887 public abstract void writeToXml(T o, XmlSerializer serializer, Context context) 888 throws IOException; 889 890 /** 891 * Read from the supplied XML into a new object, returning null in case of an 892 * unrecoverable schema mismatch or other data error. 'parser' must be already 893 * positioned at the first tag that is expected to have been emitted by this 894 * object's writeToXml(). This object tries to fail early without modifying 895 * 'parser' if it does not recognize the data it sees. 896 */ 897 public abstract T readFromXml(XmlPullParser parser, int version, Context context) 898 throws IOException, XmlPullParserException; 899 900 protected void writeTextIfNonNull(String tagName, Object value, XmlSerializer serializer) 901 throws IOException { 902 if (value != null) { 903 serializer.startTag(null, tagName); 904 serializer.text(Objects.toString(value)); 905 serializer.endTag(null, tagName); 906 } 907 } 908 909 /** 910 * Serializes a string array. 911 * 912 * @param tagName The tag name for the string array. 913 * @param values The string values to serialize. 914 * @param serializer The serializer. 915 * @throws IOException 916 */ 917 protected void writeStringList(String tagName, List<String> values, 918 XmlSerializer serializer) 919 throws IOException { 920 921 serializer.startTag(null, tagName); 922 if (values != null) { 923 serializer.attribute(null, LENGTH_ATTRIBUTE, Objects.toString(values.size())); 924 for (String toSerialize : values) { 925 serializer.startTag(null, VALUE_TAG); 926 if (toSerialize != null ){ 927 serializer.text(toSerialize); 928 } 929 serializer.endTag(null, VALUE_TAG); 930 } 931 } else { 932 serializer.attribute(null, LENGTH_ATTRIBUTE, "0"); 933 } 934 serializer.endTag(null, tagName); 935 } 936 937 protected void writeIconIfNonNull(String tagName, Icon value, XmlSerializer serializer) 938 throws IOException { 939 if (value != null) { 940 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 941 value.writeToStream(stream); 942 byte[] iconByteArray = stream.toByteArray(); 943 String text = Base64.encodeToString(iconByteArray, 0, iconByteArray.length, 0); 944 945 serializer.startTag(null, tagName); 946 serializer.text(text); 947 serializer.endTag(null, tagName); 948 } 949 } 950 951 protected void writeLong(String tagName, long value, XmlSerializer serializer) 952 throws IOException { 953 serializer.startTag(null, tagName); 954 serializer.text(Long.valueOf(value).toString()); 955 serializer.endTag(null, tagName); 956 } 957 958 /** 959 * Reads a string array from the XML parser. 960 * 961 * @param parser The XML parser. 962 * @return String array containing the parsed values. 963 * @throws IOException Exception related to IO. 964 * @throws XmlPullParserException Exception related to parsing. 965 */ 966 protected List<String> readStringList(XmlPullParser parser) 967 throws IOException, XmlPullParserException { 968 969 int length = Integer.parseInt(parser.getAttributeValue(null, LENGTH_ATTRIBUTE)); 970 List<String> arrayEntries = new ArrayList<String>(length); 971 String value = null; 972 973 if (length == 0) { 974 return arrayEntries; 975 } 976 977 int outerDepth = parser.getDepth(); 978 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 979 if (parser.getName().equals(VALUE_TAG)) { 980 parser.next(); 981 value = parser.getText(); 982 arrayEntries.add(value); 983 } 984 } 985 986 return arrayEntries; 987 } 988 989 protected Bitmap readBitmap(XmlPullParser parser) { 990 byte[] imageByteArray = Base64.decode(parser.getText(), 0); 991 return BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length); 992 } 993 994 protected Icon readIcon(XmlPullParser parser) throws IOException { 995 byte[] iconByteArray = Base64.decode(parser.getText(), 0); 996 ByteArrayInputStream stream = new ByteArrayInputStream(iconByteArray); 997 return Icon.createFromStream(stream); 998 } 999 } 1000 1001 @VisibleForTesting 1002 public static final XmlSerialization<State> sStateXml = 1003 new XmlSerialization<State>() { 1004 private static final String CLASS_STATE = "phone_account_registrar_state"; 1005 private static final String DEFAULT_OUTGOING = "default_outgoing"; 1006 private static final String SIM_CALL_MANAGER = "sim_call_manager"; 1007 private static final String ACCOUNTS = "accounts"; 1008 private static final String VERSION = "version"; 1009 1010 @Override 1011 public void writeToXml(State o, XmlSerializer serializer, Context context) 1012 throws IOException { 1013 if (o != null) { 1014 serializer.startTag(null, CLASS_STATE); 1015 serializer.attribute(null, VERSION, Objects.toString(EXPECTED_STATE_VERSION)); 1016 1017 if (o.defaultOutgoing != null) { 1018 serializer.startTag(null, DEFAULT_OUTGOING); 1019 sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer, context); 1020 serializer.endTag(null, DEFAULT_OUTGOING); 1021 } 1022 1023 if (o.simCallManager != null) { 1024 serializer.startTag(null, SIM_CALL_MANAGER); 1025 sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer, context); 1026 serializer.endTag(null, SIM_CALL_MANAGER); 1027 } 1028 1029 serializer.startTag(null, ACCOUNTS); 1030 for (PhoneAccount m : o.accounts) { 1031 sPhoneAccountXml.writeToXml(m, serializer, context); 1032 } 1033 serializer.endTag(null, ACCOUNTS); 1034 1035 serializer.endTag(null, CLASS_STATE); 1036 } 1037 } 1038 1039 @Override 1040 public State readFromXml(XmlPullParser parser, int version, Context context) 1041 throws IOException, XmlPullParserException { 1042 if (parser.getName().equals(CLASS_STATE)) { 1043 State s = new State(); 1044 1045 String rawVersion = parser.getAttributeValue(null, VERSION); 1046 s.versionNumber = TextUtils.isEmpty(rawVersion) ? 1 : 1047 Integer.parseInt(rawVersion); 1048 1049 int outerDepth = parser.getDepth(); 1050 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1051 if (parser.getName().equals(DEFAULT_OUTGOING)) { 1052 parser.nextTag(); 1053 s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser, 1054 s.versionNumber, context); 1055 } else if (parser.getName().equals(SIM_CALL_MANAGER)) { 1056 parser.nextTag(); 1057 s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser, 1058 s.versionNumber, context); 1059 if (s.simCallManager.getUserHandle() == null) { 1060 // This should never happen, but handle the upgrade case. 1061 s.simCallManager = new PhoneAccountHandle( 1062 s.simCallManager.getComponentName(), 1063 s.simCallManager.getId(), 1064 Process.myUserHandle()); 1065 } 1066 } else if (parser.getName().equals(ACCOUNTS)) { 1067 int accountsDepth = parser.getDepth(); 1068 while (XmlUtils.nextElementWithin(parser, accountsDepth)) { 1069 PhoneAccount account = sPhoneAccountXml.readFromXml(parser, 1070 s.versionNumber, context); 1071 1072 if (account != null && s.accounts != null) { 1073 s.accounts.add(account); 1074 } 1075 } 1076 } 1077 } 1078 return s; 1079 } 1080 return null; 1081 } 1082 }; 1083 1084 @VisibleForTesting 1085 public static final XmlSerialization<PhoneAccount> sPhoneAccountXml = 1086 new XmlSerialization<PhoneAccount>() { 1087 private static final String CLASS_PHONE_ACCOUNT = "phone_account"; 1088 private static final String ACCOUNT_HANDLE = "account_handle"; 1089 private static final String ADDRESS = "handle"; 1090 private static final String SUBSCRIPTION_ADDRESS = "subscription_number"; 1091 private static final String CAPABILITIES = "capabilities"; 1092 private static final String ICON_RES_ID = "icon_res_id"; 1093 private static final String ICON_PACKAGE_NAME = "icon_package_name"; 1094 private static final String ICON_BITMAP = "icon_bitmap"; 1095 private static final String ICON_TINT = "icon_tint"; 1096 private static final String HIGHLIGHT_COLOR = "highlight_color"; 1097 private static final String LABEL = "label"; 1098 private static final String SHORT_DESCRIPTION = "short_description"; 1099 private static final String SUPPORTED_URI_SCHEMES = "supported_uri_schemes"; 1100 private static final String ICON = "icon"; 1101 private static final String ENABLED = "enabled"; 1102 1103 @Override 1104 public void writeToXml(PhoneAccount o, XmlSerializer serializer, Context context) 1105 throws IOException { 1106 if (o != null) { 1107 serializer.startTag(null, CLASS_PHONE_ACCOUNT); 1108 1109 if (o.getAccountHandle() != null) { 1110 serializer.startTag(null, ACCOUNT_HANDLE); 1111 sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer, context); 1112 serializer.endTag(null, ACCOUNT_HANDLE); 1113 } 1114 1115 writeTextIfNonNull(ADDRESS, o.getAddress(), serializer); 1116 writeTextIfNonNull(SUBSCRIPTION_ADDRESS, o.getSubscriptionAddress(), serializer); 1117 writeTextIfNonNull(CAPABILITIES, Integer.toString(o.getCapabilities()), serializer); 1118 writeIconIfNonNull(ICON, o.getIcon(), serializer); 1119 writeTextIfNonNull(HIGHLIGHT_COLOR, 1120 Integer.toString(o.getHighlightColor()), serializer); 1121 writeTextIfNonNull(LABEL, o.getLabel(), serializer); 1122 writeTextIfNonNull(SHORT_DESCRIPTION, o.getShortDescription(), serializer); 1123 writeStringList(SUPPORTED_URI_SCHEMES, o.getSupportedUriSchemes(), serializer); 1124 writeTextIfNonNull(ENABLED, o.isEnabled() ? "true" : "false" , serializer); 1125 1126 serializer.endTag(null, CLASS_PHONE_ACCOUNT); 1127 } 1128 } 1129 1130 public PhoneAccount readFromXml(XmlPullParser parser, int version, Context context) 1131 throws IOException, XmlPullParserException { 1132 if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) { 1133 int outerDepth = parser.getDepth(); 1134 PhoneAccountHandle accountHandle = null; 1135 Uri address = null; 1136 Uri subscriptionAddress = null; 1137 int capabilities = 0; 1138 int iconResId = PhoneAccount.NO_RESOURCE_ID; 1139 String iconPackageName = null; 1140 Bitmap iconBitmap = null; 1141 int iconTint = PhoneAccount.NO_ICON_TINT; 1142 int highlightColor = PhoneAccount.NO_HIGHLIGHT_COLOR; 1143 String label = null; 1144 String shortDescription = null; 1145 List<String> supportedUriSchemes = null; 1146 Icon icon = null; 1147 boolean enabled = false; 1148 1149 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1150 if (parser.getName().equals(ACCOUNT_HANDLE)) { 1151 parser.nextTag(); 1152 accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version, 1153 context); 1154 } else if (parser.getName().equals(ADDRESS)) { 1155 parser.next(); 1156 address = Uri.parse(parser.getText()); 1157 } else if (parser.getName().equals(SUBSCRIPTION_ADDRESS)) { 1158 parser.next(); 1159 String nextText = parser.getText(); 1160 subscriptionAddress = nextText == null ? null : Uri.parse(nextText); 1161 } else if (parser.getName().equals(CAPABILITIES)) { 1162 parser.next(); 1163 capabilities = Integer.parseInt(parser.getText()); 1164 } else if (parser.getName().equals(ICON_RES_ID)) { 1165 parser.next(); 1166 iconResId = Integer.parseInt(parser.getText()); 1167 } else if (parser.getName().equals(ICON_PACKAGE_NAME)) { 1168 parser.next(); 1169 iconPackageName = parser.getText(); 1170 } else if (parser.getName().equals(ICON_BITMAP)) { 1171 parser.next(); 1172 iconBitmap = readBitmap(parser); 1173 } else if (parser.getName().equals(ICON_TINT)) { 1174 parser.next(); 1175 iconTint = Integer.parseInt(parser.getText()); 1176 } else if (parser.getName().equals(HIGHLIGHT_COLOR)) { 1177 parser.next(); 1178 highlightColor = Integer.parseInt(parser.getText()); 1179 } else if (parser.getName().equals(LABEL)) { 1180 parser.next(); 1181 label = parser.getText(); 1182 } else if (parser.getName().equals(SHORT_DESCRIPTION)) { 1183 parser.next(); 1184 shortDescription = parser.getText(); 1185 } else if (parser.getName().equals(SUPPORTED_URI_SCHEMES)) { 1186 supportedUriSchemes = readStringList(parser); 1187 } else if (parser.getName().equals(ICON)) { 1188 parser.next(); 1189 icon = readIcon(parser); 1190 } else if (parser.getName().equals(ENABLED)) { 1191 parser.next(); 1192 enabled = "true".equalsIgnoreCase(parser.getText()); 1193 } 1194 } 1195 1196 // Upgrade older phone accounts to specify the supported URI schemes. 1197 if (version < 2) { 1198 ComponentName sipComponentName = new ComponentName("com.android.phone", 1199 "com.android.services.telephony.sip.SipConnectionService"); 1200 1201 supportedUriSchemes = new ArrayList<>(); 1202 1203 // Handle the SIP connection service. 1204 // Check the system settings to see if it also should handle "tel" calls. 1205 if (accountHandle.getComponentName().equals(sipComponentName)) { 1206 boolean useSipForPstn = useSipForPstnCalls(context); 1207 supportedUriSchemes.add(PhoneAccount.SCHEME_SIP); 1208 if (useSipForPstn) { 1209 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL); 1210 } 1211 } else { 1212 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL); 1213 supportedUriSchemes.add(PhoneAccount.SCHEME_VOICEMAIL); 1214 } 1215 } 1216 1217 // Upgrade older phone accounts with explicit package name 1218 if (version < 5) { 1219 if (iconBitmap == null) { 1220 iconPackageName = accountHandle.getComponentName().getPackageName(); 1221 } 1222 } 1223 1224 PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, label) 1225 .setAddress(address) 1226 .setSubscriptionAddress(subscriptionAddress) 1227 .setCapabilities(capabilities) 1228 .setShortDescription(shortDescription) 1229 .setSupportedUriSchemes(supportedUriSchemes) 1230 .setHighlightColor(highlightColor) 1231 .setIsEnabled(enabled); 1232 1233 if (icon != null) { 1234 builder.setIcon(icon); 1235 } else if (iconBitmap != null) { 1236 builder.setIcon(Icon.createWithBitmap(iconBitmap)); 1237 } else if (!TextUtils.isEmpty(iconPackageName)) { 1238 builder.setIcon(Icon.createWithResource(iconPackageName, iconResId)); 1239 // TODO: Need to set tint. 1240 } 1241 1242 return builder.build(); 1243 } 1244 return null; 1245 } 1246 1247 /** 1248 * Determines if the SIP call settings specify to use SIP for all calls, including PSTN 1249 * calls. 1250 * 1251 * @param context The context. 1252 * @return {@code True} if SIP should be used for all calls. 1253 */ 1254 private boolean useSipForPstnCalls(Context context) { 1255 String option = Settings.System.getString(context.getContentResolver(), 1256 Settings.System.SIP_CALL_OPTIONS); 1257 option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY; 1258 return option.equals(Settings.System.SIP_ALWAYS); 1259 } 1260 }; 1261 1262 @VisibleForTesting 1263 public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml = 1264 new XmlSerialization<PhoneAccountHandle>() { 1265 private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle"; 1266 private static final String COMPONENT_NAME = "component_name"; 1267 private static final String ID = "id"; 1268 private static final String USER_SERIAL_NUMBER = "user_serial_number"; 1269 1270 @Override 1271 public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer, Context context) 1272 throws IOException { 1273 if (o != null) { 1274 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE); 1275 1276 if (o.getComponentName() != null) { 1277 writeTextIfNonNull( 1278 COMPONENT_NAME, o.getComponentName().flattenToString(), serializer); 1279 } 1280 1281 writeTextIfNonNull(ID, o.getId(), serializer); 1282 1283 if (o.getUserHandle() != null && context != null) { 1284 UserManager userManager = UserManager.get(context); 1285 writeLong(USER_SERIAL_NUMBER, 1286 userManager.getSerialNumberForUser(o.getUserHandle()), serializer); 1287 } 1288 1289 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE); 1290 } 1291 } 1292 1293 @Override 1294 public PhoneAccountHandle readFromXml(XmlPullParser parser, int version, Context context) 1295 throws IOException, XmlPullParserException { 1296 if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) { 1297 String componentNameString = null; 1298 String idString = null; 1299 String userSerialNumberString = null; 1300 int outerDepth = parser.getDepth(); 1301 1302 UserManager userManager = UserManager.get(context); 1303 1304 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 1305 if (parser.getName().equals(COMPONENT_NAME)) { 1306 parser.next(); 1307 componentNameString = parser.getText(); 1308 } else if (parser.getName().equals(ID)) { 1309 parser.next(); 1310 idString = parser.getText(); 1311 } else if (parser.getName().equals(USER_SERIAL_NUMBER)) { 1312 parser.next(); 1313 userSerialNumberString = parser.getText(); 1314 } 1315 } 1316 if (componentNameString != null) { 1317 UserHandle userHandle = null; 1318 if (userSerialNumberString != null) { 1319 try { 1320 long serialNumber = Long.parseLong(userSerialNumberString); 1321 userHandle = userManager.getUserForSerialNumber(serialNumber); 1322 } catch (NumberFormatException e) { 1323 Log.e(this, e, "Could not parse UserHandle " + userSerialNumberString); 1324 } 1325 } 1326 return new PhoneAccountHandle( 1327 ComponentName.unflattenFromString(componentNameString), 1328 idString, 1329 userHandle); 1330 } 1331 } 1332 return null; 1333 } 1334 }; 1335} 1336