blob: 31dd7277cdd28a453afeb329d7a7481c21ba51e9 [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;
Tyler Gunn167090b2014-09-11 12:59:13 -070021import android.content.pm.ActivityInfo;
22import android.content.pm.PackageInfo;
Evan Charltonaf51ceb2014-07-30 11:56:36 -070023import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
Tyler Gunncb59b672014-08-20 09:02:11 -070025import android.content.pm.ServiceInfo;
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
Sailesh Nepal0e1dc5a2014-07-30 11:08:54 -070038import com.android.internal.annotations.VisibleForTesting;
39import com.android.internal.util.FastXmlSerializer;
40import com.android.internal.util.XmlUtils;
41
Evan Charltonaf51ceb2014-07-30 11:56:36 -070042import org.xmlpull.v1.XmlPullParser;
43import org.xmlpull.v1.XmlPullParserException;
44import org.xmlpull.v1.XmlSerializer;
45
Ihab Awadb78b2762014-07-25 15:16:23 -070046import java.io.BufferedInputStream;
47import java.io.BufferedOutputStream;
48import java.io.File;
49import java.io.FileNotFoundException;
50import java.io.FileOutputStream;
51import java.io.IOException;
52import java.io.InputStream;
Tyler Gunn84253572014-09-02 14:50:05 -070053import java.lang.Integer;
Tyler Gunncb59b672014-08-20 09:02:11 -070054import java.lang.SecurityException;
Tyler Gunnd900ce62014-08-13 11:40:59 -070055import java.lang.String;
Santos Cordon176ae282014-07-14 02:02:14 -070056import java.util.ArrayList;
Santos Cordonafe59e52014-08-22 16:48:43 -070057import java.util.Collections;
Tyler Gunnd900ce62014-08-13 11:40:59 -070058import java.util.Iterator;
Santos Cordon176ae282014-07-14 02:02:14 -070059import java.util.List;
60import java.util.Objects;
Ihab Awadb78b2762014-07-25 15:16:23 -070061import java.util.concurrent.CopyOnWriteArrayList;
Santos Cordon176ae282014-07-14 02:02:14 -070062
63/**
Evan Charlton89176372014-07-19 18:23:09 -070064 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
Tyler Gunn7cc70b42014-09-12 22:17:27 -070065 * delegate for all the account handling methods on {@link android.telecom.TelecomManager} as implemented in
66 * {@link TelecomServiceImpl}, with the notable exception that {@link TelecomServiceImpl} is
Ihab Awad104f8062014-07-17 11:29:35 -070067 * responsible for security checking to make sure that the caller has proper authority over
Evan Charlton89176372014-07-19 18:23:09 -070068 * the {@code ComponentName}s they are declaring in their {@code PhoneAccountHandle}s.
Santos Cordon176ae282014-07-14 02:02:14 -070069 */
Ihab Awadb78b2762014-07-25 15:16:23 -070070public final class PhoneAccountRegistrar {
Santos Cordon176ae282014-07-14 02:02:14 -070071
Yorke Lee5e8836a2014-08-22 15:25:18 -070072 public static final PhoneAccountHandle NO_ACCOUNT_SELECTED =
73 new PhoneAccountHandle(new ComponentName("null", "null"), "NO_ACCOUNT_SELECTED");
74
Ihab Awadb78b2762014-07-25 15:16:23 -070075 public abstract static class Listener {
76 public void onAccountsChanged(PhoneAccountRegistrar registrar) {}
77 public void onDefaultOutgoingChanged(PhoneAccountRegistrar registrar) {}
78 public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {}
79 }
80
81 private static final String FILE_NAME = "phone-account-registrar-state.xml";
Tyler Gunn84253572014-09-02 14:50:05 -070082 @VisibleForTesting
Tyler Gunn8e0fef42014-09-08 18:34:44 -070083 public static final int EXPECTED_STATE_VERSION = 3;
Tyler Gunn84253572014-09-02 14:50:05 -070084
85 /** Keep in sync with the same in SipSettings.java */
86 private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
Ihab Awadb78b2762014-07-25 15:16:23 -070087
88 private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
89 private final AtomicFile mAtomicFile;
Santos Cordonafe59e52014-08-22 16:48:43 -070090 private final Context mContext;
Ihab Awadb78b2762014-07-25 15:16:23 -070091 private State mState;
Santos Cordon176ae282014-07-14 02:02:14 -070092
Ihab Awad26923222014-07-30 10:54:35 -070093 public PhoneAccountRegistrar(Context context) {
Ihab Awadb78b2762014-07-25 15:16:23 -070094 this(context, FILE_NAME);
95 }
96
97 @VisibleForTesting
98 public PhoneAccountRegistrar(Context context, String fileName) {
Tyler Gunn7cc70b42014-09-12 22:17:27 -070099 // TODO: Change file location when Telecom is part of system
Ihab Awadb78b2762014-07-25 15:16:23 -0700100 mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
101 mState = new State();
Santos Cordonafe59e52014-08-22 16:48:43 -0700102 mContext = context;
Ihab Awadb78b2762014-07-25 15:16:23 -0700103 read();
Santos Cordon176ae282014-07-14 02:02:14 -0700104 }
105
Tyler Gunn84253572014-09-02 14:50:05 -0700106 /**
107 * Retrieves the default outgoing phone account supporting the specified uriScheme.
108 * @param uriScheme The URI scheme for the outgoing call.
109 * @return The {@link PhoneAccountHandle} to use.
110 */
111 public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme) {
Yorke Lee5e8836a2014-08-22 15:25:18 -0700112 final PhoneAccountHandle userSelected = getUserSelectedOutgoingPhoneAccount();
Tyler Gunn84253572014-09-02 14:50:05 -0700113
Yorke Lee5e8836a2014-08-22 15:25:18 -0700114 if (userSelected != null) {
Tyler Gunn84253572014-09-02 14:50:05 -0700115 // If there is a default PhoneAccount, ensure it supports calls to handles with the
116 // specified uriScheme.
117 final PhoneAccount userSelectedAccount = getPhoneAccount(userSelected);
118 if (userSelectedAccount.supportsUriScheme(uriScheme)) {
119 return userSelected;
120 }
Ihab Awad293edf22014-07-24 17:52:29 -0700121 }
122
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700123 List<PhoneAccountHandle> outgoing = getEnabledPhoneAccounts(uriScheme);
Ihab Awad6fb37c82014-08-07 19:48:57 -0700124 switch (outgoing.size()) {
Ihab Awad293edf22014-07-24 17:52:29 -0700125 case 0:
126 // There are no accounts, so there can be no default
127 return null;
128 case 1:
129 // There is only one account, which is by definition the default
Ihab Awad6fb37c82014-08-07 19:48:57 -0700130 return outgoing.get(0);
Ihab Awad293edf22014-07-24 17:52:29 -0700131 default:
132 // There are multiple accounts with no selected default
133 return null;
Ihab Awadf2a84912014-07-22 21:09:25 -0700134 }
Ihab Awad104f8062014-07-17 11:29:35 -0700135 }
Santos Cordon176ae282014-07-14 02:02:14 -0700136
Yorke Lee5e8836a2014-08-22 15:25:18 -0700137 PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
138 if (mState.defaultOutgoing != null) {
139 // Return the registered outgoing default iff it still exists (we keep a sticky
140 // default to survive account deletion and re-addition)
141 for (int i = 0; i < mState.accounts.size(); i++) {
142 if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)) {
143 return mState.defaultOutgoing;
144 }
145 }
146 // At this point, there was a registered default but it has been deleted; proceed
147 // as though there were no default
148 }
149 return null;
150 }
151
Andrew Leea51a3862014-09-03 14:58:45 -0700152 public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
Evan Charlton89176372014-07-19 18:23:09 -0700153 if (accountHandle == null) {
Ihab Awad104f8062014-07-17 11:29:35 -0700154 // Asking to clear the default outgoing is a valid request
Ihab Awad293edf22014-07-24 17:52:29 -0700155 mState.defaultOutgoing = null;
Ihab Awad104f8062014-07-17 11:29:35 -0700156 } else {
157 boolean found = false;
Ihab Awad293edf22014-07-24 17:52:29 -0700158 for (PhoneAccount m : mState.accounts) {
Evan Charlton94d01622014-07-20 12:32:05 -0700159 if (Objects.equals(accountHandle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700160 found = true;
161 break;
162 }
Santos Cordon176ae282014-07-14 02:02:14 -0700163 }
Ihab Awad104f8062014-07-17 11:29:35 -0700164
165 if (!found) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700166 Log.w(this, "Trying to set nonexistent default outgoing %s",
167 accountHandle);
168 return;
169 }
170
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700171 if (!getPhoneAccount(accountHandle).hasCapabilities(
172 PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700173 Log.w(this, "Trying to set non-call-provider default outgoing %s",
Evan Charlton89176372014-07-19 18:23:09 -0700174 accountHandle);
Ihab Awad104f8062014-07-17 11:29:35 -0700175 return;
176 }
177
Ihab Awad293edf22014-07-24 17:52:29 -0700178 mState.defaultOutgoing = accountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -0700179 }
180
Ihab Awad293edf22014-07-24 17:52:29 -0700181 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700182 fireDefaultOutgoingChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700183 }
184
Ihab Awad293edf22014-07-24 17:52:29 -0700185 public void setSimCallManager(PhoneAccountHandle callManager) {
Santos Cordonafe59e52014-08-22 16:48:43 -0700186 if (!isEnabledConnectionManager()) {
187 return;
188 }
189
Ihab Awad293edf22014-07-24 17:52:29 -0700190 if (callManager != null) {
191 PhoneAccount callManagerAccount = getPhoneAccount(callManager);
192 if (callManagerAccount == null) {
193 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager);
194 return;
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700195 } else if (!callManagerAccount.hasCapabilities(
196 PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
Ihab Awad293edf22014-07-24 17:52:29 -0700197 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount);
198 return;
199 }
Yorke Lee5e8836a2014-08-22 15:25:18 -0700200 } else {
201 callManager = NO_ACCOUNT_SELECTED;
Ihab Awad293edf22014-07-24 17:52:29 -0700202 }
203 mState.simCallManager = callManager;
Yorke Lee5e8836a2014-08-22 15:25:18 -0700204
Ihab Awad293edf22014-07-24 17:52:29 -0700205 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700206 fireSimCallManagerChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700207 }
208
209 public PhoneAccountHandle getSimCallManager() {
Santos Cordonafe59e52014-08-22 16:48:43 -0700210 if (!isEnabledConnectionManager()) {
211 return null;
212 }
213
Ihab Awadb78b2762014-07-25 15:16:23 -0700214 if (mState.simCallManager != null) {
Yorke Lee5e8836a2014-08-22 15:25:18 -0700215 if (NO_ACCOUNT_SELECTED.equals(mState.simCallManager)) {
216 return null;
217 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700218 // Return the registered sim call manager iff it still exists (we keep a sticky
219 // setting to survive account deletion and re-addition)
220 for (int i = 0; i < mState.accounts.size(); i++) {
221 if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)) {
222 return mState.simCallManager;
223 }
224 }
225 }
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700226
227 // See if the OEM has specified a default one.
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700228 String defaultConnectionMgr =
Santos Cordonafe59e52014-08-22 16:48:43 -0700229 mContext.getResources().getString(R.string.default_connection_manager_component);
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700230 if (!TextUtils.isEmpty(defaultConnectionMgr)) {
Santos Cordonafe59e52014-08-22 16:48:43 -0700231 PackageManager pm = mContext.getPackageManager();
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700232
233 ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr);
234 Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
235 intent.setComponent(componentName);
236
237 // Make sure that the component can be resolved.
238 List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0);
239 if (!resolveInfos.isEmpty()) {
240 // See if there is registered PhoneAccount by this component.
241 List<PhoneAccountHandle> handles = getAllPhoneAccountHandles();
242 for (PhoneAccountHandle handle : handles) {
243 if (componentName.equals(handle.getComponentName())) {
244 return handle;
245 }
246 }
247 Log.d(this, "%s does not have a PhoneAccount; not using as default", componentName);
248 } else {
249 Log.d(this, "%s could not be resolved; not using as default", componentName);
250 }
251 } else {
252 Log.v(this, "No default connection manager specified");
253 }
254
Ihab Awadb78b2762014-07-25 15:16:23 -0700255 return null;
Ihab Awad293edf22014-07-24 17:52:29 -0700256 }
257
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700258 /**
259 * Retrieves a list of all {@link PhoneAccountHandle}s registered.
260 *
261 * @return The list of {@link PhoneAccountHandle}s.
262 */
Ihab Awad293edf22014-07-24 17:52:29 -0700263 public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
264 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
265 for (PhoneAccount m : mState.accounts) {
266 accountHandles.add(m.getAccountHandle());
267 }
268 return accountHandles;
269 }
270
271 public List<PhoneAccount> getAllPhoneAccounts() {
272 return new ArrayList<>(mState.accounts);
273 }
274
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700275 /**
276 * Determines the number of enabled and disabled {@link PhoneAccount}s.
277 *
278 * @return The number of enabled and disabled {@link PhoneAccount}s
279 */
280 public int getAllPhoneAccountsCount() {
281 return mState.accounts.size();
282 }
283
284 /**
285 * Retrieves a list of all enabled call provider phone accounts.
286 *
287 * @return The phone account handles.
288 */
289 public List<PhoneAccountHandle> getEnabledPhoneAccounts() {
Santos Cordonafe59e52014-08-22 16:48:43 -0700290 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CALL_PROVIDER);
291 }
292
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700293 /**
294 * Retrieves a list of all enabled phone account call provider phone accounts supporting the
295 * specified URI scheme.
296 *
297 * @param uriScheme The URI scheme.
298 * @return The phone account handles.
299 */
300 public List<PhoneAccountHandle> getEnabledPhoneAccounts(String uriScheme) {
301 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CALL_PROVIDER, uriScheme,
302 false /* includeDisabled */);
Tyler Gunn84253572014-09-02 14:50:05 -0700303 }
304
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700305 /**
306 * Retrieves a list of all enabled phone account handles with the connection manager capability.
307 *
308 * @return The phone account handles.
309 */
310 public List<PhoneAccountHandle> getConnectionManagerPhoneAccounts() {
Santos Cordonafe59e52014-08-22 16:48:43 -0700311 if (isEnabledConnectionManager()) {
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700312 return getPhoneAccountHandles(PhoneAccount.CAPABILITY_CONNECTION_MANAGER,
313 null /* supportedUriScheme */, false /* includeDisabled */);
Santos Cordonafe59e52014-08-22 16:48:43 -0700314 }
315 return Collections.emptyList();
Santos Cordon176ae282014-07-14 02:02:14 -0700316 }
317
Ihab Awad293edf22014-07-24 17:52:29 -0700318 public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
319 for (PhoneAccount m : mState.accounts) {
320 if (Objects.equals(handle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700321 return m;
Santos Cordon176ae282014-07-14 02:02:14 -0700322 }
323 }
324 return null;
325 }
326
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700327 /**
328 * Changes the enabled state of the {@link PhoneAccount} identified by a
329 * {@link PhoneAccountHandle}.
330 *
331 * @param handle The {@link PhoneAccountHandle}.
332 * @param isEnabled The new enabled state of the {@link PhoneAccount}.
333 */
334 public void setPhoneAccountEnabled(PhoneAccountHandle handle, boolean isEnabled) {
335 PhoneAccount existing = getPhoneAccount(handle);
336 if (existing.isEnabled() == isEnabled) {
337 return;
338 }
339
340 // Do not permit PhoneAccounts which are marked as always enabled to be disabled.
341 if (existing.hasCapabilities(PhoneAccount.CAPABILITY_ALWAYS_ENABLED)) {
342 return;
343 }
344
345 // If we are disabling the current default outgoing phone account or Sim call manager we
346 // need to null out those preferences.
347 if (!isEnabled) {
348 if (mState.defaultOutgoing != null && mState.defaultOutgoing.equals(handle)) {
349 setUserSelectedOutgoingPhoneAccount(null);
350 }
351
352 if (mState.simCallManager != null && mState.simCallManager.equals(handle)) {
353 setSimCallManager(null);
354 }
355 }
356
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700357 PhoneAccount.Builder builder = existing.toBuilder().setEnabled(isEnabled);
358 PhoneAccount replacement = builder.build();
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700359 addOrReplacePhoneAccount(replacement);
Tyler Gunn167090b2014-09-11 12:59:13 -0700360
361 // Notify the package which registered this PhoneAccount of its new enabled state.
362 notifyPhoneAccountEnabledStateChanged(replacement.getAccountHandle(), isEnabled);
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700363 }
364
Ihab Awad104f8062014-07-17 11:29:35 -0700365 // TODO: Should we implement an artificial limit for # of accounts associated with a single
366 // ComponentName?
Ihab Awad293edf22014-07-24 17:52:29 -0700367 public void registerPhoneAccount(PhoneAccount account) {
Tyler Gunncb59b672014-08-20 09:02:11 -0700368 // Enforce the requirement that a connection service for a phone account has the correct
369 // permission.
370 if (!phoneAccountHasPermission(account.getAccountHandle())) {
371 Log.w(this, "Phone account %s does not have BIND_CONNECTION_SERVICE permission.",
372 account.getAccountHandle());
373 throw new SecurityException(
374 "PhoneAccount connection service requires BIND_CONNECTION_SERVICE permission.");
375 }
376
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700377 // If there is an existing PhoneAccount already registered with this handle, copy its
378 // enabled state to the new phone account.
379 PhoneAccount existing = getPhoneAccount(account.getAccountHandle());
380 if (existing != null) {
381 account = account.toBuilder().setEnabled(existing.isEnabled()).build();
382 }
383
384 addOrReplacePhoneAccount(account);
385 }
386
387 /**
388 * Adds a {@code PhoneAccount}, replacing an existing one if found.
389 *
390 * @param account The {@code PhoneAccount} to add or replace.
391 */
392 private void addOrReplacePhoneAccount(PhoneAccount account) {
Ihab Awad293edf22014-07-24 17:52:29 -0700393 mState.accounts.add(account);
Ihab Awad104f8062014-07-17 11:29:35 -0700394 // Search for duplicates and remove any that are found.
Ihab Awad293edf22014-07-24 17:52:29 -0700395 for (int i = 0; i < mState.accounts.size() - 1; i++) {
396 if (Objects.equals(
397 account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700398 // replace existing entry.
Ihab Awad293edf22014-07-24 17:52:29 -0700399 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700400 break;
Santos Cordon176ae282014-07-14 02:02:14 -0700401 }
402 }
403
Ihab Awad293edf22014-07-24 17:52:29 -0700404 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700405 fireAccountsChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700406 }
407
Evan Charlton89176372014-07-19 18:23:09 -0700408 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad293edf22014-07-24 17:52:29 -0700409 for (int i = 0; i < mState.accounts.size(); i++) {
410 if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) {
411 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700412 break;
413 }
414 }
415
Ihab Awad293edf22014-07-24 17:52:29 -0700416 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700417 fireAccountsChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700418 }
419
Tyler Gunnd900ce62014-08-13 11:40:59 -0700420 /**
421 * Un-registers all phone accounts associated with a specified package.
422 *
423 * @param packageName The package for which phone accounts will be removed.
424 */
Ihab Awad104f8062014-07-17 11:29:35 -0700425 public void clearAccounts(String packageName) {
Tyler Gunnd900ce62014-08-13 11:40:59 -0700426 boolean accountsRemoved = false;
427 Iterator<PhoneAccount> it = mState.accounts.iterator();
428 while (it.hasNext()) {
429 PhoneAccount phoneAccount = it.next();
Ihab Awad104f8062014-07-17 11:29:35 -0700430 if (Objects.equals(
431 packageName,
Tyler Gunnd900ce62014-08-13 11:40:59 -0700432 phoneAccount.getAccountHandle().getComponentName().getPackageName())) {
433 Log.i(this, "Removing phone account " + phoneAccount.getLabel());
434 it.remove();
435 accountsRemoved = true;
Ihab Awad104f8062014-07-17 11:29:35 -0700436 }
437 }
438
Tyler Gunnd900ce62014-08-13 11:40:59 -0700439 if (accountsRemoved) {
440 write();
441 fireAccountsChanged();
442 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700443 }
444
445 public void addListener(Listener l) {
446 mListeners.add(l);
447 }
448
449 public void removeListener(Listener l) {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700450 if (l != null) {
451 mListeners.remove(l);
452 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700453 }
454
455 private void fireAccountsChanged() {
456 for (Listener l : mListeners) {
457 l.onAccountsChanged(this);
458 }
459 }
460
461 private void fireDefaultOutgoingChanged() {
462 for (Listener l : mListeners) {
463 l.onDefaultOutgoingChanged(this);
464 }
465 }
466
467 private void fireSimCallManagerChanged() {
468 for (Listener l : mListeners) {
469 l.onSimCallManagerChanged(this);
470 }
Santos Cordon176ae282014-07-14 02:02:14 -0700471 }
472
Santos Cordonafe59e52014-08-22 16:48:43 -0700473 private boolean isEnabledConnectionManager() {
474 return mContext.getResources().getBoolean(R.bool.connection_manager_enabled);
475 }
476
Tyler Gunncb59b672014-08-20 09:02:11 -0700477 /**
478 * Determines if the connection service specified by a {@link PhoneAccountHandle} has the
479 * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission.
480 *
481 * @param phoneAccountHandle The phone account to check.
482 * @return {@code True} if the phone account has permission.
483 */
484 public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) {
Tyler Gunn7cc70b42014-09-12 22:17:27 -0700485 PackageManager packageManager = TelecomApp.getInstance().getPackageManager();
Tyler Gunncb59b672014-08-20 09:02:11 -0700486 try {
487 ServiceInfo serviceInfo = packageManager.getServiceInfo(
488 phoneAccountHandle.getComponentName(), 0);
489
490 return serviceInfo.permission != null &&
491 serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE);
492 } catch (PackageManager.NameNotFoundException e) {
493 Log.w(this, "Name not found %s", e);
494 return false;
495 }
496 }
497
Ihab Awad293edf22014-07-24 17:52:29 -0700498 ////////////////////////////////////////////////////////////////////////////////////////////////
499
Santos Cordonafe59e52014-08-22 16:48:43 -0700500 /**
501 * Returns a list of phone account handles with the specified flag.
Tyler Gunn84253572014-09-02 14:50:05 -0700502 *
503 * @param flags Flags which the {@code PhoneAccount} must have.
Santos Cordonafe59e52014-08-22 16:48:43 -0700504 */
505 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags) {
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700506 return getPhoneAccountHandles(flags, null, false /* includeDisabled */);
Tyler Gunn84253572014-09-02 14:50:05 -0700507 }
508
509 /**
510 * Returns a list of phone account handles with the specified flag, supporting the specified
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700511 * URI scheme. By default, only enabled phone accounts are included, unless the
512 * {@code includeDisabled} parameter is set {@code true}.
Tyler Gunn84253572014-09-02 14:50:05 -0700513 *
514 * @param flags Flags which the {@code PhoneAccount} must have.
515 * @param uriScheme URI schemes the PhoneAccount must handle. {@code Null} bypasses the
516 * URI scheme check.
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700517 * @param includeDisabled When {@code true}, the list of phone accounts handles includes those
518 * which are marked as disabled.
Tyler Gunn84253572014-09-02 14:50:05 -0700519 */
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700520 private List<PhoneAccountHandle> getPhoneAccountHandles(int flags, String uriScheme,
521 boolean includeDisabled) {
Evan Charlton94d01622014-07-20 12:32:05 -0700522 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
Ihab Awad293edf22014-07-24 17:52:29 -0700523 for (PhoneAccount m : mState.accounts) {
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700524 if ((includeDisabled || m.isEnabled()) && m.hasCapabilities(flags) &&
525 (uriScheme == null || m.supportsUriScheme(uriScheme))) {
Ihab Awadf2a84912014-07-22 21:09:25 -0700526 accountHandles.add(m.getAccountHandle());
527 }
Ihab Awad104f8062014-07-17 11:29:35 -0700528 }
Evan Charlton94d01622014-07-20 12:32:05 -0700529 return accountHandles;
Ihab Awad104f8062014-07-17 11:29:35 -0700530 }
531
Ihab Awad293edf22014-07-24 17:52:29 -0700532 /**
Tyler Gunn167090b2014-09-11 12:59:13 -0700533 * Notifies the package which registered a {@link PhoneAccount} that it has been enabled.
534 * Only broadcasts the intent if the package has a {@link android.content.BroadcastReceiver}
535 * registered for the intent.
536 *
537 * @param phoneAccountHandle The {@link PhoneAccountHandle} which has been enabled or disabled.
538 * @param isEnabled {@code True} if the {@link PhoneAccount} is enabled, false otherwise.
539 */
540 private void notifyPhoneAccountEnabledStateChanged(PhoneAccountHandle phoneAccountHandle,
541 boolean isEnabled) {
542 Intent intent;
543
544 if (isEnabled) {
Tyler Gunn7cc70b42014-09-12 22:17:27 -0700545 intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_ENABLED);
Tyler Gunn167090b2014-09-11 12:59:13 -0700546 } else {
Tyler Gunn7cc70b42014-09-12 22:17:27 -0700547 intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_DISABLED);
Tyler Gunn167090b2014-09-11 12:59:13 -0700548 }
549 intent.setPackage(phoneAccountHandle.getComponentName().getPackageName());
Tyler Gunn7cc70b42014-09-12 22:17:27 -0700550 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
Tyler Gunn167090b2014-09-11 12:59:13 -0700551
552 if (isReceiverListening(intent)) {
553 Log.i(this, "notifyPhoneAccountEnabledState %s %s", phoneAccountHandle,
554 (isEnabled ? "enabled" : "disabled"));
555 mContext.sendBroadcast(intent);
556 }
557 }
558
559 /**
560 * Determines there is a {@link android.content.BroadcastReceiver} listening for an
561 * {@link Intent}.
562 *
563 * @param intent The {@link Intent}.
564 * @return {@code True} if there is a listener.
565 */
566 private boolean isReceiverListening(Intent intent) {
567 PackageManager pm = mContext.getPackageManager();
568 final List<ResolveInfo> activities = pm.queryBroadcastReceivers(intent, 0);
569 return !(activities.isEmpty());
570 }
571
572 /**
Ihab Awad293edf22014-07-24 17:52:29 -0700573 * The state of this {@code PhoneAccountRegistrar}.
574 */
Ihab Awadb78b2762014-07-25 15:16:23 -0700575 @VisibleForTesting
576 public static class State {
Ihab Awad293edf22014-07-24 17:52:29 -0700577 /**
578 * The account selected by the user to be employed by default for making outgoing calls.
579 * If the user has not made such a selection, then this is null.
580 */
581 public PhoneAccountHandle defaultOutgoing = null;
582
583 /**
Ihab Awadb78b2762014-07-25 15:16:23 -0700584 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which
Ihab Awad293edf22014-07-24 17:52:29 -0700585 * manages and optimizes a user's PSTN SIM connections.
586 */
587 public PhoneAccountHandle simCallManager;
588
589 /**
Tyler Gunn7cc70b42014-09-12 22:17:27 -0700590 * The complete list of {@code PhoneAccount}s known to the Telecom subsystem.
Ihab Awad293edf22014-07-24 17:52:29 -0700591 */
592 public final List<PhoneAccount> accounts = new ArrayList<>();
Tyler Gunn84253572014-09-02 14:50:05 -0700593
594 /**
595 * The version number of the State data.
596 */
597 public int versionNumber;
Ihab Awad293edf22014-07-24 17:52:29 -0700598 }
599
600 ////////////////////////////////////////////////////////////////////////////////////////////////
601 //
602 // State management
603 //
604
605 private void write() {
Ihab Awadb78b2762014-07-25 15:16:23 -0700606 final FileOutputStream os;
Ihab Awad104f8062014-07-17 11:29:35 -0700607 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700608 os = mAtomicFile.startWrite();
609 boolean success = false;
610 try {
611 XmlSerializer serializer = new FastXmlSerializer();
612 serializer.setOutput(new BufferedOutputStream(os), "utf-8");
613 writeToXml(mState, serializer);
614 serializer.flush();
615 success = true;
616 } finally {
617 if (success) {
618 mAtomicFile.finishWrite(os);
619 } else {
620 mAtomicFile.failWrite(os);
621 }
622 }
623 } catch (IOException e) {
624 Log.e(this, e, "Writing state to XML file");
Ihab Awad104f8062014-07-17 11:29:35 -0700625 }
626 }
627
Ihab Awadb78b2762014-07-25 15:16:23 -0700628 private void read() {
629 final InputStream is;
Ihab Awad104f8062014-07-17 11:29:35 -0700630 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700631 is = mAtomicFile.openRead();
632 } catch (FileNotFoundException ex) {
633 return;
634 }
635
Tyler Gunn84253572014-09-02 14:50:05 -0700636 boolean versionChanged = false;
637
Ihab Awadb78b2762014-07-25 15:16:23 -0700638 XmlPullParser parser;
639 try {
640 parser = Xml.newPullParser();
641 parser.setInput(new BufferedInputStream(is), null);
642 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700643 mState = readFromXml(parser, mContext);
644 versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION;
645
Ihab Awadb78b2762014-07-25 15:16:23 -0700646 } catch (IOException | XmlPullParserException e) {
647 Log.e(this, e, "Reading state from XML file");
648 mState = new State();
649 } finally {
650 try {
651 is.close();
652 } catch (IOException e) {
653 Log.e(this, e, "Closing InputStream");
654 }
Ihab Awad104f8062014-07-17 11:29:35 -0700655 }
Tyler Gunn84253572014-09-02 14:50:05 -0700656
657 // If an upgrade occurred, write out the changed data.
658 if (versionChanged) {
659 write();
660 }
Santos Cordon176ae282014-07-14 02:02:14 -0700661 }
662
Ihab Awadb78b2762014-07-25 15:16:23 -0700663 private static void writeToXml(State state, XmlSerializer serializer)
664 throws IOException {
665 sStateXml.writeToXml(state, serializer);
Santos Cordon176ae282014-07-14 02:02:14 -0700666 }
Ihab Awad104f8062014-07-17 11:29:35 -0700667
Tyler Gunn84253572014-09-02 14:50:05 -0700668 private static State readFromXml(XmlPullParser parser, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -0700669 throws IOException, XmlPullParserException {
Tyler Gunn84253572014-09-02 14:50:05 -0700670 State s = sStateXml.readFromXml(parser, 0, context);
Ihab Awadb78b2762014-07-25 15:16:23 -0700671 return s != null ? s : new State();
Ihab Awad104f8062014-07-17 11:29:35 -0700672 }
673
Ihab Awad293edf22014-07-24 17:52:29 -0700674 ////////////////////////////////////////////////////////////////////////////////////////////////
Ihab Awad104f8062014-07-17 11:29:35 -0700675 //
Ihab Awadb78b2762014-07-25 15:16:23 -0700676 // XML serialization
Ihab Awad104f8062014-07-17 11:29:35 -0700677 //
678
Ihab Awadb78b2762014-07-25 15:16:23 -0700679 @VisibleForTesting
Ihab Awad26923222014-07-30 10:54:35 -0700680 public abstract static class XmlSerialization<T> {
Tyler Gunn84253572014-09-02 14:50:05 -0700681 private static final String LENGTH_ATTRIBUTE = "length";
682 private static final String VALUE_TAG = "value";
683
Ihab Awadb78b2762014-07-25 15:16:23 -0700684 /**
685 * Write the supplied object to XML
686 */
Ihab Awad26923222014-07-30 10:54:35 -0700687 public abstract void writeToXml(T o, XmlSerializer serializer)
688 throws IOException;
Ihab Awadb78b2762014-07-25 15:16:23 -0700689
690 /**
691 * Read from the supplied XML into a new object, returning null in case of an
692 * unrecoverable schema mismatch or other data error. 'parser' must be already
693 * positioned at the first tag that is expected to have been emitted by this
694 * object's writeToXml(). This object tries to fail early without modifying
695 * 'parser' if it does not recognize the data it sees.
696 */
Tyler Gunn84253572014-09-02 14:50:05 -0700697 public abstract T readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awad26923222014-07-30 10:54:35 -0700698 throws IOException, XmlPullParserException;
699
700 protected void writeTextSafely(String tagName, Object value, XmlSerializer serializer)
701 throws IOException {
702 if (value != null) {
703 serializer.startTag(null, tagName);
704 serializer.text(Objects.toString(value));
705 serializer.endTag(null, tagName);
706 }
707 }
Tyler Gunn84253572014-09-02 14:50:05 -0700708
709 /**
710 * Serializes a string array.
711 *
712 * @param tagName The tag name for the string array.
713 * @param values The string values to serialize.
714 * @param serializer The serializer.
715 * @throws IOException
716 */
717 protected void writeStringList(String tagName, List<String> values,
718 XmlSerializer serializer)
719 throws IOException {
720
721 serializer.startTag(null, tagName);
722 if (values != null) {
723 serializer.attribute(null, LENGTH_ATTRIBUTE, Objects.toString(values.size()));
724 for (String toSerialize : values) {
725 serializer.startTag(null, VALUE_TAG);
726 if (toSerialize != null ){
727 serializer.text(toSerialize);
728 }
Tyler Gunn84253572014-09-02 14:50:05 -0700729 serializer.endTag(null, VALUE_TAG);
730 }
731 } else {
732 serializer.attribute(null, LENGTH_ATTRIBUTE, "0");
733 }
734 serializer.endTag(null, tagName);
735
736 }
737
738 /**
739 * Reads a string array from the XML parser.
740 *
741 * @param parser The XML parser.
742 * @return String array containing the parsed values.
743 * @throws IOException Exception related to IO.
744 * @throws XmlPullParserException Exception related to parsing.
745 */
746 protected List<String> readStringList(XmlPullParser parser)
747 throws IOException, XmlPullParserException {
748
749 int length = Integer.parseInt(parser.getAttributeValue(null, LENGTH_ATTRIBUTE));
750 List<String> arrayEntries = new ArrayList<String>(length);
751 String value = null;
752
753 if (length == 0) {
754 return arrayEntries;
755 }
756
757 int outerDepth = parser.getDepth();
758 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
759 if (parser.getName().equals(VALUE_TAG)) {
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700760 parser.next();
Tyler Gunn84253572014-09-02 14:50:05 -0700761 value = parser.getText();
762 arrayEntries.add(value);
763 }
764 }
765
766 return arrayEntries;
767 }
Ihab Awad104f8062014-07-17 11:29:35 -0700768 }
769
Ihab Awadb78b2762014-07-25 15:16:23 -0700770 @VisibleForTesting
771 public static final XmlSerialization<State> sStateXml =
772 new XmlSerialization<State>() {
773 private static final String CLASS_STATE = "phone_account_registrar_state";
Ihab Awad104f8062014-07-17 11:29:35 -0700774 private static final String DEFAULT_OUTGOING = "default_outgoing";
Ihab Awad293edf22014-07-24 17:52:29 -0700775 private static final String SIM_CALL_MANAGER = "sim_call_manager";
Ihab Awad104f8062014-07-17 11:29:35 -0700776 private static final String ACCOUNTS = "accounts";
Tyler Gunn84253572014-09-02 14:50:05 -0700777 private static final String VERSION = "version";
Ihab Awad104f8062014-07-17 11:29:35 -0700778
779 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700780 public void writeToXml(State o, XmlSerializer serializer)
781 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700782 if (o != null) {
783 serializer.startTag(null, CLASS_STATE);
Tyler Gunn84253572014-09-02 14:50:05 -0700784 serializer.attribute(null, VERSION, Objects.toString(EXPECTED_STATE_VERSION));
Ihab Awadb78b2762014-07-25 15:16:23 -0700785
Ihab Awad26923222014-07-30 10:54:35 -0700786 if (o.defaultOutgoing != null) {
787 serializer.startTag(null, DEFAULT_OUTGOING);
788 sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer);
789 serializer.endTag(null, DEFAULT_OUTGOING);
790 }
791
792 if (o.simCallManager != null) {
793 serializer.startTag(null, SIM_CALL_MANAGER);
794 sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer);
795 serializer.endTag(null, SIM_CALL_MANAGER);
796 }
797
798 serializer.startTag(null, ACCOUNTS);
799 for (PhoneAccount m : o.accounts) {
800 sPhoneAccountXml.writeToXml(m, serializer);
801 }
802 serializer.endTag(null, ACCOUNTS);
803
804 serializer.endTag(null, CLASS_STATE);
Ihab Awad293edf22014-07-24 17:52:29 -0700805 }
Ihab Awad104f8062014-07-17 11:29:35 -0700806 }
807
808 @Override
Tyler Gunn84253572014-09-02 14:50:05 -0700809 public State readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -0700810 throws IOException, XmlPullParserException {
811 if (parser.getName().equals(CLASS_STATE)) {
812 State s = new State();
Tyler Gunn84253572014-09-02 14:50:05 -0700813
814 String rawVersion = parser.getAttributeValue(null, VERSION);
815 s.versionNumber = TextUtils.isEmpty(rawVersion) ? 1 :
816 Integer.parseInt(rawVersion);
817
Ihab Awadb78b2762014-07-25 15:16:23 -0700818 int outerDepth = parser.getDepth();
819 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
820 if (parser.getName().equals(DEFAULT_OUTGOING)) {
821 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700822 s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser,
823 s.versionNumber, context);
Ihab Awadb78b2762014-07-25 15:16:23 -0700824 } else if (parser.getName().equals(SIM_CALL_MANAGER)) {
825 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700826 s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser,
827 s.versionNumber, context);
Ihab Awadb78b2762014-07-25 15:16:23 -0700828 } else if (parser.getName().equals(ACCOUNTS)) {
829 int accountsDepth = parser.getDepth();
830 while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
Tyler Gunn84253572014-09-02 14:50:05 -0700831 PhoneAccount account = sPhoneAccountXml.readFromXml(parser,
832 s.versionNumber, context);
833
834 if (account != null && s.accounts != null) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700835 s.accounts.add(account);
836 }
837 }
Ihab Awad104f8062014-07-17 11:29:35 -0700838 }
839 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700840 return s;
Ihab Awad104f8062014-07-17 11:29:35 -0700841 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700842 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700843 }
844 };
845
Ihab Awadb78b2762014-07-25 15:16:23 -0700846 @VisibleForTesting
847 public static final XmlSerialization<PhoneAccount> sPhoneAccountXml =
848 new XmlSerialization<PhoneAccount>() {
849 private static final String CLASS_PHONE_ACCOUNT = "phone_account";
850 private static final String ACCOUNT_HANDLE = "account_handle";
Andrew Lee7129f1c2014-09-04 11:55:07 -0700851 private static final String ADDRESS = "handle";
852 private static final String SUBSCRIPTION_ADDRESS = "subscription_number";
Ihab Awad104f8062014-07-17 11:29:35 -0700853 private static final String CAPABILITIES = "capabilities";
854 private static final String ICON_RES_ID = "icon_res_id";
855 private static final String LABEL = "label";
856 private static final String SHORT_DESCRIPTION = "short_description";
Tyler Gunn84253572014-09-02 14:50:05 -0700857 private static final String SUPPORTED_URI_SCHEMES = "supported_uri_schemes";
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700858 private static final String ENABLED = "enabled";
859 private static final String TRUE = "true";
860 private static final String FALSE = "false";
Ihab Awad104f8062014-07-17 11:29:35 -0700861
862 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700863 public void writeToXml(PhoneAccount o, XmlSerializer serializer)
864 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700865 if (o != null) {
866 serializer.startTag(null, CLASS_PHONE_ACCOUNT);
Ihab Awadb78b2762014-07-25 15:16:23 -0700867
Ihab Awad26923222014-07-30 10:54:35 -0700868 if (o.getAccountHandle() != null) {
869 serializer.startTag(null, ACCOUNT_HANDLE);
870 sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer);
871 serializer.endTag(null, ACCOUNT_HANDLE);
872 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700873
Andrew Lee7129f1c2014-09-04 11:55:07 -0700874 writeTextSafely(ADDRESS, o.getAddress(), serializer);
875 writeTextSafely(SUBSCRIPTION_ADDRESS, o.getSubscriptionAddress(), serializer);
Ihab Awad26923222014-07-30 10:54:35 -0700876 writeTextSafely(CAPABILITIES, Integer.toString(o.getCapabilities()), serializer);
877 writeTextSafely(ICON_RES_ID, Integer.toString(o.getIconResId()), serializer);
878 writeTextSafely(LABEL, o.getLabel(), serializer);
879 writeTextSafely(SHORT_DESCRIPTION, o.getShortDescription(), serializer);
Tyler Gunn84253572014-09-02 14:50:05 -0700880 writeStringList(SUPPORTED_URI_SCHEMES, o.getSupportedUriSchemes(), serializer);
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700881 writeTextSafely(ENABLED, o.isEnabled() ? TRUE : FALSE, serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -0700882
Ihab Awad26923222014-07-30 10:54:35 -0700883 serializer.endTag(null, CLASS_PHONE_ACCOUNT);
884 }
Ihab Awad104f8062014-07-17 11:29:35 -0700885 }
886
Tyler Gunn84253572014-09-02 14:50:05 -0700887 public PhoneAccount readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -0700888 throws IOException, XmlPullParserException {
889 if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) {
890 int outerDepth = parser.getDepth();
891 PhoneAccountHandle accountHandle = null;
Andrew Lee7129f1c2014-09-04 11:55:07 -0700892 Uri address = null;
893 Uri subscriptionAddress = null;
Ihab Awadb78b2762014-07-25 15:16:23 -0700894 int capabilities = 0;
895 int iconResId = 0;
896 String label = null;
897 String shortDescription = null;
Tyler Gunn84253572014-09-02 14:50:05 -0700898 List<String> supportedUriSchemes = null;
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700899 boolean enabled = false;
Ihab Awadb78b2762014-07-25 15:16:23 -0700900
901 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
902 if (parser.getName().equals(ACCOUNT_HANDLE)) {
903 parser.nextTag();
Tyler Gunn84253572014-09-02 14:50:05 -0700904 accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version,
905 context);
Andrew Lee7129f1c2014-09-04 11:55:07 -0700906 } else if (parser.getName().equals(ADDRESS)) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700907 parser.next();
Andrew Lee7129f1c2014-09-04 11:55:07 -0700908 address = Uri.parse(parser.getText());
909 } else if (parser.getName().equals(SUBSCRIPTION_ADDRESS)) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700910 parser.next();
Andrew Lee7129f1c2014-09-04 11:55:07 -0700911 String nextText = parser.getText();
912 subscriptionAddress = nextText == null ? null : Uri.parse(nextText);
Ihab Awadb78b2762014-07-25 15:16:23 -0700913 } else if (parser.getName().equals(CAPABILITIES)) {
914 parser.next();
915 capabilities = Integer.parseInt(parser.getText());
916 } else if (parser.getName().equals(ICON_RES_ID)) {
917 parser.next();
918 iconResId = Integer.parseInt(parser.getText());
919 } else if (parser.getName().equals(LABEL)) {
920 parser.next();
921 label = parser.getText();
922 } else if (parser.getName().equals(SHORT_DESCRIPTION)) {
923 parser.next();
924 shortDescription = parser.getText();
Tyler Gunn84253572014-09-02 14:50:05 -0700925 } else if (parser.getName().equals(SUPPORTED_URI_SCHEMES)) {
926 supportedUriSchemes = readStringList(parser);
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700927 } else if (parser.getName().equals(ENABLED)) {
928 parser.next();
929 enabled = parser.getText().equals(TRUE);
Ihab Awadb78b2762014-07-25 15:16:23 -0700930 }
931 }
Tyler Gunn84253572014-09-02 14:50:05 -0700932
933 // Upgrade older phone accounts to specify the supported URI schemes.
934 if (version < 2) {
935 ComponentName sipComponentName = new ComponentName("com.android.phone",
936 "com.android.services.telephony.sip.SipConnectionService");
937
938 supportedUriSchemes = new ArrayList<>();
939
940 // Handle the SIP connection service.
941 // Check the system settings to see if it also should handle "tel" calls.
942 if (accountHandle.getComponentName().equals(sipComponentName)) {
943 boolean useSipForPstn = useSipForPstnCalls(context);
944 supportedUriSchemes.add(PhoneAccount.SCHEME_SIP);
945 if (useSipForPstn) {
946 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL);
947 }
948 } else {
949 supportedUriSchemes.add(PhoneAccount.SCHEME_TEL);
950 supportedUriSchemes.add(PhoneAccount.SCHEME_VOICEMAIL);
951 }
952 }
953
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700954 // Prior to version 3, PhoneAccounts didn't include the enabled option. Enable
955 // all TelephonyConnectionService phone accounts by default.
956 if (version < 3) {
957 ComponentName telephonyComponentName = new ComponentName("com.android.phone",
958 "com.android.services.telephony.TelephonyConnectionService");
959
960 if (accountHandle.getComponentName().equals(telephonyComponentName)) {
961 enabled = true;
962 }
963 }
964
Andrew Lee7129f1c2014-09-04 11:55:07 -0700965 return PhoneAccount.builder(accountHandle, label)
966 .setAddress(address)
967 .setSubscriptionAddress(subscriptionAddress)
968 .setCapabilities(capabilities)
969 .setIconResId(iconResId)
970 .setShortDescription(shortDescription)
971 .setSupportedUriSchemes(supportedUriSchemes)
Tyler Gunn8e0fef42014-09-08 18:34:44 -0700972 .setEnabled(enabled)
Ihab Awad6fb37c82014-08-07 19:48:57 -0700973 .build();
Ihab Awadb78b2762014-07-25 15:16:23 -0700974 }
975 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700976 }
Tyler Gunn84253572014-09-02 14:50:05 -0700977
978 /**
979 * Determines if the SIP call settings specify to use SIP for all calls, including PSTN calls.
980 *
981 * @param context The context.
982 * @return {@code True} if SIP should be used for all calls.
983 */
984 private boolean useSipForPstnCalls(Context context) {
985 String option = Settings.System.getString(context.getContentResolver(),
986 Settings.System.SIP_CALL_OPTIONS);
987 option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY;
988 return option.equals(Settings.System.SIP_ALWAYS);
989 }
Ihab Awad104f8062014-07-17 11:29:35 -0700990 };
991
Ihab Awadb78b2762014-07-25 15:16:23 -0700992 @VisibleForTesting
993 public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml =
994 new XmlSerialization<PhoneAccountHandle>() {
995 private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle";
Ihab Awad104f8062014-07-17 11:29:35 -0700996 private static final String COMPONENT_NAME = "component_name";
997 private static final String ID = "id";
998
999 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -07001000 public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer)
1001 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -07001002 if (o != null) {
1003 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
Ihab Awadb78b2762014-07-25 15:16:23 -07001004
Ihab Awad26923222014-07-30 10:54:35 -07001005 if (o.getComponentName() != null) {
1006 writeTextSafely(
1007 COMPONENT_NAME, o.getComponentName().flattenToString(), serializer);
1008 }
Ihab Awadb78b2762014-07-25 15:16:23 -07001009
Ihab Awad26923222014-07-30 10:54:35 -07001010 writeTextSafely(ID, o.getId(), serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -07001011
Ihab Awad26923222014-07-30 10:54:35 -07001012 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
1013 }
Ihab Awad104f8062014-07-17 11:29:35 -07001014 }
1015
1016 @Override
Tyler Gunn84253572014-09-02 14:50:05 -07001017 public PhoneAccountHandle readFromXml(XmlPullParser parser, int version, Context context)
Ihab Awadb78b2762014-07-25 15:16:23 -07001018 throws IOException, XmlPullParserException {
1019 if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) {
1020 String componentNameString = null;
1021 String idString = null;
1022 int outerDepth = parser.getDepth();
1023 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1024 if (parser.getName().equals(COMPONENT_NAME)) {
1025 parser.next();
1026 componentNameString = parser.getText();
1027 } else if (parser.getName().equals(ID)) {
1028 parser.next();
1029 idString = parser.getText();
1030 }
1031 }
Ihab Awad26923222014-07-30 10:54:35 -07001032 if (componentNameString != null) {
Ihab Awadb78b2762014-07-25 15:16:23 -07001033 return new PhoneAccountHandle(
1034 ComponentName.unflattenFromString(componentNameString),
1035 idString);
1036 }
1037 }
1038 return null;
Ihab Awad104f8062014-07-17 11:29:35 -07001039 }
1040 };
Santos Cordon176ae282014-07-14 02:02:14 -07001041}