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