diff options
| -rw-r--r-- | services/core/java/com/android/server/pm/ShortcutService.java | 59 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java | 152 |
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( |