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