blob: e6aeac581e38c4eb47d9c9be44cc7c3b2001d5df [file] [log] [blame]
Santos Cordon176ae282014-07-14 02:02:14 -07001/*
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
Tyler Gunn7cc70b42014-09-12 22:17:27 -070017package com.android.server.telecom;
Santos Cordon176ae282014-07-14 02:02:14 -070018
Tyler Gunncb59b672014-08-20 09:02:11 -070019import android.Manifest;
Evan Charltonaf51ceb2014-07-30 11:56:36 -070020import android.content.Intent;
21import android.content.pm.PackageManager;
22import android.content.pm.ResolveInfo;
Tyler Gunncb59b672014-08-20 09:02:11 -070023import android.content.pm.ServiceInfo;
Tyler Gunn91d43cf2014-09-17 12:19:39 -070024import android.os.Environment;
25import android.os.UserHandle;
Tyler Gunn84253572014-09-02 14:50:05 -070026import android.provider.Settings;
Tyler Gunn7cc70b42014-09-12 22:17:27 -070027import android.telecom.ConnectionService;
28import android.telecom.PhoneAccount;
29import android.telecom.PhoneAccountHandle;
30import android.telecom.TelecomManager;
Santos Cordon176ae282014-07-14 02:02:14 -070031import android.content.ComponentName;
32import android.content.Context;
Santos Cordon176ae282014-07-14 02:02:14 -070033import android.net.Uri;
Evan Charltonaf51ceb2014-07-30 11:56:36 -070034import android.text.TextUtils;
Ihab Awadb78b2762014-07-25 15:16:23 -070035import android.util.AtomicFile;
36import android.util.Xml;
Santos Cordon176ae282014-07-14 02:02:14 -070037
Tyler Gunn91d43cf2014-09-17 12:19:39 -070038// TODO: Needed for move to system service: import com.android.internal.R;
Sailesh Nepal0e1dc5a2014-07-30 11:08:54 -070039import com.android.internal.annotations.VisibleForTesting;
40import com.android.internal.util.FastXmlSerializer;
41import com.android.internal.util.XmlUtils;
42
Evan Charltonaf51ceb2014-07-30 11:56:36 -070043import org.xmlpull.v1.XmlPullParser;
44import org.xmlpull.v1.XmlPullParserException;
45import org.xmlpull.v1.XmlSerializer;
46
Ihab Awadb78b2762014-07-25 15:16:23 -070047import java.io.BufferedInputStream;
48import java.io.BufferedOutputStream;
49import java.io.File;
50import java.io.FileNotFoundException;
51import java.io.FileOutputStream;
52import java.io.IOException;
53import java.io.InputStream;
Tyler Gunn84253572014-09-02 14:50:05 -070054import java.lang.Integer;
Tyler Gunncb59b672014-08-20 09:02:11 -070055import java.lang.SecurityException;
Tyler Gunnd900ce62014-08-13 11:40:59 -070056import java.lang.String;
Santos Cordon176ae282014-07-14 02:02:14 -070057import java.util.ArrayList;
Santos Cordonafe59e52014-08-22 16:48:43 -070058import java.util.Collections;
Tyler Gunnd900ce62014-08-13 11:40:59 -070059import java.util.Iterator;
Santos Cordon176ae282014-07-14 02:02:14 -070060import java.util.List;
61import java.util.Objects;
Ihab Awadb78b2762014-07-25 15:16:23 -070062import java.util.concurrent.CopyOnWriteArrayList;
Santos Cordon176ae282014-07-14 02:02:14 -070063
64/**
Evan Charlton89176372014-07-19 18:23:09 -070065 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
Tyler Gunn7cc70b42014-09-12 22:17:27 -070066 * 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
Ihab Awad104f8062014-07-17 11:29:35 -070068 * responsible for security checking to make sure that the caller has proper authority over
Evan Charlton89176372014-07-19 18:23:09 -070069 * the {@code ComponentName}s they are declaring in their {@code PhoneAccountHandle}s.
Santos Cordon176ae282014-07-14 02:02:14 -070070 */
Ihab Awadb78b2762014-07-25 15:16:23 -070071public final class PhoneAccountRegistrar {
Santos Cordon176ae282014-07-14 02:02:14 -070072
Yorke Lee5e8836a2014-08-22 15:25:18 -070073 public static final PhoneAccountHandle NO_ACCOUNT_SELECTED =
74 new PhoneAccountHandle(new ComponentName("null", "null"), "NO_ACCOUNT_SELECTED");
75
Ihab Awadb78b2762014-07-25 15:16:23 -070076 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";
Tyler Gunn84253572014-09-02 14:50:05 -070083 @VisibleForTesting
Tyler Gunn8e0fef42014-09-08 18:34:44 -070084 public static final int EXPECTED_STATE_VERSION = 3;
Tyler Gunn84253572014-09-02 14:50:05 -070085
86 /** Keep in sync with the same in SipSettings.java */
87 private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
Ihab Awadb78b2762014-07-25 15:16:23 -070088
89 private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
90 private final AtomicFile mAtomicFile;
Santos Cordonafe59e52014-08-22 16:48:43 -070091 private final Context mContext;
Ihab Awadb78b2762014-07-25 15:16:23 -070092 private State mState;
Santos Cordon176ae282014-07-14 02:02:14 -070093
Ihab Awad26923222014-07-30 10:54:35 -070094 public PhoneAccountRegistrar(Context context) {
Ihab Awadb78b2762014-07-25 15:16:23 -070095 this(context, FILE_NAME);
96 }
97
98 @VisibleForTesting
99 public PhoneAccountRegistrar(Context context, String fileName) {
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700100 // 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 */
Ihab Awadb78b2762014-07-25 15:16:23 -0700108 mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700109
Ihab Awadb78b2762014-07-25 15:16:23 -0700110 mState = new State();
Santos Cordonafe59e52014-08-22 16:48:43 -0700111 mContext = context;
Ihab Awadb78b2762014-07-25 15:16:23 -0700112 read();
Santos Cordon176ae282014-07-14 02:02:14 -0700113 }
114
Tyler Gunn84253572014-09-02 14:50:05 -0700115 /**
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) {
Yorke Lee5e8836a2014-08-22 15:25:18 -0700121 final PhoneAccountHandle userSelected = getUserSelectedOutgoingPhoneAccount();
Tyler Gunn84253572014-09-02 14:50:05 -0700122
Yorke Lee5e8836a2014-08-22 15:25:18 -0700123 if (userSelected != null) {
Tyler Gunn84253572014-09-02 14:50:05 -0700124 // 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 }
Ihab Awad293edf22014-07-24 17:52:29 -0700130 }
131
Nancy Chen309198e2014-09-15 18:02:49 -0700132 List<PhoneAccountHandle> outgoing = getCallCapablePhoneAccounts(uriScheme);
Ihab Awad6fb37c82014-08-07 19:48:57 -0700133 switch (outgoing.size()) {
Ihab Awad293edf22014-07-24 17:52:29 -0700134 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
Ihab Awad6fb37c82014-08-07 19:48:57 -0700139 return outgoing.get(0);
Ihab Awad293edf22014-07-24 17:52:29 -0700140 default:
141 // There are multiple accounts with no selected default
142 return null;
Ihab Awadf2a84912014-07-22 21:09:25 -0700143 }
Ihab Awad104f8062014-07-17 11:29:35 -0700144 }
Santos Cordon176ae282014-07-14 02:02:14 -0700145
Yorke Lee5e8836a2014-08-22 15:25:18 -0700146 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
Andrew Leea51a3862014-09-03 14:58:45 -0700161 public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
Evan Charlton89176372014-07-19 18:23:09 -0700162 if (accountHandle == null) {
Ihab Awad104f8062014-07-17 11:29:35 -0700163 // Asking to clear the default outgoing is a valid request
Ihab Awad293edf22014-07-24 17:52:29 -0700164 mState.defaultOutgoing = null;
Ihab Awad104f8062014-07-17 11:29:35 -0700165 } else {
166 boolean found = false;
Ihab Awad293edf22014-07-24 17:52:29 -0700167 for (PhoneAccount m : mState.accounts) {
Evan Charlton94d01622014-07-20 12:32:05 -0700168 if (Objects.equals(accountHandle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700169 found = true;
170 break;
171 }
Santos Cordon176ae282014-07-14 02:02:14 -0700172 }
Ihab Awad104f8062014-07-17 11:29:35 -0700173
174 if (!found) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700175 Log.w(this, "Trying to set nonexistent default outgoing %s",
176 accountHandle);
177 return;
178 }
179
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700180 if (!getPhoneAccount(accountHandle).hasCapabilities(
181 PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700182 Log.w(this, "Trying to set non-call-provider default outgoing %s",
Evan Charlton89176372014-07-19 18:23:09 -0700183 accountHandle);
Ihab Awad104f8062014-07-17 11:29:35 -0700184 return;
185 }
186
Ihab Awad293edf22014-07-24 17:52:29 -0700187 mState.defaultOutgoing = accountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -0700188 }
189
Ihab Awad293edf22014-07-24 17:52:29 -0700190 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700191 fireDefaultOutgoingChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700192 }
193
Ihab Awad293edf22014-07-24 17:52:29 -0700194 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;
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700200 } else if (!callManagerAccount.hasCapabilities(
201 PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
Ihab Awad293edf22014-07-24 17:52:29 -0700202 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount);
203 return;
204 }
Yorke Lee5e8836a2014-08-22 15:25:18 -0700205 } else {
206 callManager = NO_ACCOUNT_SELECTED;
Ihab Awad293edf22014-07-24 17:52:29 -0700207 }
208 mState.simCallManager = callManager;
Yorke Lee5e8836a2014-08-22 15:25:18 -0700209
Ihab Awad293edf22014-07-24 17:52:29 -0700210 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700211 fireSimCallManagerChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700212 }
213
214 public PhoneAccountHandle getSimCallManager() {
Ihab Awadb78b2762014-07-25 15:16:23 -0700215 if (mState.simCallManager != null) {
Yorke Lee5e8836a2014-08-22 15:25:18 -0700216 if (NO_ACCOUNT_SELECTED.equals(mState.simCallManager)) {
217 return null;
218 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700219 // 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 }
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700227
228 // See if the OEM has specified a default one.
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700229 String defaultConnectionMgr =
Santos Cordonafe59e52014-08-22 16:48:43 -0700230 mContext.getResources().getString(R.string.default_connection_manager_component);
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700231 if (!TextUtils.isEmpty(defaultConnectionMgr)) {
Santos Cordonafe59e52014-08-22 16:48:43 -0700232 PackageManager pm = mContext.getPackageManager();
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700233
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
Ihab Awadb78b2762014-07-25 15:16:23 -0700256 return null;
Ihab Awad293edf22014-07-24 17:52:29 -0700257 }
258
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700259 /**
260 * Retrieves a list of all {@link PhoneAccountHandle}s registered.
261 *
262 * @return The list of {@link PhoneAccountHandle}s.
263 */
Ihab Awad293edf22014-07-24 17:52:29 -0700264 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
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700276 /**
Nancy Chen309198e2014-09-15 18:02:49 -0700277 * Determines the number of all {@link PhoneAccount}s.
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700278 *
Nancy Chen309198e2014-09-15 18:02:49 -0700279 * @return The total number {@link PhoneAccount}s.
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700280 */
281 public int getAllPhoneAccountsCount() {
282 return mState.accounts.size();
283 }
284
285 /**
Nancy Chen309198e2014-09-15 18:02:49 -0700286 * Retrieves a list of all call provider phone accounts.
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700287 *
288 * @return The phone account handles.
289 */
Nancy Chen309198e2014-09-15 18:02:49 -0700290 public List<PhoneAccountHandle> getCallCapablePhoneAccounts() {
Santos Cordonafe59e52014-08-22 16:48:43 -0700291 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CALL_PROVIDER);
292 }
293
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700294 /**
Nancy Chen309198e2014-09-15 18:02:49 -0700295 * Retrieves a list of all phone account call provider phone accounts supporting the
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700296 * specified URI scheme.
297 *
298 * @param uriScheme The URI scheme.
299 * @return The phone account handles.
300 */
Nancy Chen309198e2014-09-15 18:02:49 -0700301 public List<PhoneAccountHandle> getCallCapablePhoneAccounts(String uriScheme) {
302 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CALL_PROVIDER, uriScheme);
Tyler Gunn84253572014-09-02 14:50:05 -0700303 }
304
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700305 /**
Nancy Chen309198e2014-09-15 18:02:49 -0700306 * Retrieves a list of all phone account handles with the connection manager capability.
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700307 *
308 * @return The phone account handles.
309 */
310 public List<PhoneAccountHandle> getConnectionManagerPhoneAccounts() {
Santos Cordone3d82452014-09-15 13:44:29 -0700311 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CONNECTION_MANAGER,
Nancy Chen309198e2014-09-15 18:02:49 -0700312 null /* supportedUriScheme */);
Santos Cordon176ae282014-07-14 02:02:14 -0700313 }
314
Ihab Awad293edf22014-07-24 17:52:29 -0700315 public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
316 for (PhoneAccount m : mState.accounts) {
317 if (Objects.equals(handle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700318 return m;
Santos Cordon176ae282014-07-14 02:02:14 -0700319 }
320 }
321 return null;
322 }
323
Ihab Awad104f8062014-07-17 11:29:35 -0700324 // TODO: Should we implement an artificial limit for # of accounts associated with a single
325 // ComponentName?
Ihab Awad293edf22014-07-24 17:52:29 -0700326 public void registerPhoneAccount(PhoneAccount account) {
Tyler Gunncb59b672014-08-20 09:02:11 -0700327 // Enforce the requirement that a connection service for a phone account has the correct
328 // permission.
329 if (!phoneAccountHasPermission(account.getAccountHandle())) {
330 Log.w(this, "Phone account %s does not have BIND_CONNECTION_SERVICE permission.",
331 account.getAccountHandle());
332 throw new SecurityException(
333 "PhoneAccount connection service requires BIND_CONNECTION_SERVICE permission.");
334 }
335
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700336 addOrReplacePhoneAccount(account);
337 }
338
339 /**
340 * Adds a {@code PhoneAccount}, replacing an existing one if found.
341 *
342 * @param account The {@code PhoneAccount} to add or replace.
343 */
344 private void addOrReplacePhoneAccount(PhoneAccount account) {
Ihab Awad293edf22014-07-24 17:52:29 -0700345 mState.accounts.add(account);
Ihab Awad104f8062014-07-17 11:29:35 -0700346 // Search for duplicates and remove any that are found.
Ihab Awad293edf22014-07-24 17:52:29 -0700347 for (int i = 0; i < mState.accounts.size() - 1; i++) {
348 if (Objects.equals(
349 account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700350 // replace existing entry.
Ihab Awad293edf22014-07-24 17:52:29 -0700351 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700352 break;
Santos Cordon176ae282014-07-14 02:02:14 -0700353 }
354 }
355
Ihab Awad293edf22014-07-24 17:52:29 -0700356 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700357 fireAccountsChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700358 }
359
Evan Charlton89176372014-07-19 18:23:09 -0700360 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad293edf22014-07-24 17:52:29 -0700361 for (int i = 0; i < mState.accounts.size(); i++) {
362 if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) {
363 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700364 break;
365 }
366 }
367
Ihab Awad293edf22014-07-24 17:52:29 -0700368 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700369 fireAccountsChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700370 }
371
Tyler Gunnd900ce62014-08-13 11:40:59 -0700372 /**
373 * Un-registers all phone accounts associated with a specified package.
374 *
375 * @param packageName The package for which phone accounts will be removed.
376 */
Ihab Awad104f8062014-07-17 11:29:35 -0700377 public void clearAccounts(String packageName) {
Tyler Gunnd900ce62014-08-13 11:40:59 -0700378 boolean accountsRemoved = false;
379 Iterator<PhoneAccount> it = mState.accounts.iterator();
380 while (it.hasNext()) {
381 PhoneAccount phoneAccount = it.next();
Ihab Awad104f8062014-07-17 11:29:35 -0700382 if (Objects.equals(
383 packageName,
Tyler Gunnd900ce62014-08-13 11:40:59 -0700384 phoneAccount.getAccountHandle().getComponentName().getPackageName())) {
385 Log.i(this, "Removing phone account " + phoneAccount.getLabel());
386 it.remove();
387 accountsRemoved = true;
Ihab Awad104f8062014-07-17 11:29:35 -0700388 }
389 }
390
Tyler Gunnd900ce62014-08-13 11:40:59 -0700391 if (accountsRemoved) {
392 write();
393 fireAccountsChanged();
394 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700395 }
396
397 public void addListener(Listener l) {
398 mListeners.add(l);
399 }
400
401 public void removeListener(Listener l) {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700402 if (l != null) {
403 mListeners.remove(l);
404 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700405 }
406
407 private void fireAccountsChanged() {
408 for (Listener l : mListeners) {
409 l.onAccountsChanged(this);
410 }
411 }
412
413 private void fireDefaultOutgoingChanged() {
414 for (Listener l : mListeners) {
415 l.onDefaultOutgoingChanged(this);
416 }
417 }
418
419 private void fireSimCallManagerChanged() {
420 for (Listener l : mListeners) {
421 l.onSimCallManagerChanged(this);
422 }
Santos Cordon176ae282014-07-14 02:02:14 -0700423 }
424
Tyler Gunncb59b672014-08-20 09:02:11 -0700425 /**
426 * Determines if the connection service specified by a {@link PhoneAccountHandle} has the
427 * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission.
428 *
429 * @param phoneAccountHandle The phone account to check.
430 * @return {@code True} if the phone account has permission.
431 */
432 public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) {
Tyler Gunn91d43cf2014-09-17 12:19:39 -0700433 PackageManager packageManager = mContext.getPackageManager();
Tyler Gunncb59b672014-08-20 09:02:11 -0700434 try {
435 ServiceInfo serviceInfo = packageManager.getServiceInfo(
436 phoneAccountHandle.getComponentName(), 0);
437
438 return serviceInfo.permission != null &&
439 serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE);
440 } catch (PackageManager.NameNotFoundException e) {
441 Log.w(this, "Name not found %s", e);
442 return false;
443 }
444 }
445
Ihab Awad293edf22014-07-24 17:52:29 -0700446 ////////////////////////////////////////////////////////////////////////////////////////////////
447
Santos Cordonafe59e52014-08-22 16:48:43 -0700448 /**
449 * Returns a list of phone account handles with the specified flag.
Tyler Gunn84253572014-09-02 14:50:05 -0700450 *
451 * @param flags Flags which the {@code PhoneAccount} must have.
Santos Cordonafe59e52014-08-22 16:48:43 -0700452 */
453 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags) {
Nancy Chen309198e2014-09-15 18:02:49 -0700454 return getPhoneAccountHandles(flags, null);
Tyler Gunn84253572014-09-02 14:50:05 -0700455 }
456
457 /**
458 * Returns a list of phone account handles with the specified flag, supporting the specified
Nancy Chen309198e2014-09-15 18:02:49 -0700459 * URI scheme.
Tyler Gunn84253572014-09-02 14:50:05 -0700460 *
461 * @param flags Flags which the {@code PhoneAccount} must have.
462 * @param uriScheme URI schemes the PhoneAccount must handle. {@code Null} bypasses the
463 * URI scheme check.
464 */
Nancy Chen309198e2014-09-15 18:02:49 -0700465 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags, String uriScheme) {
Evan Charlton94d01622014-07-20 12:32:05 -0700466 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
Ihab Awad293edf22014-07-24 17:52:29 -0700467 for (PhoneAccount m : mState.accounts) {
Nancy Chen309198e2014-09-15 18:02:49 -0700468 if (m.hasCapabilities(flags) && (uriScheme == null || m.supportsUriScheme(uriScheme))) {
Ihab Awadf2a84912014-07-22 21:09:25 -0700469 accountHandles.add(m.getAccountHandle());
470 }
Ihab Awad104f8062014-07-17 11:29:35 -0700471 }
Evan Charlton94d01622014-07-20 12:32:05 -0700472 return accountHandles;
Ihab Awad104f8062014-07-17 11:29:35 -0700473 }
474
Ihab Awad293edf22014-07-24 17:52:29 -0700475 /**
476 * The state of this {@code PhoneAccountRegistrar}.
477 */
Ihab Awadb78b2762014-07-25 15:16:23 -0700478 @VisibleForTesting
479 public static class State {
Ihab Awad293edf22014-07-24 17:52:29 -0700480 /**
481 * The account selected by the user to be employed by default for making outgoing calls.
482 * If the user has not made such a selection, then this is null.
483 */
484 public PhoneAccountHandle defaultOutgoing = null;
485
486 /**
Ihab Awadb78b2762014-07-25 15:16:23 -0700487 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which
Ihab Awad293edf22014-07-24 17:52:29 -0700488 * manages and optimizes a user's PSTN SIM connections.
489 */
490 public PhoneAccountHandle simCallManager;
491
492 /**
Tyler Gunn7cc70b42014-09-12 22:17:27 -0700493 * The complete list of {@code PhoneAccount}s known to the Telecom subsystem.
Ihab Awad293edf22014-07-24 17:52:29 -0700494 */
495 public final List<PhoneAccount> accounts = new ArrayList<>();
Tyler Gunn84253572014-09-02 14:50:05 -0700496
497 /**
498 * The version number of the State data.
499 */
500 public int versionNumber;
Ihab Awad293edf22014-07-24 17:52:29 -0700501 }
502
503 ////////////////////////////////////////////////////////////////////////////////////////////////
504 //
505 // State management
506 //
507
508 private void write() {
Ihab Awadb78b2762014-07-25 15:16:23 -0700509 final FileOutputStream os;
Ihab Awad104f8062014-07-17 11:29:35 -0700510 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700511 os = mAtomicFile.startWrite();
512 boolean success = false;
513 try {
514 XmlSerializer serializer = new FastXmlSerializer();
515 serializer.setOutput(new BufferedOutputStream(os), "utf-8");
516 writeToXml(mState, serializer);
517 serializer.flush();
518 success = true;
519 } finally {
520 if (success) {
521 mAtomicFile.finishWrite(os);
522 } else {
523 mAtomicFile.failWrite(os);
524 }
525 }
526 } catch (IOException e) {
527 Log.e(this, e, "Writing state to XML file");
Ihab Awad104f8062014-07-17 11:29:35 -0700528 }
529 }
530
Ihab Awadb78b2762014-07-25 15:16:23 -0700531 private void read() {
532 final InputStream is;
Ihab Awad104f8062014-07-17 11:29:35 -0700533 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700534 is = mAtomicFile.openRead();
535 } catch (FileNotFoundException ex) {
536 return;
537 }
538
Tyler Gunn84253572014-09-02 14:50:05 -0700539 boolean versionChanged = false;
540
Ihab Awadb78b2762014-07-25 15:16:23 -0700541 XmlPullParser parser;
542 try {
543 parser = Xml.newPullParser();
544 parser.setInput(new BufferedInputStream(is), null);
545 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700546 mState = readFromXml(parser, mContext);
547 versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION;
548
Ihab Awadb78b2762014-07-25 15:16:23 -0700549 } catch (IOException | XmlPullParserException e) {
550 Log.e(this, e, "Reading state from XML file");
551 mState = new State();
552 } finally {
553 try {
554 is.close();
555 } catch (IOException e) {
556 Log.e(this, e, "Closing InputStream");
557 }
Ihab Awad104f8062014-07-17 11:29:35 -0700558 }
Tyler Gunn84253572014-09-02 14:50:05 -0700559
560 // If an upgrade occurred, write out the changed data.
561 if (versionChanged) {
562 write();
563 }
Santos Cordon176ae282014-07-14 02:02:14 -0700564 }
565
Ihab Awadb78b2762014-07-25 15:16:23 -0700566 private static void writeToXml(State state, XmlSerializer serializer)
567 throws IOException {
568 sStateXml.writeToXml(state, serializer);
Santos Cordon176ae282014-07-14 02:02:14 -0700569 }
Ihab Awad104f8062014-07-17 11:29:35 -0700570
Tyler Gunn84253572014-09-02 14:50:05 -0700571 private static State readFromXml(XmlPullParser parser, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -0700572 throws IOException, XmlPullParserException {
Tyler Gunn84253572014-09-02 14:50:05 -0700573 State s = sStateXml.readFromXml(parser, 0, context);
Ihab Awadb78b2762014-07-25 15:16:23 -0700574 return s != null ? s : new State();
Ihab Awad104f8062014-07-17 11:29:35 -0700575 }
576
Ihab Awad293edf22014-07-24 17:52:29 -0700577 ////////////////////////////////////////////////////////////////////////////////////////////////
Ihab Awad104f8062014-07-17 11:29:35 -0700578 //
Ihab Awadb78b2762014-07-25 15:16:23 -0700579 // XML serialization
Ihab Awad104f8062014-07-17 11:29:35 -0700580 //
581
Ihab Awadb78b2762014-07-25 15:16:23 -0700582 @VisibleForTesting
Ihab Awad26923222014-07-30 10:54:35 -0700583 public abstract static class XmlSerialization<T> {
Tyler Gunn84253572014-09-02 14:50:05 -0700584 private static final String LENGTH_ATTRIBUTE = "length";
585 private static final String VALUE_TAG = "value";
586
Ihab Awadb78b2762014-07-25 15:16:23 -0700587 /**
588 * Write the supplied object to XML
589 */
Ihab Awad26923222014-07-30 10:54:35 -0700590 public abstract void writeToXml(T o, XmlSerializer serializer)
591 throws IOException;
Ihab Awadb78b2762014-07-25 15:16:23 -0700592
593 /**
594 * Read from the supplied XML into a new object, returning null in case of an
595 * unrecoverable schema mismatch or other data error. 'parser' must be already
596 * positioned at the first tag that is expected to have been emitted by this
597 * object's writeToXml(). This object tries to fail early without modifying
598 * 'parser' if it does not recognize the data it sees.
599 */
Tyler Gunn84253572014-09-02 14:50:05 -0700600 public abstract T readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awad26923222014-07-30 10:54:35 -0700601 throws IOException, XmlPullParserException;
602
603 protected void writeTextSafely(String tagName, Object value, XmlSerializer serializer)
604 throws IOException {
605 if (value != null) {
606 serializer.startTag(null, tagName);
607 serializer.text(Objects.toString(value));
608 serializer.endTag(null, tagName);
609 }
610 }
Tyler Gunn84253572014-09-02 14:50:05 -0700611
612 /**
613 * Serializes a string array.
614 *
615 * @param tagName The tag name for the string array.
616 * @param values The string values to serialize.
617 * @param serializer The serializer.
618 * @throws IOException
619 */
620 protected void writeStringList(String tagName, List<String> values,
621 XmlSerializer serializer)
622 throws IOException {
623
624 serializer.startTag(null, tagName);
625 if (values != null) {
626 serializer.attribute(null, LENGTH_ATTRIBUTE, Objects.toString(values.size()));
627 for (String toSerialize : values) {
628 serializer.startTag(null, VALUE_TAG);
629 if (toSerialize != null ){
630 serializer.text(toSerialize);
631 }
Tyler Gunn84253572014-09-02 14:50:05 -0700632 serializer.endTag(null, VALUE_TAG);
633 }
634 } else {
635 serializer.attribute(null, LENGTH_ATTRIBUTE, "0");
636 }
637 serializer.endTag(null, tagName);
638
639 }
640
641 /**
642 * Reads a string array from the XML parser.
643 *
644 * @param parser The XML parser.
645 * @return String array containing the parsed values.
646 * @throws IOException Exception related to IO.
647 * @throws XmlPullParserException Exception related to parsing.
648 */
649 protected List<String> readStringList(XmlPullParser parser)
650 throws IOException, XmlPullParserException {
651
652 int length = Integer.parseInt(parser.getAttributeValue(null, LENGTH_ATTRIBUTE));
653 List<String> arrayEntries = new ArrayList<String>(length);
654 String value = null;
655
656 if (length == 0) {
657 return arrayEntries;
658 }
659
660 int outerDepth = parser.getDepth();
661 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
662 if (parser.getName().equals(VALUE_TAG)) {
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700663 parser.next();
Tyler Gunn84253572014-09-02 14:50:05 -0700664 value = parser.getText();
665 arrayEntries.add(value);
666 }
667 }
668
669 return arrayEntries;
670 }
Ihab Awad104f8062014-07-17 11:29:35 -0700671 }
672
Ihab Awadb78b2762014-07-25 15:16:23 -0700673 @VisibleForTesting
674 public static final XmlSerialization<State> sStateXml =
675 new XmlSerialization<State>() {
676 private static final String CLASS_STATE = "phone_account_registrar_state";
Ihab Awad104f8062014-07-17 11:29:35 -0700677 private static final String DEFAULT_OUTGOING = "default_outgoing";
Ihab Awad293edf22014-07-24 17:52:29 -0700678 private static final String SIM_CALL_MANAGER = "sim_call_manager";
Ihab Awad104f8062014-07-17 11:29:35 -0700679 private static final String ACCOUNTS = "accounts";
Tyler Gunn84253572014-09-02 14:50:05 -0700680 private static final String VERSION = "version";
Ihab Awad104f8062014-07-17 11:29:35 -0700681
682 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700683 public void writeToXml(State o, XmlSerializer serializer)
684 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700685 if (o != null) {
686 serializer.startTag(null, CLASS_STATE);
Tyler Gunn84253572014-09-02 14:50:05 -0700687 serializer.attribute(null, VERSION, Objects.toString(EXPECTED_STATE_VERSION));
Ihab Awadb78b2762014-07-25 15:16:23 -0700688
Ihab Awad26923222014-07-30 10:54:35 -0700689 if (o.defaultOutgoing != null) {
690 serializer.startTag(null, DEFAULT_OUTGOING);
691 sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer);
692 serializer.endTag(null, DEFAULT_OUTGOING);
693 }
694
695 if (o.simCallManager != null) {
696 serializer.startTag(null, SIM_CALL_MANAGER);
697 sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer);
698 serializer.endTag(null, SIM_CALL_MANAGER);
699 }
700
701 serializer.startTag(null, ACCOUNTS);
702 for (PhoneAccount m : o.accounts) {
703 sPhoneAccountXml.writeToXml(m, serializer);
704 }
705 serializer.endTag(null, ACCOUNTS);
706
707 serializer.endTag(null, CLASS_STATE);
Ihab Awad293edf22014-07-24 17:52:29 -0700708 }
Ihab Awad104f8062014-07-17 11:29:35 -0700709 }
710
711 @Override
Tyler Gunn84253572014-09-02 14:50:05 -0700712 public State readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -0700713 throws IOException, XmlPullParserException {
714 if (parser.getName().equals(CLASS_STATE)) {
715 State s = new State();
Tyler Gunn84253572014-09-02 14:50:05 -0700716
717 String rawVersion = parser.getAttributeValue(null, VERSION);
718 s.versionNumber = TextUtils.isEmpty(rawVersion) ? 1 :
719 Integer.parseInt(rawVersion);
720
Ihab Awadb78b2762014-07-25 15:16:23 -0700721 int outerDepth = parser.getDepth();
722 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
723 if (parser.getName().equals(DEFAULT_OUTGOING)) {
724 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700725 s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser,
726 s.versionNumber, context);
Ihab Awadb78b2762014-07-25 15:16:23 -0700727 } else if (parser.getName().equals(SIM_CALL_MANAGER)) {
728 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700729 s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser,
730 s.versionNumber, context);
Ihab Awadb78b2762014-07-25 15:16:23 -0700731 } else if (parser.getName().equals(ACCOUNTS)) {
732 int accountsDepth = parser.getDepth();
733 while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
Tyler Gunn84253572014-09-02 14:50:05 -0700734 PhoneAccount account = sPhoneAccountXml.readFromXml(parser,
735 s.versionNumber, context);
736
737 if (account != null && s.accounts != null) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700738 s.accounts.add(account);
739 }
740 }
Ihab Awad104f8062014-07-17 11:29:35 -0700741 }
742 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700743 return s;
Ihab Awad104f8062014-07-17 11:29:35 -0700744 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700745 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700746 }
747 };
748
Ihab Awadb78b2762014-07-25 15:16:23 -0700749 @VisibleForTesting
750 public static final XmlSerialization<PhoneAccount> sPhoneAccountXml =
751 new XmlSerialization<PhoneAccount>() {
752 private static final String CLASS_PHONE_ACCOUNT = "phone_account";
753 private static final String ACCOUNT_HANDLE = "account_handle";
Andrew Lee7129f1c2014-09-04 11:55:07 -0700754 private static final String ADDRESS = "handle";
755 private static final String SUBSCRIPTION_ADDRESS = "subscription_number";
Ihab Awad104f8062014-07-17 11:29:35 -0700756 private static final String CAPABILITIES = "capabilities";
757 private static final String ICON_RES_ID = "icon_res_id";
758 private static final String LABEL = "label";
759 private static final String SHORT_DESCRIPTION = "short_description";
Tyler Gunn84253572014-09-02 14:50:05 -0700760 private static final String SUPPORTED_URI_SCHEMES = "supported_uri_schemes";
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700761 private static final String TRUE = "true";
762 private static final String FALSE = "false";
Ihab Awad104f8062014-07-17 11:29:35 -0700763
764 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700765 public void writeToXml(PhoneAccount o, XmlSerializer serializer)
766 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700767 if (o != null) {
768 serializer.startTag(null, CLASS_PHONE_ACCOUNT);
Ihab Awadb78b2762014-07-25 15:16:23 -0700769
Ihab Awad26923222014-07-30 10:54:35 -0700770 if (o.getAccountHandle() != null) {
771 serializer.startTag(null, ACCOUNT_HANDLE);
772 sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer);
773 serializer.endTag(null, ACCOUNT_HANDLE);
774 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700775
Andrew Lee7129f1c2014-09-04 11:55:07 -0700776 writeTextSafely(ADDRESS, o.getAddress(), serializer);
777 writeTextSafely(SUBSCRIPTION_ADDRESS, o.getSubscriptionAddress(), serializer);
Ihab Awad26923222014-07-30 10:54:35 -0700778 writeTextSafely(CAPABILITIES, Integer.toString(o.getCapabilities()), serializer);
779 writeTextSafely(ICON_RES_ID, Integer.toString(o.getIconResId()), serializer);
780 writeTextSafely(LABEL, o.getLabel(), serializer);
781 writeTextSafely(SHORT_DESCRIPTION, o.getShortDescription(), serializer);
Tyler Gunn84253572014-09-02 14:50:05 -0700782 writeStringList(SUPPORTED_URI_SCHEMES, o.getSupportedUriSchemes(), serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -0700783
Ihab Awad26923222014-07-30 10:54:35 -0700784 serializer.endTag(null, CLASS_PHONE_ACCOUNT);
785 }
Ihab Awad104f8062014-07-17 11:29:35 -0700786 }
787
Tyler Gunn84253572014-09-02 14:50:05 -0700788 public PhoneAccount readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -0700789 throws IOException, XmlPullParserException {
790 if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) {
791 int outerDepth = parser.getDepth();
792 PhoneAccountHandle accountHandle = null;
Andrew Lee7129f1c2014-09-04 11:55:07 -0700793 Uri address = null;
794 Uri subscriptionAddress = null;
Ihab Awadb78b2762014-07-25 15:16:23 -0700795 int capabilities = 0;
796 int iconResId = 0;
797 String label = null;
798 String shortDescription = null;
Tyler Gunn84253572014-09-02 14:50:05 -0700799 List<String> supportedUriSchemes = null;
Ihab Awadb78b2762014-07-25 15:16:23 -0700800
801 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
802 if (parser.getName().equals(ACCOUNT_HANDLE)) {
803 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700804 accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version,
805 context);
Andrew Lee7129f1c2014-09-04 11:55:07 -0700806 } else if (parser.getName().equals(ADDRESS)) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700807 parser.next();
Andrew Lee7129f1c2014-09-04 11:55:07 -0700808 address = Uri.parse(parser.getText());
809 } else if (parser.getName().equals(SUBSCRIPTION_ADDRESS)) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700810 parser.next();
Andrew Lee7129f1c2014-09-04 11:55:07 -0700811 String nextText = parser.getText();
812 subscriptionAddress = nextText == null ? null : Uri.parse(nextText);
Ihab Awadb78b2762014-07-25 15:16:23 -0700813 } else if (parser.getName().equals(CAPABILITIES)) {
814 parser.next();
815 capabilities = Integer.parseInt(parser.getText());
816 } else if (parser.getName().equals(ICON_RES_ID)) {
817 parser.next();
818 iconResId = Integer.parseInt(parser.getText());
819 } else if (parser.getName().equals(LABEL)) {
820 parser.next();
821 label = parser.getText();
822 } else if (parser.getName().equals(SHORT_DESCRIPTION)) {
823 parser.next();
824 shortDescription = parser.getText();
Tyler Gunn84253572014-09-02 14:50:05 -0700825 } else if (parser.getName().equals(SUPPORTED_URI_SCHEMES)) {
826 supportedUriSchemes = readStringList(parser);
Ihab Awadb78b2762014-07-25 15:16:23 -0700827 }
828 }
Tyler Gunn84253572014-09-02 14:50:05 -0700829
830 // Upgrade older phone accounts to specify the supported URI schemes.
831 if (version < 2) {
832 ComponentName sipComponentName = new ComponentName("com.android.phone",
833 "com.android.services.telephony.sip.SipConnectionService");
834
835 supportedUriSchemes = new ArrayList<>();
836
837 // Handle the SIP connection service.
838 // Check the system settings to see if it also should handle "tel" calls.
839 if (accountHandle.getComponentName().equals(sipComponentName)) {
840 boolean useSipForPstn = useSipForPstnCalls(context);
841 supportedUriSchemes.add(PhoneAccount.SCHEME_SIP);
842 if (useSipForPstn) {
843 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL);
844 }
845 } else {
846 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL);
847 supportedUriSchemes.add(PhoneAccount.SCHEME_VOICEMAIL);
848 }
849 }
850
Andrew Lee7129f1c2014-09-04 11:55:07 -0700851 return PhoneAccount.builder(accountHandle, label)
852 .setAddress(address)
853 .setSubscriptionAddress(subscriptionAddress)
854 .setCapabilities(capabilities)
855 .setIconResId(iconResId)
856 .setShortDescription(shortDescription)
857 .setSupportedUriSchemes(supportedUriSchemes)
Ihab Awad6fb37c82014-08-07 19:48:57 -0700858 .build();
Ihab Awadb78b2762014-07-25 15:16:23 -0700859 }
860 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700861 }
Tyler Gunn84253572014-09-02 14:50:05 -0700862
863 /**
864 * Determines if the SIP call settings specify to use SIP for all calls, including PSTN calls.
865 *
866 * @param context The context.
867 * @return {@code True} if SIP should be used for all calls.
868 */
869 private boolean useSipForPstnCalls(Context context) {
870 String option = Settings.System.getString(context.getContentResolver(),
871 Settings.System.SIP_CALL_OPTIONS);
872 option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY;
873 return option.equals(Settings.System.SIP_ALWAYS);
874 }
Ihab Awad104f8062014-07-17 11:29:35 -0700875 };
876
Ihab Awadb78b2762014-07-25 15:16:23 -0700877 @VisibleForTesting
878 public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml =
879 new XmlSerialization<PhoneAccountHandle>() {
880 private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle";
Ihab Awad104f8062014-07-17 11:29:35 -0700881 private static final String COMPONENT_NAME = "component_name";
882 private static final String ID = "id";
883
884 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700885 public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer)
886 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700887 if (o != null) {
888 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
Ihab Awadb78b2762014-07-25 15:16:23 -0700889
Ihab Awad26923222014-07-30 10:54:35 -0700890 if (o.getComponentName() != null) {
891 writeTextSafely(
892 COMPONENT_NAME, o.getComponentName().flattenToString(), serializer);
893 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700894
Ihab Awad26923222014-07-30 10:54:35 -0700895 writeTextSafely(ID, o.getId(), serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -0700896
Ihab Awad26923222014-07-30 10:54:35 -0700897 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
898 }
Ihab Awad104f8062014-07-17 11:29:35 -0700899 }
900
901 @Override
Tyler Gunn84253572014-09-02 14:50:05 -0700902 public PhoneAccountHandle readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -0700903 throws IOException, XmlPullParserException {
904 if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) {
905 String componentNameString = null;
906 String idString = null;
907 int outerDepth = parser.getDepth();
908 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
909 if (parser.getName().equals(COMPONENT_NAME)) {
910 parser.next();
911 componentNameString = parser.getText();
912 } else if (parser.getName().equals(ID)) {
913 parser.next();
914 idString = parser.getText();
915 }
916 }
Ihab Awad26923222014-07-30 10:54:35 -0700917 if (componentNameString != null) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700918 return new PhoneAccountHandle(
919 ComponentName.unflattenFromString(componentNameString),
920 idString);
921 }
922 }
923 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700924 }
925 };
Santos Cordon176ae282014-07-14 02:02:14 -0700926}