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