summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java152
2 files changed, 195 insertions, 16 deletions
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c6411ff94b45..b7c3b97bf019 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -119,10 +119,9 @@ import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.security.FileIntegrity;
import com.android.server.uri.UriGrantsManagerInternal;
-import libcore.io.IoUtils;
-
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -207,6 +206,10 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
static final String FILENAME_USER_PACKAGES = "shortcuts.xml";
+ @VisibleForTesting
+ static final String FILENAME_USER_PACKAGES_RESERVE_COPY =
+ FILENAME_USER_PACKAGES + ".reservecopy";
+
static final String DIRECTORY_BITMAPS = "bitmaps";
private static final String TAG_ROOT = "root";
@@ -1055,6 +1058,11 @@ public class ShortcutService extends IShortcutService.Stub {
return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES);
}
+ @VisibleForTesting
+ final File getReserveCopyUserFile(@UserIdInt int userId) {
+ return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES_RESERVE_COPY);
+ }
+
@GuardedBy("mLock")
private void saveUserLocked(@UserIdInt int userId) {
final File path = getUserFile(userId);
@@ -1062,6 +1070,9 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, "Saving to " + path);
}
+ final File reservePath = getReserveCopyUserFile(userId);
+ reservePath.delete();
+
path.getParentFile().mkdirs();
final AtomicFile file = new AtomicFile(path);
FileOutputStream os = null;
@@ -1079,6 +1090,23 @@ public class ShortcutService extends IShortcutService.Stub {
file.failWrite(os);
}
+ // Store the reserve copy of the file.
+ try (FileInputStream in = new FileInputStream(path);
+ FileOutputStream out = new FileOutputStream(reservePath)) {
+ FileUtils.copy(in, out);
+ FileUtils.sync(out);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write reserve copy: " + path, e);
+ }
+
+ // Protect both primary and reserve copy with fs-verity.
+ try {
+ FileIntegrity.setUpFsVerity(path);
+ FileIntegrity.setUpFsVerity(reservePath);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to verity-protect", e);
+ }
+
getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger);
}
@@ -1117,26 +1145,25 @@ public class ShortcutService extends IShortcutService.Stub {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Loading from " + path);
}
- final AtomicFile file = new AtomicFile(path);
- final FileInputStream in;
- try {
- in = file.openRead();
+ try (FileInputStream in = new AtomicFile(path).openRead()) {
+ return loadUserInternal(userId, in, /* forBackup= */ false);
} catch (FileNotFoundException e) {
if (DEBUG || DEBUG_REBOOT) {
Slog.d(TAG, "Not found " + path);
}
- return null;
- }
- try {
- final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
- return ret;
- } catch (IOException | XmlPullParserException | InvalidFileFormatException e) {
- Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
- return null;
- } finally {
- IoUtils.closeQuietly(in);
+ } catch (Exception e) {
+ final File reservePath = getReserveCopyUserFile(userId);
+ Slog.e(TAG, "Reading from reserve copy: " + reservePath, e);
+ try (FileInputStream in = new AtomicFile(reservePath).openRead()) {
+ return loadUserInternal(userId, in, /* forBackup= */ false);
+ } catch (Exception exceptionReadingReserveFile) {
+ Slog.e(TAG, "Failed to read reserve copy: " + reservePath,
+ exceptionReadingReserveFile);
+ }
+ Slog.e(TAG, "Failed to read file " + path, e);
}
+ return null;
}
private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is,
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 b20c63ca504f..0a718e329498 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -118,10 +118,12 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -4005,6 +4007,156 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// TODO Check all other fields
}
+ public void testSaveCorruptAndLoadUser() throws Exception {
+ // First, create some shortcuts and save.
+ runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+ final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.icon2));
+
+ final ShortcutInfo si1 = makeShortcut(
+ "s1",
+ "title1-1",
+ makeComponent(ShortcutActivity.class),
+ icon1,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+ "key1", "val1", "nest", makeBundle("key", 123)),
+ /* weight */ 10);
+
+ final ShortcutInfo si2 = makeShortcut(
+ "s2",
+ "title1-2",
+ /* activity */ null,
+ icon2,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+ /* weight */ 12);
+
+ assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+ assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+ assertEquals(2, mManager.getRemainingCallCount());
+ });
+ runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+ final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_16x64);
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.icon2));
+
+ final ShortcutInfo si1 = makeShortcut(
+ "s1",
+ "title2-1",
+ makeComponent(ShortcutActivity.class),
+ icon1,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+ "key1", "val1", "nest", makeBundle("key", 123)),
+ /* weight */ 10);
+
+ final ShortcutInfo si2 = makeShortcut(
+ "s2",
+ "title2-2",
+ /* activity */ null,
+ icon2,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+ /* weight */ 12);
+
+ assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+ assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+ assertEquals(2, mManager.getRemainingCallCount());
+ });
+
+ mRunningUsers.put(USER_10, true);
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
+ final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ getTestContext().getResources(), R.drawable.icon2));
+
+ final ShortcutInfo si1 = makeShortcut(
+ "s1",
+ "title10-1-1",
+ makeComponent(ShortcutActivity.class),
+ icon1,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity2.class,
+ "key1", "val1", "nest", makeBundle("key", 123)),
+ /* weight */ 10);
+
+ final ShortcutInfo si2 = makeShortcut(
+ "s2",
+ "title10-1-2",
+ /* activity */ null,
+ icon2,
+ makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class),
+ /* weight */ 12);
+
+ assertTrue(mManager.setDynamicShortcuts(list(si1, si2)));
+
+ assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
+ assertEquals(2, mManager.getRemainingCallCount());
+ });
+
+ // Save and corrupt the primary files.
+ mService.saveDirtyInfo();
+ try (Writer os = new FileWriter(mService.getUserFile(UserHandle.USER_SYSTEM))) {
+ os.write("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<user locales=\"en\" last-app-scan-time2=\"14400000");
+ }
+ try (Writer os = new FileWriter(mService.getUserFile(USER_10))) {
+ os.write("<?xml version='1.0' encoding='utf");
+ }
+
+ // Restore.
+ initService();
+
+ // Before the load, the map should be empty.
+ assertEquals(0, mService.getShortcutsForTest().size());
+
+ // this will pre-load the per-user info.
+ mService.handleUnlockUser(UserHandle.USER_SYSTEM);
+
+ // Now it's loaded.
+ assertEquals(1, mService.getShortcutsForTest().size());
+
+ runWithCaller(CALLING_PACKAGE_1, UserHandle.USER_SYSTEM, () -> {
+ assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+ mManager.getDynamicShortcuts()))), "s1", "s2");
+ assertEquals(2, mManager.getRemainingCallCount());
+
+ assertEquals("title1-1", getCallerShortcut("s1").getTitle());
+ assertEquals("title1-2", getCallerShortcut("s2").getTitle());
+ });
+ runWithCaller(CALLING_PACKAGE_2, UserHandle.USER_SYSTEM, () -> {
+ assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+ mManager.getDynamicShortcuts()))), "s1", "s2");
+ assertEquals(2, mManager.getRemainingCallCount());
+
+ assertEquals("title2-1", getCallerShortcut("s1").getTitle());
+ assertEquals("title2-2", getCallerShortcut("s2").getTitle());
+ });
+
+ // Start another user
+ mService.handleUnlockUser(USER_10);
+
+ // Now the size is 2.
+ assertEquals(2, mService.getShortcutsForTest().size());
+
+ runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
+ assertShortcutIds(assertAllDynamic(assertAllHaveIntents(assertAllHaveIcon(
+ mManager.getDynamicShortcuts()))), "s1", "s2");
+ assertEquals(2, mManager.getRemainingCallCount());
+
+ assertEquals("title10-1-1", getCallerShortcut("s1").getTitle());
+ assertEquals("title10-1-2", getCallerShortcut("s2").getTitle());
+ });
+
+ // Try stopping the user
+ mService.handleStopUser(USER_10);
+
+ // Now it's unloaded.
+ assertEquals(1, mService.getShortcutsForTest().size());
+
+ // TODO Check all other fields
+ }
+
public void testCleanupPackage() {
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(