Home | History | Annotate | Download | only in pm
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.server.pm;
     17 
     18 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyOrNull;
     19 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
     20 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertForLauncherCallbackNoThrow;
     21 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
     22 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
     23 
     24 import static org.mockito.Matchers.any;
     25 import static org.mockito.Matchers.eq;
     26 import static org.mockito.Matchers.isNull;
     27 import static org.mockito.Matchers.notNull;
     28 import static org.mockito.Mockito.reset;
     29 import static org.mockito.Mockito.times;
     30 import static org.mockito.Mockito.verify;
     31 
     32 import android.annotation.Nullable;
     33 import android.app.PendingIntent;
     34 import android.content.ComponentName;
     35 import android.content.Intent;
     36 import android.content.IntentSender;
     37 import android.content.pm.LauncherApps;
     38 import android.content.pm.LauncherApps.PinItemRequest;
     39 import android.content.pm.ShortcutInfo;
     40 import android.content.pm.ShortcutManager;
     41 import android.graphics.drawable.Icon;
     42 import android.os.UserHandle;
     43 import android.test.MoreAsserts;
     44 import android.test.suitebuilder.annotation.SmallTest;
     45 import android.util.Log;
     46 import android.util.Pair;
     47 
     48 import com.android.frameworks.servicestests.R;
     49 
     50 import 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
     67 public 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