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