Merge \"Implement dumpsys --checkin for shortcut manager\" into nyc-mr1-dev
am: 121480cda8
Change-Id: I38508e2b47dcb2fe2e8046f7c148b4ddbf6a2697
This commit is contained in:
@@ -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)));
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -1198,6 +1206,42 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
pw.println(")");
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user