• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server.telecom;
18 
19 import android.Manifest;
20 import android.content.Intent;
21 import android.content.pm.PackageManager;
22 import android.content.pm.ResolveInfo;
23 import android.content.pm.ServiceInfo;
24 import android.os.Environment;
25 import android.os.UserHandle;
26 import android.provider.Settings;
27 import android.telecom.ConnectionService;
28 import android.telecom.PhoneAccount;
29 import android.telecom.PhoneAccountHandle;
30 import android.telecom.TelecomManager;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.net.Uri;
34 import android.text.TextUtils;
35 import android.util.AtomicFile;
36 import android.util.Xml;
37 
38 // TODO: Needed for move to system service: import com.android.internal.R;
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.util.FastXmlSerializer;
41 import com.android.internal.util.XmlUtils;
42 
43 import org.xmlpull.v1.XmlPullParser;
44 import org.xmlpull.v1.XmlPullParserException;
45 import org.xmlpull.v1.XmlSerializer;
46 
47 import java.io.BufferedInputStream;
48 import java.io.BufferedOutputStream;
49 import java.io.File;
50 import java.io.FileNotFoundException;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.lang.Integer;
55 import java.lang.SecurityException;
56 import java.lang.String;
57 import java.util.ArrayList;
58 import java.util.Collections;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Objects;
62 import 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  */
71 public 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 {
onAccountsChanged(PhoneAccountRegistrar registrar)77         public void onAccountsChanged(PhoneAccountRegistrar registrar) {}
onDefaultOutgoingChanged(PhoneAccountRegistrar registrar)78         public void onDefaultOutgoingChanged(PhoneAccountRegistrar registrar) {}
onSimCallManagerChanged(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 
PhoneAccountRegistrar(Context context)94     public PhoneAccountRegistrar(Context context) {
95         this(context, FILE_NAME);
96     }
97 
98     @VisibleForTesting
PhoneAccountRegistrar(Context context, String fileName)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      */
getDefaultOutgoingPhoneAccount(String uriScheme)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 
getUserSelectedOutgoingPhoneAccount()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 
setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle)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 
setSimCallManager(PhoneAccountHandle callManager)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 
getSimCallManager()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      */
getAllPhoneAccountHandles()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 
getAllPhoneAccounts()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      */
getAllPhoneAccountsCount()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      */
getCallCapablePhoneAccounts()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      */
getCallCapablePhoneAccounts(String uriScheme)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      */
getPhoneAccountsForPackage(String packageName)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      */
getConnectionManagerPhoneAccounts()328     public List<PhoneAccountHandle> getConnectionManagerPhoneAccounts() {
329         return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CONNECTION_MANAGER,
330                 null /* supportedUriScheme */);
331     }
332 
getPhoneAccount(PhoneAccountHandle handle)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?
registerPhoneAccount(PhoneAccount account)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      */
addOrReplacePhoneAccount(PhoneAccount account)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 
unregisterPhoneAccount(PhoneAccountHandle accountHandle)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      */
clearAccounts(String packageName)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 
addListener(Listener l)415     public void addListener(Listener l) {
416         mListeners.add(l);
417     }
418 
removeListener(Listener l)419     public void removeListener(Listener l) {
420         if (l != null) {
421             mListeners.remove(l);
422         }
423     }
424 
fireAccountsChanged()425     private void fireAccountsChanged() {
426         for (Listener l : mListeners) {
427             l.onAccountsChanged(this);
428         }
429     }
430 
fireDefaultOutgoingChanged()431     private void fireDefaultOutgoingChanged() {
432         for (Listener l : mListeners) {
433             l.onDefaultOutgoingChanged(this);
434         }
435     }
436 
fireSimCallManagerChanged()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      */
phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle)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      */
getPhoneAccountHandles(int flags)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      */
getPhoneAccountHandles(int flags, String uriScheme)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 
write()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 
read()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