blob: dbf974ef0c36d2ecf93305deaef6bc16c967b63f [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
17package com.android.telecomm;
18
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;
Evan Charltonaf51ceb2014-07-30 11:56:36 -070024import android.telecomm.ConnectionService;
Evan Charlton94d01622014-07-20 12:32:05 -070025import android.telecomm.PhoneAccount;
Evan Charlton89176372014-07-19 18:23:09 -070026import android.telecomm.PhoneAccountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -070027import android.content.ComponentName;
28import android.content.Context;
Santos Cordon176ae282014-07-14 02:02:14 -070029import android.net.Uri;
Ihab Awad104f8062014-07-17 11:29:35 -070030import android.telecomm.TelecommManager;
Evan Charltonaf51ceb2014-07-30 11:56:36 -070031import android.text.TextUtils;
Ihab Awadb78b2762014-07-25 15:16:23 -070032import android.util.AtomicFile;
33import android.util.Xml;
Santos Cordon176ae282014-07-14 02:02:14 -070034
Sailesh Nepal0e1dc5a2014-07-30 11:08:54 -070035import com.android.internal.annotations.VisibleForTesting;
36import com.android.internal.util.FastXmlSerializer;
37import com.android.internal.util.XmlUtils;
38
Evan Charltonaf51ceb2014-07-30 11:56:36 -070039import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41import org.xmlpull.v1.XmlSerializer;
42
Ihab Awadb78b2762014-07-25 15:16:23 -070043import java.io.BufferedInputStream;
44import java.io.BufferedOutputStream;
45import java.io.File;
46import java.io.FileNotFoundException;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.io.InputStream;
Tyler Gunncb59b672014-08-20 09:02:11 -070050import java.lang.SecurityException;
Tyler Gunnd900ce62014-08-13 11:40:59 -070051import java.lang.String;
Santos Cordon176ae282014-07-14 02:02:14 -070052import java.util.ArrayList;
Tyler Gunnd900ce62014-08-13 11:40:59 -070053import java.util.Iterator;
Santos Cordon176ae282014-07-14 02:02:14 -070054import java.util.List;
55import java.util.Objects;
Ihab Awadb78b2762014-07-25 15:16:23 -070056import java.util.concurrent.CopyOnWriteArrayList;
Santos Cordon176ae282014-07-14 02:02:14 -070057
58/**
Evan Charlton89176372014-07-19 18:23:09 -070059 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
Ihab Awad104f8062014-07-17 11:29:35 -070060 * delegate for all the account handling methods on {@link TelecommManager} as implemented in
61 * {@link TelecommServiceImpl}, with the notable exception that {@link TelecommServiceImpl} is
62 * responsible for security checking to make sure that the caller has proper authority over
Evan Charlton89176372014-07-19 18:23:09 -070063 * the {@code ComponentName}s they are declaring in their {@code PhoneAccountHandle}s.
Santos Cordon176ae282014-07-14 02:02:14 -070064 */
Ihab Awadb78b2762014-07-25 15:16:23 -070065public final class PhoneAccountRegistrar {
Santos Cordon176ae282014-07-14 02:02:14 -070066
Yorke Lee5e8836a2014-08-22 15:25:18 -070067 public static final PhoneAccountHandle NO_ACCOUNT_SELECTED =
68 new PhoneAccountHandle(new ComponentName("null", "null"), "NO_ACCOUNT_SELECTED");
69
Ihab Awadb78b2762014-07-25 15:16:23 -070070 public abstract static class Listener {
71 public void onAccountsChanged(PhoneAccountRegistrar registrar) {}
72 public void onDefaultOutgoingChanged(PhoneAccountRegistrar registrar) {}
73 public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {}
74 }
75
76 private static final String FILE_NAME = "phone-account-registrar-state.xml";
77
78 private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
79 private final AtomicFile mAtomicFile;
80 private State mState;
Santos Cordon176ae282014-07-14 02:02:14 -070081
Ihab Awad26923222014-07-30 10:54:35 -070082 public PhoneAccountRegistrar(Context context) {
Ihab Awadb78b2762014-07-25 15:16:23 -070083 this(context, FILE_NAME);
84 }
85
86 @VisibleForTesting
87 public PhoneAccountRegistrar(Context context, String fileName) {
88 // TODO: Change file location when Telecomm is part of system
89 mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
90 mState = new State();
91 read();
Santos Cordon176ae282014-07-14 02:02:14 -070092 }
93
Evan Charlton89176372014-07-19 18:23:09 -070094 public PhoneAccountHandle getDefaultOutgoingPhoneAccount() {
Yorke Lee5e8836a2014-08-22 15:25:18 -070095 final PhoneAccountHandle userSelected = getUserSelectedOutgoingPhoneAccount();
96 if (userSelected != null) {
97 return userSelected;
Ihab Awad293edf22014-07-24 17:52:29 -070098 }
99
Ihab Awad6fb37c82014-08-07 19:48:57 -0700100 List<PhoneAccountHandle> outgoing = getOutgoingPhoneAccounts();
101 switch (outgoing.size()) {
Ihab Awad293edf22014-07-24 17:52:29 -0700102 case 0:
103 // There are no accounts, so there can be no default
104 return null;
105 case 1:
106 // There is only one account, which is by definition the default
Ihab Awad6fb37c82014-08-07 19:48:57 -0700107 return outgoing.get(0);
Ihab Awad293edf22014-07-24 17:52:29 -0700108 default:
109 // There are multiple accounts with no selected default
110 return null;
Ihab Awadf2a84912014-07-22 21:09:25 -0700111 }
Ihab Awad104f8062014-07-17 11:29:35 -0700112 }
Santos Cordon176ae282014-07-14 02:02:14 -0700113
Yorke Lee5e8836a2014-08-22 15:25:18 -0700114 PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
115 if (mState.defaultOutgoing != null) {
116 // Return the registered outgoing default iff it still exists (we keep a sticky
117 // default to survive account deletion and re-addition)
118 for (int i = 0; i < mState.accounts.size(); i++) {
119 if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)) {
120 return mState.defaultOutgoing;
121 }
122 }
123 // At this point, there was a registered default but it has been deleted; proceed
124 // as though there were no default
125 }
126 return null;
127 }
128
Evan Charlton89176372014-07-19 18:23:09 -0700129 public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
Evan Charlton89176372014-07-19 18:23:09 -0700130 if (accountHandle == null) {
Ihab Awad104f8062014-07-17 11:29:35 -0700131 // Asking to clear the default outgoing is a valid request
Ihab Awad293edf22014-07-24 17:52:29 -0700132 mState.defaultOutgoing = null;
Ihab Awad104f8062014-07-17 11:29:35 -0700133 } else {
134 boolean found = false;
Ihab Awad293edf22014-07-24 17:52:29 -0700135 for (PhoneAccount m : mState.accounts) {
Evan Charlton94d01622014-07-20 12:32:05 -0700136 if (Objects.equals(accountHandle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700137 found = true;
138 break;
139 }
Santos Cordon176ae282014-07-14 02:02:14 -0700140 }
Ihab Awad104f8062014-07-17 11:29:35 -0700141
142 if (!found) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700143 Log.w(this, "Trying to set nonexistent default outgoing %s",
144 accountHandle);
145 return;
146 }
147
148 if (!has(getPhoneAccount(accountHandle), PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
149 Log.w(this, "Trying to set non-call-provider default outgoing %s",
Evan Charlton89176372014-07-19 18:23:09 -0700150 accountHandle);
Ihab Awad104f8062014-07-17 11:29:35 -0700151 return;
152 }
153
Ihab Awad293edf22014-07-24 17:52:29 -0700154 mState.defaultOutgoing = accountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -0700155 }
156
Ihab Awad293edf22014-07-24 17:52:29 -0700157 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700158 fireDefaultOutgoingChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700159 }
160
Ihab Awad293edf22014-07-24 17:52:29 -0700161 public void setSimCallManager(PhoneAccountHandle callManager) {
162 if (callManager != null) {
163 PhoneAccount callManagerAccount = getPhoneAccount(callManager);
164 if (callManagerAccount == null) {
165 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager);
166 return;
Ihab Awadb78b2762014-07-25 15:16:23 -0700167 } else if (!has(callManagerAccount, PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
Ihab Awad293edf22014-07-24 17:52:29 -0700168 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount);
169 return;
170 }
Yorke Lee5e8836a2014-08-22 15:25:18 -0700171 } else {
172 callManager = NO_ACCOUNT_SELECTED;
Ihab Awad293edf22014-07-24 17:52:29 -0700173 }
174 mState.simCallManager = callManager;
Yorke Lee5e8836a2014-08-22 15:25:18 -0700175
Ihab Awad293edf22014-07-24 17:52:29 -0700176 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700177 fireSimCallManagerChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700178 }
179
180 public PhoneAccountHandle getSimCallManager() {
Ihab Awadb78b2762014-07-25 15:16:23 -0700181 if (mState.simCallManager != null) {
Yorke Lee5e8836a2014-08-22 15:25:18 -0700182 if (NO_ACCOUNT_SELECTED.equals(mState.simCallManager)) {
183 return null;
184 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700185 // Return the registered sim call manager iff it still exists (we keep a sticky
186 // setting to survive account deletion and re-addition)
187 for (int i = 0; i < mState.accounts.size(); i++) {
188 if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)) {
189 return mState.simCallManager;
190 }
191 }
192 }
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700193
194 // See if the OEM has specified a default one.
195 Context context = TelecommApp.getInstance();
196 String defaultConnectionMgr =
197 context.getResources().getString(R.string.default_connection_manager_component);
198 if (!TextUtils.isEmpty(defaultConnectionMgr)) {
199 PackageManager pm = context.getPackageManager();
200
201 ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr);
202 Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
203 intent.setComponent(componentName);
204
205 // Make sure that the component can be resolved.
206 List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0);
207 if (!resolveInfos.isEmpty()) {
208 // See if there is registered PhoneAccount by this component.
209 List<PhoneAccountHandle> handles = getAllPhoneAccountHandles();
210 for (PhoneAccountHandle handle : handles) {
211 if (componentName.equals(handle.getComponentName())) {
212 return handle;
213 }
214 }
215 Log.d(this, "%s does not have a PhoneAccount; not using as default", componentName);
216 } else {
217 Log.d(this, "%s could not be resolved; not using as default", componentName);
218 }
219 } else {
220 Log.v(this, "No default connection manager specified");
221 }
222
Ihab Awadb78b2762014-07-25 15:16:23 -0700223 return null;
Ihab Awad293edf22014-07-24 17:52:29 -0700224 }
225
226 public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
227 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
228 for (PhoneAccount m : mState.accounts) {
229 accountHandles.add(m.getAccountHandle());
230 }
231 return accountHandles;
232 }
233
234 public List<PhoneAccount> getAllPhoneAccounts() {
235 return new ArrayList<>(mState.accounts);
236 }
237
Ihab Awad6fb37c82014-08-07 19:48:57 -0700238 public List<PhoneAccountHandle> getOutgoingPhoneAccounts() {
Ihab Awad293edf22014-07-24 17:52:29 -0700239 return getCallProviderAccountHandles();
Santos Cordon176ae282014-07-14 02:02:14 -0700240 }
241
Ihab Awad293edf22014-07-24 17:52:29 -0700242 public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
243 for (PhoneAccount m : mState.accounts) {
244 if (Objects.equals(handle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700245 return m;
Santos Cordon176ae282014-07-14 02:02:14 -0700246 }
247 }
248 return null;
249 }
250
Ihab Awad104f8062014-07-17 11:29:35 -0700251 // TODO: Should we implement an artificial limit for # of accounts associated with a single
252 // ComponentName?
Ihab Awad293edf22014-07-24 17:52:29 -0700253 public void registerPhoneAccount(PhoneAccount account) {
Tyler Gunncb59b672014-08-20 09:02:11 -0700254 // Enforce the requirement that a connection service for a phone account has the correct
255 // permission.
256 if (!phoneAccountHasPermission(account.getAccountHandle())) {
257 Log.w(this, "Phone account %s does not have BIND_CONNECTION_SERVICE permission.",
258 account.getAccountHandle());
259 throw new SecurityException(
260 "PhoneAccount connection service requires BIND_CONNECTION_SERVICE permission.");
261 }
262
Ihab Awad293edf22014-07-24 17:52:29 -0700263 mState.accounts.add(account);
Ihab Awad104f8062014-07-17 11:29:35 -0700264 // Search for duplicates and remove any that are found.
Ihab Awad293edf22014-07-24 17:52:29 -0700265 for (int i = 0; i < mState.accounts.size() - 1; i++) {
266 if (Objects.equals(
267 account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700268 // replace existing entry.
Ihab Awad293edf22014-07-24 17:52:29 -0700269 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700270 break;
Santos Cordon176ae282014-07-14 02:02:14 -0700271 }
272 }
273
Ihab Awad293edf22014-07-24 17:52:29 -0700274 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700275 fireAccountsChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700276 }
277
Evan Charlton89176372014-07-19 18:23:09 -0700278 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad293edf22014-07-24 17:52:29 -0700279 for (int i = 0; i < mState.accounts.size(); i++) {
280 if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) {
281 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700282 break;
283 }
284 }
285
Ihab Awad293edf22014-07-24 17:52:29 -0700286 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700287 fireAccountsChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700288 }
289
Tyler Gunnd900ce62014-08-13 11:40:59 -0700290 /**
291 * Un-registers all phone accounts associated with a specified package.
292 *
293 * @param packageName The package for which phone accounts will be removed.
294 */
Ihab Awad104f8062014-07-17 11:29:35 -0700295 public void clearAccounts(String packageName) {
Tyler Gunnd900ce62014-08-13 11:40:59 -0700296 boolean accountsRemoved = false;
297 Iterator<PhoneAccount> it = mState.accounts.iterator();
298 while (it.hasNext()) {
299 PhoneAccount phoneAccount = it.next();
Ihab Awad104f8062014-07-17 11:29:35 -0700300 if (Objects.equals(
301 packageName,
Tyler Gunnd900ce62014-08-13 11:40:59 -0700302 phoneAccount.getAccountHandle().getComponentName().getPackageName())) {
303 Log.i(this, "Removing phone account " + phoneAccount.getLabel());
304 it.remove();
305 accountsRemoved = true;
Ihab Awad104f8062014-07-17 11:29:35 -0700306 }
307 }
308
Tyler Gunnd900ce62014-08-13 11:40:59 -0700309 if (accountsRemoved) {
310 write();
311 fireAccountsChanged();
312 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700313 }
314
315 public void addListener(Listener l) {
316 mListeners.add(l);
317 }
318
319 public void removeListener(Listener l) {
Jay Shraunera82c8f72014-08-14 15:49:16 -0700320 if (l != null) {
321 mListeners.remove(l);
322 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700323 }
324
325 private void fireAccountsChanged() {
326 for (Listener l : mListeners) {
327 l.onAccountsChanged(this);
328 }
329 }
330
331 private void fireDefaultOutgoingChanged() {
332 for (Listener l : mListeners) {
333 l.onDefaultOutgoingChanged(this);
334 }
335 }
336
337 private void fireSimCallManagerChanged() {
338 for (Listener l : mListeners) {
339 l.onSimCallManagerChanged(this);
340 }
Santos Cordon176ae282014-07-14 02:02:14 -0700341 }
342
Tyler Gunncb59b672014-08-20 09:02:11 -0700343 /**
344 * Determines if the connection service specified by a {@link PhoneAccountHandle} has the
345 * {@link Manifest.permission#BIND_CONNECTION_SERVICE} permission.
346 *
347 * @param phoneAccountHandle The phone account to check.
348 * @return {@code True} if the phone account has permission.
349 */
350 public boolean phoneAccountHasPermission(PhoneAccountHandle phoneAccountHandle) {
351 PackageManager packageManager = TelecommApp.getInstance().getPackageManager();
352 try {
353 ServiceInfo serviceInfo = packageManager.getServiceInfo(
354 phoneAccountHandle.getComponentName(), 0);
355
356 return serviceInfo.permission != null &&
357 serviceInfo.permission.equals(Manifest.permission.BIND_CONNECTION_SERVICE);
358 } catch (PackageManager.NameNotFoundException e) {
359 Log.w(this, "Name not found %s", e);
360 return false;
361 }
362 }
363
Ihab Awad293edf22014-07-24 17:52:29 -0700364 ////////////////////////////////////////////////////////////////////////////////////////////////
365
366 // TODO: Add a corresponding has(...) method to class PhoneAccount itself and remove this one
367 // Return true iff the given account has all the specified capability flags
368 static boolean has(PhoneAccount account, int capability) {
369 return (account.getCapabilities() & capability) == capability;
370 }
371
372 private List<PhoneAccountHandle> getCallProviderAccountHandles() {
Evan Charlton94d01622014-07-20 12:32:05 -0700373 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
Ihab Awad293edf22014-07-24 17:52:29 -0700374 for (PhoneAccount m : mState.accounts) {
375 if (has(m, PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
Ihab Awadf2a84912014-07-22 21:09:25 -0700376 accountHandles.add(m.getAccountHandle());
377 }
Ihab Awad104f8062014-07-17 11:29:35 -0700378 }
Evan Charlton94d01622014-07-20 12:32:05 -0700379 return accountHandles;
Ihab Awad104f8062014-07-17 11:29:35 -0700380 }
381
Ihab Awad293edf22014-07-24 17:52:29 -0700382 /**
383 * The state of this {@code PhoneAccountRegistrar}.
384 */
Ihab Awadb78b2762014-07-25 15:16:23 -0700385 @VisibleForTesting
386 public static class State {
Ihab Awad293edf22014-07-24 17:52:29 -0700387 /**
388 * The account selected by the user to be employed by default for making outgoing calls.
389 * If the user has not made such a selection, then this is null.
390 */
391 public PhoneAccountHandle defaultOutgoing = null;
392
393 /**
Ihab Awadb78b2762014-07-25 15:16:23 -0700394 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which
Ihab Awad293edf22014-07-24 17:52:29 -0700395 * manages and optimizes a user's PSTN SIM connections.
396 */
397 public PhoneAccountHandle simCallManager;
398
399 /**
400 * The complete list of {@code PhoneAccount}s known to the Telecomm subsystem.
401 */
402 public final List<PhoneAccount> accounts = new ArrayList<>();
403 }
404
405 ////////////////////////////////////////////////////////////////////////////////////////////////
406 //
407 // State management
408 //
409
410 private void write() {
Ihab Awadb78b2762014-07-25 15:16:23 -0700411 final FileOutputStream os;
Ihab Awad104f8062014-07-17 11:29:35 -0700412 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700413 os = mAtomicFile.startWrite();
414 boolean success = false;
415 try {
416 XmlSerializer serializer = new FastXmlSerializer();
417 serializer.setOutput(new BufferedOutputStream(os), "utf-8");
418 writeToXml(mState, serializer);
419 serializer.flush();
420 success = true;
421 } finally {
422 if (success) {
423 mAtomicFile.finishWrite(os);
424 } else {
425 mAtomicFile.failWrite(os);
426 }
427 }
428 } catch (IOException e) {
429 Log.e(this, e, "Writing state to XML file");
Ihab Awad104f8062014-07-17 11:29:35 -0700430 }
431 }
432
Ihab Awadb78b2762014-07-25 15:16:23 -0700433 private void read() {
434 final InputStream is;
Ihab Awad104f8062014-07-17 11:29:35 -0700435 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700436 is = mAtomicFile.openRead();
437 } catch (FileNotFoundException ex) {
438 return;
439 }
440
441 XmlPullParser parser;
442 try {
443 parser = Xml.newPullParser();
444 parser.setInput(new BufferedInputStream(is), null);
445 parser.nextTag();
446 mState = readFromXml(parser);
447 } catch (IOException | XmlPullParserException e) {
448 Log.e(this, e, "Reading state from XML file");
449 mState = new State();
450 } finally {
451 try {
452 is.close();
453 } catch (IOException e) {
454 Log.e(this, e, "Closing InputStream");
455 }
Ihab Awad104f8062014-07-17 11:29:35 -0700456 }
Santos Cordon176ae282014-07-14 02:02:14 -0700457 }
458
Ihab Awadb78b2762014-07-25 15:16:23 -0700459 private static void writeToXml(State state, XmlSerializer serializer)
460 throws IOException {
461 sStateXml.writeToXml(state, serializer);
Santos Cordon176ae282014-07-14 02:02:14 -0700462 }
Ihab Awad104f8062014-07-17 11:29:35 -0700463
Ihab Awadb78b2762014-07-25 15:16:23 -0700464 private static State readFromXml(XmlPullParser parser)
465 throws IOException, XmlPullParserException {
466 State s = sStateXml.readFromXml(parser);
467 return s != null ? s : new State();
Ihab Awad104f8062014-07-17 11:29:35 -0700468 }
469
Ihab Awad293edf22014-07-24 17:52:29 -0700470 ////////////////////////////////////////////////////////////////////////////////////////////////
Ihab Awad104f8062014-07-17 11:29:35 -0700471 //
Ihab Awadb78b2762014-07-25 15:16:23 -0700472 // XML serialization
Ihab Awad104f8062014-07-17 11:29:35 -0700473 //
474
Ihab Awadb78b2762014-07-25 15:16:23 -0700475 @VisibleForTesting
Ihab Awad26923222014-07-30 10:54:35 -0700476 public abstract static class XmlSerialization<T> {
Ihab Awadb78b2762014-07-25 15:16:23 -0700477 /**
478 * Write the supplied object to XML
479 */
Ihab Awad26923222014-07-30 10:54:35 -0700480 public abstract void writeToXml(T o, XmlSerializer serializer)
481 throws IOException;
Ihab Awadb78b2762014-07-25 15:16:23 -0700482
483 /**
484 * Read from the supplied XML into a new object, returning null in case of an
485 * unrecoverable schema mismatch or other data error. 'parser' must be already
486 * positioned at the first tag that is expected to have been emitted by this
487 * object's writeToXml(). This object tries to fail early without modifying
488 * 'parser' if it does not recognize the data it sees.
489 */
Ihab Awad26923222014-07-30 10:54:35 -0700490 public abstract T readFromXml(XmlPullParser parser)
491 throws IOException, XmlPullParserException;
492
493 protected void writeTextSafely(String tagName, Object value, XmlSerializer serializer)
494 throws IOException {
495 if (value != null) {
496 serializer.startTag(null, tagName);
497 serializer.text(Objects.toString(value));
498 serializer.endTag(null, tagName);
499 }
500 }
Ihab Awad104f8062014-07-17 11:29:35 -0700501 }
502
Ihab Awadb78b2762014-07-25 15:16:23 -0700503 @VisibleForTesting
504 public static final XmlSerialization<State> sStateXml =
505 new XmlSerialization<State>() {
506 private static final String CLASS_STATE = "phone_account_registrar_state";
Ihab Awad104f8062014-07-17 11:29:35 -0700507 private static final String DEFAULT_OUTGOING = "default_outgoing";
Ihab Awad293edf22014-07-24 17:52:29 -0700508 private static final String SIM_CALL_MANAGER = "sim_call_manager";
Ihab Awad104f8062014-07-17 11:29:35 -0700509 private static final String ACCOUNTS = "accounts";
510
511 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700512 public void writeToXml(State o, XmlSerializer serializer)
513 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700514 if (o != null) {
515 serializer.startTag(null, CLASS_STATE);
Ihab Awadb78b2762014-07-25 15:16:23 -0700516
Ihab Awad26923222014-07-30 10:54:35 -0700517 if (o.defaultOutgoing != null) {
518 serializer.startTag(null, DEFAULT_OUTGOING);
519 sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer);
520 serializer.endTag(null, DEFAULT_OUTGOING);
521 }
522
523 if (o.simCallManager != null) {
524 serializer.startTag(null, SIM_CALL_MANAGER);
525 sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer);
526 serializer.endTag(null, SIM_CALL_MANAGER);
527 }
528
529 serializer.startTag(null, ACCOUNTS);
530 for (PhoneAccount m : o.accounts) {
531 sPhoneAccountXml.writeToXml(m, serializer);
532 }
533 serializer.endTag(null, ACCOUNTS);
534
535 serializer.endTag(null, CLASS_STATE);
Ihab Awad293edf22014-07-24 17:52:29 -0700536 }
Ihab Awad104f8062014-07-17 11:29:35 -0700537 }
538
539 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700540 public State readFromXml(XmlPullParser parser)
541 throws IOException, XmlPullParserException {
542 if (parser.getName().equals(CLASS_STATE)) {
543 State s = new State();
544 int outerDepth = parser.getDepth();
545 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
546 if (parser.getName().equals(DEFAULT_OUTGOING)) {
547 parser.nextTag();
548 s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser);
549 } else if (parser.getName().equals(SIM_CALL_MANAGER)) {
550 parser.nextTag();
551 s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser);
552 } else if (parser.getName().equals(ACCOUNTS)) {
553 int accountsDepth = parser.getDepth();
554 while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
555 PhoneAccount account = sPhoneAccountXml.readFromXml(parser);
556 if (account != null) {
557 s.accounts.add(account);
558 }
559 }
Ihab Awad104f8062014-07-17 11:29:35 -0700560 }
561 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700562 return s;
Ihab Awad104f8062014-07-17 11:29:35 -0700563 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700564 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700565 }
566 };
567
Ihab Awadb78b2762014-07-25 15:16:23 -0700568 @VisibleForTesting
569 public static final XmlSerialization<PhoneAccount> sPhoneAccountXml =
570 new XmlSerialization<PhoneAccount>() {
571 private static final String CLASS_PHONE_ACCOUNT = "phone_account";
572 private static final String ACCOUNT_HANDLE = "account_handle";
Ihab Awad104f8062014-07-17 11:29:35 -0700573 private static final String HANDLE = "handle";
Evan Charlton484f8d62014-07-18 14:06:58 -0700574 private static final String SUBSCRIPTION_NUMBER = "subscription_number";
Ihab Awad104f8062014-07-17 11:29:35 -0700575 private static final String CAPABILITIES = "capabilities";
576 private static final String ICON_RES_ID = "icon_res_id";
577 private static final String LABEL = "label";
578 private static final String SHORT_DESCRIPTION = "short_description";
Ihab Awad104f8062014-07-17 11:29:35 -0700579
580 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700581 public void writeToXml(PhoneAccount o, XmlSerializer serializer)
582 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700583 if (o != null) {
584 serializer.startTag(null, CLASS_PHONE_ACCOUNT);
Ihab Awadb78b2762014-07-25 15:16:23 -0700585
Ihab Awad26923222014-07-30 10:54:35 -0700586 if (o.getAccountHandle() != null) {
587 serializer.startTag(null, ACCOUNT_HANDLE);
588 sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer);
589 serializer.endTag(null, ACCOUNT_HANDLE);
590 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700591
Ihab Awad26923222014-07-30 10:54:35 -0700592 writeTextSafely(HANDLE, o.getHandle(), serializer);
593 writeTextSafely(SUBSCRIPTION_NUMBER, o.getSubscriptionNumber(), serializer);
594 writeTextSafely(CAPABILITIES, Integer.toString(o.getCapabilities()), serializer);
595 writeTextSafely(ICON_RES_ID, Integer.toString(o.getIconResId()), serializer);
596 writeTextSafely(LABEL, o.getLabel(), serializer);
597 writeTextSafely(SHORT_DESCRIPTION, o.getShortDescription(), serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -0700598
Ihab Awad26923222014-07-30 10:54:35 -0700599 serializer.endTag(null, CLASS_PHONE_ACCOUNT);
600 }
Ihab Awad104f8062014-07-17 11:29:35 -0700601 }
602
603 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700604 public PhoneAccount readFromXml(XmlPullParser parser)
605 throws IOException, XmlPullParserException {
606 if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) {
607 int outerDepth = parser.getDepth();
608 PhoneAccountHandle accountHandle = null;
609 Uri handle = null;
610 String subscriptionNumber = null;
611 int capabilities = 0;
612 int iconResId = 0;
613 String label = null;
614 String shortDescription = null;
615
616 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
617 if (parser.getName().equals(ACCOUNT_HANDLE)) {
618 parser.nextTag();
619 accountHandle = sPhoneAccountHandleXml.readFromXml(parser);
620 } else if (parser.getName().equals(HANDLE)) {
621 parser.next();
622 handle = Uri.parse(parser.getText());
623 } else if (parser.getName().equals(SUBSCRIPTION_NUMBER)) {
624 parser.next();
625 subscriptionNumber = parser.getText();
626 } else if (parser.getName().equals(CAPABILITIES)) {
627 parser.next();
628 capabilities = Integer.parseInt(parser.getText());
629 } else if (parser.getName().equals(ICON_RES_ID)) {
630 parser.next();
631 iconResId = Integer.parseInt(parser.getText());
632 } else if (parser.getName().equals(LABEL)) {
633 parser.next();
634 label = parser.getText();
635 } else if (parser.getName().equals(SHORT_DESCRIPTION)) {
636 parser.next();
637 shortDescription = parser.getText();
638 }
639 }
Ihab Awad6fb37c82014-08-07 19:48:57 -0700640 return PhoneAccount.builder()
641 .withAccountHandle(accountHandle)
642 .withHandle(handle)
643 .withSubscriptionNumber(subscriptionNumber)
644 .withCapabilities(capabilities)
645 .withIconResId(iconResId)
646 .withLabel(label)
647 .withShortDescription(shortDescription)
648 .build();
Ihab Awadb78b2762014-07-25 15:16:23 -0700649 }
650 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700651 }
652 };
653
Ihab Awadb78b2762014-07-25 15:16:23 -0700654 @VisibleForTesting
655 public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml =
656 new XmlSerialization<PhoneAccountHandle>() {
657 private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle";
Ihab Awad104f8062014-07-17 11:29:35 -0700658 private static final String COMPONENT_NAME = "component_name";
659 private static final String ID = "id";
660
661 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700662 public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer)
663 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700664 if (o != null) {
665 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
Ihab Awadb78b2762014-07-25 15:16:23 -0700666
Ihab Awad26923222014-07-30 10:54:35 -0700667 if (o.getComponentName() != null) {
668 writeTextSafely(
669 COMPONENT_NAME, o.getComponentName().flattenToString(), serializer);
670 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700671
Ihab Awad26923222014-07-30 10:54:35 -0700672 writeTextSafely(ID, o.getId(), serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -0700673
Ihab Awad26923222014-07-30 10:54:35 -0700674 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
675 }
Ihab Awad104f8062014-07-17 11:29:35 -0700676 }
677
678 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700679 public PhoneAccountHandle readFromXml(XmlPullParser parser)
680 throws IOException, XmlPullParserException {
681 if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) {
682 String componentNameString = null;
683 String idString = null;
684 int outerDepth = parser.getDepth();
685 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
686 if (parser.getName().equals(COMPONENT_NAME)) {
687 parser.next();
688 componentNameString = parser.getText();
689 } else if (parser.getName().equals(ID)) {
690 parser.next();
691 idString = parser.getText();
692 }
693 }
Ihab Awad26923222014-07-30 10:54:35 -0700694 if (componentNameString != null) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700695 return new PhoneAccountHandle(
696 ComponentName.unflattenFromString(componentNameString),
697 idString);
698 }
699 }
700 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700701 }
702 };
Santos Cordon176ae282014-07-14 02:02:14 -0700703}