Merge "Add dumpsys option to print last restore status"

This commit is contained in:
TreeHugger Robot
2017-06-05 19:18:43 +00:00
committed by Android (Google) Code Review
8 changed files with 259 additions and 9 deletions

View File

@@ -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);
}
}
}

View File

@@ -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.

View File

@@ -180,6 +180,9 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
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";
@@ -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();

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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"));
}
}

View File

@@ -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);
}