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