diff options
8 files changed, 259 insertions, 9 deletions
diff --git a/services/core/java/com/android/server/pm/ShortcutDumpFiles.java b/services/core/java/com/android/server/pm/ShortcutDumpFiles.java new file mode 100644 index 000000000000..dc380cc2b195 --- /dev/null +++ b/services/core/java/com/android/server/pm/ShortcutDumpFiles.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 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 android.util.Slog; + +import com.android.internal.util.ArrayUtils; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Comparator; +import java.util.function.Consumer; + +public class ShortcutDumpFiles { + private static final String TAG = ShortcutService.TAG; + private static final boolean DEBUG = ShortcutService.DEBUG; + private final ShortcutService mService; + + public ShortcutDumpFiles(ShortcutService service) { + mService = service; + } + + public boolean save(String filename, Consumer<PrintWriter> dumper) { + try { + final File directory = mService.getDumpPath(); + directory.mkdirs(); + if (!directory.exists()) { + Slog.e(TAG, "Failed to create directory: " + directory); + return false; + } + + final File path = new File(directory, filename); + + if (DEBUG) { + Slog.d(TAG, "Dumping to " + path); + } + + try (PrintWriter pw = new PrintWriter(new BufferedOutputStream( + new FileOutputStream(path)))) { + dumper.accept(pw); + } + return true; + } catch (RuntimeException|IOException e) { + Slog.w(TAG, "Failed to create dump file: " + filename, e); + return false; + } + } + + public boolean save(String filename, byte[] utf8bytes) { + return save(filename, pw -> pw.println(StandardCharsets.UTF_8.decode( + ByteBuffer.wrap(utf8bytes)).toString())); + } + + public void dumpAll(PrintWriter pw) { + try { + final File directory = mService.getDumpPath(); + final File[] files = directory.listFiles(f -> f.isFile()); + if (!directory.exists() || ArrayUtils.isEmpty(files)) { + pw.print(" No dump files found."); + return; + } + Arrays.sort(files, Comparator.comparing(f -> f.getName())); + + for (File path : files) { + pw.print("*** Dumping: "); + pw.println(path.getName()); + + pw.print("mtime: "); + pw.println(ShortcutService.formatTime(path.lastModified())); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader( + new FileInputStream(path)))) { + String line = null; + while ((line = reader.readLine()) != null) { + pw.println(line); + } + } + } + } catch (RuntimeException|IOException e) { + Slog.w(TAG, "Failed to print dump files", e); + } + } +} diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 103b25d4cea0..6f70f4c89ad2 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -150,6 +150,10 @@ class ShortcutPackage extends ShortcutPackageItem { getPackageName(), getPackageUserId()); } + public int getShortcutCount() { + return mShortcuts.size(); + } + @Override protected void onRestoreBlocked() { // Can't restore due to version/signature mismatch. Remove all shortcuts. diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 6d48a05273cb..791197267ecb 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -181,6 +181,9 @@ public class ShortcutService extends IShortcutService.Stub { static final String DIRECTORY_PER_USER = "shortcut_service"; @VisibleForTesting + static final String DIRECTORY_DUMP = "shortcut_dump"; + + @VisibleForTesting static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; static final String DIRECTORY_BITMAPS = "bitmaps"; @@ -308,6 +311,7 @@ public class ShortcutService extends IShortcutService.Stub { private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor; private final ShortcutBitmapSaver mShortcutBitmapSaver; + private final ShortcutDumpFiles mShortcutDumpFiles; @GuardedBy("mLock") final SparseIntArray mUidState = new SparseIntArray(); @@ -429,6 +433,7 @@ public class ShortcutService extends IShortcutService.Stub { mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock); mShortcutBitmapSaver = new ShortcutBitmapSaver(this); + mShortcutDumpFiles = new ShortcutDumpFiles(this); if (onlyForPackageManagerApis) { return; // Don't do anything further. For unit tests only. @@ -3395,6 +3400,16 @@ public class ShortcutService extends IShortcutService.Stub { wtf("Can't restore: user " + userId + " is locked or not running"); return; } + + // Note we print the file timestamps in dumpsys too, but also printing the timestamp + // in the files anyway. + mShortcutDumpFiles.save("restore-0-start.txt", pw -> { + pw.print("Start time: "); + dumpCurrentTime(pw); + pw.println(); + }); + mShortcutDumpFiles.save("restore-1-payload.xml", payload); + // Actually do restore. final ShortcutUser restored; final ByteArrayInputStream is = new ByteArrayInputStream(payload); @@ -3404,13 +3419,25 @@ public class ShortcutService extends IShortcutService.Stub { Slog.w(TAG, "Restoration failed.", e); return; } + mShortcutDumpFiles.save("restore-2.txt", this::dumpInner); + getUserShortcutsLocked(userId).mergeRestoredFile(restored); + mShortcutDumpFiles.save("restore-3.txt", this::dumpInner); + // Rescan all packages to re-publish manifest shortcuts and do other checks. rescanUpdatedPackagesLocked(userId, 0 // lastScanTime = 0; rescan all packages. ); + mShortcutDumpFiles.save("restore-4.txt", this::dumpInner); + + mShortcutDumpFiles.save("restore-5-finish.txt", pw -> { + pw.print("Finish time: "); + dumpCurrentTime(pw); + pw.println(); + }); + saveUserLocked(userId); } } @@ -3425,23 +3452,54 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) { + + boolean dumpMain = true; boolean checkin = false; boolean clear = false; + boolean dumpUid = false; + boolean dumpFiles = false; + if (args != null) { for (String arg : args) { if ("-c".equals(arg)) { checkin = true; + } else if ("--checkin".equals(arg)) { checkin = true; clear = true; + + } else if ("-a".equals(arg) || "--all".equals(arg)) { + dumpUid = true; + dumpFiles = true; + + } else if ("-u".equals(arg) || "--uid".equals(arg)) { + dumpUid = true; + + } else if ("-f".equals(arg) || "--files".equals(arg)) { + dumpFiles = true; + + } else if ("-n".equals(arg) || "--no-main".equals(arg)) { + dumpMain = false; } } } if (checkin) { + // Other flags are not supported for checkin. dumpCheckin(pw, clear); } else { - dumpInner(pw); + if (dumpMain) { + dumpInner(pw); + pw.println(); + } + if (dumpUid) { + dumpUid(pw); + pw.println(); + } + if (dumpFiles) { + dumpDumpFiles(pw); + pw.println(); + } } } @@ -3510,9 +3568,12 @@ public class ShortcutService extends IShortcutService.Stub { pw.println(); mUsers.valueAt(i).dump(pw, " "); } + } + } - pw.println(); - pw.println(" UID state:"); + private void dumpUid(PrintWriter pw) { + synchronized (mLock) { + pw.println("** SHORTCUT MANAGER UID STATES (dumpsys shortcut -n -u)"); for (int i = 0; i < mUidState.size(); i++) { final int uid = mUidState.keyAt(i); @@ -3537,6 +3598,10 @@ public class ShortcutService extends IShortcutService.Stub { return tobj.format("%Y-%m-%d %H:%M:%S"); } + private void dumpCurrentTime(PrintWriter pw) { + pw.print(formatTime(injectCurrentTimeMillis())); + } + private void dumpStatLS(PrintWriter pw, String prefix, int statId) { pw.print(prefix); final int count = mCountStats[statId]; @@ -3574,6 +3639,13 @@ public class ShortcutService extends IShortcutService.Stub { } } + private void dumpDumpFiles(PrintWriter pw) { + synchronized (mLock) { + pw.println("** SHORTCUT MANAGER FILES (dumpsys shortcut -n -f)"); + mShortcutDumpFiles.dumpAll(pw); + } + } + // === Shell support === @Override @@ -3876,6 +3948,10 @@ public class ShortcutService extends IShortcutService.Stub { return new File(Environment.getDataSystemCeDirectory(userId), DIRECTORY_PER_USER); } + public File getDumpPath() { + return new File(injectUserDataPath(UserHandle.USER_SYSTEM), DIRECTORY_DUMP); + } + @VisibleForTesting boolean injectIsLowRamDevice() { return ActivityManager.isLowRamDeviceStatic(); diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 5d4bfa4dcb3e..2c388c4a44d1 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -25,9 +25,7 @@ import android.text.format.Formatter; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; -import android.util.SparseArray; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.pm.ShortcutService.InvalidFileFormatException; @@ -492,6 +490,10 @@ class ShortcutUser { // without users interaction it's really not a big deal, so we just clear existing // ShortcutLauncher instances in mLaunchers and add all the restored ones here. + int[] restoredLaunchers = new int[1]; + int[] restoredPackages = new int[1]; + int[] restoredShortcuts = new int[1]; + mLaunchers.clear(); restored.forAllLaunchers(sl -> { // If the app is already installed and allowbackup = false, then ignore the restored @@ -501,6 +503,7 @@ class ShortcutUser { return; } addLauncher(sl); + restoredLaunchers[0]++; }); restored.forAllPackages(sp -> { // If the app is already installed and allowbackup = false, then ignore the restored @@ -516,10 +519,16 @@ class ShortcutUser { + " Existing non-manifeset shortcuts will be overwritten."); } addPackage(sp); + restoredPackages[0]++; + restoredShortcuts[0] += sp.getShortcutCount(); }); // Empty the launchers and packages in restored to avoid accidentally using them. restored.mLaunchers.clear(); restored.mPackages.clear(); + + Slog.i(TAG, "Restored: L=" + restoredLaunchers[0] + + " P=" + restoredPackages[0] + + " S=" + restoredShortcuts[0]); } public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 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 f1d592789c41..3c64582a8cd2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -1266,16 +1266,16 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (force || !ENABLE_DUMP) return; Log.v(TAG, "Dumping ShortcutService: " + message); - for (String line : dumpsys(null).split("\n")) { + for (String line : dumpsys("-u").split("\n")) { Log.v(TAG, line); } } protected String dumpCheckin() { - return dumpsys(new String[]{"--checkin"}); + return dumpsys("--checkin"); } - private String dumpsys(String[] args) { + protected String dumpsys(String... args) { final ArrayList<String> origPermissions = new ArrayList<>(mCallerPermissions); mCallerPermissions.add(android.Manifest.permission.DUMP); try { @@ -2139,4 +2139,15 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { mService.mPackageMonitor.onReceive(getTestContext(), genPackageAddIntent(getCallingPackage(), getCallingUserId())); } + + protected void assertFileNotExists(String path) { + final File f = new File(mInjectedFilePathRoot, path); + assertFalse("File shouldn't exist: " + f.getAbsolutePath(), f.exists()); + } + + protected void assertFileExistsWithContent(String path) { + final File f = new File(mInjectedFilePathRoot, path); + assertTrue("File should exist: " + f.getAbsolutePath(), f.exists()); + assertTrue("File should be larger than 0b: " + f.getAbsolutePath(), f.length() > 0); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index f4944f9ee83d..951f226676b0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -4732,8 +4732,23 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { * - Bitmaps */ public void testBackupAndRestore() { + + assertFileNotExists("user-0/shortcut_dump/restore-0-start.txt"); + assertFileNotExists("user-0/shortcut_dump/restore-1-payload.xml"); + assertFileNotExists("user-0/shortcut_dump/restore-2.txt"); + assertFileNotExists("user-0/shortcut_dump/restore-3.txt"); + assertFileNotExists("user-0/shortcut_dump/restore-4.txt"); + assertFileNotExists("user-0/shortcut_dump/restore-5-finish.txt"); + prepareForBackupTest(); + assertFileExistsWithContent("user-0/shortcut_dump/restore-0-start.txt"); + assertFileExistsWithContent("user-0/shortcut_dump/restore-1-payload.xml"); + assertFileExistsWithContent("user-0/shortcut_dump/restore-2.txt"); + assertFileExistsWithContent("user-0/shortcut_dump/restore-3.txt"); + assertFileExistsWithContent("user-0/shortcut_dump/restore-4.txt"); + assertFileExistsWithContent("user-0/shortcut_dump/restore-5-finish.txt"); + checkBackupAndRestore_success(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java index 25f9100d9252..da641b973537 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest7.java @@ -15,6 +15,7 @@ */ package com.android.server.pm; +import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.array; 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; @@ -40,7 +41,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** - * Unit test for "cmd shortcut" + * Unit test for "cmd shortcut" and "dumpsys shortcut". * * Launcher related commands are tested in */ @@ -336,4 +337,29 @@ public class ShortcutManagerTest7 extends BaseShortcutManagerTest { .areAllNotPinned(); }); } + + public void testDumpsysArgs() { + checkDumpsysArgs(null, true, false, false); + checkDumpsysArgs(array("-u"), true, true, false); + checkDumpsysArgs(array("--uid"), true, true, false); + + checkDumpsysArgs(array("-f"), true, false, true); + checkDumpsysArgs(array("--files"), true, false, true); + + checkDumpsysArgs(array("-a"), true, true, true); + checkDumpsysArgs(array("--all"), true, true, true); + + checkDumpsysArgs(array("-a", "-n"), false, true, true); + checkDumpsysArgs(array("-a", "--no-main"), false, true, true); + } + + private void checkDumpsysArgs(String[] args, boolean expectMain, boolean expectUid, + boolean expectDumpFiles) { + String dump = dumpsys(args); + + assertEquals(expectMain, dump.contains("Icon format: PNG")); + assertEquals(expectUid, dump.contains("SHORTCUT MANAGER UID STATES")); + assertEquals(expectDumpFiles, dump.contains("SHORTCUT MANAGER FILES")); + } + } diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java index 922f08da643f..926a606df294 100644 --- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java +++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java @@ -339,6 +339,10 @@ public class ShortcutManagerTestUtils { return ret; } + public static <T> T[] array(T... array) { + return array; + } + public static <T> List<T> list(T... array) { return Arrays.asList(array); } |