• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.contacts;
17 
18 import static org.hamcrest.MatcherAssert.assertThat;
19 import static org.hamcrest.Matchers.equalTo;
20 import static org.mockito.Matchers.anyString;
21 import static org.mockito.Matchers.eq;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.verify;
24 import static org.mockito.Mockito.when;
25 
26 import android.annotation.TargetApi;
27 import android.app.job.JobScheduler;
28 import android.content.ContentProvider;
29 import android.content.ContentResolver;
30 import android.content.Context;
31 import android.content.pm.ShortcutInfo;
32 import android.content.pm.ShortcutManager;
33 import android.database.Cursor;
34 import android.database.MatrixCursor;
35 import android.net.Uri;
36 import android.os.Build;
37 import android.provider.ContactsContract;
38 import android.provider.ContactsContract.Contacts;
39 import android.test.AndroidTestCase;
40 import android.test.mock.MockContentResolver;
41 import android.test.suitebuilder.annotation.SmallTest;
42 
43 import androidx.test.filters.SdkSuppress;
44 
45 import com.android.contacts.test.mocks.MockContentProvider;
46 
47 import org.hamcrest.BaseMatcher;
48 import org.hamcrest.Description;
49 import org.hamcrest.Matcher;
50 import org.hamcrest.Matchers;
51 import org.mockito.ArgumentCaptor;
52 
53 import java.lang.reflect.Method;
54 import java.util.Arrays;
55 import java.util.Collections;
56 import java.util.List;
57 
58 @TargetApi(Build.VERSION_CODES.N_MR1)
59 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.N_MR1)
60 @SmallTest
61 public class DynamicShortcutsTests extends AndroidTestCase {
62 
63 
64     @Override
tearDown()65     protected void tearDown() throws Exception {
66         super.tearDown();
67 
68         // Clean up the job if it was scheduled by these tests.
69         final JobScheduler scheduler = (JobScheduler) getContext()
70                 .getSystemService(Context.JOB_SCHEDULER_SERVICE);
71         scheduler.cancel(ContactsJobService.DYNAMIC_SHORTCUTS_JOB_ID);
72     }
73 
74     // Basic smoke test to make sure the queries executed by DynamicShortcuts are valid as well
75     // as the integration with ShortcutManager. Note that this may change the state of the shortcuts
76     // on the device it is executed on.
test_refresh_doesntCrash()77     public void test_refresh_doesntCrash() {
78         final DynamicShortcuts sut = new DynamicShortcuts(getContext());
79         sut.refresh();
80         // Pass because it didn't throw an exception.
81     }
82 
test_createShortcutFromRow_hasCorrectResult()83     public void test_createShortcutFromRow_hasCorrectResult() {
84         final DynamicShortcuts sut = createDynamicShortcuts();
85 
86         final Cursor row = queryResult(
87                 // ID, LOOKUP_KEY, DISPLAY_NAME_PRIMARY
88                 1l, "lookup_key", "John Smith"
89         );
90 
91         row.moveToFirst();
92         final ShortcutInfo shortcut = sut.builderForContactShortcut(row).build();
93 
94         assertEquals("lookup_key", shortcut.getId());
95         assertEquals(Contacts.getLookupUri(1, "lookup_key"), shortcut.getIntent().getData());
96         assertEquals(ContactsContract.QuickContact.ACTION_QUICK_CONTACT,
97                 shortcut.getIntent().getAction());
98         assertEquals("John Smith", shortcut.getShortLabel());
99         assertEquals("John Smith", shortcut.getLongLabel());
100         assertEquals(1l, shortcut.getExtras().getLong(Contacts._ID));
101     }
102 
test_builderForContactShortcut_returnsNullWhenNameIsNull()103     public void test_builderForContactShortcut_returnsNullWhenNameIsNull() {
104         final DynamicShortcuts sut = createDynamicShortcuts();
105 
106         final ShortcutInfo.Builder shortcut = sut.builderForContactShortcut(1l, "lookup_key", null);
107 
108         assertNull(shortcut);
109     }
110 
test_builderForContactShortcut_ellipsizesLongNamesForLabels()111     public void test_builderForContactShortcut_ellipsizesLongNamesForLabels() {
112         final DynamicShortcuts sut = createDynamicShortcuts();
113         sut.setShortLabelMaxLength(5);
114         sut.setLongLabelMaxLength(10);
115 
116         final ShortcutInfo shortcut = sut.builderForContactShortcut(1l, "lookup_key",
117                 "123456789 1011").build();
118 
119         assertEquals("1234…", shortcut.getShortLabel());
120         assertEquals("123456789…", shortcut.getLongLabel());
121     }
122 
test_updatePinned_disablesShortcutsForRemovedContacts()123     public void test_updatePinned_disablesShortcutsForRemovedContacts() throws Exception {
124         final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
125         when(mockShortcutManager.getPinnedShortcuts()).thenReturn(
126                 Collections.singletonList(makeDynamic(shortcutFor(1l, "key1", "name1"))));
127 
128         final DynamicShortcuts sut = createDynamicShortcuts(emptyResolver(), mockShortcutManager);
129 
130         sut.updatePinned();
131 
132         verify(mockShortcutManager).disableShortcuts(
133                 eq(Collections.singletonList("key1")), anyString());
134     }
135 
test_updatePinned_updatesExistingShortcutsWithMatchingKeys()136     public void test_updatePinned_updatesExistingShortcutsWithMatchingKeys() throws Exception {
137         final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
138         when(mockShortcutManager.getPinnedShortcuts()).thenReturn(
139                 Arrays.asList(
140                         makeDynamic(shortcutFor(1l, "key1", "name1")),
141                         makeDynamic(shortcutFor(2l, "key2", "name2")),
142                         makeDynamic(shortcutFor(3l, "key3", "name3"))
143                 ));
144 
145         final DynamicShortcuts sut = createDynamicShortcuts(resolverWithExpectedQueries(
146                 queryForSingleRow(Contacts.getLookupUri(1l, "key1"), 11l, "key1", "New Name1"),
147                 queryForSingleRow(Contacts.getLookupUri(2l, "key2"), 2l, "key2", "name2"),
148                 queryForSingleRow(Contacts.getLookupUri(3l, "key3"), 33l, "key3", "name3")
149         ), mockShortcutManager);
150 
151         sut.updatePinned();
152 
153         final ArgumentCaptor<List<ShortcutInfo>> updateArgs =
154                 ArgumentCaptor.forClass((Class) List.class);
155 
156         verify(mockShortcutManager).disableShortcuts(
157                 eq(Collections.<String>emptyList()), anyString());
158         verify(mockShortcutManager).updateShortcuts(updateArgs.capture());
159 
160         final List<ShortcutInfo> arg = updateArgs.getValue();
161         assertThat(arg.size(), equalTo(3));
162         assertThat(arg.get(0),
163                 isShortcutForContact(11l, "key1", "New Name1"));
164         assertThat(arg.get(1),
165                 isShortcutForContact(2l, "key2", "name2"));
166         assertThat(arg.get(2),
167                 isShortcutForContact(33l, "key3", "name3"));
168     }
169 
test_refresh_setsDynamicShortcutsToStrequentContacts()170     public void test_refresh_setsDynamicShortcutsToStrequentContacts() {
171         final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
172         when(mockShortcutManager.getPinnedShortcuts()).thenReturn(
173                 Collections.<ShortcutInfo>emptyList());
174         final DynamicShortcuts sut = createDynamicShortcuts(resolverWithExpectedQueries(
175                 queryFor(Contacts.CONTENT_STREQUENT_URI,
176                         1l, "starred_key", "starred name",
177                         2l, "freq_key", "freq name",
178                         3l, "starred_2", "Starred Two")), mockShortcutManager);
179 
180         sut.refresh();
181 
182         final ArgumentCaptor<List<ShortcutInfo>> updateArgs =
183                 ArgumentCaptor.forClass((Class) List.class);
184 
185         verify(mockShortcutManager).setDynamicShortcuts(updateArgs.capture());
186 
187         final List<ShortcutInfo> arg = updateArgs.getValue();
188         assertThat(arg.size(), equalTo(3));
189         assertThat(arg.get(0), isShortcutForContact(1l, "starred_key", "starred name"));
190         assertThat(arg.get(1), isShortcutForContact(2l, "freq_key", "freq name"));
191         assertThat(arg.get(2), isShortcutForContact(3l, "starred_2", "Starred Two"));
192     }
193 
test_refresh_skipsContactsWithNullName()194     public void test_refresh_skipsContactsWithNullName() {
195         final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
196         when(mockShortcutManager.getPinnedShortcuts()).thenReturn(
197                 Collections.<ShortcutInfo>emptyList());
198         final DynamicShortcuts sut = createDynamicShortcuts(resolverWithExpectedQueries(
199                 queryFor(Contacts.CONTENT_STREQUENT_URI,
200                         1l, "key1", "first",
201                         2l, "key2", "second",
202                         3l, "key3", null,
203                         4l, null, null,
204                         5l, "key5", "fifth",
205                         6l, "key6", "sixth")), mockShortcutManager);
206 
207         sut.refresh();
208 
209         final ArgumentCaptor<List<ShortcutInfo>> updateArgs =
210                 ArgumentCaptor.forClass((Class) List.class);
211 
212         verify(mockShortcutManager).setDynamicShortcuts(updateArgs.capture());
213 
214         final List<ShortcutInfo> arg = updateArgs.getValue();
215         assertThat(arg.size(), equalTo(3));
216         assertThat(arg.get(0), isShortcutForContact(1l, "key1", "first"));
217         assertThat(arg.get(1), isShortcutForContact(2l, "key2", "second"));
218         assertThat(arg.get(2), isShortcutForContact(5l, "key5", "fifth"));
219 
220 
221         // Also verify that it doesn't crash if there are fewer than 3 valid strequent contacts
222         createDynamicShortcuts(resolverWithExpectedQueries(
223                 queryFor(Contacts.CONTENT_STREQUENT_URI,
224                         1l, "key1", "first",
225                         2l, "key2", "second",
226                         3l, "key3", null,
227                         4l, null, null)), mock(ShortcutManager.class)).refresh();
228     }
229 
230 
test_handleFlagDisabled_stopsJob()231     public void test_handleFlagDisabled_stopsJob() {
232         final ShortcutManager mockShortcutManager = mock(ShortcutManager.class);
233         final JobScheduler mockJobScheduler = mock(JobScheduler.class);
234         final DynamicShortcuts sut = createDynamicShortcuts(emptyResolver(), mockShortcutManager,
235                 mockJobScheduler);
236 
237         sut.handleFlagDisabled();
238 
239         verify(mockJobScheduler).cancel(eq(ContactsJobService.DYNAMIC_SHORTCUTS_JOB_ID));
240     }
241 
242 
test_scheduleUpdateJob_schedulesJob()243     public void test_scheduleUpdateJob_schedulesJob() {
244         final DynamicShortcuts sut = new DynamicShortcuts(getContext());
245         sut.scheduleUpdateJob();
246         assertThat(DynamicShortcuts.isJobScheduled(getContext()), Matchers.is(true));
247     }
248 
isShortcutForContact(final long id, final String lookupKey, final String name)249     private Matcher<ShortcutInfo> isShortcutForContact(final long id,
250             final String lookupKey, final String name) {
251         return new BaseMatcher<ShortcutInfo>() {
252             @Override
253             public boolean matches(Object o) {
254                 if (!(o instanceof  ShortcutInfo)) return false;
255                 final ShortcutInfo other = (ShortcutInfo)o;
256                 return id == other.getExtras().getLong(Contacts._ID)
257                         && lookupKey.equals(other.getId())
258                         && name.equals(other.getLongLabel())
259                         && name.equals(other.getShortLabel());
260             }
261 
262             @Override
263             public void describeTo(Description description) {
264                 description.appendText("Should be a shortcut for contact with _ID=" + id +
265                         " lookup=" + lookupKey + " and display_name=" + name);
266             }
267         };
268     }
269 
270     private ShortcutInfo shortcutFor(long contactId, String lookupKey, String name) {
271         return new DynamicShortcuts(getContext())
272                 .builderForContactShortcut(contactId, lookupKey, name).build();
273     }
274 
275     private ContentResolver emptyResolver() {
276         final MockContentProvider provider = new MockContentProvider();
277         provider.expect(MockContentProvider.Query.forAnyUri())
278                 .withAnyProjection()
279                 .withAnySelection()
280                 .withAnySortOrder()
281                 .returnEmptyCursor();
282         return resolverWithContactsProvider(provider);
283     }
284 
285     private MockContentProvider.Query queryFor(Uri uri, Object... rows) {
286         final MockContentProvider.Query query = MockContentProvider.Query
287                 .forUrisMatching(uri.getAuthority(), uri.getPath())
288                 .withProjection(DynamicShortcuts.PROJECTION)
289                 .withAnySelection()
290                 .withAnySortOrder();
291 
292         populateQueryRows(query, DynamicShortcuts.PROJECTION.length, rows);
293         return query;
294     }
295 
296     private MockContentProvider.Query queryForSingleRow(Uri uri, Object... row) {
297         return new MockContentProvider.Query(uri)
298                 .withProjection(DynamicShortcuts.PROJECTION)
299                 .withAnySelection()
300                 .withAnySortOrder()
301                 .returnRow(row);
302     }
303 
304     private ContentResolver resolverWithExpectedQueries(MockContentProvider.Query... queries) {
305         final MockContentProvider provider = new MockContentProvider();
306         for (MockContentProvider.Query query : queries) {
307             provider.expect(query);
308         }
309         return resolverWithContactsProvider(provider);
310     }
311 
312     private ContentResolver resolverWithContactsProvider(ContentProvider provider) {
313         final MockContentResolver resolver = new MockContentResolver();
314         resolver.addProvider(ContactsContract.AUTHORITY, provider);
315         return resolver;
316     }
317 
318     private DynamicShortcuts createDynamicShortcuts() {
319         return createDynamicShortcuts(emptyResolver(), mock(ShortcutManager.class));
320     }
321 
322 
323     private DynamicShortcuts createDynamicShortcuts(ContentResolver resolver,
324             ShortcutManager shortcutManager) {
325         return createDynamicShortcuts(resolver, shortcutManager, mock(JobScheduler.class));
326     }
327 
328     private DynamicShortcuts createDynamicShortcuts(ContentResolver resolver,
329             ShortcutManager shortcutManager, JobScheduler jobScheduler) {
330         final DynamicShortcuts result = new DynamicShortcuts(getContext(), resolver,
331                 shortcutManager, jobScheduler);
332         // Use very long label limits to make checking shortcuts easier to understand
333         result.setShortLabelMaxLength(100);
334         result.setLongLabelMaxLength(100);
335         return result;
336     }
337 
338     private void populateQueryRows(MockContentProvider.Query query, int numColumns,
339             Object... rows) {
340         for (int i = 0; i < rows.length; i += numColumns) {
341             Object[] row = new Object[numColumns];
342             for (int j = 0; j < numColumns; j++) {
343                 row[j] = rows[i + j];
344             }
345             query.returnRow(row);
346         }
347     }
348 
349     private Cursor queryResult(Object... values) {
350         return queryResult(DynamicShortcuts.PROJECTION, values);
351     }
352 
353     // Ugly hack because the API is hidden. Alternative is to actually set the shortcut on the real
354     // ShortcutManager but this seems simpler for now.
355     private ShortcutInfo makeDynamic(ShortcutInfo shortcutInfo) throws Exception {
356         final Method addFlagsMethod = ShortcutInfo.class.getMethod("addFlags", int.class);
357         // 1 = FLAG_DYNAMIC
358         addFlagsMethod.invoke(shortcutInfo, 1);
359         return shortcutInfo;
360     }
361 
362     private Cursor queryResult(String[] columns, Object... values) {
363         MatrixCursor result = new MatrixCursor(new String[] {
364                 Contacts._ID, Contacts.LOOKUP_KEY,
365                 Contacts.DISPLAY_NAME_PRIMARY
366         });
367         for (int i = 0; i < values.length; i += columns.length) {
368             MatrixCursor.RowBuilder builder = result.newRow();
369             for (int j = 0; j < columns.length; j++) {
370                 builder.add(values[i + j]);
371             }
372         }
373         return result;
374     }
375 }
376