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