blob: f236f58f79f96c7f6c682ef12e9e10d5a095f6c5 [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 Charlton94d01622014-07-20 12:32:05 -070019import android.telecomm.PhoneAccount;
Evan Charlton89176372014-07-19 18:23:09 -070020import android.telecomm.PhoneAccountHandle;
Ihab Awad104f8062014-07-17 11:29:35 -070021import org.json.JSONArray;
22import org.json.JSONException;
23import org.json.JSONObject;
24import org.json.JSONTokener;
25
Santos Cordon176ae282014-07-14 02:02:14 -070026import android.content.ComponentName;
27import android.content.Context;
28
29import android.content.SharedPreferences;
Santos Cordon176ae282014-07-14 02:02:14 -070030import android.net.Uri;
Ihab Awad104f8062014-07-17 11:29:35 -070031import android.telecomm.TelecommManager;
Santos Cordon176ae282014-07-14 02:02:14 -070032
33import java.util.ArrayList;
34import java.util.List;
35import java.util.Objects;
36
37/**
Evan Charlton89176372014-07-19 18:23:09 -070038 * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
Ihab Awad104f8062014-07-17 11:29:35 -070039 * delegate for all the account handling methods on {@link TelecommManager} as implemented in
40 * {@link TelecommServiceImpl}, with the notable exception that {@link TelecommServiceImpl} is
41 * responsible for security checking to make sure that the caller has proper authority over
Evan Charlton89176372014-07-19 18:23:09 -070042 * the {@code ComponentName}s they are declaring in their {@code PhoneAccountHandle}s.
Ihab Awad104f8062014-07-17 11:29:35 -070043 *
Santos Cordon176ae282014-07-14 02:02:14 -070044 * TODO(santoscordon): Replace this implementation with a proper database stored in a Telecomm
45 * provider.
46 */
47final class PhoneAccountRegistrar {
Santos Cordon176ae282014-07-14 02:02:14 -070048 private static final String TELECOMM_PREFERENCES = "telecomm_prefs";
49 private static final String PREFERENCE_PHONE_ACCOUNTS = "phone_accounts";
50
51 private final Context mContext;
Ihab Awad293edf22014-07-24 17:52:29 -070052 private final State mState;
Santos Cordon176ae282014-07-14 02:02:14 -070053
Santos Cordon176ae282014-07-14 02:02:14 -070054 PhoneAccountRegistrar(Context context) {
55 mContext = context;
Ihab Awad293edf22014-07-24 17:52:29 -070056 mState = readState();
Santos Cordon176ae282014-07-14 02:02:14 -070057 }
58
Evan Charlton89176372014-07-19 18:23:09 -070059 public PhoneAccountHandle getDefaultOutgoingPhoneAccount() {
Ihab Awad293edf22014-07-24 17:52:29 -070060 if (mState.defaultOutgoing != null) {
Ihab Awadf2a84912014-07-22 21:09:25 -070061 // Return the registered outgoing default iff it still exists (we keep a sticky
62 // default to survive account deletion and re-addition)
Ihab Awad293edf22014-07-24 17:52:29 -070063 for (int i = 0; i < mState.accounts.size(); i++) {
64 if (mState.accounts.get(i).getAccountHandle().equals(mState.defaultOutgoing)) {
65 return mState.defaultOutgoing;
Ihab Awadf2a84912014-07-22 21:09:25 -070066 }
67 }
Ihab Awad293edf22014-07-24 17:52:29 -070068 // At this point, there was a registered default but it has been deleted; proceed
69 // as though there were no default
70 }
71
72 List<PhoneAccountHandle> enabled = getEnabledPhoneAccounts();
73 switch (enabled.size()) {
74 case 0:
75 // There are no accounts, so there can be no default
76 return null;
77 case 1:
78 // There is only one account, which is by definition the default
79 return enabled.get(0);
80 default:
81 // There are multiple accounts with no selected default
82 return null;
Ihab Awadf2a84912014-07-22 21:09:25 -070083 }
Ihab Awad104f8062014-07-17 11:29:35 -070084 }
Santos Cordon176ae282014-07-14 02:02:14 -070085
Evan Charlton89176372014-07-19 18:23:09 -070086 public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
Evan Charlton89176372014-07-19 18:23:09 -070087 if (accountHandle == null) {
Ihab Awad104f8062014-07-17 11:29:35 -070088 // Asking to clear the default outgoing is a valid request
Ihab Awad293edf22014-07-24 17:52:29 -070089 mState.defaultOutgoing = null;
Ihab Awad104f8062014-07-17 11:29:35 -070090 } else {
91 boolean found = false;
Ihab Awad293edf22014-07-24 17:52:29 -070092 for (PhoneAccount m : mState.accounts) {
Evan Charlton94d01622014-07-20 12:32:05 -070093 if (Objects.equals(accountHandle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -070094 found = true;
95 break;
96 }
Santos Cordon176ae282014-07-14 02:02:14 -070097 }
Ihab Awad104f8062014-07-17 11:29:35 -070098
99 if (!found) {
Evan Charlton89176372014-07-19 18:23:09 -0700100 Log.w(this, "Trying to set nonexistent default outgoing phone accountHandle %s",
101 accountHandle);
Ihab Awad104f8062014-07-17 11:29:35 -0700102 return;
103 }
104
Ihab Awad293edf22014-07-24 17:52:29 -0700105 mState.defaultOutgoing = accountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -0700106 }
107
Ihab Awad293edf22014-07-24 17:52:29 -0700108 write();
Santos Cordon176ae282014-07-14 02:02:14 -0700109 }
110
Ihab Awad293edf22014-07-24 17:52:29 -0700111 public void setSimCallManager(PhoneAccountHandle callManager) {
112 if (callManager != null) {
113 PhoneAccount callManagerAccount = getPhoneAccount(callManager);
114 if (callManagerAccount == null) {
115 Log.d(this, "setSimCallManager: Nonexistent call manager: %s", callManager);
116 return;
117 } else if (!has(callManagerAccount, PhoneAccount.CAPABILITY_SIM_CALL_MANAGER)) {
118 Log.d(this, "setSimCallManager: Not a call manager: %s", callManagerAccount);
119 return;
120 }
121 }
122 mState.simCallManager = callManager;
123 write();
124 }
125
126 public PhoneAccountHandle getSimCallManager() {
127 return mState.simCallManager;
128 }
129
130 public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
131 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
132 for (PhoneAccount m : mState.accounts) {
133 accountHandles.add(m.getAccountHandle());
134 }
135 return accountHandles;
136 }
137
138 public List<PhoneAccount> getAllPhoneAccounts() {
139 return new ArrayList<>(mState.accounts);
140 }
141
142 // TODO: Rename systemwide to "getCallProviderPhoneAccounts"?
Evan Charlton89176372014-07-19 18:23:09 -0700143 public List<PhoneAccountHandle> getEnabledPhoneAccounts() {
Ihab Awad293edf22014-07-24 17:52:29 -0700144 return getCallProviderAccountHandles();
Santos Cordon176ae282014-07-14 02:02:14 -0700145 }
146
Ihab Awad293edf22014-07-24 17:52:29 -0700147 public PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
148 for (PhoneAccount m : mState.accounts) {
149 if (Objects.equals(handle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700150 return m;
Santos Cordon176ae282014-07-14 02:02:14 -0700151 }
152 }
153 return null;
154 }
155
Ihab Awad104f8062014-07-17 11:29:35 -0700156 // TODO: Should we implement an artificial limit for # of accounts associated with a single
157 // ComponentName?
Ihab Awad293edf22014-07-24 17:52:29 -0700158 public void registerPhoneAccount(PhoneAccount account) {
159 account = hackFixBabelAccount(account);
160 mState.accounts.add(account);
Ihab Awad104f8062014-07-17 11:29:35 -0700161 // Search for duplicates and remove any that are found.
Ihab Awad293edf22014-07-24 17:52:29 -0700162 for (int i = 0; i < mState.accounts.size() - 1; i++) {
163 if (Objects.equals(
164 account.getAccountHandle(), mState.accounts.get(i).getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700165 // replace existing entry.
Ihab Awad293edf22014-07-24 17:52:29 -0700166 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700167 break;
Santos Cordon176ae282014-07-14 02:02:14 -0700168 }
169 }
170
Ihab Awad293edf22014-07-24 17:52:29 -0700171 write();
172 }
173
174 // STOPSHIP: Hack to edit the account registered by Babel so it shows up properly
175 private PhoneAccount hackFixBabelAccount(PhoneAccount account) {
176 String pkg = account.getAccountHandle().getComponentName().getPackageName();
177 return "com.google.android.talk".equals(pkg)
178 ? new PhoneAccount(
179 account.getAccountHandle(),
180 account.getHandle(),
181 account.getSubscriptionNumber(),
182 PhoneAccount.CAPABILITY_SIM_CALL_MANAGER,
183 account.getIconResId(),
184 account.getLabel(),
185 account.getShortDescription(),
186 account.isVideoCallingSupported())
187 : account;
Santos Cordon176ae282014-07-14 02:02:14 -0700188 }
189
Evan Charlton89176372014-07-19 18:23:09 -0700190 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad293edf22014-07-24 17:52:29 -0700191 for (int i = 0; i < mState.accounts.size(); i++) {
192 if (Objects.equals(accountHandle, mState.accounts.get(i).getAccountHandle())) {
193 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700194 break;
195 }
196 }
197
Ihab Awad293edf22014-07-24 17:52:29 -0700198 write();
Santos Cordon176ae282014-07-14 02:02:14 -0700199 }
200
Ihab Awad104f8062014-07-17 11:29:35 -0700201 public void clearAccounts(String packageName) {
Ihab Awad293edf22014-07-24 17:52:29 -0700202 for (int i = 0; i < mState.accounts.size(); i++) {
Ihab Awad104f8062014-07-17 11:29:35 -0700203 if (Objects.equals(
204 packageName,
Ihab Awad293edf22014-07-24 17:52:29 -0700205 mState.accounts.get(i).getAccountHandle()
206 .getComponentName().getPackageName())) {
207 mState.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700208 }
209 }
210
Ihab Awad293edf22014-07-24 17:52:29 -0700211 write();
Santos Cordon176ae282014-07-14 02:02:14 -0700212 }
213
Ihab Awad293edf22014-07-24 17:52:29 -0700214 ////////////////////////////////////////////////////////////////////////////////////////////////
215
216 // TODO: Add a corresponding has(...) method to class PhoneAccount itself and remove this one
217 // Return true iff the given account has all the specified capability flags
218 static boolean has(PhoneAccount account, int capability) {
219 return (account.getCapabilities() & capability) == capability;
220 }
221
222 private List<PhoneAccountHandle> getCallProviderAccountHandles() {
Evan Charlton94d01622014-07-20 12:32:05 -0700223 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
Ihab Awad293edf22014-07-24 17:52:29 -0700224 for (PhoneAccount m : mState.accounts) {
225 if (has(m, PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
Ihab Awadf2a84912014-07-22 21:09:25 -0700226 accountHandles.add(m.getAccountHandle());
227 }
Ihab Awad104f8062014-07-17 11:29:35 -0700228 }
Evan Charlton94d01622014-07-20 12:32:05 -0700229 return accountHandles;
Ihab Awad104f8062014-07-17 11:29:35 -0700230 }
231
Ihab Awad293edf22014-07-24 17:52:29 -0700232 /**
233 * The state of this {@code PhoneAccountRegistrar}.
234 */
235 private static class State {
236 /**
237 * The account selected by the user to be employed by default for making outgoing calls.
238 * If the user has not made such a selection, then this is null.
239 */
240 public PhoneAccountHandle defaultOutgoing = null;
241
242 /**
243 * A {@code PhoneAccount} having {@link PhoneAccount#CAPABILITY_SIM_CALL_MANAGER} which
244 * manages and optimizes a user's PSTN SIM connections.
245 */
246 public PhoneAccountHandle simCallManager;
247
248 /**
249 * The complete list of {@code PhoneAccount}s known to the Telecomm subsystem.
250 */
251 public final List<PhoneAccount> accounts = new ArrayList<>();
252 }
253
254 ////////////////////////////////////////////////////////////////////////////////////////////////
255 //
256 // State management
257 //
258
259 private void write() {
260 writeState(mState);
261 }
262
263 private State readState() {
Ihab Awad104f8062014-07-17 11:29:35 -0700264 try {
265 String serialized = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
Santos Cordon7cdb9092014-07-24 21:33:33 -0700266 Log.v(this, "read() obtained serialized state: %s", serialized);
Ihab Awad104f8062014-07-17 11:29:35 -0700267 State state = serialized == null
268 ? new State()
269 : deserializeState(serialized);
Santos Cordon7cdb9092014-07-24 21:33:33 -0700270 Log.v(this, "read() obtained state: %s", state);
Ihab Awad104f8062014-07-17 11:29:35 -0700271 return state;
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700272 } catch (JSONException e) {
Ihab Awad104f8062014-07-17 11:29:35 -0700273 Log.e(this, e, "read");
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700274 return new State();
Ihab Awad104f8062014-07-17 11:29:35 -0700275 }
276 }
277
Ihab Awad293edf22014-07-24 17:52:29 -0700278 private boolean writeState(State state) {
Ihab Awad104f8062014-07-17 11:29:35 -0700279 try {
Santos Cordon7cdb9092014-07-24 21:33:33 -0700280 Log.v(this, "write() writing state: %s", state);
Ihab Awad104f8062014-07-17 11:29:35 -0700281 String serialized = serializeState(state);
Santos Cordon7cdb9092014-07-24 21:33:33 -0700282 Log.v(this, "write() writing serialized state: %s", serialized);
Ihab Awad104f8062014-07-17 11:29:35 -0700283 boolean success = getPreferences()
284 .edit()
285 .putString(PREFERENCE_PHONE_ACCOUNTS, serialized)
286 .commit();
Santos Cordon7cdb9092014-07-24 21:33:33 -0700287 Log.v(this, "serialized state was written with success = %b", success);
Ihab Awad104f8062014-07-17 11:29:35 -0700288 return success;
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700289 } catch (JSONException e) {
Ihab Awad104f8062014-07-17 11:29:35 -0700290 Log.e(this, e, "write");
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700291 return false;
Ihab Awad104f8062014-07-17 11:29:35 -0700292 }
Santos Cordon176ae282014-07-14 02:02:14 -0700293 }
294
295 private SharedPreferences getPreferences() {
296 return mContext.getSharedPreferences(TELECOMM_PREFERENCES, Context.MODE_PRIVATE);
297 }
Ihab Awad104f8062014-07-17 11:29:35 -0700298
299 private String serializeState(State s) throws JSONException {
300 // TODO: If this is used in production, remove the indent (=> do not pretty print)
301 return sStateJson.toJson(s).toString(2);
302 }
303
304 private State deserializeState(String s) throws JSONException {
305 return sStateJson.fromJson(new JSONObject(new JSONTokener(s)));
306 }
307
Ihab Awad293edf22014-07-24 17:52:29 -0700308 ////////////////////////////////////////////////////////////////////////////////////////////////
Ihab Awad104f8062014-07-17 11:29:35 -0700309 //
310 // JSON serialization
311 //
312
313 private interface Json<T> {
314 JSONObject toJson(T o) throws JSONException;
315 T fromJson(JSONObject json) throws JSONException;
316 }
317
318 private static final Json<State> sStateJson =
319 new Json<State>() {
320 private static final String DEFAULT_OUTGOING = "default_outgoing";
Ihab Awad293edf22014-07-24 17:52:29 -0700321 private static final String SIM_CALL_MANAGER = "sim_call_manager";
Ihab Awad104f8062014-07-17 11:29:35 -0700322 private static final String ACCOUNTS = "accounts";
323
324 @Override
325 public JSONObject toJson(State o) throws JSONException {
326 JSONObject json = new JSONObject();
Ihab Awad293edf22014-07-24 17:52:29 -0700327 if (o.defaultOutgoing != null) {
328 json.put(DEFAULT_OUTGOING, sPhoneAccountHandleJson.toJson(o.defaultOutgoing));
329 }
330 if (o.simCallManager != null) {
331 json.put(SIM_CALL_MANAGER, sPhoneAccountHandleJson.toJson(o.simCallManager));
Ihab Awad104f8062014-07-17 11:29:35 -0700332 }
333 JSONArray accounts = new JSONArray();
Evan Charlton94d01622014-07-20 12:32:05 -0700334 for (PhoneAccount m : o.accounts) {
Ihab Awad293edf22014-07-24 17:52:29 -0700335 accounts.put(sPhoneAccountJson.toJson(m));
Ihab Awad104f8062014-07-17 11:29:35 -0700336 }
337 json.put(ACCOUNTS, accounts);
338 return json;
339 }
340
341 @Override
342 public State fromJson(JSONObject json) throws JSONException {
343 State s = new State();
344 if (json.has(DEFAULT_OUTGOING)) {
Ihab Awad293edf22014-07-24 17:52:29 -0700345 try {
346 s.defaultOutgoing = sPhoneAccountHandleJson.fromJson(
347 (JSONObject) json.get(DEFAULT_OUTGOING));
348 } catch (Exception e) {
349 Log.e(this, e, "Extracting PhoneAccountHandle");
350 }
351 }
352 if (json.has(SIM_CALL_MANAGER)) {
353 try {
354 s.simCallManager = sPhoneAccountHandleJson.fromJson(
355 (JSONObject) json.get(SIM_CALL_MANAGER));
356 } catch (Exception e) {
357 Log.e(this, e, "Extracting PhoneAccountHandle");
358 }
Ihab Awad104f8062014-07-17 11:29:35 -0700359 }
360 if (json.has(ACCOUNTS)) {
361 JSONArray accounts = (JSONArray) json.get(ACCOUNTS);
362 for (int i = 0; i < accounts.length(); i++) {
363 try {
Ihab Awad293edf22014-07-24 17:52:29 -0700364 s.accounts.add(sPhoneAccountJson.fromJson(
Ihab Awad104f8062014-07-17 11:29:35 -0700365 (JSONObject) accounts.get(i)));
366 } catch (Exception e) {
367 Log.e(this, e, "Extracting phone account");
368 }
369 }
370 }
371 return s;
372 }
373 };
374
Ihab Awad293edf22014-07-24 17:52:29 -0700375 private static final Json<PhoneAccount> sPhoneAccountJson =
Evan Charlton94d01622014-07-20 12:32:05 -0700376 new Json<PhoneAccount>() {
Ihab Awad104f8062014-07-17 11:29:35 -0700377 private static final String ACCOUNT = "account";
378 private static final String HANDLE = "handle";
Evan Charlton484f8d62014-07-18 14:06:58 -0700379 private static final String SUBSCRIPTION_NUMBER = "subscription_number";
Ihab Awad104f8062014-07-17 11:29:35 -0700380 private static final String CAPABILITIES = "capabilities";
381 private static final String ICON_RES_ID = "icon_res_id";
382 private static final String LABEL = "label";
383 private static final String SHORT_DESCRIPTION = "short_description";
384 private static final String VIDEO_CALLING_SUPPORTED = "video_calling_supported";
385
386 @Override
Evan Charlton94d01622014-07-20 12:32:05 -0700387 public JSONObject toJson(PhoneAccount o) throws JSONException {
Ihab Awad104f8062014-07-17 11:29:35 -0700388 return new JSONObject()
Ihab Awad293edf22014-07-24 17:52:29 -0700389 .put(ACCOUNT, sPhoneAccountHandleJson.toJson(o.getAccountHandle()))
Ihab Awad104f8062014-07-17 11:29:35 -0700390 .put(HANDLE, o.getHandle().toString())
Evan Charlton484f8d62014-07-18 14:06:58 -0700391 .put(SUBSCRIPTION_NUMBER, o.getSubscriptionNumber())
Ihab Awad104f8062014-07-17 11:29:35 -0700392 .put(CAPABILITIES, o.getCapabilities())
393 .put(ICON_RES_ID, o.getIconResId())
394 .put(LABEL, o.getLabel())
395 .put(SHORT_DESCRIPTION, o.getShortDescription())
396 .put(VIDEO_CALLING_SUPPORTED, (Boolean) o.isVideoCallingSupported());
397 }
398
399 @Override
Evan Charlton94d01622014-07-20 12:32:05 -0700400 public PhoneAccount fromJson(JSONObject json) throws JSONException {
401 return new PhoneAccount(
Ihab Awad293edf22014-07-24 17:52:29 -0700402 sPhoneAccountHandleJson.fromJson((JSONObject) json.get(ACCOUNT)),
Ihab Awad104f8062014-07-17 11:29:35 -0700403 Uri.parse((String) json.get(HANDLE)),
Evan Charlton484f8d62014-07-18 14:06:58 -0700404 (String) json.get(SUBSCRIPTION_NUMBER),
Ihab Awad104f8062014-07-17 11:29:35 -0700405 (int) json.get(CAPABILITIES),
406 (int) json.get(ICON_RES_ID),
407 (String) json.get(LABEL),
408 (String) json.get(SHORT_DESCRIPTION),
409 (Boolean) json.get(VIDEO_CALLING_SUPPORTED));
410 }
411 };
412
Ihab Awad293edf22014-07-24 17:52:29 -0700413 private static final Json<PhoneAccountHandle> sPhoneAccountHandleJson =
Evan Charlton89176372014-07-19 18:23:09 -0700414 new Json<PhoneAccountHandle>() {
Ihab Awad104f8062014-07-17 11:29:35 -0700415 private static final String COMPONENT_NAME = "component_name";
416 private static final String ID = "id";
417
418 @Override
Evan Charlton89176372014-07-19 18:23:09 -0700419 public JSONObject toJson(PhoneAccountHandle o) throws JSONException {
Ihab Awad104f8062014-07-17 11:29:35 -0700420 return new JSONObject()
421 .put(COMPONENT_NAME, o.getComponentName().flattenToString())
422 .put(ID, o.getId());
423 }
424
425 @Override
Evan Charlton89176372014-07-19 18:23:09 -0700426 public PhoneAccountHandle fromJson(JSONObject json) throws JSONException {
427 return new PhoneAccountHandle(
Ihab Awad104f8062014-07-17 11:29:35 -0700428 ComponentName.unflattenFromString((String) json.get(COMPONENT_NAME)),
429 (String) json.get(ID));
430 }
431 };
Santos Cordon176ae282014-07-14 02:02:14 -0700432}