blob: 294fedba0c5e3131d2291c39827a45fdaf50983d [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
Evan Charltonaf51ceb2014-07-30 11:56:36 -070019import android.content.Intent;
20import android.content.pm.PackageManager;
21import android.content.pm.ResolveInfo;
Evan Charltonaf51ceb2014-07-30 11:56:36 -070022import android.telecomm.ConnectionService;
Evan Charlton94d01622014-07-20 12:32:05 -070023import android.telecomm.PhoneAccount;
Evan Charlton89176372014-07-19 18:23:09 -070024import android.telecomm.PhoneAccountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -070025import android.content.ComponentName;
26import android.content.Context;
Santos Cordon176ae282014-07-14 02:02:14 -070027import android.net.Uri;
Ihab Awad104f8062014-07-17 11:29:35 -070028import android.telecomm.TelecommManager;
Evan Charltonaf51ceb2014-07-30 11:56:36 -070029import android.text.TextUtils;
Ihab Awadb78b2762014-07-25 15:16:23 -070030import android.util.AtomicFile;
31import android.util.Xml;
Santos Cordon176ae282014-07-14 02:02:14 -070032
Sailesh Nepal0e1dc5a2014-07-30 11:08:54 -070033import com.android.internal.annotations.VisibleForTesting;
34import com.android.internal.util.FastXmlSerializer;
35import com.android.internal.util.XmlUtils;
36
Evan Charltonaf51ceb2014-07-30 11:56:36 -070037import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39import org.xmlpull.v1.XmlSerializer;
40
Ihab Awadb78b2762014-07-25 15:16:23 -070041import java.io.BufferedInputStream;
42import java.io.BufferedOutputStream;
43import java.io.File;
44import java.io.FileNotFoundException;
45import java.io.FileOutputStream;
46import java.io.IOException;
47import java.io.InputStream;
Santos Cordon176ae282014-07-14 02:02:14 -070048import java.util.ArrayList;
49import java.util.List;
50import java.util.Objects;
Ihab Awadb78b2762014-07-25 15:16:23 -070051import java.util.concurrent.CopyOnWriteArrayList;
Santos Cordon176ae282014-07-14 02:02:14 -070052
53/**
Evan Charlton89176372014-07-19 18:23:09 -070054 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
Ihab Awad104f8062014-07-17 11:29:35 -070055 * delegate for all the account handling methods on {@link TelecommManager} as implemented in
56 * {@link TelecommServiceImpl}, with the notable exception that {@link TelecommServiceImpl} is
57 * responsible for security checking to make sure that the caller has proper authority over
Evan Charlton89176372014-07-19 18:23:09 -070058 * the {@code ComponentName}s they are declaring in their {@code PhoneAccountHandle}s.
Santos Cordon176ae282014-07-14 02:02:14 -070059 */
Ihab Awadb78b2762014-07-25 15:16:23 -070060public final class PhoneAccountRegistrar {
Santos Cordon176ae282014-07-14 02:02:14 -070061
Ihab Awadb78b2762014-07-25 15:16:23 -070062 public abstract static class Listener {
63 public void onAccountsChanged(PhoneAccountRegistrar registrar) {}
64 public void onDefaultOutgoingChanged(PhoneAccountRegistrar registrar) {}
65 public void onSimCallManagerChanged(PhoneAccountRegistrar registrar) {}
66 }
67
68 private static final String FILE_NAME = "phone-account-registrar-state.xml";
69
70 private final List<Listener> mListeners = new CopyOnWriteArrayList<>();
71 private final AtomicFile mAtomicFile;
72 private State mState;
Santos Cordon176ae282014-07-14 02:02:14 -070073
Ihab Awad26923222014-07-30 10:54:35 -070074 public PhoneAccountRegistrar(Context context) {
Ihab Awadb78b2762014-07-25 15:16:23 -070075 this(context, FILE_NAME);
76 }
77
78 @VisibleForTesting
79 public PhoneAccountRegistrar(Context context, String fileName) {
80 // TODO: Change file location when Telecomm is part of system
81 mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
82 mState = new State();
83 read();
Santos Cordon176ae282014-07-14 02:02:14 -070084 }
85
Evan Charlton89176372014-07-19 18:23:09 -070086 public PhoneAccountHandle getDefaultOutgoingPhoneAccount() {
Ihab Awad293edf22014-07-24 17:52:29 -070087 if (mState.defaultOutgoing != null) {
Ihab Awadf2a84912014-07-22 21:09:25 -070088 // Return the registered outgoing default iff it still exists (we keep a sticky
89 // default to survive account deletion and re-addition)
Ihab Awad293edf22014-07-24 17:52:29 -070090 for (int i = 0; i < mState.accounts.size(); i++) {
91 if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)) {
92 return mState.defaultOutgoing;
Ihab Awadf2a84912014-07-22 21:09:25 -070093 }
94 }
Ihab Awad293edf22014-07-24 17:52:29 -070095 // At this point, there was a registered default but it has been deleted; proceed
96 // as though there were no default
97 }
98
Ihab Awad6fb37c82014-08-07 19:48:57 -070099 List<PhoneAccountHandle> outgoing = getOutgoingPhoneAccounts();
100 switch (outgoing.size()) {
Ihab Awad293edf22014-07-24 17:52:29 -0700101 case 0:
102 // There are no accounts, so there can be no default
103 return null;
104 case 1:
105 // There is only one account, which is by definition the default
Ihab Awad6fb37c82014-08-07 19:48:57 -0700106 return outgoing.get(0);
Ihab Awad293edf22014-07-24 17:52:29 -0700107 default:
108 // There are multiple accounts with no selected default
109 return null;
Ihab Awadf2a84912014-07-22 21:09:25 -0700110 }
Ihab Awad104f8062014-07-17 11:29:35 -0700111 }
Santos Cordon176ae282014-07-14 02:02:14 -0700112
Evan Charlton89176372014-07-19 18:23:09 -0700113 public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
Evan Charlton89176372014-07-19 18:23:09 -0700114 if (accountHandle == null) {
Ihab Awad104f8062014-07-17 11:29:35 -0700115 // Asking to clear the default outgoing is a valid request
Ihab Awad293edf22014-07-24 17:52:29 -0700116 mState.defaultOutgoing = null;
Ihab Awad104f8062014-07-17 11:29:35 -0700117 } else {
118 boolean found = false;
Ihab Awad293edf22014-07-24 17:52:29 -0700119 for (PhoneAccount m : mState.accounts) {
Evan Charlton94d01622014-07-20 12:32:05 -0700120 if (Objects.equals(accountHandle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700121 found = true;
122 break;
123 }
Santos Cordon176ae282014-07-14 02:02:14 -0700124 }
Ihab Awad104f8062014-07-17 11:29:35 -0700125
126 if (!found) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700127 Log.w(this, "Trying to set nonexistent default outgoing %s",
128 accountHandle);
129 return;
130 }
131
132 if (!has(getPhoneAccount(accountHandle), PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
133 Log.w(this, "Trying to set non-call-provider default outgoing %s",
Evan Charlton89176372014-07-19 18:23:09 -0700134 accountHandle);
Ihab Awad104f8062014-07-17 11:29:35 -0700135 return;
136 }
137
Ihab Awad293edf22014-07-24 17:52:29 -0700138 mState.defaultOutgoing = accountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -0700139 }
140
Ihab Awad293edf22014-07-24 17:52:29 -0700141 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700142 fireDefaultOutgoingChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700143 }
144
Ihab Awad293edf22014-07-24 17:52:29 -0700145 public void setSimCallManager(PhoneAccountHandle callManager) {
146 if (callManager != null) {
147 PhoneAccount callManagerAccount = getPhoneAccount(callManager);
148 if (callManagerAccount == null) {
149 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager);
150 return;
Ihab Awadb78b2762014-07-25 15:16:23 -0700151 } else if (!has(callManagerAccount, PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
Ihab Awad293edf22014-07-24 17:52:29 -0700152 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount);
153 return;
154 }
155 }
156 mState.simCallManager = callManager;
157 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700158 fireSimCallManagerChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700159 }
160
161 public PhoneAccountHandle getSimCallManager() {
Ihab Awadb78b2762014-07-25 15:16:23 -0700162 if (mState.simCallManager != null) {
163 // Return the registered sim call manager iff it still exists (we keep a sticky
164 // setting to survive account deletion and re-addition)
165 for (int i = 0; i < mState.accounts.size(); i++) {
166 if (mState.accounts.get(i).getAccountHandle().equals(mState.simCallManager)) {
167 return mState.simCallManager;
168 }
169 }
170 }
Evan Charltonaf51ceb2014-07-30 11:56:36 -0700171
172 // See if the OEM has specified a default one.
173 Context context = TelecommApp.getInstance();
174 String defaultConnectionMgr =
175 context.getResources().getString(R.string.default_connection_manager_component);
176 if (!TextUtils.isEmpty(defaultConnectionMgr)) {
177 PackageManager pm = context.getPackageManager();
178
179 ComponentName componentName = ComponentName.unflattenFromString(defaultConnectionMgr);
180 Intent intent = new Intent(ConnectionService.SERVICE_INTERFACE);
181 intent.setComponent(componentName);
182
183 // Make sure that the component can be resolved.
184 List<ResolveInfo> resolveInfos = pm.queryIntentServices(intent, 0);
185 if (!resolveInfos.isEmpty()) {
186 // See if there is registered PhoneAccount by this component.
187 List<PhoneAccountHandle> handles = getAllPhoneAccountHandles();
188 for (PhoneAccountHandle handle : handles) {
189 if (componentName.equals(handle.getComponentName())) {
190 return handle;
191 }
192 }
193 Log.d(this, "%s does not have a PhoneAccount; not using as default", componentName);
194 } else {
195 Log.d(this, "%s could not be resolved; not using as default", componentName);
196 }
197 } else {
198 Log.v(this, "No default connection manager specified");
199 }
200
Ihab Awadb78b2762014-07-25 15:16:23 -0700201 return null;
Ihab Awad293edf22014-07-24 17:52:29 -0700202 }
203
204 public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
205 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
206 for (PhoneAccount m : mState.accounts) {
207 accountHandles.add(m.getAccountHandle());
208 }
209 return accountHandles;
210 }
211
212 public List<PhoneAccount> getAllPhoneAccounts() {
213 return new ArrayList<>(mState.accounts);
214 }
215
Ihab Awad6fb37c82014-08-07 19:48:57 -0700216 public List<PhoneAccountHandle> getOutgoingPhoneAccounts() {
Ihab Awad293edf22014-07-24 17:52:29 -0700217 return getCallProviderAccountHandles();
Santos Cordon176ae282014-07-14 02:02:14 -0700218 }
219
Ihab Awad293edf22014-07-24 17:52:29 -0700220 public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
221 for (PhoneAccount m : mState.accounts) {
222 if (Objects.equals(handle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700223 return m;
Santos Cordon176ae282014-07-14 02:02:14 -0700224 }
225 }
226 return null;
227 }
228
Ihab Awad104f8062014-07-17 11:29:35 -0700229 // TODO: Should we implement an artificial limit for # of accounts associated with a single
230 // ComponentName?
Ihab Awad293edf22014-07-24 17:52:29 -0700231 public void registerPhoneAccount(PhoneAccount account) {
Ihab Awad293edf22014-07-24 17:52:29 -0700232 mState.accounts.add(account);
Ihab Awad104f8062014-07-17 11:29:35 -0700233 // Search for duplicates and remove any that are found.
Ihab Awad293edf22014-07-24 17:52:29 -0700234 for (int i = 0; i < mState.accounts.size() - 1; i++) {
235 if (Objects.equals(
236 account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700237 // replace existing entry.
Ihab Awad293edf22014-07-24 17:52:29 -0700238 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700239 break;
Santos Cordon176ae282014-07-14 02:02:14 -0700240 }
241 }
242
Ihab Awad293edf22014-07-24 17:52:29 -0700243 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700244 fireAccountsChanged();
Ihab Awad293edf22014-07-24 17:52:29 -0700245 }
246
Evan Charlton89176372014-07-19 18:23:09 -0700247 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad293edf22014-07-24 17:52:29 -0700248 for (int i = 0; i < mState.accounts.size(); i++) {
249 if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) {
250 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700251 break;
252 }
253 }
254
Ihab Awad293edf22014-07-24 17:52:29 -0700255 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700256 fireAccountsChanged();
Santos Cordon176ae282014-07-14 02:02:14 -0700257 }
258
Ihab Awad104f8062014-07-17 11:29:35 -0700259 public void clearAccounts(String packageName) {
Ihab Awad293edf22014-07-24 17:52:29 -0700260 for (int i = 0; i < mState.accounts.size(); i++) {
Ihab Awad104f8062014-07-17 11:29:35 -0700261 if (Objects.equals(
262 packageName,
Ihab Awad293edf22014-07-24 17:52:29 -0700263 mState.accounts.get(i).getAccountHandle()
264 .getComponentName().getPackageName())) {
265 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700266 }
267 }
268
Ihab Awad293edf22014-07-24 17:52:29 -0700269 write();
Ihab Awadb78b2762014-07-25 15:16:23 -0700270 fireAccountsChanged();
271 }
272
273 public void addListener(Listener l) {
274 mListeners.add(l);
275 }
276
277 public void removeListener(Listener l) {
278 mListeners.remove(l);
279 }
280
281 private void fireAccountsChanged() {
282 for (Listener l : mListeners) {
283 l.onAccountsChanged(this);
284 }
285 }
286
287 private void fireDefaultOutgoingChanged() {
288 for (Listener l : mListeners) {
289 l.onDefaultOutgoingChanged(this);
290 }
291 }
292
293 private void fireSimCallManagerChanged() {
294 for (Listener l : mListeners) {
295 l.onSimCallManagerChanged(this);
296 }
Santos Cordon176ae282014-07-14 02:02:14 -0700297 }
298
Ihab Awad293edf22014-07-24 17:52:29 -0700299 ////////////////////////////////////////////////////////////////////////////////////////////////
300
301 // TODO: Add a corresponding has(...) method to class PhoneAccount itself and remove this one
302 // Return true iff the given account has all the specified capability flags
303 static boolean has(PhoneAccount account, int capability) {
304 return (account.getCapabilities() & capability) == capability;
305 }
306
307 private List<PhoneAccountHandle> getCallProviderAccountHandles() {
Evan Charlton94d01622014-07-20 12:32:05 -0700308 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
Ihab Awad293edf22014-07-24 17:52:29 -0700309 for (PhoneAccount m : mState.accounts) {
310 if (has(m, PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
Ihab Awadf2a84912014-07-22 21:09:25 -0700311 accountHandles.add(m.getAccountHandle());
312 }
Ihab Awad104f8062014-07-17 11:29:35 -0700313 }
Evan Charlton94d01622014-07-20 12:32:05 -0700314 return accountHandles;
Ihab Awad104f8062014-07-17 11:29:35 -0700315 }
316
Ihab Awad293edf22014-07-24 17:52:29 -0700317 /**
318 * The state of this {@code PhoneAccountRegistrar}.
319 */
Ihab Awadb78b2762014-07-25 15:16:23 -0700320 @VisibleForTesting
321 public static class State {
Ihab Awad293edf22014-07-24 17:52:29 -0700322 /**
323 * The account selected by the user to be employed by default for making outgoing calls.
324 * If the user has not made such a selection, then this is null.
325 */
326 public PhoneAccountHandle defaultOutgoing = null;
327
328 /**
Ihab Awadb78b2762014-07-25 15:16:23 -0700329 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} which
Ihab Awad293edf22014-07-24 17:52:29 -0700330 * manages and optimizes a user's PSTN SIM connections.
331 */
332 public PhoneAccountHandle simCallManager;
333
334 /**
335 * The complete list of {@code PhoneAccount}s known to the Telecomm subsystem.
336 */
337 public final List<PhoneAccount> accounts = new ArrayList<>();
338 }
339
340 ////////////////////////////////////////////////////////////////////////////////////////////////
341 //
342 // State management
343 //
344
345 private void write() {
Ihab Awadb78b2762014-07-25 15:16:23 -0700346 final FileOutputStream os;
Ihab Awad104f8062014-07-17 11:29:35 -0700347 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700348 os = mAtomicFile.startWrite();
349 boolean success = false;
350 try {
351 XmlSerializer serializer = new FastXmlSerializer();
352 serializer.setOutput(new BufferedOutputStream(os), "utf-8");
353 writeToXml(mState, serializer);
354 serializer.flush();
355 success = true;
356 } finally {
357 if (success) {
358 mAtomicFile.finishWrite(os);
359 } else {
360 mAtomicFile.failWrite(os);
361 }
362 }
363 } catch (IOException e) {
364 Log.e(this, e, "Writing state to XML file");
Ihab Awad104f8062014-07-17 11:29:35 -0700365 }
366 }
367
Ihab Awadb78b2762014-07-25 15:16:23 -0700368 private void read() {
369 final InputStream is;
Ihab Awad104f8062014-07-17 11:29:35 -0700370 try {
Ihab Awadb78b2762014-07-25 15:16:23 -0700371 is = mAtomicFile.openRead();
372 } catch (FileNotFoundException ex) {
373 return;
374 }
375
376 XmlPullParser parser;
377 try {
378 parser = Xml.newPullParser();
379 parser.setInput(new BufferedInputStream(is), null);
380 parser.nextTag();
381 mState = readFromXml(parser);
382 } catch (IOException | XmlPullParserException e) {
383 Log.e(this, e, "Reading state from XML file");
384 mState = new State();
385 } finally {
386 try {
387 is.close();
388 } catch (IOException e) {
389 Log.e(this, e, "Closing InputStream");
390 }
Ihab Awad104f8062014-07-17 11:29:35 -0700391 }
Santos Cordon176ae282014-07-14 02:02:14 -0700392 }
393
Ihab Awadb78b2762014-07-25 15:16:23 -0700394 private static void writeToXml(State state, XmlSerializer serializer)
395 throws IOException {
396 sStateXml.writeToXml(state, serializer);
Santos Cordon176ae282014-07-14 02:02:14 -0700397 }
Ihab Awad104f8062014-07-17 11:29:35 -0700398
Ihab Awadb78b2762014-07-25 15:16:23 -0700399 private static State readFromXml(XmlPullParser parser)
400 throws IOException, XmlPullParserException {
401 State s = sStateXml.readFromXml(parser);
402 return s != null ? s : new State();
Ihab Awad104f8062014-07-17 11:29:35 -0700403 }
404
Ihab Awad293edf22014-07-24 17:52:29 -0700405 ////////////////////////////////////////////////////////////////////////////////////////////////
Ihab Awad104f8062014-07-17 11:29:35 -0700406 //
Ihab Awadb78b2762014-07-25 15:16:23 -0700407 // XML serialization
Ihab Awad104f8062014-07-17 11:29:35 -0700408 //
409
Ihab Awadb78b2762014-07-25 15:16:23 -0700410 @VisibleForTesting
Ihab Awad26923222014-07-30 10:54:35 -0700411 public abstract static class XmlSerialization<T> {
Ihab Awadb78b2762014-07-25 15:16:23 -0700412 /**
413 * Write the supplied object to XML
414 */
Ihab Awad26923222014-07-30 10:54:35 -0700415 public abstract void writeToXml(T o, XmlSerializer serializer)
416 throws IOException;
Ihab Awadb78b2762014-07-25 15:16:23 -0700417
418 /**
419 * Read from the supplied XML into a new object, returning null in case of an
420 * unrecoverable schema mismatch or other data error. 'parser' must be already
421 * positioned at the first tag that is expected to have been emitted by this
422 * object's writeToXml(). This object tries to fail early without modifying
423 * 'parser' if it does not recognize the data it sees.
424 */
Ihab Awad26923222014-07-30 10:54:35 -0700425 public abstract T readFromXml(XmlPullParser parser)
426 throws IOException, XmlPullParserException;
427
428 protected void writeTextSafely(String tagName, Object value, XmlSerializer serializer)
429 throws IOException {
430 if (value != null) {
431 serializer.startTag(null, tagName);
432 serializer.text(Objects.toString(value));
433 serializer.endTag(null, tagName);
434 }
435 }
Ihab Awad104f8062014-07-17 11:29:35 -0700436 }
437
Ihab Awadb78b2762014-07-25 15:16:23 -0700438 @VisibleForTesting
439 public static final XmlSerialization<State> sStateXml =
440 new XmlSerialization<State>() {
441 private static final String CLASS_STATE = "phone_account_registrar_state";
Ihab Awad104f8062014-07-17 11:29:35 -0700442 private static final String DEFAULT_OUTGOING = "default_outgoing";
Ihab Awad293edf22014-07-24 17:52:29 -0700443 private static final String SIM_CALL_MANAGER = "sim_call_manager";
Ihab Awad104f8062014-07-17 11:29:35 -0700444 private static final String ACCOUNTS = "accounts";
445
446 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700447 public void writeToXml(State o, XmlSerializer serializer)
448 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700449 if (o != null) {
450 serializer.startTag(null, CLASS_STATE);
Ihab Awadb78b2762014-07-25 15:16:23 -0700451
Ihab Awad26923222014-07-30 10:54:35 -0700452 if (o.defaultOutgoing != null) {
453 serializer.startTag(null, DEFAULT_OUTGOING);
454 sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer);
455 serializer.endTag(null, DEFAULT_OUTGOING);
456 }
457
458 if (o.simCallManager != null) {
459 serializer.startTag(null, SIM_CALL_MANAGER);
460 sPhoneAccountHandleXml.writeToXml(o.simCallManager, serializer);
461 serializer.endTag(null, SIM_CALL_MANAGER);
462 }
463
464 serializer.startTag(null, ACCOUNTS);
465 for (PhoneAccount m : o.accounts) {
466 sPhoneAccountXml.writeToXml(m, serializer);
467 }
468 serializer.endTag(null, ACCOUNTS);
469
470 serializer.endTag(null, CLASS_STATE);
Ihab Awad293edf22014-07-24 17:52:29 -0700471 }
Ihab Awad104f8062014-07-17 11:29:35 -0700472 }
473
474 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700475 public State readFromXml(XmlPullParser parser)
476 throws IOException, XmlPullParserException {
477 if (parser.getName().equals(CLASS_STATE)) {
478 State s = new State();
479 int outerDepth = parser.getDepth();
480 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
481 if (parser.getName().equals(DEFAULT_OUTGOING)) {
482 parser.nextTag();
483 s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser);
484 } else if (parser.getName().equals(SIM_CALL_MANAGER)) {
485 parser.nextTag();
486 s.simCallManager = sPhoneAccountHandleXml.readFromXml(parser);
487 } else if (parser.getName().equals(ACCOUNTS)) {
488 int accountsDepth = parser.getDepth();
489 while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
490 PhoneAccount account = sPhoneAccountXml.readFromXml(parser);
491 if (account != null) {
492 s.accounts.add(account);
493 }
494 }
Ihab Awad104f8062014-07-17 11:29:35 -0700495 }
496 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700497 return s;
Ihab Awad104f8062014-07-17 11:29:35 -0700498 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700499 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700500 }
501 };
502
Ihab Awadb78b2762014-07-25 15:16:23 -0700503 @VisibleForTesting
504 public static final XmlSerialization<PhoneAccount> sPhoneAccountXml =
505 new XmlSerialization<PhoneAccount>() {
506 private static final String CLASS_PHONE_ACCOUNT = "phone_account";
507 private static final String ACCOUNT_HANDLE = "account_handle";
Ihab Awad104f8062014-07-17 11:29:35 -0700508 private static final String HANDLE = "handle";
Evan Charlton484f8d62014-07-18 14:06:58 -0700509 private static final String SUBSCRIPTION_NUMBER = "subscription_number";
Ihab Awad104f8062014-07-17 11:29:35 -0700510 private static final String CAPABILITIES = "capabilities";
511 private static final String ICON_RES_ID = "icon_res_id";
512 private static final String LABEL = "label";
513 private static final String SHORT_DESCRIPTION = "short_description";
Ihab Awad104f8062014-07-17 11:29:35 -0700514
515 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700516 public void writeToXml(PhoneAccount o, XmlSerializer serializer)
517 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700518 if (o != null) {
519 serializer.startTag(null, CLASS_PHONE_ACCOUNT);
Ihab Awadb78b2762014-07-25 15:16:23 -0700520
Ihab Awad26923222014-07-30 10:54:35 -0700521 if (o.getAccountHandle() != null) {
522 serializer.startTag(null, ACCOUNT_HANDLE);
523 sPhoneAccountHandleXml.writeToXml(o.getAccountHandle(), serializer);
524 serializer.endTag(null, ACCOUNT_HANDLE);
525 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700526
Ihab Awad26923222014-07-30 10:54:35 -0700527 writeTextSafely(HANDLE, o.getHandle(), serializer);
528 writeTextSafely(SUBSCRIPTION_NUMBER, o.getSubscriptionNumber(), serializer);
529 writeTextSafely(CAPABILITIES, Integer.toString(o.getCapabilities()), serializer);
530 writeTextSafely(ICON_RES_ID, Integer.toString(o.getIconResId()), serializer);
531 writeTextSafely(LABEL, o.getLabel(), serializer);
532 writeTextSafely(SHORT_DESCRIPTION, o.getShortDescription(), serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -0700533
Ihab Awad26923222014-07-30 10:54:35 -0700534 serializer.endTag(null, CLASS_PHONE_ACCOUNT);
535 }
Ihab Awad104f8062014-07-17 11:29:35 -0700536 }
537
538 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700539 public PhoneAccount readFromXml(XmlPullParser parser)
540 throws IOException, XmlPullParserException {
541 if (parser.getName().equals(CLASS_PHONE_ACCOUNT)) {
542 int outerDepth = parser.getDepth();
543 PhoneAccountHandle accountHandle = null;
544 Uri handle = null;
545 String subscriptionNumber = null;
546 int capabilities = 0;
547 int iconResId = 0;
548 String label = null;
549 String shortDescription = null;
550
551 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
552 if (parser.getName().equals(ACCOUNT_HANDLE)) {
553 parser.nextTag();
554 accountHandle = sPhoneAccountHandleXml.readFromXml(parser);
555 } else if (parser.getName().equals(HANDLE)) {
556 parser.next();
557 handle = Uri.parse(parser.getText());
558 } else if (parser.getName().equals(SUBSCRIPTION_NUMBER)) {
559 parser.next();
560 subscriptionNumber = parser.getText();
561 } else if (parser.getName().equals(CAPABILITIES)) {
562 parser.next();
563 capabilities = Integer.parseInt(parser.getText());
564 } else if (parser.getName().equals(ICON_RES_ID)) {
565 parser.next();
566 iconResId = Integer.parseInt(parser.getText());
567 } else if (parser.getName().equals(LABEL)) {
568 parser.next();
569 label = parser.getText();
570 } else if (parser.getName().equals(SHORT_DESCRIPTION)) {
571 parser.next();
572 shortDescription = parser.getText();
573 }
574 }
Ihab Awad6fb37c82014-08-07 19:48:57 -0700575 return PhoneAccount.builder()
576 .withAccountHandle(accountHandle)
577 .withHandle(handle)
578 .withSubscriptionNumber(subscriptionNumber)
579 .withCapabilities(capabilities)
580 .withIconResId(iconResId)
581 .withLabel(label)
582 .withShortDescription(shortDescription)
583 .build();
Ihab Awadb78b2762014-07-25 15:16:23 -0700584 }
585 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700586 }
587 };
588
Ihab Awadb78b2762014-07-25 15:16:23 -0700589 @VisibleForTesting
590 public static final XmlSerialization<PhoneAccountHandle> sPhoneAccountHandleXml =
591 new XmlSerialization<PhoneAccountHandle>() {
592 private static final String CLASS_PHONE_ACCOUNT_HANDLE = "phone_account_handle";
Ihab Awad104f8062014-07-17 11:29:35 -0700593 private static final String COMPONENT_NAME = "component_name";
594 private static final String ID = "id";
595
596 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700597 public void writeToXml(PhoneAccountHandle o, XmlSerializer serializer)
598 throws IOException {
Ihab Awad26923222014-07-30 10:54:35 -0700599 if (o != null) {
600 serializer.startTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
Ihab Awadb78b2762014-07-25 15:16:23 -0700601
Ihab Awad26923222014-07-30 10:54:35 -0700602 if (o.getComponentName() != null) {
603 writeTextSafely(
604 COMPONENT_NAME, o.getComponentName().flattenToString(), serializer);
605 }
Ihab Awadb78b2762014-07-25 15:16:23 -0700606
Ihab Awad26923222014-07-30 10:54:35 -0700607 writeTextSafely(ID, o.getId(), serializer);
Ihab Awadb78b2762014-07-25 15:16:23 -0700608
Ihab Awad26923222014-07-30 10:54:35 -0700609 serializer.endTag(null, CLASS_PHONE_ACCOUNT_HANDLE);
610 }
Ihab Awad104f8062014-07-17 11:29:35 -0700611 }
612
613 @Override
Ihab Awadb78b2762014-07-25 15:16:23 -0700614 public PhoneAccountHandle readFromXml(XmlPullParser parser)
615 throws IOException, XmlPullParserException {
616 if (parser.getName().equals(CLASS_PHONE_ACCOUNT_HANDLE)) {
617 String componentNameString = null;
618 String idString = null;
619 int outerDepth = parser.getDepth();
620 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
621 if (parser.getName().equals(COMPONENT_NAME)) {
622 parser.next();
623 componentNameString = parser.getText();
624 } else if (parser.getName().equals(ID)) {
625 parser.next();
626 idString = parser.getText();
627 }
628 }
Ihab Awad26923222014-07-30 10:54:35 -0700629 if (componentNameString != null) {
Ihab Awadb78b2762014-07-25 15:16:23 -0700630 return new PhoneAccountHandle(
631 ComponentName.unflattenFromString(componentNameString),
632 idString);
633 }
634 }
635 return null;
Ihab Awad104f8062014-07-17 11:29:35 -0700636 }
637 };
Santos Cordon176ae282014-07-14 02:02:14 -0700638}