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