blob: e9a329ca5a049cbcc5d429d5500901f648e0e250 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.anyOrNull;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertForLauncherCallbackNoThrow;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Matchers.notNull;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.PinItemRequest;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
import android.os.UserHandle;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.util.Pair;
import com.android.frameworks.servicestests.R;
import org.mockito.ArgumentCaptor;
/**
* Tests for {@link ShortcutManager#requestPinShortcut} and relevant APIs.
*
m FrameworksServicesTests &&
adb install \
-r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest8 \
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
* TODO for CTS
* - Foreground check.
* - Reading icons from requested shortcuts.
* - Invalid pre-approved token.
*/
@SmallTest
public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
private ShortcutRequestPinProcessor mProcessor;
@Override
protected void initService() {
super.initService();
mProcessor = mService.getShortcutRequestPinProcessorForTest();
}
@Override
protected void setCaller(String packageName, int userId) {
super.setCaller(packageName, userId);
// Note during this test, assume all callers are in the foreground by default.
makeCallerForeground();
}
public void testGetParentOrSelfUserId() {
assertEquals(USER_0, mService.getParentOrSelfUserId(USER_0));
assertEquals(USER_10, mService.getParentOrSelfUserId(USER_10));
assertEquals(USER_11, mService.getParentOrSelfUserId(USER_11));
assertEquals(USER_0, mService.getParentOrSelfUserId(USER_P0));
}
public void testIsRequestPinShortcutSupported() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
Pair<ComponentName, Integer> actual;
// User 0
actual = mProcessor.getRequestPinConfirmationActivity(USER_0,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_1, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_0, (int) actual.second);
// User 10
actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_2, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_10, (int) actual.second);
// User P0 -> managed profile, return user-0's launcher.
actual = mProcessor.getRequestPinConfirmationActivity(USER_P0,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_1, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_0, (int) actual.second);
// Check from the public API.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.isRequestPinShortcutSupported());
});
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
assertTrue(mManager.isRequestPinShortcutSupported());
});
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.isRequestPinShortcutSupported());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertTrue(mManager.isRequestPinShortcutSupported());
});
// Now, USER_0's launcher no longer has a confirm activity.
mPinConfirmActivityFetcher = (packageName, userId) ->
!LAUNCHER_2.equals(packageName)
? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
// User 10 -- still has confirm activity.
actual = mProcessor.getRequestPinConfirmationActivity(USER_10,
PinItemRequest.REQUEST_TYPE_SHORTCUT);
assertEquals(LAUNCHER_2, actual.first.getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS, actual.first.getClassName());
assertEquals(USER_10, (int) actual.second);
// But user-0 and user p0 no longer has a confirmation activity.
assertNull(mProcessor.getRequestPinConfirmationActivity(USER_0,
PinItemRequest.REQUEST_TYPE_SHORTCUT));
assertNull(mProcessor.getRequestPinConfirmationActivity(USER_P0,
PinItemRequest.REQUEST_TYPE_SHORTCUT));
// Check from the public API.
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertFalse(mManager.isRequestPinShortcutSupported());
});
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
assertFalse(mManager.isRequestPinShortcutSupported());
});
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.isRequestPinShortcutSupported());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertFalse(mManager.isRequestPinShortcutSupported());
});
}
public void testRequestPinShortcut_notSupported() {
// User-0's launcher has no confirmation activity.
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
mPinConfirmActivityFetcher = (packageName, userId) ->
!LAUNCHER_2.equals(packageName)
? null : new ComponentName(packageName, PIN_CONFIRM_ACTIVITY_CLASS);
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
ShortcutInfo s1 = makeShortcut("s1");
assertFalse(mManager.requestPinShortcut(s1,
/*PendingIntent=*/ null));
verify(mServiceContext, times(0))
.startActivityAsUser(any(Intent.class), any(UserHandle.class));
verify(mServiceContext, times(0))
.sendIntentSender(any(IntentSender.class));
});
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
ShortcutInfo s1 = makeShortcut("s1");
assertFalse(mManager.requestPinShortcut(s1,
/*PendingIntent=*/ null));
verify(mServiceContext, times(0))
.startActivityAsUser(any(Intent.class), any(UserHandle.class));
verify(mServiceContext, times(0))
.sendIntentSender(any(IntentSender.class));
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
ShortcutInfo s1 = makeShortcut("s1");
assertFalse(mManager.requestPinShortcut(s1,
/*PendingIntent=*/ null));
verify(mServiceContext, times(0))
.startActivityAsUser(any(Intent.class), any(UserHandle.class));
verify(mServiceContext, times(0))
.sendIntentSender(any(IntentSender.class));
});
}
private void assertPinItemRequestIntent(Intent actualIntent, String expectedPackage) {
assertEquals(LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT, actualIntent.getAction());
assertEquals(expectedPackage, actualIntent.getComponent().getPackageName());
assertEquals(PIN_CONFIRM_ACTIVITY_CLASS,
actualIntent.getComponent().getClassName());
assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK,
actualIntent.getFlags());
}
public void testNotForeground() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
makeCallerBackground();
assertExpectException(IllegalStateException.class, "foreground activity", () -> {
assertTrue(mManager.requestPinShortcut(makeShortcut("s1"),
/* resultIntent= */ null));
});
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
verify(mServiceContext, times(0)).startActivityAsUser(
any(Intent.class), any(UserHandle.class));
});
}
private void assertPinItemRequest(PinItemRequest actualRequest) {
assertNotNull(actualRequest);
assertEquals(PinItemRequest.REQUEST_TYPE_SHORTCUT, actualRequest.getRequestType());
Log.i(TAG, "Requested shortcut: " + actualRequest.getShortcutInfo().toInsecureString());
}
/**
* Basic flow:
* - Launcher supports the feature.
* - Shortcut doesn't pre-exist.
*/
private void checkRequestPinShortcut(@Nullable IntentSender resultIntent) {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
/// Create a shortcut with no target activity.
final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, "s1")
.setShortLabel("Title-" + "s1")
.setIcon(res32x32)
.setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class));
final ShortcutInfo s = b.build();
assertNull(s.getActivity());
assertTrue(mManager.requestPinShortcut(s, resultIntent));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
// Shortcut shouldn't be registered yet.
assertWith(getCallerShortcuts())
.isEmpty();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllOrphan()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, MAIN_ACTIVITY_CLASS))
.areAllWithNoIntent();
assertAllHaveIcon(list(request.getShortcutInfo()));
// Accept the request.
assertForLauncherCallbackNoThrow(mLauncherApps,
() -> assertTrue(request.accept()))
.assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0)
.haveIds("s1");
});
// This method is always called, even with PI == null.
if (resultIntent == null) {
verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
} else {
verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
}
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllNotDynamic()
.areAllEnabled()
.areAllPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, MAIN_ACTIVITY_CLASS))
.areAllWithIntent();
});
}
public void testRequestPinShortcut() {
checkRequestPinShortcut(/* resultIntent=*/ null);
}
private IntentSender makeResultIntent() {
return PendingIntent.getActivity(getTestContext(), 0, new Intent(), 0).getIntentSender();
}
public void testRequestPinShortcut_withCallback() {
checkRequestPinShortcut(makeResultIntent());
}
public void testRequestPinShortcut_explicitTargetActivity() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
ShortcutInfo s1 = makeShortcutWithActivity("s1",
new ComponentName(CALLING_PACKAGE_1, "different_activity"));
assertTrue(mManager.requestPinShortcut(s1, null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
// Shortcut shouldn't be registered yet.
assertWith(getCallerShortcuts())
.isEmpty();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllOrphan()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "different_activity"))
.areAllWithNoIntent();
// Accept the request.
assertForLauncherCallbackNoThrow(mLauncherApps,
() -> assertTrue(request.accept()))
.assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0)
.haveIds("s1");
});
verify(mServiceContext, times(1)).sendIntentSender(eq(null));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllNotDynamic()
.areAllEnabled()
.areAllPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "different_activity"))
.areAllWithIntent();
});
}
public void testRequestPinShortcut_wrongTargetActivity() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
// Create dynamic shortcut
ShortcutInfo s1 = makeShortcutWithActivity("s1",
new ComponentName("wrong_package", "different_activity"));
assertExpectException(IllegalStateException.class, "not belong to package", () -> {
assertTrue(mManager.requestPinShortcut(s1, /* resultIntent=*/ null));
});
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
verify(mServiceContext, times(0)).startActivityAsUser(
any(Intent.class), any(UserHandle.class));
});
}
public void testRequestPinShortcut_noTargetActivity_noMainActivity() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
/// Create a shortcut with no target activity.
final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, "s1")
.setShortLabel("Title-" + "s1")
.setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class));
final ShortcutInfo s = b.build();
assertNull(s.getActivity());
// Caller has no main activity.
mMainActivityFetcher = (packageName, userId) -> null;
assertTrue(mManager.requestPinShortcut(s, null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
// Shortcut shouldn't be registered yet.
assertWith(getCallerShortcuts())
.isEmpty();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllOrphan()
.areAllWithNoActivity() // Activity is not set; expected.
.areAllWithNoIntent();
// Accept the request.
assertForLauncherCallbackNoThrow(mLauncherApps,
() -> assertTrue(request.accept()))
.assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0)
.haveIds("s1");
});
verify(mServiceContext, times(1)).sendIntentSender(eq(null));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllNotDynamic()
.areAllEnabled()
.areAllPinned()
.areAllWithNoActivity() // Activity is not set; expected.
.areAllWithIntent();
});
}
public void testRequestPinShortcut_dynamicExists() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
// Create dynamic shortcut
ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32);
assertTrue(mManager.setDynamicShortcuts(list(s1)));
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllNotPinned();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllWithNoIntent();
assertAllHaveIcon(list(request.getShortcutInfo()));
// Accept the request.
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllPinned();
});
}
public void testRequestPinShortcut_manifestExists() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllNotPinned();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("ms1")
.areAllManifest()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllWithNoIntent();
assertAllHaveIcon(list(request.getShortcutInfo()));
// Accept the request.
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllPinned();
});
}
public void testRequestPinShortcut_dynamicExists_alreadyPinned() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
final ShortcutInfo.Builder b = new ShortcutInfo.Builder(mClientContext, "s1")
.setShortLabel("Title-" + "s1")
.setIcon(res32x32)
.setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class));
final ShortcutInfo s = b.build();
assertTrue(mManager.setDynamicShortcuts(list(s)));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
.areAllPinned();
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
makeResultIntent()));
// The intent should be sent right away.
verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
});
// Already pinned.
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
.areAllPinned();
});
// ... But the launcher will still receive the request.
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllPinned() // Note it's pinned already.
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
.areAllWithNoIntent();
assertAllHaveIcon(list(request.getShortcutInfo()));
reset(mServiceContext);
// Accept the request.
assertTrue(request.accept());
// The intent is only sent once, so times(1).
verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
});
// Still pinned.
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "MainActivity"))
.areAllPinned();
});
}
public void testRequestPinShortcut_manifestExists_alreadyPinned() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllPinned();
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
makeResultIntent()));
// The intent should be sent right away.
verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
});
// Already pinned.
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllPinned();
});
// ... But the launcher will still receive the request.
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("ms1")
.areAllManifest()
.areAllPinned() // Note it's pinned already.
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllWithNoIntent();
assertAllHaveIcon(list(request.getShortcutInfo()));
reset(mServiceContext);
// Accept the request.
assertTrue(request.accept());
// The intent is only sent once, so times(1).
verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
});
// Still pinned.
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllPinned();
});
}
public void testRequestPinShortcut_wasDynamic_alreadyPinned() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
mManager.removeAllDynamicShortcuts();
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllNotDynamic()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllPinned();
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
/* resultIntent=*/ null));
// The intent should be sent right away.
verify(mServiceContext, times(1)).sendIntentSender(anyOrNull(IntentSender.class));
});
}
public void testRequestPinShortcut_wasDynamic_disabled_alreadyPinned() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
mManager.disableShortcuts(list("s1"));
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllNotDynamic()
.areAllDisabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllPinned();
assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> {
mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
/* resultIntent=*/ null);
});
// Shouldn't be called.
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
}
public void testRequestPinShortcut_wasManifest_alreadyPinned() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_0);
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllNotManifest()
.areAllDisabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllPinned();
assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> {
mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
/* resultIntent=*/ null);
});
// Shouldn't be called.
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
}
public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() {
// Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
});
runWithCaller(LAUNCHER_2, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
});
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllPinned();
// The shortcut is already pinned, but not by the current launcher, so it'll still
// invoke the whole flow.
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllNotPinned() // Note it's not pinned by this launcher.
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllWithNoIntent();
// Accept the request.
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllPinned();
});
}
public void testRequestPinShortcut_manifestExists_alreadyPinnedByAnother() {
// Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
});
runWithCaller(LAUNCHER_2, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
});
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllPinned();
// The shortcut is already pinned, but not by the current launcher, so it'll still
// invoke the whole flow.
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("ms1")
.areAllManifest()
.areAllNotPinned() // Note it's not pinned by this launcher.
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllWithNoIntent();
// Accept the request.
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllPinned();
});
}
/**
* The launcher already has a pinned shortuct. The new one should be added, not replace
* the existing one.
*/
public void testRequestPinShortcut_launcherAlreadyHasPinned() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"))));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllWithNoIntent();
// Accept the request.
assertTrue(request.accept());
assertWith(getShortcutAsLauncher(USER_P0))
.haveIds("s1", "s2")
.areAllDynamic()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllPinned();
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1", "s2")
.areAllDynamic()
.areAllEnabled()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllPinned();
});
}
/**
* When trying to pin an existing shortcut, the new fields shouldn't override existing fields.
*/
public void testRequestPinShortcut_dynamicExists_titleWontChange() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
// Create dynamic shortcut
ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32);
assertTrue(mManager.setDynamicShortcuts(list(s1)));
assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "xxx"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllNotPinned();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllWithNoIntent();
assertAllHaveIcon(list(request.getShortcutInfo()));
// Accept the request.
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllDynamic()
.areAllEnabled()
.areAllPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.forShortcutWithId("s1", (si) -> {
// Still the original title.
assertEquals("Title-s1", si.getShortLabel());
});
});
}
/**
* When trying to pin an existing shortcut, the new fields shouldn't override existing fields.
*/
public void testRequestPinShortcut_manifestExists_titleWontChange() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "xxx"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllNotPinned();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("ms1")
.areAllManifest()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllWithNoIntent();
assertAllHaveIcon(list(request.getShortcutInfo()));
// Accept the request.
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllManifest()
.areAllEnabled()
.areAllPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.forShortcutWithId("ms1", (si) -> {
// Still the original title.
// Title should be something like:
// "string-com.android.test.1-user:20-res:2131034112/en"
MoreAsserts.assertContainsRegex("^string-", si.getShortLabel().toString());
});
});
}
/**
* The dynamic shortcut existed, but before accepting(), it's removed. Because the request
* has a partial shortcut, accept() should fail.
*/
public void testRequestPinShortcut_dynamicExists_thenRemoved_error() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
// Create dynamic shortcut
ShortcutInfo s1 = makeShortcut("s1");
assertTrue(mManager.setDynamicShortcuts(list(s1)));
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
mManager.removeAllDynamicShortcuts();
assertWith(getCallerShortcuts())
.isEmpty();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllWithNoIntent();
// Accept the request -> should fail.
assertForLauncherCallbackNoThrow(mLauncherApps,
() -> assertFalse(request.accept()))
.assertNoCallbackCalled();
});
// Intent shouldn't be sent.
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.isEmpty();
});
}
/**
* The dynamic shortcut existed, but before accepting(), it's removed. Because the request
* has all the mandatory fields, we can go ahead and still publish it.
*/
public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
// Create dynamic shortcut
ShortcutInfo s1 = makeShortcut("s1");
assertTrue(mManager.setDynamicShortcuts(list(s1)));
assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "new"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
mManager.removeAllDynamicShortcuts();
assertWith(getCallerShortcuts())
.isEmpty();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllWithNoIntent();
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllFloating()
.forShortcutWithId("s1", si -> {
assertEquals("new", si.getShortLabel());
});
});
}
/**
* The manifest shortcut existed, but before accepting(), it's removed. Because the request
* has a partial shortcut, accept() should fail.
*/
public void testRequestPinShortcut_manifestExists_thenRemoved_error() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
publishManifestShortcutsAsCaller(R.xml.shortcut_0);
assertWith(getCallerShortcuts())
.isEmpty();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("ms1")
.areAllManifest()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllWithNoIntent();
// Accept the request -> should fail.
assertForLauncherCallbackNoThrow(mLauncherApps,
() -> assertFalse(request.accept()))
.assertNoCallbackCalled();
});
// Intent shouldn't be sent.
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.isEmpty();
});
}
/**
* The manifest shortcut existed, but before accepting(), it's removed. Because the request
* has all the mandatory fields, we can go ahead and still publish it.
*/
public void testRequestPinShortcut_manifestExists_thenRemoved_okay() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "new"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
publishManifestShortcutsAsCaller(R.xml.shortcut_0);
assertWith(getCallerShortcuts())
.isEmpty();
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("ms1")
.areAllManifest()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllWithNoIntent();
assertTrue(request.accept());
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllMutable() // Note it's no longer immutable.
.areAllFloating()
// Note it's the activity from makeShortcutWithShortLabel().
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.forShortcutWithId("ms1", si -> {
assertEquals("new", si.getShortLabel());
});
});
}
/**
* The dynamic shortcut existed, but before accepting(), it's removed. Because the request
* has a partial shortcut, accept() should fail.
*/
public void testRequestPinShortcut_dynamicExists_thenDisabled_error() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
ShortcutInfo s1 = makeShortcut("s1");
assertTrue(mManager.setDynamicShortcuts(list(s1)));
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
// Then, pin by another launcher and disable it.
// We have to pin it here so that disable() won't remove it.
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
runWithCaller(LAUNCHER_2, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
mManager.disableShortcuts(list("s1"));
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllDisabled();
});
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("s1")
.areAllDynamic()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllWithNoIntent();
// Accept the request -> should fail.
assertForLauncherCallbackNoThrow(mLauncherApps,
() -> assertFalse(request.accept()))
.assertNoCallbackCalled();
// Note s1 is floating and pinned by another launcher, so it shouldn't be
// visible here.
assertWith(getShortcutAsLauncher(USER_P0))
.isEmpty();
});
// Intent shouldn't be sent.
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("s1")
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1, "main"))
.areAllDisabled();
});
}
/**
* The manifest shortcut existed, but before accepting(), it's removed. Because the request
* has a partial shortcut, accept() should fail.
*/
public void testRequestPinShortcut_manifestExists_thenDisabled_error() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_1);
assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
/* resultIntent=*/ null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
// Then, pin by another launcher and disable it.
// We have to pin it here so that disable() won't remove it.
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
runWithCaller(LAUNCHER_2, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
});
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
publishManifestShortcutsAsCaller(R.xml.shortcut_0);
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllDisabled();
});
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Check the intent passed to startActivityAsUser().
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
// Check the request object.
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
assertPinItemRequest(request);
assertWith(request.getShortcutInfo())
.haveIds("ms1")
.areAllManifest()
.areAllNotPinned()
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllWithNoIntent();
// Accept the request -> should fail.
assertForLauncherCallbackNoThrow(mLauncherApps,
() -> assertFalse(request.accept()))
.assertNoCallbackCalled();
// Note ms1 is floating and pinned by another launcher, so it shouldn't be
// visible here.
assertWith(getShortcutAsLauncher(USER_P0))
.isEmpty();
});
// Intent shouldn't be sent.
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
assertWith(getCallerShortcuts())
.haveIds("ms1")
.areAllWithActivity(new ComponentName(CALLING_PACKAGE_1,
ShortcutActivity.class.getName()))
.areAllDisabled();
});
}
public void testRequestPinShortcut_wrongLauncherCannotAccept() {
setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
ShortcutInfo s1 = makeShortcut("s1");
assertTrue(mManager.requestPinShortcut(s1, null));
verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
});
final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
// Verify that other launcher can't use this request
runWithCaller(LAUNCHER_1, USER_0, () -> {
// Set some random caller UID.
mInjectedCallingUid = 12345;
assertFalse(request.isValid());
assertExpectException(SecurityException.class, "Calling uid mismatch", request::accept);
});
// The default launcher can still use this request
runWithCaller(LAUNCHER_1, USER_0, () -> {
assertTrue(request.isValid());
assertTrue(request.accept());
});
}
// TODO More tests:
// Cancel previous pending request and release memory?
// Check the launcher callback too.
// Missing fields -- pre and post, both.
}