* - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
* -> But TypedValue.applyDimension() doesn't differentiate x and y..?
*
- * - Default launcher check does take a few ms. Worth caching.
- *
* - Detect when already registered instances are passed to APIs again, which might break
* internal bitmap handling.
- *
- * - Add more call stats.
*/
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
}
private void enforceSystemOrShell() {
- Preconditions.checkState(isCallerSystem() || isCallerShell(),
- "Caller must be system or shell");
+ if (!(isCallerSystem() || isCallerShell())) {
+ throw new SecurityException("Caller must be system or shell");
+ }
}
private void enforceShell() {
- Preconditions.checkState(isCallerShell(), "Caller must be shell");
+ if (!isCallerShell()) {
+ throw new SecurityException("Caller must be shell");
+ }
}
private void enforceSystem() {
- Preconditions.checkState(isCallerSystem(), "Caller must be system");
+ if (!isCallerSystem()) {
+ throw new SecurityException("Caller must be system");
+ }
}
private void enforceResetThrottlingPermission() {
enforceShell();
- final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
-
- resultReceiver.send(status, null);
+ final long token = injectClearCallingIdentity();
+ try {
+ final int status = (new MyShellCommand()).exec(this, in, out, err, args, resultReceiver);
+ resultReceiver.send(status, null);
+ } finally {
+ injectRestoreCallingIdentity(token);
+ }
}
static class CommandException extends Exception {
final PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
- case "reset-package-throttling":
- handleResetPackageThrottling();
- break;
case "reset-throttling":
handleResetThrottling();
break;
case "get-default-launcher":
handleGetDefaultLauncher();
break;
- case "refresh-default-launcher":
- handleRefreshDefaultLauncher();
- break;
case "unload-user":
handleUnloadUser();
break;
final PrintWriter pw = getOutPrintWriter();
pw.println("Usage: cmd shortcut COMMAND [options ...]");
pw.println();
- pw.println("cmd shortcut reset-package-throttling [--user USER_ID] PACKAGE");
- pw.println(" Reset throttling for a package");
- pw.println();
pw.println("cmd shortcut reset-throttling [--user USER_ID]");
pw.println(" Reset throttling for all packages and users");
pw.println();
pw.println(" Clear the cached default launcher");
pw.println();
pw.println("cmd shortcut get-default-launcher [--user USER_ID]");
- pw.println(" Show the cached default launcher");
- pw.println();
- pw.println("cmd shortcut refresh-default-launcher [--user USER_ID]");
- pw.println(" Refresh the cached default launcher");
+ pw.println(" Show the default launcher");
pw.println();
pw.println("cmd shortcut unload-user [--user USER_ID]");
pw.println(" Unload a user from the memory");
private void handleResetThrottling() throws CommandException {
parseOptions(/* takeUser =*/ true);
- Slog.i(TAG, "cmd: handleResetThrottling");
+ Slog.i(TAG, "cmd: handleResetThrottling: user=" + mUserId);
resetThrottlingInner(mUserId);
}
resetAllThrottlingInner();
}
- private void handleResetPackageThrottling() throws CommandException {
- parseOptions(/* takeUser =*/ true);
-
- final String packageName = getNextArgRequired();
-
- Slog.i(TAG, "cmd: handleResetPackageThrottling: " + packageName);
-
- resetPackageThrottling(packageName, mUserId);
- }
-
private void handleOverrideConfig() throws CommandException {
final String config = getNextArgRequired();
private void handleGetDefaultLauncher() throws CommandException {
parseOptions(/* takeUser =*/ true);
- showLauncher();
- }
-
- private void handleRefreshDefaultLauncher() throws CommandException {
- parseOptions(/* takeUser =*/ true);
-
clearLauncher();
showLauncher();
}
private void handleUnloadUser() throws CommandException {
parseOptions(/* takeUser =*/ true);
- Slog.i(TAG, "cmd: handleUnloadUser: " + mUserId);
+ Slog.i(TAG, "cmd: handleUnloadUser: user=" + mUserId);
ShortcutService.this.handleCleanupUser(mUserId);
}
parseOptions(/* takeUser =*/ true);
final String packageName = getNextArgRequired();
- Slog.i(TAG, "cmd: handleClearShortcuts: " + mUserId + ", " + packageName);
+ Slog.i(TAG, "cmd: handleClearShortcuts: user" + mUserId + ", " + packageName);
ShortcutService.this.cleanUpPackageForAllLoadedUsers(packageName, mUserId,
/* appStillExists = */ true);
--- /dev/null
+/*
+ * 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.assertContains;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertSuccess;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.readAll;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resultContains;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.frameworks.servicestests.R;
+import com.android.server.pm.ShortcutService.ConfigConstants;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Unit test for "cmd shortcut"
+ *
+ * Launcher related commands are tested in
+ */
+@SmallTest
+public class ShortcutManagerTest7 extends BaseShortcutManagerTest {
+ private List<String> callShellCommand(String... args) throws IOException, RemoteException {
+
+ // For reset to work, the current time needs to be incrementing.
+ mInjectedCurrentTimeMillis++;
+
+ final AtomicInteger resultCode = new AtomicInteger(Integer.MIN_VALUE);
+
+ final ResultReceiver rr = new ResultReceiver(mHandler) {
+ @Override
+ public void send(int resultCode_, Bundle resultData) {
+ resultCode.set(resultCode_);
+ }
+ };
+ final File out = File.createTempFile("shellout-", ".tmp",
+ getTestContext().getCacheDir());
+ try {
+ try (final ParcelFileDescriptor fd = ParcelFileDescriptor.open(out,
+ ParcelFileDescriptor.MODE_READ_WRITE)) {
+ mService.onShellCommand(
+ /* fdin*/ null,
+ /* fdout*/ fd.getFileDescriptor(),
+ /* fderr*/ fd.getFileDescriptor(),
+ args, rr);
+ }
+ return readAll(out);
+ } finally {
+ out.delete();
+ }
+ }
+
+ public void testNonShell() throws Exception {
+ mService.mMaxUpdatesPerInterval = 99;
+
+ mInjectedCallingUid = 12345;
+ assertExpectException(SecurityException.class, "must be shell",
+ () -> callShellCommand("reset-config"));
+
+ mInjectedCallingUid = Process.SYSTEM_UID;
+ assertExpectException(SecurityException.class, "must be shell",
+ () -> callShellCommand("reset-config"));
+
+ assertEquals(99, mService.mMaxUpdatesPerInterval);
+ }
+
+ public void testRoot() throws Exception {
+ mService.mMaxUpdatesPerInterval = 99;
+
+ mInjectedCallingUid = Process.ROOT_UID;
+ assertSuccess(callShellCommand("reset-config"));
+
+ assertEquals(3, mService.mMaxUpdatesPerInterval);
+ }
+
+ public void testRestConfig() throws Exception {
+ mService.mMaxUpdatesPerInterval = 99;
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("reset-config"));
+
+ assertEquals(3, mService.mMaxUpdatesPerInterval);
+ }
+
+ public void testOverrideConfig() throws Exception {
+ mService.mMaxUpdatesPerInterval = 99;
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("override-config",
+ ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=1"));
+
+ assertEquals(1, mService.mMaxUpdatesPerInterval);
+ }
+
+ public void testResetThrottling() throws Exception {
+ prepareCrossProfileDataSet();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("reset-throttling"));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(3, mManager.getRemainingCallCount());
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ }
+
+ public void testResetThrottling_user_not_running() throws Exception {
+ prepareCrossProfileDataSet();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+
+ mInjectedCallingUid = Process.SHELL_UID;
+
+ assertTrue(resultContains(
+ callShellCommand("reset-throttling", "--user", "10"),
+ "User 10 is not running or locked"));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ }
+
+ public void testResetThrottling_user_running() throws Exception {
+ prepareCrossProfileDataSet();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+
+ mRunningUsers.put(USER_10, true);
+ mUnlockedUsers.put(USER_10, true);
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("reset-throttling", "--user", "10"));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertEquals(3, mManager.getRemainingCallCount());
+ });
+ }
+
+ public void testResetAllThrottling() throws Exception {
+ prepareCrossProfileDataSet();
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.getRemainingCallCount() < 3);
+ });
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("reset-all-throttling"));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertEquals(3, mManager.getRemainingCallCount());
+ });
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertEquals(3, mManager.getRemainingCallCount());
+ });
+ }
+
+ public void testLauncherCommands() throws Exception {
+ prepareGetHomeActivitiesAsUser(
+ /* preferred */ null,
+ list(getSystemLauncher(), getFallbackLauncher()),
+ USER_0);
+
+ prepareGetHomeActivitiesAsUser(
+ /* preferred */ cn(CALLING_PACKAGE_2, "name"),
+ list(getSystemLauncher(), getFallbackLauncher(),
+ ri(CALLING_PACKAGE_1, "name", false, 0),
+ ri(CALLING_PACKAGE_2, "name", false, 0)
+ ),
+ USER_10);
+
+ assertTrue(mService.hasShortcutHostPermissionInner(PACKAGE_SYSTEM_LAUNCHER, USER_0));
+
+ // First, test "get".
+
+ mRunningUsers.put(USER_10, true);
+ mUnlockedUsers.put(USER_10, true);
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertContains(
+ assertSuccess(callShellCommand("get-default-launcher")),
+ "Launcher: ComponentInfo{com.android.systemlauncher/systemlauncher_name}");
+
+ assertContains(
+ assertSuccess(callShellCommand("get-default-launcher", "--user", "10")),
+ "Launcher: ComponentInfo{com.android.test.2/name}");
+
+ // Next, test "clear".
+ assertSuccess(callShellCommand("clear-default-launcher", "--user", "10"));
+
+ // User-10's launcher should be cleared.
+ assertEquals(null, mService.getUserShortcutsLocked(USER_10).getLastKnownLauncher());
+ assertEquals(null, mService.getUserShortcutsLocked(USER_10).getCachedLauncher());
+
+ // but user'0's shouldn't.
+ assertEquals(cn(PACKAGE_SYSTEM_LAUNCHER, PACKAGE_SYSTEM_LAUNCHER_NAME),
+ mService.getUserShortcutsLocked(USER_0).getCachedLauncher());
+
+ // Change user-0's launcher.
+ prepareGetHomeActivitiesAsUser(
+ /* preferred */ cn(CALLING_PACKAGE_1, "name"),
+ list(
+ ri(CALLING_PACKAGE_1, "name", false, 0)
+ ),
+ USER_0);
+ assertContains(
+ assertSuccess(callShellCommand("get-default-launcher")),
+ "Launcher: ComponentInfo{com.android.test.1/name}");
+ }
+
+ public void testUnloadUser() throws Exception {
+ prepareCrossProfileDataSet();
+
+ assertNotNull(mService.getShortcutsForTest().get(USER_10));
+
+ mRunningUsers.put(USER_10, true);
+ mUnlockedUsers.put(USER_10, true);
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("unload-user", "--user", "10"));
+
+ assertNull(mService.getShortcutsForTest().get(USER_10));
+ }
+
+ public void testClearShortcuts() throws Exception {
+
+ mRunningUsers.put(USER_10, true);
+
+ // Add two manifests and two dynamics.
+ addManifestShortcutResource(
+ new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+ R.xml.shortcut_2);
+ updatePackageVersion(CALLING_PACKAGE_1, 1);
+ mService.mPackageMonitor.onReceive(getTestContext(),
+ genPackageAddIntent(CALLING_PACKAGE_1, USER_10));
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertTrue(mManager.addDynamicShortcuts(list(
+ makeShortcut("s1"), makeShortcut("s2"))));
+ });
+ runWithCaller(LAUNCHER_1, USER_10, () -> {
+ mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms2", "s2"), HANDLE_USER_10);
+ });
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllEnabled()
+
+ .selectPinned()
+ .haveIds("ms2", "s2");
+ });
+
+ // First, call for a different package.
+
+ mRunningUsers.put(USER_10, true);
+ mUnlockedUsers.put(USER_10, true);
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("clear-shortcuts", "--user", "10", CALLING_PACKAGE_2));
+
+ // Shouldn't be cleared yet.
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2", "s1", "s2")
+ .areAllEnabled()
+
+ .selectPinned()
+ .haveIds("ms2", "s2");
+ });
+
+ mInjectedCallingUid = Process.SHELL_UID;
+ assertSuccess(callShellCommand("clear-shortcuts", "--user", "10", CALLING_PACKAGE_1));
+
+ // Only manifest shortcuts will remain, and are no longer pinned.
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertWith(getCallerShortcuts())
+ .haveIds("ms1", "ms2")
+ .areAllEnabled()
+ .areAllNotPinned();
+ });
+ }
+}
import org.mockito.Mockito;
import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
private ShortcutManagerTestUtils() {
}
- private static List<String> readAll(ParcelFileDescriptor pfd) {
+ public static List<String> readAll(File file) throws FileNotFoundException {
+ return readAll(ParcelFileDescriptor.open(
+ file.getAbsoluteFile(), ParcelFileDescriptor.MODE_READ_ONLY));
+ }
+
+ public static List<String> readAll(ParcelFileDescriptor pfd) {
try {
try {
final ArrayList<String> ret = new ArrayList<>();
}
}
- private static String concatResult(List<String> result) {
+ public static String concatResult(List<String> result) {
final StringBuilder sb = new StringBuilder();
for (String s : result) {
sb.append(s);
return sb.toString();
}
+ public static boolean resultContains(List<String> result, String expected) {
+ for (String line : result) {
+ if (line.contains(expected)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static List<String> assertSuccess(List<String> result) {
+ if (!resultContains(result, "Success")) {
+ fail("Command failed. Result was:\n" + concatResult(result));
+ }
+ return result;
+ }
+
+ public static List<String> assertContains(List<String> result, String expected) {
+ if (!resultContains(result, expected)) {
+ fail("Didn't contain expected string=" + expected
+ + "\nActual:\n" + concatResult(result));
+ }
+ return result;
+ }
+
private static List<String> runCommand(Instrumentation instrumentation, String command) {
return runCommand(instrumentation, command, null);
}
return filter(list, si -> si.getLastChangedTimestamp() >= time);
}
+ @FunctionalInterface
+ public interface ExceptionRunnable {
+ void run() throws Exception;
+ }
+
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
- String expectedExceptionMessageRegex, Runnable r) {
+ String expectedExceptionMessageRegex, ExceptionRunnable r) {
assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
}
public static void assertCannotUpdateImmutable(Runnable r) {
assertExpectException(
- IllegalArgumentException.class, "may not be manipulated via APIs", r);
+ IllegalArgumentException.class, "may not be manipulated via APIs", r::run);
}
public static void assertDynamicShortcutCountExceeded(Runnable r) {
assertExpectException(IllegalArgumentException.class,
- "Max number of dynamic shortcuts exceeded", r);
+ "Max number of dynamic shortcuts exceeded", r::run);
}
public static void assertExpectException(String message,
Class<? extends Throwable> expectedExceptionType,
- String expectedExceptionMessageRegex, Runnable r) {
+ String expectedExceptionMessageRegex, ExceptionRunnable r) {
try {
r.run();
} catch (Throwable e) {