diff options
8 files changed, 346 insertions, 18 deletions
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java index e667838ad5e5..df51923c97d3 100644 --- a/services/core/java/com/android/server/pm/ShortcutLauncher.java +++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java @@ -25,6 +25,8 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.ShortcutUser.PackageWithUser; +import org.json.JSONException; +import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -288,6 +290,15 @@ class ShortcutLauncher extends ShortcutPackageItem { } } + @Override + public JSONObject dumpCheckin(boolean clear) throws JSONException { + final JSONObject result = super.dumpCheckin(clear); + + // Nothing really interesting to dump. + + return result; + } + @VisibleForTesting ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) { return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName))); diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 3c1819880806..7d57f33604d8 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -36,6 +36,8 @@ import com.android.internal.util.XmlUtils; import com.android.server.pm.ShortcutService.ShortcutOperation; import com.android.server.pm.ShortcutService.Stats; +import org.json.JSONException; +import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -93,6 +95,12 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String TAG_STRING_ARRAY_XMLUTILS = "string-array"; private static final String ATTR_NAME_XMLUTILS = "name"; + private static final String KEY_DYNAMIC = "dynamic"; + private static final String KEY_MANIFEST = "manifest"; + private static final String KEY_PINNED = "pinned"; + private static final String KEY_BITMAPS = "bitmaps"; + private static final String KEY_BITMAP_BYTES = "bitmapBytes"; + /** * All the shortcuts from the package, keyed on IDs. */ @@ -1199,6 +1207,42 @@ class ShortcutPackage extends ShortcutPackageItem { } @Override + public JSONObject dumpCheckin(boolean clear) throws JSONException { + final JSONObject result = super.dumpCheckin(clear); + + int numDynamic = 0; + int numPinned = 0; + int numManifest = 0; + int numBitmaps = 0; + long totalBitmapSize = 0; + + final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts; + final int size = shortcuts.size(); + for (int i = 0; i < size; i++) { + final ShortcutInfo si = shortcuts.valueAt(i); + + if (si.isDynamic()) numDynamic++; + if (si.isDeclaredInManifest()) numManifest++; + if (si.isPinned()) numPinned++; + + if (si.getBitmapPath() != null) { + numBitmaps++; + totalBitmapSize += new File(si.getBitmapPath()).length(); + } + } + + result.put(KEY_DYNAMIC, numDynamic); + result.put(KEY_MANIFEST, numManifest); + result.put(KEY_PINNED, numPinned); + result.put(KEY_BITMAPS, numBitmaps); + result.put(KEY_BITMAP_BYTES, totalBitmapSize); + + // TODO Log update frequency too. + + return result; + } + + @Override public void saveToXml(@NonNull XmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException { final int size = mShortcuts.size(); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 26b52e918a04..79b5c4eae1dc 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -21,6 +21,8 @@ import android.util.Slog; import com.android.internal.util.Preconditions; +import org.json.JSONException; +import org.json.JSONObject; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -31,6 +33,7 @@ import java.io.IOException; */ abstract class ShortcutPackageItem { private static final String TAG = ShortcutService.TAG; + private static final String KEY_NAME = "name"; private final int mPackageUserId; private final String mPackageName; @@ -137,6 +140,12 @@ abstract class ShortcutPackageItem { public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException; + public JSONObject dumpCheckin(boolean clear) throws JSONException { + final JSONObject result = new JSONObject(); + result.put(KEY_NAME, mPackageName); + return result; + } + /** * Verify various internal states. */ diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 10f1b4b7c967..a91e2842261d 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -95,6 +95,9 @@ import com.android.server.pm.ShortcutUser.PackageWithUser; import libcore.io.IoUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -184,6 +187,10 @@ public class ShortcutService extends IShortcutService.Stub { private static final String LAUNCHER_INTENT_CATEGORY = Intent.CATEGORY_LAUNCHER; + private static final String KEY_SHORTCUT = "shortcut"; + private static final String KEY_LOW_RAM = "lowRam"; + private static final String KEY_ICON_SIZE = "iconSize"; + @VisibleForTesting interface ConfigConstants { /** @@ -1352,10 +1359,18 @@ public class ShortcutService extends IShortcutService.Stub { if (isCallerSystem()) { return; } - injectEnforceCallingPermission( + enforceCallingOrSelfPermission( android.Manifest.permission.RESET_SHORTCUT_MANAGER_THROTTLING, null); } + private void enforceCallingOrSelfPermission( + @NonNull String permission, @Nullable String message) { + if (isCallerSystem()) { + return; + } + injectEnforceCallingPermission(permission, message); + } + /** * Somehow overriding ServiceContext.enforceCallingPermission() in the unit tests would confuse * mockito. So instead we extracted it here and override it in the tests. @@ -2981,20 +2996,29 @@ public class ShortcutService extends IShortcutService.Stub { @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) - != PackageManager.PERMISSION_GRANTED) { - pw.println("Permission Denial: can't dump UserManager from from pid=" - + Binder.getCallingPid() - + ", uid=" + Binder.getCallingUid() - + " without permission " - + android.Manifest.permission.DUMP); - return; + enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, + "can't dump by this caller"); + boolean checkin = false; + boolean clear = false; + if (args != null) { + for (String arg : args) { + if ("-c".equals(arg)) { + checkin = true; + } else if ("--checkin".equals(arg)) { + checkin = true; + clear = true; + } + } + } + + if (checkin) { + dumpCheckin(pw, clear); + } else { + dumpInner(pw); } - dumpInner(pw, args); } - @VisibleForTesting - void dumpInner(PrintWriter pw, String[] args) { + private void dumpInner(PrintWriter pw) { synchronized (mLock) { final long now = injectCurrentTimeMillis(); pw.print("Now: ["); @@ -3106,6 +3130,34 @@ public class ShortcutService extends IShortcutService.Stub { (count == 0 ? 0 : ((double) dur) / count))); } + /** + * Dumpsys for checkin. + * + * @param clear if true, clear the history information. Some other system services have this + * behavior but shortcut service doesn't for now. + */ + private void dumpCheckin(PrintWriter pw, boolean clear) { + synchronized (mLock) { + try { + final JSONArray users = new JSONArray(); + + for (int i = 0; i < mUsers.size(); i++) { + users.put(mUsers.valueAt(i).dumpCheckin(clear)); + } + + final JSONObject result = new JSONObject(); + + result.put(KEY_SHORTCUT, users); + result.put(KEY_LOW_RAM, injectIsLowRamDevice()); + result.put(KEY_ICON_SIZE, mMaxIconDimension); + + pw.println(result.toString(1)); + } catch (JSONException e) { + Slog.e(TAG, "Unable to write in json", e); + } + } + } + // === Shell support === @Override diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 3a43ece9506f..21e4165e6a67 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -32,6 +32,9 @@ import com.android.internal.util.Preconditions; import libcore.util.Objects; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -55,6 +58,9 @@ class ShortcutUser { private static final String ATTR_VALUE = "value"; private static final String ATTR_KNOWN_LOCALES = "locales"; private static final String ATTR_LAST_APP_SCAN_TIME = "last-app-scan-time"; + private static final String KEY_USER_ID = "userId"; + private static final String KEY_LAUNCHERS = "launchers"; + private static final String KEY_PACKAGES = "packages"; static final class PackageWithUser { final int userId; @@ -503,4 +509,28 @@ class ShortcutUser { pw.print(Formatter.formatFileSize(mService.mContext, size)); pw.println(")"); } + + public JSONObject dumpCheckin(boolean clear) throws JSONException { + final JSONObject result = new JSONObject(); + + result.put(KEY_USER_ID, mUserId); + + { + final JSONArray launchers = new JSONArray(); + for (int i = 0; i < mLaunchers.size(); i++) { + launchers.put(mLaunchers.valueAt(i).dumpCheckin(clear)); + } + result.put(KEY_LAUNCHERS, launchers); + } + + { + final JSONArray packages = new JSONArray(); + for (int i = 0; i < mPackages.size(); i++) { + packages.put(mPackages.valueAt(i).dumpCheckin(clear)); + } + result.put(KEY_PACKAGES, packages); + } + + return result; + } } diff --git a/services/tests/servicestests/assets/shortcut/dumpsys_expected.txt b/services/tests/servicestests/assets/shortcut/dumpsys_expected.txt new file mode 100644 index 000000000000..eed2087f80ef --- /dev/null +++ b/services/tests/servicestests/assets/shortcut/dumpsys_expected.txt @@ -0,0 +1,105 @@ +{ + "shortcut": [ + { + "userId": 0, + "launchers": [ + { + "name": "com.android.launcher.1" + }, + { + "name": "com.android.launcher.2" + }, + { + "name": "com.android.launcher.3" + }, + { + "name": "com.android.launcher.4" + }, + { + "name": "com.android.launcher.1" + } + ], + "packages": [ + { + "name": "com.android.test.1", + "dynamic": 3, + "manifest": 0, + "pinned": 4, + "bitmaps": 0, + "bitmapBytes": 0 + }, + { + "name": "com.android.test.2", + "dynamic": 4, + "manifest": 0, + "pinned": 5, + "bitmaps": 2, + "bitmapBytes": ***BITMAP_SIZE*** + }, + { + "name": "com.android.test.3", + "dynamic": 3, + "manifest": 0, + "pinned": 6, + "bitmaps": 0, + "bitmapBytes": 0 + }, + { + "name": "com.android.test.4", + "dynamic": 0, + "manifest": 0, + "pinned": 0, + "bitmaps": 0, + "bitmapBytes": 0 + } + ] + }, + { + "userId": 10, + "launchers": [ + { + "name": "com.android.launcher.1" + } + ], + "packages": [ + { + "name": "com.android.test.1", + "dynamic": 3, + "manifest": 0, + "pinned": 2, + "bitmaps": 0, + "bitmapBytes": 0 + } + ] + }, + { + "userId": 20, + "launchers": [ + { + "name": "com.android.launcher.1" + }, + { + "name": "com.android.launcher.2" + }, + { + "name": "com.android.launcher.3" + }, + { + "name": "com.android.launcher.1" + } + ], + "packages": [ + { + "name": "com.android.test.1", + "dynamic": 3, + "manifest": 0, + "pinned": 6, + "bitmaps": 0, + "bitmapBytes": 0 + } + ] + } + ], + "lowRam": false, + "iconSize": 128 +} diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 037b24ef0208..586425564129 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -93,6 +93,8 @@ import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; @@ -1106,17 +1108,32 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { protected void dumpsysOnLogcat(String message, boolean force) { if (force || !ENABLE_DUMP) return; - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - final PrintWriter pw = new PrintWriter(out); - mService.dumpInner(pw, null); - pw.close(); - Log.v(TAG, "Dumping ShortcutService: " + message); - for (String line : out.toString().split("\n")) { + for (String line : dumpsys(null).split("\n")) { Log.v(TAG, line); } } + protected String dumpCheckin() { + return dumpsys(new String[]{"--checkin"}); + } + + private String dumpsys(String[] args) { + final ArrayList<String> origPermissions = new ArrayList<>(mCallerPermissions); + mCallerPermissions.add(android.Manifest.permission.DUMP); + try { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final PrintWriter pw = new PrintWriter(out); + mService.dump(/* fd */ null, pw, args); + pw.close(); + + return out.toString(); + } finally { + mCallerPermissions.clear(); + mCallerPermissions.addAll(origPermissions); + } + } + /** * For debugging, dump arbitrary file on logcat. */ @@ -1793,4 +1810,18 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } return actualShortcuts; } + + public String readTestAsset(String assetPath) throws IOException { + final StringBuilder sb = new StringBuilder(); + try (BufferedReader br = new BufferedReader( + new InputStreamReader( + getTestContext().getResources().getAssets().open(assetPath)))) { + String line; + while ((line = br.readLine()) != null) { + sb.append(line); + sb.append(System.lineSeparator()); + } + } + return sb.toString(); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index bd413beedc47..dc70583c2f8f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -47,6 +47,10 @@ 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.io.PrintWriter; +import java.io.StringWriter; import java.util.Locale; /** @@ -1852,4 +1856,46 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { ShortcutInfo.lookUpResourceId(res, "drawable/black_16x64", null, getTestContext().getPackageName())); } + + public void testDumpCheckin() throws IOException { + prepareCrossProfileDataSet(); + + // prepareCrossProfileDataSet() doesn't set any icons, so do set here. + final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32); + final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64); + final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.black_32x32)); + final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.black_64x64)); + + runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { + assertTrue(mManager.setDynamicShortcuts(list( + makeShortcutWithIcon("res32x32", res32x32), + makeShortcutWithIcon("res64x64", res64x64), + makeShortcutWithIcon("bmp32x32", bmp32x32), + makeShortcutWithIcon("bmp64x64", bmp64x64)))); + }); + // We can't predict the compressed bitmap sizes, so get the real sizes here. + final long bitmapTotal = + new File(getPackageShortcut(CALLING_PACKAGE_2, "bmp32x32", USER_0) + .getBitmapPath()).length() + + new File(getPackageShortcut(CALLING_PACKAGE_2, "bmp64x64", USER_0) + .getBitmapPath()).length(); + + // Read the expected output and inject the bitmap size. + final String expected = readTestAsset("shortcut/dumpsys_expected.txt") + .replace("***BITMAP_SIZE***", String.valueOf(bitmapTotal)); + + assertEquals(expected, dumpCheckin()); + } + + public void testDumpsysNoPermission() { + assertExpectException(SecurityException.class, "android.permission.DUMP", + () -> mService.dump(null, new PrintWriter(new StringWriter()), null)); + + // System can call it without the permission. + runWithSystemUid(() -> { + mService.dump(null, new PrintWriter(new StringWriter()), null); + }); + } } |