Merge "Add dumpsys option to print last restore status"
This commit is contained in:
committed by
Android (Google) Code Review
commit
a3112ceedc
105
services/core/java/com/android/server/pm/ShortcutDumpFiles.java
Normal file
105
services/core/java/com/android/server/pm/ShortcutDumpFiles.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user