diff options
author | Marcus Hagerott <mhagerott@google.com> | 2016-09-14 08:34:29 -0700 |
---|---|---|
committer | Marcus Hagerott <mhagerott@google.com> | 2016-09-20 14:48:17 -0700 |
commit | c5083f9a9cf22f3b4acc2f8a6f098a37c6759800 (patch) | |
tree | c5ce011a67e3f24d2a381c81a23d3f1f5e9fd0bd /tests | |
parent | d5cbb9eff63386f884645fba40fed34dd03bd070 (diff) | |
download | packages_apps_Contacts-c5083f9a9cf22f3b4acc2f8a6f098a37c6759800.tar.gz packages_apps_Contacts-c5083f9a9cf22f3b4acc2f8a6f098a37c6759800.tar.bz2 packages_apps_Contacts-c5083f9a9cf22f3b4acc2f8a6f098a37c6759800.zip |
Add dynamic launcher shortcuts.
Currently the shortcuts are created for the top 3 contacts returned from
Contacts.CONTENT_STREQUENT_URI
Test:
Added unit tests for DynamicShortcuts but currently suppressed because they
require AndroidJUnitRunner
Manual:
* Use N_MR1 device with recent dogfood Nexus launcher installed.
* launch app
* star some contacts if needed
* press home
* long press launcher icon
* verify that starred contacts show in list of shortcuts
* unstar some contacts
* verify that shortcuts change
* pin a shortcut
* remove contact for pinned shortcut
* verify that pinned shortcut is disabled
* pin a shortcut
* change name of contact for pinned shortcut
* verify that name on pinned shortcut changes
Also prevent disambiguation dialog for other home screen shortcuts
Bug 30189449
Bug 31628994
Change-Id: Iace4b1c88b51ba1f7973c6f4ef90002fb92d0784
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Android.mk | 5 | ||||
-rw-r--r-- | tests/src/com/android/contacts/DynamicShortcutsTests.java | 307 | ||||
-rw-r--r-- | tests/src/com/android/contacts/common/test/mocks/MockContentProvider.java | 44 |
3 files changed, 350 insertions, 6 deletions
diff --git a/tests/Android.mk b/tests/Android.mk index 48a00f42a..1176687d3 100644 --- a/tests/Android.mk +++ b/tests/Android.mk @@ -17,8 +17,9 @@ LOCAL_INSTRUMENTATION_FOR := Contacts LOCAL_SDK_VERSION := current LOCAL_MIN_SDK_VERSION := 21 -LOCAL_STATIC_JAVA_LIBRARIES := \ - mockito-target +LOCAL_STATIC_JAVA_LIBRARIES += \ + hamcrest-library \ + mockito-target-minus-junit4 LOCAL_AAPT_FLAGS := \ --auto-add-overlay \ diff --git a/tests/src/com/android/contacts/DynamicShortcutsTests.java b/tests/src/com/android/contacts/DynamicShortcutsTests.java new file mode 100644 index 000000000..168b6465c --- /dev/null +++ b/tests/src/com/android/contacts/DynamicShortcutsTests.java @@ -0,0 +1,307 @@ +/* + * 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.contacts; + +import android.annotation.TargetApi; +import android.app.job.JobScheduler; +import android.content.ContentProvider; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.net.Uri; +import android.os.Build; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Contacts; +import android.support.test.filters.SdkSuppress; +import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; +import android.test.suitebuilder.annotation.SmallTest; +import android.test.suitebuilder.annotation.Suppress; + +import com.android.contacts.common.test.mocks.MockContentProvider; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.mockito.ArgumentCaptor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@TargetApi(Build.VERSION_CODES.N_MR1) +@SdkSuppress(minSdkVersion = Build.VERSION_CODES.N_MR1) +// TODO: need to switch to android.support.test.runner.AndroidJUnitRunner for the @SdkSuppress +// annotation to be respected. So for now we suppress this test to keep it from failing when run +// by the build system. +@Suppress +@SmallTest +public class DynamicShortcutsTests extends AndroidTestCase { + + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + // Clean up the job if it was scheduled by these tests. + final JobScheduler scheduler = (JobScheduler) getContext() + .getSystemService(Context.JOB_SCHEDULER_SERVICE); + scheduler.cancel(ContactsJobService.DYNAMIC_SHORTCUTS_JOB_ID); + } + + // Basic smoke test to make sure the queries executed by DynamicShortcuts are valid as well + // as the integration with ShortcutManager. Note that this may change the state of the shortcuts + // on the device it is executed on. + public void test_refresh_doesntCrash() { + final DynamicShortcuts sut = new DynamicShortcuts(getContext()); + sut.refresh(); + // Pass because it didn't throw an exception. + } + + public void test_createShortcutFromRow_hasCorrectResult() { + final DynamicShortcuts sut = createDynamicShortcuts(); + + final Cursor row = queryResult( + // ID, LOOKUP_KEY, DISPLAY_NAME_PRIMARY + 1l, "lookup_key", "John Smith" + ); + + row.moveToFirst(); + final ShortcutInfo shortcut = sut.builderForContactShortcut(row).build(); + + assertEquals("lookup_key", shortcut.getId()); + assertEquals(Contacts.getLookupUri(1, "lookup_key"), shortcut.getIntent().getData()); + assertEquals(ContactsContract.QuickContact.ACTION_QUICK_CONTACT, + shortcut.getIntent().getAction()); + assertEquals("John Smith", shortcut.getShortLabel()); + assertEquals("John Smith", shortcut.getLongLabel()); + assertEquals(1l, shortcut.getExtras().getLong(Contacts._ID)); + } + + public void test_builderForContactShortcut_ellipsizesLongNamesForLabels() { + final DynamicShortcuts sut = createDynamicShortcuts(); + sut.setShortLabelMaxLength(5); + sut.setLongLabelMaxLength(10); + + final ShortcutInfo shortcut = sut.builderForContactShortcut(1l, "lookup_key", + "123456789 1011").build(); + + assertEquals("1234…", shortcut.getShortLabel()); + assertEquals("123456789…", shortcut.getLongLabel()); + } + + public void test_updatePinned_disablesShortcutsForRemovedContacts() { + final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); + when(mockShortcutManager.getPinnedShortcuts()).thenReturn( + Collections.singletonList(shortcutFor(1l, "key1", "name1"))); + + final DynamicShortcuts sut = new DynamicShortcuts(getContext(), emptyResolver(), + mockShortcutManager); + + sut.updatePinned(); + + verify(mockShortcutManager).disableShortcuts( + eq(Collections.singletonList("key1")), anyString()); + } + + public void test_updatePinned_updatesExistingShortcutsWithMatchingKeys() { + final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); + when(mockShortcutManager.getPinnedShortcuts()).thenReturn( + Arrays.asList( + shortcutFor(1l, "key1", "name1"), + shortcutFor(2l, "key2", "name2"), + shortcutFor(3l, "key3", "name3") + )); + + final DynamicShortcuts sut = createDynamicShortcuts(resolverWithExpectedQueries( + queryForSingleRow(Contacts.getLookupUri(1l, "key1"), 11l, "key1", "New Name1"), + queryForSingleRow(Contacts.getLookupUri(2l, "key2"), 2l, "key2", "name2"), + queryForSingleRow(Contacts.getLookupUri(3l, "key3"), 33l, "key3", "name3") + ), mockShortcutManager); + + sut.updatePinned(); + + final ArgumentCaptor<List<ShortcutInfo>> updateArgs = + ArgumentCaptor.forClass((Class) List.class); + + verify(mockShortcutManager).disableShortcuts( + eq(Collections.<String>emptyList()), anyString()); + verify(mockShortcutManager).updateShortcuts(updateArgs.capture()); + + final List<ShortcutInfo> arg = updateArgs.getValue(); + assertThat(arg.size(), equalTo(3)); + assertThat(arg.get(0), + isShortcutForContact(11l, "key1", "New Name1")); + assertThat(arg.get(1), + isShortcutForContact(2l, "key2", "name2")); + assertThat(arg.get(2), + isShortcutForContact(33l, "key3", "name3")); + } + + public void test_refresh_setsDynamicShortcutsToStrequentContacts() { + final ShortcutManager mockShortcutManager = mock(ShortcutManager.class); + when(mockShortcutManager.getPinnedShortcuts()).thenReturn( + Collections.<ShortcutInfo>emptyList()); + final DynamicShortcuts sut = createDynamicShortcuts(resolverWithExpectedQueries( + queryFor(Contacts.CONTENT_STREQUENT_URI, + 1l, "starred_key", "starred name", + 2l, "freq_key", "freq name", + 3l, "starred_2", "Starred Two")), mockShortcutManager); + + sut.refresh(); + + final ArgumentCaptor<List<ShortcutInfo>> updateArgs = + ArgumentCaptor.forClass((Class) List.class); + + verify(mockShortcutManager).setDynamicShortcuts(updateArgs.capture()); + + final List<ShortcutInfo> arg = updateArgs.getValue(); + assertThat(arg.size(), equalTo(3)); + assertThat(arg.get(0), isShortcutForContact(1l, "starred_key", "starred name")); + assertThat(arg.get(1), isShortcutForContact(2l, "freq_key", "freq name")); + assertThat(arg.get(2), isShortcutForContact(3l, "starred_2", "Starred Two")); + } + + public void test_scheduleUpdateJob_schedulesJob() { + final DynamicShortcuts sut = createDynamicShortcuts(); + sut.scheduleUpdateJob(); + assertThat(DynamicShortcuts.isJobScheduled(getContext()), Matchers.is(true)); + } + + private Matcher<ShortcutInfo> isShortcutForContact(final long id, + final String lookupKey, final String name) { + return new BaseMatcher<ShortcutInfo>() { + @Override + public boolean matches(Object o) { + if (!(o instanceof ShortcutInfo)) return false; + final ShortcutInfo other = (ShortcutInfo)o; + return id == other.getExtras().getLong(Contacts._ID) + && lookupKey.equals(other.getId()) + && name.equals(other.getLongLabel()) + && name.equals(other.getShortLabel()); + } + + @Override + public void describeTo(Description description) { + description.appendText("Should be a shortcut for contact with _ID=" + id + + " lookup=" + lookupKey + " and display_name=" + name); + } + }; + } + + private ShortcutInfo shortcutFor(long contactId, String lookupKey, String name) { + return new DynamicShortcuts(getContext()) + .builderForContactShortcut(contactId, lookupKey, name).build(); + } + + private ContentResolver emptyResolver() { + final MockContentProvider provider = new MockContentProvider(); + provider.expect(MockContentProvider.Query.forAnyUri()) + .withAnyProjection() + .withAnySelection() + .withAnySortOrder() + .returnEmptyCursor(); + return resolverWithContactsProvider(provider); + } + + private MockContentProvider.Query queryFor(Uri uri, Object... rows) { + final MockContentProvider.Query query = MockContentProvider.Query + .forUrisMatching(uri.getAuthority(), uri.getPath()) + .withProjection(DynamicShortcuts.PROJECTION) + .withAnySelection() + .withAnySortOrder(); + + populateQueryRows(query, DynamicShortcuts.PROJECTION.length, rows); + return query; + } + + private MockContentProvider.Query queryForSingleRow(Uri uri, Object... row) { + return new MockContentProvider.Query(uri) + .withProjection(DynamicShortcuts.PROJECTION) + .withAnySelection() + .withAnySortOrder() + .returnRow(row); + } + + private ContentResolver resolverWithExpectedQueries(MockContentProvider.Query... queries) { + final MockContentProvider provider = new MockContentProvider(); + for (MockContentProvider.Query query : queries) { + provider.expect(query); + } + return resolverWithContactsProvider(provider); + } + + private ContentResolver resolverWithContactsProvider(ContentProvider provider) { + final MockContentResolver resolver = new MockContentResolver(); + resolver.addProvider(ContactsContract.AUTHORITY, provider); + return resolver; + } + + private DynamicShortcuts createDynamicShortcuts() { + return createDynamicShortcuts(emptyResolver(), mock(ShortcutManager.class)); + } + + private DynamicShortcuts createDynamicShortcuts(ContentResolver resolver, + ShortcutManager shortcutManager) { + final DynamicShortcuts result = new DynamicShortcuts(getContext(), resolver, + shortcutManager); + // Use very long label limits to make checking shortcuts easier to understand + result.setShortLabelMaxLength(100); + result.setLongLabelMaxLength(100); + return result; + } + + private void populateQueryRows(MockContentProvider.Query query, int numColumns, + Object... rows) { + for (int i = 0; i < rows.length; i += numColumns) { + Object[] row = new Object[numColumns]; + for (int j = 0; j < numColumns; j++) { + row[j] = rows[i + j]; + } + query.returnRow(row); + } + } + + private Cursor queryResult(Object... values) { + return queryResult(DynamicShortcuts.PROJECTION, values); + } + + private Cursor queryResult(String[] columns, Object... values) { + MatrixCursor result = new MatrixCursor(new String[] { + Contacts._ID, Contacts.LOOKUP_KEY, + Contacts.DISPLAY_NAME_PRIMARY + }); + for (int i = 0; i < values.length; i += columns.length) { + MatrixCursor.RowBuilder builder = result.newRow(); + for (int j = 0; j < columns.length; j++) { + builder.add(values[i + j]); + } + } + return result; + } +} diff --git a/tests/src/com/android/contacts/common/test/mocks/MockContentProvider.java b/tests/src/com/android/contacts/common/test/mocks/MockContentProvider.java index 335e8d2a9..336467da7 100644 --- a/tests/src/com/android/contacts/common/test/mocks/MockContentProvider.java +++ b/tests/src/com/android/contacts/common/test/mocks/MockContentProvider.java @@ -20,6 +20,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import android.content.ContentValues; +import android.content.UriMatcher; import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; @@ -43,6 +44,8 @@ public class MockContentProvider extends android.test.mock.MockContentProvider { public static class Query { private final Uri mUri; + private UriMatcher mMatcher; + private String[] mProjection; private String[] mDefaultProjection; private String mSelection; @@ -56,6 +59,15 @@ public class MockContentProvider extends android.test.mock.MockContentProvider { private boolean mExecuted; + private Query() { + mUri = null; + } + + private Query(UriMatcher matcher) { + mUri = null; + mMatcher = matcher; + } + public Query(Uri uri) { mUri = uri; } @@ -123,7 +135,11 @@ public class MockContentProvider extends android.test.mock.MockContentProvider { public boolean equals(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - if (!uri.equals(mUri)) { + if (mUri == null) { + if (mMatcher != null && mMatcher.match(uri) == UriMatcher.NO_MATCH) { + return false; + } + } else if (!uri.equals(mUri)) { return false; } @@ -169,6 +185,23 @@ public class MockContentProvider extends android.test.mock.MockContentProvider { } return cursor; } + + public static Query forAnyUri() { + return new Query(); + } + + public static Query forUrisMatching(UriMatcher matcher) { + return new Query(matcher); + } + + public static Query forUrisMatching(String authority, String... paths) { + final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); + for (int i = 0; i < paths.length; i++) { + matcher.addURI(authority, paths[i], i); + } + return new Query(matcher); + } + } public static class TypeQuery { @@ -443,12 +476,15 @@ public class MockContentProvider extends android.test.mock.MockContentProvider { return true; } - public Query expectQuery(Uri contentUri) { - Query query = new Query(contentUri); + public Query expect(Query query) { mExpectedQueries.add(query); return query; } + public Query expectQuery(Uri contentUri) { + return expect(new Query(contentUri)); + } + public void expectTypeQuery(Uri uri, String type) { mExpectedTypeQueries.put(uri, type); } @@ -598,7 +634,7 @@ public class MockContentProvider extends android.test.mock.MockContentProvider { private static String queryToString(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { StringBuilder sb = new StringBuilder(); - sb.append(uri).append(" "); + sb.append(uri == null ? "<Any Uri>" : uri).append(" "); if (projection != null) { sb.append(Arrays.toString(projection)); } else { |