blob: a739250a6c75bc657611acc342630baae8d0eee3 [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 Charlton89176372014-07-19 18:23:09 -070019import android.telecomm.PhoneAccountHandle;
Ihab Awad104f8062014-07-17 11:29:35 -070020import org.json.JSONArray;
21import org.json.JSONException;
22import org.json.JSONObject;
23import org.json.JSONTokener;
24
Santos Cordon176ae282014-07-14 02:02:14 -070025import android.content.ComponentName;
26import android.content.Context;
27
28import android.content.SharedPreferences;
Santos Cordon176ae282014-07-14 02:02:14 -070029import android.net.Uri;
Ihab Awad104f8062014-07-17 11:29:35 -070030import android.telecomm.PhoneAccountMetadata;
31import 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();
Evan Charlton89176372014-07-19 18:23:09 -070059 return s.defaultOutgoingHandle;
Ihab Awad104f8062014-07-17 11:29:35 -070060 }
Santos Cordon176ae282014-07-14 02:02:14 -070061
Evan Charlton89176372014-07-19 18:23:09 -070062 public void setDefaultOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad104f8062014-07-17 11:29:35 -070063 State s = read();
64
Evan Charlton89176372014-07-19 18:23:09 -070065 if (accountHandle == null) {
Ihab Awad104f8062014-07-17 11:29:35 -070066 // Asking to clear the default outgoing is a valid request
Evan Charlton89176372014-07-19 18:23:09 -070067 s.defaultOutgoingHandle = null;
Ihab Awad104f8062014-07-17 11:29:35 -070068 } else {
69 boolean found = false;
Ihab Awad78ac0ce2014-07-18 13:32:53 -070070 for (PhoneAccountMetadata m : s.accounts) {
Evan Charlton89176372014-07-19 18:23:09 -070071 if (Objects.equals(accountHandle, m.getAccount())) {
Ihab Awad104f8062014-07-17 11:29:35 -070072 found = true;
73 break;
74 }
Santos Cordon176ae282014-07-14 02:02:14 -070075 }
Ihab Awad104f8062014-07-17 11:29:35 -070076
77 if (!found) {
Evan Charlton89176372014-07-19 18:23:09 -070078 Log.w(this, "Trying to set nonexistent default outgoing phone accountHandle %s",
79 accountHandle);
Ihab Awad104f8062014-07-17 11:29:35 -070080 return;
81 }
82
Evan Charlton89176372014-07-19 18:23:09 -070083 s.defaultOutgoingHandle = accountHandle;
Santos Cordon176ae282014-07-14 02:02:14 -070084 }
85
Ihab Awad104f8062014-07-17 11:29:35 -070086 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -070087 }
88
Evan Charlton89176372014-07-19 18:23:09 -070089 public List<PhoneAccountHandle> getEnabledPhoneAccounts() {
Ihab Awad104f8062014-07-17 11:29:35 -070090 State s = read();
Evan Charlton89176372014-07-19 18:23:09 -070091 return accountHandlesOnly(s);
Santos Cordon176ae282014-07-14 02:02:14 -070092 }
93
Evan Charlton89176372014-07-19 18:23:09 -070094 public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccountHandle accountHandle) {
Ihab Awad104f8062014-07-17 11:29:35 -070095 State s = read();
Ihab Awad78ac0ce2014-07-18 13:32:53 -070096 for (PhoneAccountMetadata m : s.accounts) {
Evan Charlton89176372014-07-19 18:23:09 -070097 if (Objects.equals(accountHandle, m.getAccount())) {
Ihab Awad104f8062014-07-17 11:29:35 -070098 return m;
Santos Cordon176ae282014-07-14 02:02:14 -070099 }
100 }
101 return null;
102 }
103
Ihab Awad104f8062014-07-17 11:29:35 -0700104 // TODO: Should we implement an artificial limit for # of accounts associated with a single
105 // ComponentName?
106 public void registerPhoneAccount(PhoneAccountMetadata metadata) {
107 State s = read();
Santos Cordon176ae282014-07-14 02:02:14 -0700108
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700109 s.accounts.add(metadata);
Ihab Awad104f8062014-07-17 11:29:35 -0700110 // Search for duplicates and remove any that are found.
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700111 for (int i = 0; i < s.accounts.size() - 1; i++) {
112 if (Objects.equals(metadata.getAccount(), s.accounts.get(i).getAccount())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700113 // replace existing entry.
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700114 s.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700115 break;
Santos Cordon176ae282014-07-14 02:02:14 -0700116 }
117 }
118
Ihab Awad104f8062014-07-17 11:29:35 -0700119 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700120 }
121
Evan Charlton89176372014-07-19 18:23:09 -0700122 public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
Ihab Awad104f8062014-07-17 11:29:35 -0700123 State s = read();
124
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700125 for (int i = 0; i < s.accounts.size(); i++) {
Evan Charlton89176372014-07-19 18:23:09 -0700126 if (Objects.equals(accountHandle, s.accounts.get(i).getAccount())) {
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700127 s.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700128 break;
129 }
130 }
131
132 checkDefaultOutgoing(s);
133
134 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700135 }
136
Ihab Awad104f8062014-07-17 11:29:35 -0700137 public void clearAccounts(String packageName) {
138 State s = read();
139
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700140 for (int i = 0; i < s.accounts.size(); i++) {
Ihab Awad104f8062014-07-17 11:29:35 -0700141 if (Objects.equals(
142 packageName,
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700143 s.accounts.get(i).getAccount().getComponentName().getPackageName())) {
144 s.accounts.remove(i);
Ihab Awad104f8062014-07-17 11:29:35 -0700145 }
146 }
147
148 checkDefaultOutgoing(s);
149
150 write(s);
Santos Cordon176ae282014-07-14 02:02:14 -0700151 }
152
Ihab Awad104f8062014-07-17 11:29:35 -0700153 private void checkDefaultOutgoing(State s) {
154 // Check that, after an operation that removes accounts, the account set up as the "default
155 // outgoing" has not been deleted. If it has, then clear out the setting.
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700156 for (PhoneAccountMetadata m : s.accounts) {
Evan Charlton89176372014-07-19 18:23:09 -0700157 if (Objects.equals(s.defaultOutgoingHandle, m.getAccount())) {
Ihab Awad104f8062014-07-17 11:29:35 -0700158 return;
159 }
160 }
Evan Charlton89176372014-07-19 18:23:09 -0700161 s.defaultOutgoingHandle = null;
Santos Cordon176ae282014-07-14 02:02:14 -0700162 }
163
Evan Charlton89176372014-07-19 18:23:09 -0700164 private List<PhoneAccountHandle> accountHandlesOnly(State s) {
165 List<PhoneAccountHandle> result = new ArrayList<>();
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700166 for (PhoneAccountMetadata m : s.accounts) {
Ihab Awad104f8062014-07-17 11:29:35 -0700167 result.add(m.getAccount());
168 }
169 return result;
170 }
171
172 private State read() {
173 try {
174 String serialized = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
175 Log.d(this, "read() obtained serialized state: %s", serialized);
176 State state = serialized == null
177 ? new State()
178 : deserializeState(serialized);
179 Log.d(this, "read() obtained state: %s", state);
180 return state;
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700181 } catch (JSONException e) {
Ihab Awad104f8062014-07-17 11:29:35 -0700182 Log.e(this, e, "read");
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700183 return new State();
Ihab Awad104f8062014-07-17 11:29:35 -0700184 }
185 }
186
187 private boolean write(State state) {
188 try {
189 Log.d(this, "write() writing state: %s", state);
190 String serialized = serializeState(state);
191 Log.d(this, "write() writing serialized state: %s", serialized);
192 boolean success = getPreferences()
193 .edit()
194 .putString(PREFERENCE_PHONE_ACCOUNTS, serialized)
195 .commit();
196 Log.d(this, "serialized state was written with succcess = %b", success);
197 return success;
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700198 } catch (JSONException e) {
Ihab Awad104f8062014-07-17 11:29:35 -0700199 Log.e(this, e, "write");
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700200 return false;
Ihab Awad104f8062014-07-17 11:29:35 -0700201 }
Santos Cordon176ae282014-07-14 02:02:14 -0700202 }
203
204 private SharedPreferences getPreferences() {
205 return mContext.getSharedPreferences(TELECOMM_PREFERENCES, Context.MODE_PRIVATE);
206 }
Ihab Awad104f8062014-07-17 11:29:35 -0700207
208 private String serializeState(State s) throws JSONException {
209 // TODO: If this is used in production, remove the indent (=> do not pretty print)
210 return sStateJson.toJson(s).toString(2);
211 }
212
213 private State deserializeState(String s) throws JSONException {
214 return sStateJson.fromJson(new JSONObject(new JSONTokener(s)));
215 }
216
217 private static class State {
Evan Charlton89176372014-07-19 18:23:09 -0700218 public PhoneAccountHandle defaultOutgoingHandle = null;
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700219 public final List<PhoneAccountMetadata> accounts = new ArrayList<>();
Ihab Awad104f8062014-07-17 11:29:35 -0700220 }
221
222 //
223 // JSON serialization
224 //
225
226 private interface Json<T> {
227 JSONObject toJson(T o) throws JSONException;
228 T fromJson(JSONObject json) throws JSONException;
229 }
230
231 private static final Json<State> sStateJson =
232 new Json<State>() {
233 private static final String DEFAULT_OUTGOING = "default_outgoing";
234 private static final String ACCOUNTS = "accounts";
235
236 @Override
237 public JSONObject toJson(State o) throws JSONException {
238 JSONObject json = new JSONObject();
Evan Charlton89176372014-07-19 18:23:09 -0700239 if (o.defaultOutgoingHandle != null) {
240 json.put(DEFAULT_OUTGOING, sPhoneAccountJson.toJson(o.defaultOutgoingHandle));
Ihab Awad104f8062014-07-17 11:29:35 -0700241 }
242 JSONArray accounts = new JSONArray();
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700243 for (PhoneAccountMetadata m : o.accounts) {
Ihab Awad104f8062014-07-17 11:29:35 -0700244 accounts.put(sPhoneAccountMetadataJson.toJson(m));
245 }
246 json.put(ACCOUNTS, accounts);
247 return json;
248 }
249
250 @Override
251 public State fromJson(JSONObject json) throws JSONException {
252 State s = new State();
253 if (json.has(DEFAULT_OUTGOING)) {
Evan Charlton89176372014-07-19 18:23:09 -0700254 s.defaultOutgoingHandle = sPhoneAccountJson.fromJson(
Ihab Awad104f8062014-07-17 11:29:35 -0700255 (JSONObject) json.get(DEFAULT_OUTGOING));
256 }
257 if (json.has(ACCOUNTS)) {
258 JSONArray accounts = (JSONArray) json.get(ACCOUNTS);
259 for (int i = 0; i < accounts.length(); i++) {
260 try {
Ihab Awad78ac0ce2014-07-18 13:32:53 -0700261 s.accounts.add(sPhoneAccountMetadataJson.fromJson(
Ihab Awad104f8062014-07-17 11:29:35 -0700262 (JSONObject) accounts.get(i)));
263 } catch (Exception e) {
264 Log.e(this, e, "Extracting phone account");
265 }
266 }
267 }
268 return s;
269 }
270 };
271
272 private static final Json<PhoneAccountMetadata> sPhoneAccountMetadataJson =
273 new Json<PhoneAccountMetadata>() {
274 private static final String ACCOUNT = "account";
275 private static final String HANDLE = "handle";
Evan Charlton484f8d62014-07-18 14:06:58 -0700276 private static final String SUBSCRIPTION_NUMBER = "subscription_number";
Ihab Awad104f8062014-07-17 11:29:35 -0700277 private static final String CAPABILITIES = "capabilities";
278 private static final String ICON_RES_ID = "icon_res_id";
279 private static final String LABEL = "label";
280 private static final String SHORT_DESCRIPTION = "short_description";
281 private static final String VIDEO_CALLING_SUPPORTED = "video_calling_supported";
282
283 @Override
284 public JSONObject toJson(PhoneAccountMetadata o) throws JSONException {
285 return new JSONObject()
286 .put(ACCOUNT, sPhoneAccountJson.toJson(o.getAccount()))
287 .put(HANDLE, o.getHandle().toString())
Evan Charlton484f8d62014-07-18 14:06:58 -0700288 .put(SUBSCRIPTION_NUMBER, o.getSubscriptionNumber())
Ihab Awad104f8062014-07-17 11:29:35 -0700289 .put(CAPABILITIES, o.getCapabilities())
290 .put(ICON_RES_ID, o.getIconResId())
291 .put(LABEL, o.getLabel())
292 .put(SHORT_DESCRIPTION, o.getShortDescription())
293 .put(VIDEO_CALLING_SUPPORTED, (Boolean) o.isVideoCallingSupported());
294 }
295
296 @Override
297 public PhoneAccountMetadata fromJson(JSONObject json) throws JSONException {
298 return new PhoneAccountMetadata(
299 sPhoneAccountJson.fromJson((JSONObject) json.get(ACCOUNT)),
300 Uri.parse((String) json.get(HANDLE)),
Evan Charlton484f8d62014-07-18 14:06:58 -0700301 (String) json.get(SUBSCRIPTION_NUMBER),
Ihab Awad104f8062014-07-17 11:29:35 -0700302 (int) json.get(CAPABILITIES),
303 (int) json.get(ICON_RES_ID),
304 (String) json.get(LABEL),
305 (String) json.get(SHORT_DESCRIPTION),
306 (Boolean) json.get(VIDEO_CALLING_SUPPORTED));
307 }
308 };
309
Evan Charlton89176372014-07-19 18:23:09 -0700310 private static final Json<PhoneAccountHandle> sPhoneAccountJson =
311 new Json<PhoneAccountHandle>() {
Ihab Awad104f8062014-07-17 11:29:35 -0700312 private static final String COMPONENT_NAME = "component_name";
313 private static final String ID = "id";
314
315 @Override
Evan Charlton89176372014-07-19 18:23:09 -0700316 public JSONObject toJson(PhoneAccountHandle o) throws JSONException {
Ihab Awad104f8062014-07-17 11:29:35 -0700317 return new JSONObject()
318 .put(COMPONENT_NAME, o.getComponentName().flattenToString())
319 .put(ID, o.getId());
320 }
321
322 @Override
Evan Charlton89176372014-07-19 18:23:09 -0700323 public PhoneAccountHandle fromJson(JSONObject json) throws JSONException {
324 return new PhoneAccountHandle(
Ihab Awad104f8062014-07-17 11:29:35 -0700325 ComponentName.unflattenFromString((String) json.get(COMPONENT_NAME)),
326 (String) json.get(ID));
327 }
328 };
Santos Cordon176ae282014-07-14 02:02:14 -0700329}