Cache default dialer in Telecom (part 1)
In order to avoid a flurry of recursive calls into Telecom on every
Telecom API call, cache the default dialer in the Telecom process and
only update it when either the relevant setting changes or when the
current default dialer has been uninstalled/disabled.
Part 1 of change. Adds the default dialer cache class, but does not
integrate it into the rest of Telecom.
Test: added unit tests
Change-Id: I22225ed1f1b516dc98e80fa4f82c5b72ed289ba0
Merged-In: Ie81e8a052b2463bef5df4c60c443c73db880780b
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
new file mode 100644
index 0000000..9417c5e
--- /dev/null
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 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.server.telecom;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telecom.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+public class DefaultDialerCache {
+ private static final String LOG_TAG = "DefaultDialerCache";
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.startSession("DDC.oR");
+ try {
+ String packageName;
+ if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
+ packageName = intent.getData().getSchemeSpecificPart();
+ } else if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
+ && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ packageName = intent.getData().getSchemeSpecificPart();
+ } else if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
+ packageName = null;
+ } else {
+ return;
+ }
+
+ synchronized (mLock) {
+ refreshCachesForUsersWithPackage(packageName);
+ }
+
+ } finally {
+ Log.endSession();
+ }
+ }
+ };
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final ContentObserver mDefaultDialerObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ Log.startSession("DDC.oC");
+ try {
+ // We don't get the user ID of the user that changed here, so we'll have to
+ // refresh all of the users.
+ synchronized (mLock) {
+ refreshCachesForUsersWithPackage(null);
+ }
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
+ public boolean deliverSelfNotifications() {
+ return true;
+ }
+ };
+
+ private final Context mContext;
+ private final TelecomServiceImpl.DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+ private final TelecomSystem.SyncRoot mLock;
+ private final String mSystemDialerName;
+ private SparseArray<String> mCurrentDefaultDialerPerUser = new SparseArray<>();
+
+ public DefaultDialerCache(Context context,
+ TelecomServiceImpl.DefaultDialerManagerAdapter defaultDialerManagerAdapter,
+ TelecomSystem.SyncRoot lock) {
+ mContext = context;
+ mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
+ mLock = lock;
+ mSystemDialerName = mContext.getResources().getString(R.string.ui_default_package);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ intentFilter.addDataScheme("package");
+ context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
+
+ Uri defaultDialerSetting =
+ Settings.Secure.getUriFor(Settings.Secure.DIALER_DEFAULT_APPLICATION);
+ context.getContentResolver()
+ .registerContentObserver(defaultDialerSetting, false, mDefaultDialerObserver,
+ UserHandle.USER_ALL);
+ }
+
+ public String getDefaultDialerApplication(int userId) {
+ if (userId == UserHandle.USER_CURRENT) {
+ userId = ActivityManager.getCurrentUser();
+ }
+
+ if (userId < 0) {
+ Log.w(LOG_TAG, "Attempting to get default dialer for a meta-user %d", userId);
+ return null;
+ }
+
+ synchronized (mLock) {
+ String defaultDialer = mCurrentDefaultDialerPerUser.get(userId);
+ if (defaultDialer != null) {
+ return defaultDialer;
+ }
+ }
+ return refreshCacheForUser(userId);
+ }
+
+ public String getDefaultDialerApplication() {
+ return getDefaultDialerApplication(mContext.getUserId());
+ }
+
+ public boolean isDefaultOrSystemDialer(Context context, String packageName) {
+ String defaultDialer = getDefaultDialerApplication(context.getUserId());
+ return Objects.equals(packageName, defaultDialer)
+ || Objects.equals(packageName, mSystemDialerName);
+ }
+
+ private String refreshCacheForUser(int userId) {
+ String currentDefaultDialer =
+ mDefaultDialerManagerAdapter.getDefaultDialerApplication(mContext, userId);
+ synchronized (mLock) {
+ mCurrentDefaultDialerPerUser.put(userId, currentDefaultDialer);
+ }
+ return currentDefaultDialer;
+ }
+
+ /**
+ * Refreshes the cache for users that currently have packageName as their cached default dialer.
+ * If packageName is null, refresh all caches.
+ * @param packageName Name of the affected package.
+ */
+ private void refreshCachesForUsersWithPackage(String packageName) {
+ for (int i = 0; i < mCurrentDefaultDialerPerUser.size(); i++) {
+ int userId = mCurrentDefaultDialerPerUser.keyAt(i);
+ if (packageName == null ||
+ Objects.equals(packageName, mCurrentDefaultDialerPerUser.get(userId))) {
+ String newDefaultDialer = refreshCacheForUser(userId);
+ Log.i(LOG_TAG, "Refreshing default dialer for user %d: now %s",
+ userId, newDefaultDialer);
+ }
+ }
+ }
+
+ /**
+ * registerContentObserver is really hard to mock out, so here is a getter method for the
+ * content observer for testing instead.
+ * @return The content observer
+ */
+ @VisibleForTesting
+ public ContentObserver getContentObserver() {
+ return mDefaultDialerObserver;
+ }
+}
\ No newline at end of file
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index f84a545..eccd513 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -29,6 +29,7 @@
<!-- TODO: Needed because we call ActivityManager.getCurrentUser() statically. -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<!-- Used to access TelephonyManager APIs -->
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 65352f9..b14a5e5 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -259,6 +259,12 @@
}
@Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle handle,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ return null;
+ }
+
+ @Override
public void sendBroadcast(Intent intent) {
// TODO -- need to ensure this is captured
}
diff --git a/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
new file mode 100644
index 0000000..8ddcb1d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/DefaultDialerCacheTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2016 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.server.telecom.tests;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.TelecomSystem;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DefaultDialerCacheTest extends TelecomTestCase {
+
+ private static final String DIALER1 = "com.android.dialer";
+ private static final String DIALER2 = "xyz.abc.dialer";
+ private static final String DIALER3 = "aaa.bbb.ccc.ddd";
+ private static final int USER0 = 0;
+ private static final int USER1 = 1;
+ private static final int USER2 = 2;
+
+ private DefaultDialerCache mDefaultDialerCache;
+ private ContentObserver mDefaultDialerSettingObserver;
+ private BroadcastReceiver mPackageChangeReceiver;
+
+ @Mock private TelecomServiceImpl.DefaultDialerManagerAdapter mMockDefaultDialerManager;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ mDefaultDialerCache = new DefaultDialerCache(
+ mContext, mMockDefaultDialerManager, new TelecomSystem.SyncRoot() { });
+
+ verify(mContext).registerReceiverAsUser(
+ receiverCaptor.capture(), eq(UserHandle.ALL), any(IntentFilter.class),
+ isNull(String.class), isNull(Handler.class));
+ mPackageChangeReceiver = receiverCaptor.getValue();
+ mDefaultDialerSettingObserver = mDefaultDialerCache.getContentObserver();
+
+ when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER0)))
+ .thenReturn(DIALER1);
+ when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER1)))
+ .thenReturn(DIALER2);
+ when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER2)))
+ .thenReturn(DIALER3);
+ }
+
+ @SmallTest
+ public void testThreeUsers() {
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER1));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+ }
+
+ @SmallTest
+ public void testDialer1PackageChanged() {
+ // Populate the caches first
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+
+ Intent packageChangeIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED,
+ Uri.fromParts("package", DIALER1, null));
+ when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER0)))
+ .thenReturn(DIALER2);
+ mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER1));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER2);
+ }
+
+ @SmallTest
+ public void testRandomOtherPackageChanged() {
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+
+ Intent packageChangeIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED,
+ Uri.fromParts("package", "red.orange.blue", null));
+ mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER1));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+ }
+
+ @SmallTest
+ public void testPackageRemovedWithoutReplace() {
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+
+ Intent packageChangeIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED,
+ Uri.fromParts("package", DIALER1, null));
+ packageChangeIntent.putExtra(Intent.EXTRA_REPLACING, false);
+
+ mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER1));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+ }
+
+ @SmallTest
+ public void testPackageAdded() {
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+
+ Intent packageChangeIntent = new Intent(Intent.ACTION_PACKAGE_ADDED,
+ Uri.fromParts("package", "ppp.qqq.zzz", null));
+
+ mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER1));
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+ }
+
+ @SmallTest
+ public void testPackageRemovedWithReplace() {
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+
+ Intent packageChangeIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED,
+ Uri.fromParts("package", DIALER1, null));
+ packageChangeIntent.putExtra(Intent.EXTRA_REPLACING, true);
+
+ mPackageChangeReceiver.onReceive(mContext, packageChangeIntent);
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER1));
+ verify(mMockDefaultDialerManager, times(1))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+ }
+
+ @SmallTest
+ public void testDefaultDialerSettingChanged() {
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER1);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER3);
+
+ when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER0)))
+ .thenReturn(DIALER2);
+ when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER1)))
+ .thenReturn(DIALER2);
+ when(mMockDefaultDialerManager.getDefaultDialerApplication(any(Context.class), eq(USER2)))
+ .thenReturn(DIALER2);
+ mDefaultDialerSettingObserver.onChange(false);
+
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER0));
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+ verify(mMockDefaultDialerManager, times(2))
+ .getDefaultDialerApplication(any(Context.class), eq(USER2));
+
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER0), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER1), DIALER2);
+ assertEquals(mDefaultDialerCache.getDefaultDialerApplication(USER2), DIALER2);
+ }
+}