ShortcutManagerTest8.java revision a6be88a10d6f6391b09f626ead051d0c698fb2d1
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 */
16package com.android.server.pm;
17
18import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
19import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertForLauncherCallbackNoThrow;
20import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
21import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
22
23import static org.mockito.Matchers.any;
24import static org.mockito.Matchers.eq;
25import static org.mockito.Mockito.times;
26import static org.mockito.Mockito.verify;
27
28import android.annotation.Nullable;
29import android.app.PendingIntent;
30import android.content.ComponentName;
31import android.content.Intent;
32import android.content.IntentSender;
33import android.content.pm.LauncherApps;
34import android.content.pm.LauncherApps.PinItemRequest;
35import android.content.pm.ShortcutInfo;
36import android.content.pm.ShortcutManager;
37import android.graphics.drawable.Icon;
38import android.os.UserHandle;
39import android.test.MoreAsserts;
40import android.test.suitebuilder.annotation.SmallTest;
41import android.util.Log;
42import android.util.Pair;
43
44import com.android.frameworks.servicestests.R;
45
46import org.mockito.ArgumentCaptor;
47
48/**
49 * Tests for {@link ShortcutManager#requestPinShortcut} and relevant APIs.
50 *
51 m FrameworksServicesTests &&
52 adb install \
53 -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
54 adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest8 \
55 -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
56
57 * TODO for CTS
58 * - Foreground check.
59 * - Reading icons from requested shortcuts.
60 * - Invalid pre-approved token.
61 */
62@SmallTest
63public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
64    private ShortcutRequestPinProcessor mProcessor;
65
66    @Override
67    protected void initService() {
68        super.initService();
69        mProcessor = mService.getShortcutRequestPinProcessorForTest();
70    }
71
72    @Override
73    protected void setCaller(String packageName, int userId) {
74        super.setCaller(packageName, userId);
75
76        // Note during this test, assume all callers are in the foreground by default.
77        makeCallerForeground();
78    }
79
80    public void testGetParentOrSelfUserId() {
81        assertEquals(USER_0, mService.getParentOrSelfUserId(USER_0));
82        assertEquals(USER_10, mService.getParentOrSelfUserId(USER_10));
83        assertEquals(USER_11, mService.getParentOrSelfUserId(USER_11));
84        assertEquals(USER_0, mService.getParentOrSelfUserId(USER_P0));
85    }
86
87    public void testIsRequestPinShortcutSupported() {
88        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
89        setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
90
91        Pair<ComponentName, Integer> actual;
92        // User 0
93        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_0);
94
95        assertEquals(LAUNCHER_1, actual.first.getPackageName());
96        assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
97        assertEquals(USER_0, (int) actual.second);
98
99        // User 10
100        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
101
102        assertEquals(LAUNCHER_2, actual.first.getPackageName());
103        assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
104        assertEquals(USER_10, (int) actual.second);
105
106        // User P0 -> managed profile, return user-0's launcher.
107        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0);
108
109        assertEquals(LAUNCHER_1, actual.first.getPackageName());
110        assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
111        assertEquals(USER_0, (int) actual.second);
112
113        // Check from the public API.
114        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
115            assertTrue(mManager.isRequestPinShortcutSupported());
116        });
117        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
118            assertTrue(mManager.isRequestPinShortcutSupported());
119        });
120        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
121            assertTrue(mManager.isRequestPinShortcutSupported());
122        });
123        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
124            assertTrue(mManager.isRequestPinShortcutSupported());
125        });
126
127        // Now, USER_0's launcher no longer has a confirm activity.
128        mPinConfirmActivityFetcher = (packageName, userId) ->
129                !LAUNCHER_2.equals(packageName)
130                        ? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
131
132        // User 10 -- still has confirm activity.
133        actual = mProcessor.getRequestPinShortcutConfirmationActivity(USER_10);
134
135        assertEquals(LAUNCHER_2, actual.first.getPackageName());
136        assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
137        assertEquals(USER_10, (int) actual.second);
138
139        // But user-0 and user p0 no longer has a confirmation activity.
140        assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_0));
141        assertNull(mProcessor.getRequestPinShortcutConfirmationActivity(USER_P0));
142
143        // Check from the public API.
144        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
145            assertFalse(mManager.isRequestPinShortcutSupported());
146        });
147        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
148            assertFalse(mManager.isRequestPinShortcutSupported());
149        });
150        runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
151            assertTrue(mManager.isRequestPinShortcutSupported());
152        });
153        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
154            assertFalse(mManager.isRequestPinShortcutSupported());
155        });
156    }
157
158    public void testRequestPinShortcut_notSupported() {
159        // User-0's launcher has no confirmation activity.
160        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
161
162        mPinConfirmActivityFetcher = (packageName, userId) ->
163                !LAUNCHER_2.equals(packageName)
164                        ? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
165
166        runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
167            ShortcutInfo s1 = makeShortcut("s1");
168
169            assertFalse(mManager.requestPinShortcut(s1,
170                    /*PendingIntent=*/ null));
171
172            verify(mServiceContext, times(0))
173                    .startActivityAsUser(any(Intent.class), any(UserHandle.class));
174            verify(mServiceContext, times(0))
175                    .sendIntentSender(any(IntentSender.class));
176        });
177
178        runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
179            ShortcutInfo s1 = makeShortcut("s1");
180
181            assertFalse(mManager.requestPinShortcut(s1,
182                    /*PendingIntent=*/ null));
183
184            verify(mServiceContext, times(0))
185                    .startActivityAsUser(any(Intent.class), any(UserHandle.class));
186            verify(mServiceContext, times(0))
187                    .sendIntentSender(any(IntentSender.class));
188        });
189
190        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
191            ShortcutInfo s1 = makeShortcut("s1");
192
193            assertFalse(mManager.requestPinShortcut(s1,
194                    /*PendingIntent=*/ null));
195
196            verify(mServiceContext, times(0))
197                    .startActivityAsUser(any(Intent.class), any(UserHandle.class));
198            verify(mServiceContext, times(0))
199                    .sendIntentSender(any(IntentSender.class));
200        });
201    }
202
203    private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
204        assertEquals(LauncherApps.ACTION_CONFIRM_PIN_ITEM, actualIntent.getAction());
205        assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
206        assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
207                actualIntent.getComponent().getClassName());
208        assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK,
209                actualIntent.getFlags());
210    }
211
212    public void testNotForeground() {
213        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
214
215        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
216            makeCallerBackground();
217
218            assertExpectException(IllegalStateException.class, "foreground activity", () -> {
219                assertTrue(mManager.requestPinShortcut(makeShortcut("s1"),
220                        /* resultIntent= */ null));
221            });
222
223            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
224            verify(mServiceContext, times(0)).startActivityAsUser(
225                    any(Intent.class), any(UserHandle.class));
226        });
227    }
228
229    private void assertPinItemRequest(PinItemRequest actualRequest) {
230        assertNotNull(actualRequest);
231        assertEquals(PinItemRequest.REQUEST_TYPE_SHORTCUT, actualRequest.getRequestType());
232
233        Log.i(TAG, "Requested shortcut: " + actualRequest.getShortcutInfo().toInsecureString());
234    }
235
236    /**
237     * Basic flow:
238     * - Launcher supports the feature.
239     * - Shortcut doesn't pre-exist.
240     */
241    private void checkRequestPinShortcut(@Nullable PendingIntent resultIntent) {
242        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
243        setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
244
245        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
246
247        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
248            /// Create a shortcut with no target activity.
249            final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext, "s1")
250                    .setShortLabel("Title-" + "s1")
251                    .setIcon(res32x32)
252                    .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class));
253            final ShortcutInfo s = b.build();
254
255            assertNull(s.getActivity());
256
257            assertTrue(mManager.requestPinShortcut(s,
258                    resultIntent == null ? null : resultIntent.getIntentSender()));
259
260            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
261
262            // Shortcut shouldn't be registered yet.
263            assertWith(getCallerShortcuts())
264                    .isEmpty();
265        });
266
267        runWithCaller(LAUNCHER_1, USER_0, () -> {
268            // Check the intent passed to startActivityAsUser().
269            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
270
271            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
272
273            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
274
275            // Check the request object.
276            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
277
278            assertPinItemRequest(request);
279
280            assertWith(request.getShortcutInfo())
281                    .haveIds("s1")
282                    .areAllOrphan()
283                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, MAIN_ACTIVITY_CLASS))
284                    .areAllWithNoIntent();
285
286            assertAllHaveIcon(list(request.getShortcutInfo()));
287
288            // Accept the request.
289            assertForLauncherCallbackNoThrow(mLauncherApps,
290                    () -> assertTrue(request.accept()))
291                    .assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0)
292                    .haveIds("s1");
293        });
294
295        // This method is always called, even with PI == null.
296        if (resultIntent == null) {
297            verify(mServiceContext, times(1)).sendIntentSender(eq(null));
298        } else {
299            verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
300        }
301
302        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
303            assertWith(getCallerShortcuts())
304                    .haveIds("s1")
305                    .areAllNotDynamic()
306                    .areAllEnabled()
307                    .areAllPinned()
308                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, MAIN_ACTIVITY_CLASS))
309                    .areAllWithIntent();
310        });
311    }
312
313    public void testRequestPinShortcut() {
314        checkRequestPinShortcut(/* resultIntent=*/ null);
315    }
316
317    public void testRequestPinShortcut_withCallback() {
318        final PendingIntent resultIntent =
319                PendingIntent.getActivity(getTestContext(), 0, new Intent(), 0);
320
321        checkRequestPinShortcut(resultIntent);
322    }
323
324    public void testRequestPinShortcut_explicitTargetActivity() {
325        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
326        setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
327
328        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
329            ShortcutInfo s1 = makeShortcutWithActivity("s1",
330                    new ComponentName(CALLING_PACKAGE_1, "different_activity"));
331
332            assertTrue(mManager.requestPinShortcut(s1, null));
333
334            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
335
336            // Shortcut shouldn't be registered yet.
337            assertWith(getCallerShortcuts())
338                    .isEmpty();
339        });
340
341        runWithCaller(LAUNCHER_1, USER_0, () -> {
342            // Check the intent passed to startActivityAsUser().
343            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
344
345            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
346
347            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
348
349            // Check the request object.
350            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
351
352            assertPinItemRequest(request);
353
354            assertWith(request.getShortcutInfo())
355                    .haveIds("s1")
356                    .areAllOrphan()
357                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "different_activity"))
358                    .areAllWithNoIntent();
359
360            // Accept the request.
361            assertForLauncherCallbackNoThrow(mLauncherApps,
362                    () -> assertTrue(request.accept()))
363                    .assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0)
364                    .haveIds("s1");
365        });
366
367        verify(mServiceContext, times(1)).sendIntentSender(eq(null));
368
369        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
370            assertWith(getCallerShortcuts())
371                    .haveIds("s1")
372                    .areAllNotDynamic()
373                    .areAllEnabled()
374                    .areAllPinned()
375                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "different_activity"))
376                    .areAllWithIntent();
377        });
378    }
379
380    public void testRequestPinShortcut_wrongTargetActivity() {
381        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
382
383        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
384            // Create dynamic shortcut
385            ShortcutInfo s1 = makeShortcutWithActivity("s1",
386                    new ComponentName("wrong_package", "different_activity"));
387
388            assertExpectException(IllegalStateException.class, "not belong to package", () -> {
389                assertTrue(mManager.requestPinShortcut(s1, /* resultIntent=*/ null));
390            });
391
392            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
393            verify(mServiceContext, times(0)).startActivityAsUser(
394                    any(Intent.class), any(UserHandle.class));
395        });
396    }
397
398    public void testRequestPinShortcut_noTargetActivity_noMainActivity() {
399        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
400        setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
401
402        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
403            /// Create a shortcut with no target activity.
404            final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext, "s1")
405                    .setShortLabel("Title-" + "s1")
406                    .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class));
407            final ShortcutInfo s = b.build();
408
409            assertNull(s.getActivity());
410
411            // Caller has no main activity.
412            mMainActivityFetcher = (packageName, userId) -> null;
413
414            assertTrue(mManager.requestPinShortcut(s, null));
415
416            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
417
418            // Shortcut shouldn't be registered yet.
419            assertWith(getCallerShortcuts())
420                    .isEmpty();
421        });
422
423        runWithCaller(LAUNCHER_1, USER_0, () -> {
424            // Check the intent passed to startActivityAsUser().
425            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
426
427            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
428
429            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
430
431            // Check the request object.
432            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
433
434            assertPinItemRequest(request);
435
436            assertWith(request.getShortcutInfo())
437                    .haveIds("s1")
438                    .areAllOrphan()
439                    .areAllWithNoActivity() // Activity is not set; expected.
440                    .areAllWithNoIntent();
441
442            // Accept the request.
443            assertForLauncherCallbackNoThrow(mLauncherApps,
444                    () -> assertTrue(request.accept()))
445                    .assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0)
446                    .haveIds("s1");
447        });
448
449        verify(mServiceContext, times(1)).sendIntentSender(eq(null));
450
451        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
452            assertWith(getCallerShortcuts())
453                    .haveIds("s1")
454                    .areAllNotDynamic()
455                    .areAllEnabled()
456                    .areAllPinned()
457                    .areAllWithNoActivity() // Activity is not set; expected.
458                    .areAllWithIntent();
459        });
460
461    }
462
463    public void testRequestPinShortcut_dynamicExists() {
464        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
465
466        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
467
468        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
469            // Create dynamic shortcut
470            ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32);
471            assertTrue(mManager.setDynamicShortcuts(list(s1)));
472
473            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
474                    /* resultIntent=*/ null));
475
476            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
477
478            assertWith(getCallerShortcuts())
479                    .haveIds("s1")
480                    .areAllDynamic()
481                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
482                    .areAllNotPinned();
483        });
484
485        runWithCaller(LAUNCHER_1, USER_0, () -> {
486            // Check the intent passed to startActivityAsUser().
487            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
488
489            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
490
491            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
492
493            // Check the request object.
494            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
495
496            assertPinItemRequest(request);
497
498            assertWith(request.getShortcutInfo())
499                    .haveIds("s1")
500                    .areAllDynamic()
501                    .areAllNotPinned()
502                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
503                    .areAllWithNoIntent();
504
505            assertAllHaveIcon(list(request.getShortcutInfo()));
506
507            // Accept the request.
508            assertTrue(request.accept());
509        });
510
511        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
512            assertWith(getCallerShortcuts())
513                    .haveIds("s1")
514                    .areAllDynamic()
515                    .areAllEnabled()
516                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
517                    .areAllPinned();
518        });
519    }
520
521    public void testRequestPinShortcut_manifestExists() {
522        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
523
524        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
525            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
526
527            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
528                    /* resultIntent=*/ null));
529
530            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
531
532            assertWith(getCallerShortcuts())
533                    .haveIds("ms1")
534                    .areAllManifest()
535                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
536                            ShortcutActivity.class.getName()))
537                    .areAllNotPinned();
538        });
539
540        runWithCaller(LAUNCHER_1, USER_0, () -> {
541            // Check the intent passed to startActivityAsUser().
542            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
543
544            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
545
546            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
547
548            // Check the request object.
549            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
550
551            assertPinItemRequest(request);
552
553            assertWith(request.getShortcutInfo())
554                    .haveIds("ms1")
555                    .areAllManifest()
556                    .areAllNotPinned()
557                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
558                            ShortcutActivity.class.getName()))
559                    .areAllWithNoIntent();
560
561            assertAllHaveIcon(list(request.getShortcutInfo()));
562
563            // Accept the request.
564            assertTrue(request.accept());
565        });
566
567        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
568            assertWith(getCallerShortcuts())
569                    .haveIds("ms1")
570                    .areAllManifest()
571                    .areAllEnabled()
572                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
573                            ShortcutActivity.class.getName()))
574                    .areAllPinned();
575        });
576    }
577
578    public void testRequestPinShortcut_dynamicExists_alreadyPinned() {
579        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
580
581        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
582            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
583        });
584
585        runWithCaller(LAUNCHER_1, USER_0, () -> {
586            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
587        });
588
589        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
590            assertWith(getCallerShortcuts())
591                    .haveIds("s1")
592                    .areAllDynamic()
593                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
594                    .areAllPinned();
595
596            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
597                    /* resultIntent=*/ null));
598
599            // The intent should be sent right away.
600            verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
601        });
602    }
603
604    public void testRequestPinShortcut_manifestExists_alreadyPinned() {
605        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
606
607        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
608            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
609        });
610
611        runWithCaller(LAUNCHER_1, USER_0, () -> {
612            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
613        });
614
615        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
616            assertWith(getCallerShortcuts())
617                    .haveIds("ms1")
618                    .areAllManifest()
619                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
620                            ShortcutActivity.class.getName()))
621                    .areAllPinned();
622
623            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
624                    /* resultIntent=*/ null));
625
626            // The intent should be sent right away.
627            verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
628        });
629    }
630
631    public void testRequestPinShortcut_wasDynamic_alreadyPinned() {
632        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
633
634        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
635            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
636        });
637
638        runWithCaller(LAUNCHER_1, USER_0, () -> {
639            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
640        });
641
642        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
643            mManager.removeAllDynamicShortcuts();
644            assertWith(getCallerShortcuts())
645                    .haveIds("s1")
646                    .areAllNotDynamic()
647                    .areAllEnabled()
648                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
649                    .areAllPinned();
650
651            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
652                    /* resultIntent=*/ null));
653
654            // The intent should be sent right away.
655            verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
656        });
657    }
658
659    public void testRequestPinShortcut_wasDynamic_disabled_alreadyPinned() {
660        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
661
662        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
663            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
664        });
665
666        runWithCaller(LAUNCHER_1, USER_0, () -> {
667            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
668        });
669
670        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
671            mManager.disableShortcuts(list("s1"));
672
673            assertWith(getCallerShortcuts())
674                    .haveIds("s1")
675                    .areAllNotDynamic()
676                    .areAllDisabled()
677                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
678                    .areAllPinned();
679
680            assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> {
681                mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
682                        /* resultIntent=*/ null);
683            });
684
685            // Shouldn't be called.
686            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
687        });
688    }
689
690    public void testRequestPinShortcut_wasManifest_alreadyPinned() {
691        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
692
693        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
694            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
695        });
696
697        runWithCaller(LAUNCHER_1, USER_0, () -> {
698            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
699        });
700
701        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
702            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
703
704            assertWith(getCallerShortcuts())
705                    .haveIds("ms1")
706                    .areAllNotManifest()
707                    .areAllDisabled()
708                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
709                            ShortcutActivity.class.getName()))
710                    .areAllPinned();
711
712            assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> {
713                mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
714                        /* resultIntent=*/ null);
715            });
716
717            // Shouldn't be called.
718            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
719        });
720    }
721
722    public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() {
723        // Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
724
725        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
726            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
727        });
728
729        runWithCaller(LAUNCHER_2, USER_0, () -> {
730            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
731        });
732
733        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
734
735        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
736            assertWith(getCallerShortcuts())
737                    .haveIds("s1")
738                    .areAllDynamic()
739                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
740                    .areAllPinned();
741
742            // The shortcut is already pinned, but not by the current launcher, so it'll still
743            // invoke the whole flow.
744            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
745                    /* resultIntent=*/ null));
746
747            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
748        });
749
750        runWithCaller(LAUNCHER_1, USER_0, () -> {
751            // Check the intent passed to startActivityAsUser().
752            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
753
754            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
755
756            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
757
758            // Check the request object.
759            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
760
761            assertPinItemRequest(request);
762
763            assertWith(request.getShortcutInfo())
764                    .haveIds("s1")
765                    .areAllDynamic()
766                    .areAllNotPinned() // Note it's not pinned by this launcher.
767                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
768                    .areAllWithNoIntent();
769
770            // Accept the request.
771            assertTrue(request.accept());
772        });
773
774        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
775            assertWith(getCallerShortcuts())
776                    .haveIds("s1")
777                    .areAllDynamic()
778                    .areAllEnabled()
779                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
780                    .areAllPinned();
781        });
782    }
783
784    public void testRequestPinShortcut_manifestExists_alreadyPinnedByAnother() {
785        // Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
786
787        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
788            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
789        });
790
791        runWithCaller(LAUNCHER_2, USER_0, () -> {
792            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
793        });
794
795        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
796
797        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
798            assertWith(getCallerShortcuts())
799                    .haveIds("ms1")
800                    .areAllManifest()
801                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
802                            ShortcutActivity.class.getName()))
803                    .areAllPinned();
804
805            // The shortcut is already pinned, but not by the current launcher, so it'll still
806            // invoke the whole flow.
807            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
808                    /* resultIntent=*/ null));
809
810            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
811        });
812
813        runWithCaller(LAUNCHER_1, USER_0, () -> {
814            // Check the intent passed to startActivityAsUser().
815            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
816
817            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
818
819            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
820
821            // Check the request object.
822            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
823
824            assertPinItemRequest(request);
825
826            assertWith(request.getShortcutInfo())
827                    .haveIds("ms1")
828                    .areAllManifest()
829                    .areAllNotPinned() // Note it's not pinned by this launcher.
830                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
831                            ShortcutActivity.class.getName()))
832                    .areAllWithNoIntent();
833
834            // Accept the request.
835            assertTrue(request.accept());
836        });
837
838        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
839            assertWith(getCallerShortcuts())
840                    .haveIds("ms1")
841                    .areAllManifest()
842                    .areAllEnabled()
843                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
844                            ShortcutActivity.class.getName()))
845                    .areAllPinned();
846        });
847    }
848
849    /**
850     * The launcher already has a pinned shortuct.  The new one should be added, not replace
851     * the existing one.
852     */
853    public void testRequestPinShortcut_launcherAlreadyHasPinned() {
854        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
855
856        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
857            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"))));
858        });
859
860        runWithCaller(LAUNCHER_1, USER_0, () -> {
861            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_P0);
862        });
863
864        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
865            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
866                    /* resultIntent=*/ null));
867
868            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
869        });
870
871        runWithCaller(LAUNCHER_1, USER_0, () -> {
872            // Check the intent passed to startActivityAsUser().
873            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
874
875            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
876
877            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
878
879            // Check the request object.
880            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
881
882            assertPinItemRequest(request);
883
884            assertWith(request.getShortcutInfo())
885                    .haveIds("s1")
886                    .areAllDynamic()
887                    .areAllNotPinned()
888                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
889                    .areAllWithNoIntent();
890
891            // Accept the request.
892            assertTrue(request.accept());
893
894            assertWith(getShortcutAsLauncher(USER_P0))
895                    .haveIds("s1", "s2")
896                    .areAllDynamic()
897                    .areAllEnabled()
898                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
899                    .areAllPinned();
900        });
901
902        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
903            assertWith(getCallerShortcuts())
904                    .haveIds("s1", "s2")
905                    .areAllDynamic()
906                    .areAllEnabled()
907                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
908                    .areAllPinned();
909        });
910    }
911
912    /**
913     * When trying to pin an existing shortcut, the new fields shouldn't override existing fields.
914     */
915    public void testRequestPinShortcut_dynamicExists_titleWontChange() {
916        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
917
918        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
919
920        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
921            // Create dynamic shortcut
922            ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32);
923            assertTrue(mManager.setDynamicShortcuts(list(s1)));
924
925            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "xxx"),
926                    /* resultIntent=*/ null));
927
928            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
929
930            assertWith(getCallerShortcuts())
931                    .haveIds("s1")
932                    .areAllDynamic()
933                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
934                    .areAllNotPinned();
935        });
936
937        runWithCaller(LAUNCHER_1, USER_0, () -> {
938            // Check the intent passed to startActivityAsUser().
939            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
940
941            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
942
943            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
944
945            // Check the request object.
946            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
947
948            assertPinItemRequest(request);
949
950            assertWith(request.getShortcutInfo())
951                    .haveIds("s1")
952                    .areAllDynamic()
953                    .areAllNotPinned()
954                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
955                    .areAllWithNoIntent();
956
957            assertAllHaveIcon(list(request.getShortcutInfo()));
958
959            // Accept the request.
960            assertTrue(request.accept());
961        });
962
963        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
964            assertWith(getCallerShortcuts())
965                    .haveIds("s1")
966                    .areAllDynamic()
967                    .areAllEnabled()
968                    .areAllPinned()
969                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
970                    .forShortcutWithId("s1", (si) -> {
971                        // Still the original title.
972                        assertEquals("Title-s1", si.getShortLabel());
973                    });
974        });
975    }
976
977    /**
978     * When trying to pin an existing shortcut, the new fields shouldn't override existing fields.
979     */
980    public void testRequestPinShortcut_manifestExists_titleWontChange() {
981        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
982
983        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
984            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
985
986            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "xxx"),
987                    /* resultIntent=*/ null));
988
989            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
990
991            assertWith(getCallerShortcuts())
992                    .haveIds("ms1")
993                    .areAllManifest()
994                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
995                            ShortcutActivity.class.getName()))
996                    .areAllNotPinned();
997        });
998
999        runWithCaller(LAUNCHER_1, USER_0, () -> {
1000            // Check the intent passed to startActivityAsUser().
1001            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1002
1003            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1004
1005            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
1006
1007            // Check the request object.
1008            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1009
1010            assertPinItemRequest(request);
1011
1012            assertWith(request.getShortcutInfo())
1013                    .haveIds("ms1")
1014                    .areAllManifest()
1015                    .areAllNotPinned()
1016                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
1017                            ShortcutActivity.class.getName()))
1018                    .areAllWithNoIntent();
1019
1020            assertAllHaveIcon(list(request.getShortcutInfo()));
1021
1022            // Accept the request.
1023            assertTrue(request.accept());
1024        });
1025
1026        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1027            assertWith(getCallerShortcuts())
1028                    .haveIds("ms1")
1029                    .areAllManifest()
1030                    .areAllEnabled()
1031                    .areAllPinned()
1032                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
1033                            ShortcutActivity.class.getName()))
1034                    .forShortcutWithId("ms1", (si) -> {
1035                        // Still the original title.
1036                        // Title should be something like:
1037                        // "string-com.android.test.1-user:20-res:2131034112/en"
1038                        MoreAsserts.assertContainsRegex("^string-", si.getShortLabel().toString());
1039                    });
1040        });
1041    }
1042
1043    /**
1044     * The dynamic shortcut existed, but before accepting(), it's removed.  Because the request
1045     * has a partial shortcut, accept() should fail.
1046     */
1047    public void testRequestPinShortcut_dynamicExists_thenRemoved_error() {
1048        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1049
1050        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1051            // Create dynamic shortcut
1052            ShortcutInfo s1 = makeShortcut("s1");
1053            assertTrue(mManager.setDynamicShortcuts(list(s1)));
1054
1055            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
1056                    /* resultIntent=*/ null));
1057
1058            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1059
1060            mManager.removeAllDynamicShortcuts();
1061
1062            assertWith(getCallerShortcuts())
1063                    .isEmpty();
1064        });
1065
1066        runWithCaller(LAUNCHER_1, USER_0, () -> {
1067            // Check the intent passed to startActivityAsUser().
1068            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1069
1070            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1071
1072            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
1073
1074            // Check the request object.
1075            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1076
1077            assertPinItemRequest(request);
1078
1079            assertWith(request.getShortcutInfo())
1080                    .haveIds("s1")
1081                    .areAllDynamic()
1082                    .areAllNotPinned()
1083                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
1084                    .areAllWithNoIntent();
1085
1086            // Accept the request -> should fail.
1087            assertForLauncherCallbackNoThrow(mLauncherApps,
1088                    () -> assertFalse(request.accept()))
1089                    .assertNoCallbackCalled();
1090        });
1091
1092        // Intent shouldn't be sent.
1093        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1094
1095        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1096            assertWith(getCallerShortcuts())
1097                    .isEmpty();
1098        });
1099    }
1100
1101    /**
1102     * The dynamic shortcut existed, but before accepting(), it's removed.  Because the request
1103     * has all the mandatory fields, we can go ahead and still publish it.
1104     */
1105    public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() {
1106        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1107
1108        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1109            // Create dynamic shortcut
1110            ShortcutInfo s1 = makeShortcut("s1");
1111            assertTrue(mManager.setDynamicShortcuts(list(s1)));
1112
1113            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "new"),
1114                    /* resultIntent=*/ null));
1115
1116            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1117
1118            mManager.removeAllDynamicShortcuts();
1119
1120            assertWith(getCallerShortcuts())
1121                    .isEmpty();
1122        });
1123
1124        runWithCaller(LAUNCHER_1, USER_0, () -> {
1125            // Check the intent passed to startActivityAsUser().
1126            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1127
1128            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1129
1130            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
1131
1132            // Check the request object.
1133            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1134
1135            assertPinItemRequest(request);
1136
1137            assertWith(request.getShortcutInfo())
1138                    .haveIds("s1")
1139                    .areAllDynamic()
1140                    .areAllNotPinned()
1141                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
1142                    .areAllWithNoIntent();
1143
1144            assertTrue(request.accept());
1145        });
1146
1147        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1148            assertWith(getCallerShortcuts())
1149                    .haveIds("s1")
1150                    .areAllFloating()
1151                    .forShortcutWithId("s1", si -> {
1152                        assertEquals("new", si.getShortLabel());
1153                    });
1154        });
1155    }
1156
1157    /**
1158     * The manifest shortcut existed, but before accepting(), it's removed.  Because the request
1159     * has a partial shortcut, accept() should fail.
1160     */
1161    public void testRequestPinShortcut_manifestExists_thenRemoved_error() {
1162        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1163
1164        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1165            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
1166
1167            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
1168                    /* resultIntent=*/ null));
1169
1170            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1171
1172            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
1173
1174            assertWith(getCallerShortcuts())
1175                    .isEmpty();
1176        });
1177
1178        runWithCaller(LAUNCHER_1, USER_0, () -> {
1179            // Check the intent passed to startActivityAsUser().
1180            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1181
1182            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1183
1184            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
1185
1186            // Check the request object.
1187            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1188
1189            assertPinItemRequest(request);
1190
1191            assertWith(request.getShortcutInfo())
1192                    .haveIds("ms1")
1193                    .areAllManifest()
1194                    .areAllNotPinned()
1195                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
1196                            ShortcutActivity.class.getName()))
1197                    .areAllWithNoIntent();
1198
1199            // Accept the request -> should fail.
1200            assertForLauncherCallbackNoThrow(mLauncherApps,
1201                    () -> assertFalse(request.accept()))
1202                    .assertNoCallbackCalled();
1203        });
1204
1205        // Intent shouldn't be sent.
1206        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1207
1208        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1209            assertWith(getCallerShortcuts())
1210                    .isEmpty();
1211        });
1212    }
1213
1214    /**
1215     * The manifest shortcut existed, but before accepting(), it's removed.  Because the request
1216     * has all the mandatory fields, we can go ahead and still publish it.
1217     */
1218    public void testRequestPinShortcut_manifestExists_thenRemoved_okay() {
1219        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1220
1221        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1222            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
1223
1224            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "new"),
1225                    /* resultIntent=*/ null));
1226
1227            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1228
1229            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
1230
1231            assertWith(getCallerShortcuts())
1232                    .isEmpty();
1233        });
1234
1235        runWithCaller(LAUNCHER_1, USER_0, () -> {
1236            // Check the intent passed to startActivityAsUser().
1237            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1238
1239            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1240
1241            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
1242
1243            // Check the request object.
1244            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1245
1246            assertPinItemRequest(request);
1247
1248            assertWith(request.getShortcutInfo())
1249                    .haveIds("ms1")
1250                    .areAllManifest()
1251                    .areAllNotPinned()
1252                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
1253                            ShortcutActivity.class.getName()))
1254                    .areAllWithNoIntent();
1255
1256
1257            assertTrue(request.accept());
1258        });
1259
1260        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1261            assertWith(getCallerShortcuts())
1262                    .haveIds("ms1")
1263                    .areAllMutable() // Note it's no longer immutable.
1264                    .areAllFloating()
1265
1266                    // Note it's the activity from makeShortcutWithShortLabel().
1267                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
1268                    .forShortcutWithId("ms1", si -> {
1269                        assertEquals("new", si.getShortLabel());
1270                    });
1271        });
1272    }
1273
1274    /**
1275     * The dynamic shortcut existed, but before accepting(), it's removed.  Because the request
1276     * has a partial shortcut, accept() should fail.
1277     */
1278    public void testRequestPinShortcut_dynamicExists_thenDisabled_error() {
1279        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1280
1281        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1282            ShortcutInfo s1 = makeShortcut("s1");
1283            assertTrue(mManager.setDynamicShortcuts(list(s1)));
1284
1285            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
1286                    /* resultIntent=*/ null));
1287
1288            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1289        });
1290
1291        // Then, pin by another launcher and disable it.
1292        // We have to pin it here so that disable() won't remove it.
1293        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
1294        runWithCaller(LAUNCHER_2, USER_0, () -> {
1295            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
1296        });
1297        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1298            mManager.disableShortcuts(list("s1"));
1299            assertWith(getCallerShortcuts())
1300                    .haveIds("s1")
1301                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
1302                    .areAllDisabled();
1303        });
1304
1305        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1306        runWithCaller(LAUNCHER_1, USER_0, () -> {
1307            // Check the intent passed to startActivityAsUser().
1308            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1309
1310            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1311
1312            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
1313
1314            // Check the request object.
1315            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1316
1317            assertPinItemRequest(request);
1318
1319            assertWith(request.getShortcutInfo())
1320                    .haveIds("s1")
1321                    .areAllDynamic()
1322                    .areAllNotPinned()
1323                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
1324                    .areAllWithNoIntent();
1325
1326            // Accept the request -> should fail.
1327            assertForLauncherCallbackNoThrow(mLauncherApps,
1328                    () -> assertFalse(request.accept()))
1329                    .assertNoCallbackCalled();
1330
1331            // Note s1 is floating and pinned by another launcher, so it shouldn't be
1332            // visible here.
1333            assertWith(getShortcutAsLauncher(USER_P0))
1334                    .isEmpty();
1335        });
1336
1337        // Intent shouldn't be sent.
1338        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1339
1340        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1341            assertWith(getCallerShortcuts())
1342                    .haveIds("s1")
1343                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
1344                    .areAllDisabled();
1345        });
1346    }
1347
1348    /**
1349     * The manifest shortcut existed, but before accepting(), it's removed.  Because the request
1350     * has a partial shortcut, accept() should fail.
1351     */
1352    public void testRequestPinShortcut_manifestExists_thenDisabled_error() {
1353        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1354
1355        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1356            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
1357
1358            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
1359                    /* resultIntent=*/ null));
1360
1361            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1362        });
1363
1364        // Then, pin by another launcher and disable it.
1365        // We have to pin it here so that disable() won't remove it.
1366        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
1367        runWithCaller(LAUNCHER_2, USER_0, () -> {
1368            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
1369        });
1370        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1371            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
1372            assertWith(getCallerShortcuts())
1373                    .haveIds("ms1")
1374                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
1375                            ShortcutActivity.class.getName()))
1376                    .areAllDisabled();
1377        });
1378
1379        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1380        runWithCaller(LAUNCHER_1, USER_0, () -> {
1381            // Check the intent passed to startActivityAsUser().
1382            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1383
1384            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1385
1386            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
1387
1388            // Check the request object.
1389            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1390
1391            assertPinItemRequest(request);
1392
1393            assertWith(request.getShortcutInfo())
1394                    .haveIds("ms1")
1395                    .areAllManifest()
1396                    .areAllNotPinned()
1397                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
1398                            ShortcutActivity.class.getName()))
1399                    .areAllWithNoIntent();
1400
1401            // Accept the request -> should fail.
1402            assertForLauncherCallbackNoThrow(mLauncherApps,
1403                    () -> assertFalse(request.accept()))
1404                    .assertNoCallbackCalled();
1405
1406            // Note ms1 is floating and pinned by another launcher, so it shouldn't be
1407            // visible here.
1408            assertWith(getShortcutAsLauncher(USER_P0))
1409                    .isEmpty();
1410        });
1411
1412        // Intent shouldn't be sent.
1413        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1414
1415        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1416            assertWith(getCallerShortcuts())
1417                    .haveIds("ms1")
1418                    .areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
1419                            ShortcutActivity.class.getName()))
1420                    .areAllDisabled();
1421        });
1422    }
1423
1424    public void testRequestPinShortcut_wrongLauncherCannotAccept() {
1425        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
1426
1427        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
1428            ShortcutInfo s1 = makeShortcut("s1");
1429            assertTrue(mManager.requestPinShortcut(s1, null));
1430            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
1431        });
1432
1433        final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
1434        verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
1435        final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
1436
1437        // Verify that other launcher can't use this request
1438        runWithCaller(LAUNCHER_1, USER_0, () -> {
1439            // Set some random caller UID.
1440            mInjectedCallingUid = 12345;
1441
1442            assertFalse(request.isValid());
1443            assertExpectException(SecurityException.class, "Calling uid mismatch", request::accept);
1444        });
1445
1446        // The default launcher can still use this request
1447        runWithCaller(LAUNCHER_1, USER_0, () -> {
1448            assertTrue(request.isValid());
1449            assertTrue(request.accept());
1450        });
1451    }
1452
1453    // TODO More tests:
1454
1455    // Cancel previous pending request and release memory?
1456
1457    // Check the launcher callback too.
1458
1459    // Missing fields -- pre and post, both.
1460}
1461