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