Add PhoneAccount registration.
Writes/Reads PhoneAccount registration as they are added and
removed through the TelecommManager APIs.
Ultimately, we may want to use a proper Telecomm-provider DB instead of
string-based serialized setting.
Bug: 16292368
Change-Id: I1214fcdd8728cddc949945a590b20e328de5ee7f
diff --git a/src/com/android/telecomm/PhoneAccountRegistrar.java b/src/com/android/telecomm/PhoneAccountRegistrar.java
new file mode 100644
index 0000000..e8abb8a
--- /dev/null
+++ b/src/com/android/telecomm/PhoneAccountRegistrar.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.telecomm;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.net.Uri;
+import android.provider.Settings;
+import android.telecomm.PhoneAccount;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Handles writing and reading PhoneAccount registration entries.
+ * TODO(santoscordon): Replace this implementation with a proper database stored in a Telecomm
+ * provider.
+ */
+final class PhoneAccountRegistrar {
+ private static final int VERSION = 1;
+ private static final String TELECOMM_PREFERENCES = "telecomm_prefs";
+ private static final String PREFERENCE_PHONE_ACCOUNTS = "phone_accounts";
+
+ private final Context mContext;
+
+ private final class DeserializationToken {
+ int currentIndex = 0;
+ final String source;
+
+ DeserializationToken(String source) {
+ this.source = source;
+ }
+ }
+
+ PhoneAccountRegistrar(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Adds a new phone account entry or updates an existing one.
+ */
+ boolean addAccount(PhoneAccount account) {
+ List<PhoneAccount> allAccounts = getAllAccounts();
+ // Should we implement an artificial limit for # of accounts associated with a single
+ // ComponentName?
+ allAccounts.add(account);
+
+ // Search for duplicates and remove any that are found.
+ for (int i = 0; i < allAccounts.size() - 1; i++) {
+ if (account.equalsComponentAndId(allAccounts.get(i))) {
+ // replace existing entry.
+ allAccounts.remove(i);
+ break;
+ }
+ }
+
+ return writeAllAccounts(allAccounts);
+ }
+
+ /**
+ * Removes an existing phone account entry.
+ */
+ boolean removeAccount(PhoneAccount account) {
+ List<PhoneAccount> allAccounts = getAllAccounts();
+
+ for (int i = 0; i < allAccounts.size(); i++) {
+ if (account.equalsComponentAndId(allAccounts.get(i))) {
+ allAccounts.remove(i);
+ return writeAllAccounts(allAccounts);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a list of all accounts which the user has enabled.
+ */
+ List<PhoneAccount> getEnabledAccounts() {
+ List<PhoneAccount> allAccounts = getAllAccounts();
+ // TODO: filter list
+ return allAccounts;
+ }
+
+ /**
+ * Returns the list of all accounts registered with the system, whether or not the user
+ * has explicitly enabled them.
+ */
+ List<PhoneAccount> getAllAccounts() {
+ String value = getPreferences().getString(PREFERENCE_PHONE_ACCOUNTS, null);
+ return deserializeAllAccounts(value);
+ }
+
+ /**
+ * Returns the registered version of the account matching the component name and ID of the
+ * specified account.
+ */
+ PhoneAccount getRegisteredAccount(PhoneAccount account) {
+ for (PhoneAccount registeredAccount : getAllAccounts()) {
+ if (registeredAccount.equalsComponentAndId(account)) {
+ return registeredAccount;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Replaces the contents of our list of accounts with this new list.
+ */
+ private boolean writeAllAccounts(List<PhoneAccount> allAccounts) {
+ Editor editor = getPreferences().edit();
+ editor.putString(PREFERENCE_PHONE_ACCOUNTS, serializeAllAccounts(allAccounts));
+ return editor.commit();
+ }
+
+ // Serialization implementation
+ // Serializes all strings into the format "len:string-value"
+ // Example, we will serialize the following PhoneAccount.
+ // PhoneAccount
+ // ComponentName: "abc"
+ // Id: "def"
+ // Handle: "555"
+ // Capabilities: 1
+ //
+ // Each value serializes into (spaces added for readability)
+ // 3:abc 3:def 3:555 1:1
+ //
+ // Two identical accounts would likewise be serialized as a list of strings with a prepended
+ // size of 2.
+ // 1:2 3:abc 3:def 3:555 1:1 3:abc 3:def 3:555 1:1
+ //
+ // The final result with a prepended version ("1:1") would be:
+ // "1:11:23:abc3:def3:5551:13:abc3:def3:5551:1"
+
+ private String serializeAllAccounts(List<PhoneAccount> allAccounts) {
+ StringBuilder buffer = new StringBuilder();
+
+ // Version
+ serializeIntValue(VERSION, buffer);
+
+ // Number of accounts
+ serializeIntValue(allAccounts.size(), buffer);
+
+ // The actual accounts
+ for (int i = 0; i < allAccounts.size(); i++) {
+ PhoneAccount account = allAccounts.get(i);
+ serializeStringValue(account.getComponentName().flattenToShortString(), buffer);
+ serializeStringValue(account.getId(), buffer);
+ serializeStringValue(account.getHandle().toString(), buffer);
+ serializeIntValue(account.getCapabilities(), buffer);
+ }
+
+ return buffer.toString();
+ }
+
+ private List<PhoneAccount> deserializeAllAccounts(String source) {
+ List<PhoneAccount> accounts = new ArrayList<PhoneAccount>();
+
+ if (source != null) {
+ DeserializationToken token = new DeserializationToken(source);
+ int version = deserializeIntValue(token);
+ if (version == 1) {
+ int size = deserializeIntValue(token);
+
+ for (int i = 0; i < size; i++) {
+ String strComponentName = deserializeStringValue(token);
+ String strId = deserializeStringValue(token);
+ String strHandle = deserializeStringValue(token);
+ int capabilities = deserializeIntValue(token);
+
+ accounts.add(new PhoneAccount(
+ ComponentName.unflattenFromString(strComponentName),
+ strId,
+ Uri.parse(strHandle),
+ capabilities));
+ }
+ }
+ }
+
+ return accounts;
+ }
+
+ private void serializeIntValue(int value, StringBuilder buffer) {
+ serializeStringValue(String.valueOf(value), buffer);
+ }
+
+ private void serializeStringValue(String value, StringBuilder buffer) {
+ buffer.append(value.length()).append(":").append(value);
+ }
+
+ private int deserializeIntValue(DeserializationToken token) {
+ return Integer.parseInt(deserializeStringValue(token));
+ }
+
+ private String deserializeStringValue(DeserializationToken token) {
+ int colonIndex = token.source.indexOf(':', token.currentIndex);
+ int valueLength = Integer.parseInt(token.source.substring(token.currentIndex, colonIndex));
+ int endIndex = colonIndex + 1 + valueLength;
+ token.currentIndex = endIndex;
+ return token.source.substring(colonIndex + 1, endIndex);
+ }
+
+ private SharedPreferences getPreferences() {
+ return mContext.getSharedPreferences(TELECOMM_PREFERENCES, Context.MODE_PRIVATE);
+ }
+}
diff --git a/src/com/android/telecomm/TelecommApp.java b/src/com/android/telecomm/TelecommApp.java
index 37c7aa0..2b2f160 100644
--- a/src/com/android/telecomm/TelecommApp.java
+++ b/src/com/android/telecomm/TelecommApp.java
@@ -33,14 +33,21 @@
*/
private MissedCallNotifier mMissedCallNotifier;
+ /**
+ * Maintains the list of registered {@link PhoneAccount}s.
+ */
+ private PhoneAccountRegistrar mPhoneAccountRegistrar;
+
/** {@inheritDoc} */
@Override public void onCreate() {
super.onCreate();
sInstance = this;
mMissedCallNotifier = new MissedCallNotifier(this);
+ mPhoneAccountRegistrar = new PhoneAccountRegistrar(this);
+
if (UserHandle.myUserId() == UserHandle.USER_OWNER) {
- TelecommServiceImpl.init(mMissedCallNotifier);
+ TelecommServiceImpl.init(mMissedCallNotifier, mPhoneAccountRegistrar);
}
}
diff --git a/src/com/android/telecomm/TelecommServiceImpl.java b/src/com/android/telecomm/TelecommServiceImpl.java
index 509307b..d848029 100644
--- a/src/com/android/telecomm/TelecommServiceImpl.java
+++ b/src/com/android/telecomm/TelecommServiceImpl.java
@@ -20,7 +20,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
-import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -35,10 +34,7 @@
import com.android.internal.telecomm.ITelecommService;
-import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* Implementation of the ITelecomm interface.
@@ -117,10 +113,13 @@
private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
private final CallsManager mCallsManager = CallsManager.getInstance();
private final MissedCallNotifier mMissedCallNotifier;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final AppOpsManager mAppOpsManager;
- private TelecommServiceImpl(MissedCallNotifier missedCallNotifier) {
+ private TelecommServiceImpl(
+ MissedCallNotifier missedCallNotifier, PhoneAccountRegistrar phoneAccountRegistrar) {
mMissedCallNotifier = missedCallNotifier;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
mAppOpsManager =
(AppOpsManager) TelecommApp.getInstance().getSystemService(Context.APP_OPS_SERVICE);
@@ -131,10 +130,11 @@
* Initialize the singleton TelecommServiceImpl instance.
* This is only done once, at startup, from TelecommApp.onCreate().
*/
- static TelecommServiceImpl init(MissedCallNotifier missedCallNotifier) {
+ static TelecommServiceImpl init(
+ MissedCallNotifier missedCallNotifier, PhoneAccountRegistrar phoneAccountRegistrar) {
synchronized (TelecommServiceImpl.class) {
if (sInstance == null) {
- sInstance = new TelecommServiceImpl(missedCallNotifier);
+ sInstance = new TelecommServiceImpl(missedCallNotifier, phoneAccountRegistrar);
} else {
Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
}
@@ -146,98 +146,43 @@
// Implementation of the ITelecommService interface.
//
- private static Map<PhoneAccount, PhoneAccountMetadata> sMetadataByAccount = new HashMap<>();
-
- static {
- // TODO (STOPSHIP): Static list of Accounts for testing and UX work only.
- ComponentName componentName = new ComponentName(
- "com.android.telecomm",
- TelecommServiceImpl.class.getName()); // This field is a no-op
- Context app = TelecommApp.getInstance();
-
- PhoneAccount[] accounts = new PhoneAccount[] {
- new PhoneAccount(
- componentName,
- "account0",
- Uri.parse("tel:999-555-1212"),
- 0),
- new PhoneAccount(
- componentName,
- "account1",
- Uri.parse("tel:333-111-2222"),
- 0),
- new PhoneAccount(
- componentName,
- "account2",
- Uri.parse("mailto:two@example.com"),
- 0),
- new PhoneAccount(
- componentName,
- "account3",
- Uri.parse("mailto:three@example.com"),
- 0)
- };
-
- sMetadataByAccount.put(
- accounts[0],
- new PhoneAccountMetadata(
- accounts[0],
- 0,
- app.getString(R.string.test_account_0_label),
- app.getString(R.string.test_account_0_short_description)));
- sMetadataByAccount.put(
- accounts[1],
- new PhoneAccountMetadata(
- accounts[1],
- 0,
- app.getString(R.string.test_account_1_label),
- app.getString(R.string.test_account_1_short_description)));
- sMetadataByAccount.put(
- accounts[2],
- new PhoneAccountMetadata(
- accounts[2],
- 0,
- app.getString(R.string.test_account_2_label),
- app.getString(R.string.test_account_2_short_description)));
- sMetadataByAccount.put(
- accounts[3],
- new PhoneAccountMetadata(
- accounts[3],
- 0,
- app.getString(R.string.test_account_3_label),
- app.getString(R.string.test_account_3_short_description)));
- }
-
@Override
public List<PhoneAccount> getEnabledPhoneAccounts() {
- return new ArrayList<>(sMetadataByAccount.keySet());
+ return mPhoneAccountRegistrar.getEnabledAccounts();
}
@Override
public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) {
- return sMetadataByAccount.get(account);
+ PhoneAccount registeredAccount = mPhoneAccountRegistrar.getRegisteredAccount(account);
+ if (registeredAccount != null) {
+ return new PhoneAccountMetadata(
+ registeredAccount, 0, account.getComponentName().getPackageName(), null);
+ }
+ return null;
}
@Override
public void registerPhoneAccount(PhoneAccount account, PhoneAccountMetadata metadata) {
enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
- // TODO(santoscordon) -- IMPLEMENT ...
+ mPhoneAccountRegistrar.addAccount(account);
+ // TODO(santoscordon): Implement metadata
}
@Override
public void unregisterPhoneAccount(PhoneAccount account) {
enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
- // TODO(santoscordon) -- IMPLEMENT ...
+ mPhoneAccountRegistrar.removeAccount(account);
}
@Override
public void clearAccounts(String packageName) {
enforceModifyPermissionOrCallingPackage(packageName);
- // TODO(santoscordon) -- IMPLEMENT ...
+ // TODO(santoscordon): Is this needed?
+ Log.e(TAG, null, "Unexpected method call: clearAccounts()");
}
/**
- * @see TelecommManager#silenceringer
+ * @see TelecommManager#silenceRinger
*/
@Override
public void silenceRinger() {