blob: 9c45c24c1edfe70d7fe9526f2a675822ec510988 [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;
52
Santos Cordon176ae282014-07-14 02:02:14 -070053 PhoneAccountRegistrar(Context context) {
54 mContext = context;
55 }
56
Evan Charlton89176372014-07-19 18:23:09 -070057 public PhoneAccountHandle getDefaultOutgoingPhoneAccount() {
Ihab Awad104f8062014-07-17 11:29:35 -070058 State s = read();
Ihab Awadf2a84912014-07-22 21:09:25 -070059
60 if (s.defaultOutgoingHandle != null) {
61 // Return the registered outgoing default iff it still exists (we keep a sticky
62 // default to survive account deletion and re-addition)
63 for (int i = 0; i < s.accounts.size(); i++) {
64 if (s.accounts.get(i).getAccountHandle().equals(s.defaultOutgoingHandle)) {
65 return s.defaultOutgoingHandle;
66 }
67 }
68 // At this point, there was a registered default but it has been deleted; remember
69 // it for the future, but return null from this method
70 return null;
71 } else {
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;
83 }
84 }
Ihab Awad104f8062014-07-17 11:29:35 -070085 }
Santos Cordon176ae282014-07-14 02:02:14 -070086
Evan Charlton89176372014-07-19 18:23:09 -070087 public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad104f8062014-07-17 11:29:35 -070088 State s = read();
89
Evan Charlton89176372014-07-19 18:23:09 -070090 if (accountHandle == null) {
Ihab Awad104f8062014-07-17 11:29:35 -070091 // Asking to clear the default outgoing is a valid request
Evan Charlton89176372014-07-19 18:23:09 -070092 s.defaultOutgoingHandle = null;
Ihab Awad104f8062014-07-17 11:29:35 -070093 } else {
94 boolean found = false;
Evan Charlton94d01622014-07-20 12:32:05 -070095 for (PhoneAccount m : s.accounts) {
96 if (Objects.equals(accountHandle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -070097 found = true;
98 break;
99 }
Santos Cordon176ae282014-07-14 02:02:14 -0700100 }
Ihab Awad104f8062014-07-17 11:29:35 -0700101
102 if (!found) {
Evan Charlton89176372014-07-19 18:23:09 -0700103 Log.w(this, "Trying to set nonexistent default outgoing phone accountHandle %s",
104 accountHandle);
Ihab Awad104f8062014-07-17 11:29:35 -0700105 return;
106 }
107
Evan Charlton89176372014-07-19 18:23:09 -0700108 s.defaultOutgoingHandle = accountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -0700109 }
110
Ihab Awad104f8062014-07-17 11:29:35 -0700111 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700112 }
113
Evan Charlton89176372014-07-19 18:23:09 -0700114 public List<PhoneAccountHandle> getEnabledPhoneAccounts() {
Ihab Awad104f8062014-07-17 11:29:35 -0700115 State s = read();
Ihab Awadf2a84912014-07-22 21:09:25 -0700116 return simSubscriptionAccountHandles(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700117 }
118
Evan Charlton94d01622014-07-20 12:32:05 -0700119 public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad104f8062014-07-17 11:29:35 -0700120 State s = read();
Evan Charlton94d01622014-07-20 12:32:05 -0700121 for (PhoneAccount m : s.accounts) {
122 if (Objects.equals(accountHandle, m.getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700123 return m;
Santos Cordon176ae282014-07-14 02:02:14 -0700124 }
125 }
126 return null;
127 }
128
Ihab Awad104f8062014-07-17 11:29:35 -0700129 // TODO: Should we implement an artificial limit for # of accounts associated with a single
130 // ComponentName?
Evan Charlton94d01622014-07-20 12:32:05 -0700131 public void registerPhoneAccount(PhoneAccount metadata) {
Ihab Awad104f8062014-07-17 11:29:35 -0700132 State s = read();
Santos Cordon176ae282014-07-14 02:02:14 -0700133
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700134 s.accounts.add(metadata);
Ihab Awad104f8062014-07-17 11:29:35 -0700135 // Search for duplicates and remove any that are found.
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700136 for (int i = 0; i < s.accounts.size() - 1; i++) {
Evan Charlton94d01622014-07-20 12:32:05 -0700137 if (Objects.equals(metadata.getAccountHandle(), s.accounts.get(i).getAccountHandle())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700138 // replace existing entry.
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700139 s.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700140 break;
Santos Cordon176ae282014-07-14 02:02:14 -0700141 }
142 }
143
Ihab Awad104f8062014-07-17 11:29:35 -0700144 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700145 }
146
Evan Charlton89176372014-07-19 18:23:09 -0700147 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad104f8062014-07-17 11:29:35 -0700148 State s = read();
149
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700150 for (int i = 0; i < s.accounts.size(); i++) {
Evan Charlton94d01622014-07-20 12:32:05 -0700151 if (Objects.equals(accountHandle, s.accounts.get(i).getAccountHandle())) {
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700152 s.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700153 break;
154 }
155 }
156
Ihab Awad104f8062014-07-17 11:29:35 -0700157 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700158 }
159
Ihab Awad104f8062014-07-17 11:29:35 -0700160 public void clearAccounts(String packageName) {
161 State s = read();
162
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700163 for (int i = 0; i < s.accounts.size(); i++) {
Ihab Awad104f8062014-07-17 11:29:35 -0700164 if (Objects.equals(
165 packageName,
Evan Charlton94d01622014-07-20 12:32:05 -0700166 s.accounts.get(i).getAccountHandle().getComponentName().getPackageName())) {
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700167 s.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700168 }
169 }
170
Ihab Awad104f8062014-07-17 11:29:35 -0700171 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700172 }
173
Ihab Awadf2a84912014-07-22 21:09:25 -0700174 private List<PhoneAccountHandle> simSubscriptionAccountHandles(State s) {
Evan Charlton94d01622014-07-20 12:32:05 -0700175 List<PhoneAccountHandle> accountHandles = new ArrayList<>();
176 for (PhoneAccount m : s.accounts) {
Ihab Awadf2a84912014-07-22 21:09:25 -0700177 if ((m.getCapabilities() & PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0) {
178 accountHandles.add(m.getAccountHandle());
179 }
Ihab Awad104f8062014-07-17 11:29:35 -0700180 }
Evan Charlton94d01622014-07-20 12:32:05 -0700181 return accountHandles;
Ihab Awad104f8062014-07-17 11:29:35 -0700182 }
183
184 private State read() {
185 try {
186 String serialized = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
187 Log.d(this, "read() obtained serialized state: %s", serialized);
188 State state = serialized == null
189 ? new State()
190 : deserializeState(serialized);
191 Log.d(this, "read() obtained state: %s", state);
192 return state;
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700193 } catch (JSONException e) {
Ihab Awad104f8062014-07-17 11:29:35 -0700194 Log.e(this, e, "read");
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700195 return new State();
Ihab Awad104f8062014-07-17 11:29:35 -0700196 }
197 }
198
199 private boolean write(State state) {
200 try {
201 Log.d(this, "write() writing state: %s", state);
202 String serialized = serializeState(state);
203 Log.d(this, "write() writing serialized state: %s", serialized);
204 boolean success = getPreferences()
205 .edit()
206 .putString(PREFERENCE_PHONE_ACCOUNTS, serialized)
207 .commit();
208 Log.d(this, "serialized state was written with succcess = %b", success);
209 return success;
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700210 } catch (JSONException e) {
Ihab Awad104f8062014-07-17 11:29:35 -0700211 Log.e(this, e, "write");
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700212 return false;
Ihab Awad104f8062014-07-17 11:29:35 -0700213 }
Santos Cordon176ae282014-07-14 02:02:14 -0700214 }
215
216 private SharedPreferences getPreferences() {
217 return mContext.getSharedPreferences(TELECOMM_PREFERENCES, Context.MODE_PRIVATE);
218 }
Ihab Awad104f8062014-07-17 11:29:35 -0700219
220 private String serializeState(State s) throws JSONException {
221 // TODO: If this is used in production, remove the indent (=> do not pretty print)
222 return sStateJson.toJson(s).toString(2);
223 }
224
225 private State deserializeState(String s) throws JSONException {
226 return sStateJson.fromJson(new JSONObject(new JSONTokener(s)));
227 }
228
229 private static class State {
Evan Charlton89176372014-07-19 18:23:09 -0700230 public PhoneAccountHandle defaultOutgoingHandle = null;
Evan Charlton94d01622014-07-20 12:32:05 -0700231 public final List<PhoneAccount> accounts = new ArrayList<>();
Ihab Awad104f8062014-07-17 11:29:35 -0700232 }
233
234 //
235 // JSON serialization
236 //
237
238 private interface Json<T> {
239 JSONObject toJson(T o) throws JSONException;
240 T fromJson(JSONObject json) throws JSONException;
241 }
242
243 private static final Json<State> sStateJson =
244 new Json<State>() {
245 private static final String DEFAULT_OUTGOING = "default_outgoing";
246 private static final String ACCOUNTS = "accounts";
247
248 @Override
249 public JSONObject toJson(State o) throws JSONException {
250 JSONObject json = new JSONObject();
Evan Charlton89176372014-07-19 18:23:09 -0700251 if (o.defaultOutgoingHandle != null) {
252 json.put(DEFAULT_OUTGOING, sPhoneAccountJson.toJson(o.defaultOutgoingHandle));
Ihab Awad104f8062014-07-17 11:29:35 -0700253 }
254 JSONArray accounts = new JSONArray();
Evan Charlton94d01622014-07-20 12:32:05 -0700255 for (PhoneAccount m : o.accounts) {
Ihab Awad104f8062014-07-17 11:29:35 -0700256 accounts.put(sPhoneAccountMetadataJson.toJson(m));
257 }
258 json.put(ACCOUNTS, accounts);
259 return json;
260 }
261
262 @Override
263 public State fromJson(JSONObject json) throws JSONException {
264 State s = new State();
265 if (json.has(DEFAULT_OUTGOING)) {
Evan Charlton89176372014-07-19 18:23:09 -0700266 s.defaultOutgoingHandle = sPhoneAccountJson.fromJson(
Ihab Awad104f8062014-07-17 11:29:35 -0700267 (JSONObject) json.get(DEFAULT_OUTGOING));
268 }
269 if (json.has(ACCOUNTS)) {
270 JSONArray accounts = (JSONArray) json.get(ACCOUNTS);
271 for (int i = 0; i < accounts.length(); i++) {
272 try {
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700273 s.accounts.add(sPhoneAccountMetadataJson.fromJson(
Ihab Awad104f8062014-07-17 11:29:35 -0700274 (JSONObject) accounts.get(i)));
275 } catch (Exception e) {
276 Log.e(this, e, "Extracting phone account");
277 }
278 }
279 }
280 return s;
281 }
282 };
283
Evan Charlton94d01622014-07-20 12:32:05 -0700284 private static final Json<PhoneAccount> sPhoneAccountMetadataJson =
285 new Json<PhoneAccount>() {
Ihab Awad104f8062014-07-17 11:29:35 -0700286 private static final String ACCOUNT = "account";
287 private static final String HANDLE = "handle";
Evan Charlton484f8d62014-07-18 14:06:58 -0700288 private static final String SUBSCRIPTION_NUMBER = "subscription_number";
Ihab Awad104f8062014-07-17 11:29:35 -0700289 private static final String CAPABILITIES = "capabilities";
290 private static final String ICON_RES_ID = "icon_res_id";
291 private static final String LABEL = "label";
292 private static final String SHORT_DESCRIPTION = "short_description";
293 private static final String VIDEO_CALLING_SUPPORTED = "video_calling_supported";
294
295 @Override
Evan Charlton94d01622014-07-20 12:32:05 -0700296 public JSONObject toJson(PhoneAccount o) throws JSONException {
Ihab Awad104f8062014-07-17 11:29:35 -0700297 return new JSONObject()
Evan Charlton94d01622014-07-20 12:32:05 -0700298 .put(ACCOUNT, sPhoneAccountJson.toJson(o.getAccountHandle()))
Ihab Awad104f8062014-07-17 11:29:35 -0700299 .put(HANDLE, o.getHandle().toString())
Evan Charlton484f8d62014-07-18 14:06:58 -0700300 .put(SUBSCRIPTION_NUMBER, o.getSubscriptionNumber())
Ihab Awad104f8062014-07-17 11:29:35 -0700301 .put(CAPABILITIES, o.getCapabilities())
302 .put(ICON_RES_ID, o.getIconResId())
303 .put(LABEL, o.getLabel())
304 .put(SHORT_DESCRIPTION, o.getShortDescription())
305 .put(VIDEO_CALLING_SUPPORTED, (Boolean) o.isVideoCallingSupported());
306 }
307
308 @Override
Evan Charlton94d01622014-07-20 12:32:05 -0700309 public PhoneAccount fromJson(JSONObject json) throws JSONException {
310 return new PhoneAccount(
Ihab Awad104f8062014-07-17 11:29:35 -0700311 sPhoneAccountJson.fromJson((JSONObject) json.get(ACCOUNT)),
312 Uri.parse((String) json.get(HANDLE)),
Evan Charlton484f8d62014-07-18 14:06:58 -0700313 (String) json.get(SUBSCRIPTION_NUMBER),
Ihab Awad104f8062014-07-17 11:29:35 -0700314 (int) json.get(CAPABILITIES),
315 (int) json.get(ICON_RES_ID),
316 (String) json.get(LABEL),
317 (String) json.get(SHORT_DESCRIPTION),
318 (Boolean) json.get(VIDEO_CALLING_SUPPORTED));
319 }
320 };
321
Evan Charlton89176372014-07-19 18:23:09 -0700322 private static final Json<PhoneAccountHandle> sPhoneAccountJson =
323 new Json<PhoneAccountHandle>() {
Ihab Awad104f8062014-07-17 11:29:35 -0700324 private static final String COMPONENT_NAME = "component_name";
325 private static final String ID = "id";
326
327 @Override
Evan Charlton89176372014-07-19 18:23:09 -0700328 public JSONObject toJson(PhoneAccountHandle o) throws JSONException {
Ihab Awad104f8062014-07-17 11:29:35 -0700329 return new JSONObject()
330 .put(COMPONENT_NAME, o.getComponentName().flattenToString())
331 .put(ID, o.getId());
332 }
333
334 @Override
Evan Charlton89176372014-07-19 18:23:09 -0700335 public PhoneAccountHandle fromJson(JSONObject json) throws JSONException {
336 return new PhoneAccountHandle(
Ihab Awad104f8062014-07-17 11:29:35 -0700337 ComponentName.unflattenFromString((String) json.get(COMPONENT_NAME)),
338 (String) json.get(ID));
339 }
340 };
Santos Cordon176ae282014-07-14 02:02:14 -0700341}