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