Loading services/core/java/com/android/server/pm/ShortcutService.java +43 −16 Original line number Original line Diff line number Diff line Loading @@ -119,10 +119,9 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemService; import com.android.server.security.FileIntegrity; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import libcore.io.IoUtils; import org.json.JSONArray; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONObject; Loading Loading @@ -207,6 +206,10 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting @VisibleForTesting static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; 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"; static final String DIRECTORY_BITMAPS = "bitmaps"; private static final String TAG_ROOT = "root"; private static final String TAG_ROOT = "root"; Loading Loading @@ -1055,6 +1058,11 @@ public class ShortcutService extends IShortcutService.Stub { return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 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") @GuardedBy("mLock") private void saveUserLocked(@UserIdInt int userId) { private void saveUserLocked(@UserIdInt int userId) { final File path = getUserFile(userId); final File path = getUserFile(userId); Loading @@ -1062,6 +1070,9 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, "Saving to " + path); Slog.d(TAG, "Saving to " + path); } } final File reservePath = getReserveCopyUserFile(userId); reservePath.delete(); path.getParentFile().mkdirs(); path.getParentFile().mkdirs(); final AtomicFile file = new AtomicFile(path); final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; FileOutputStream os = null; Loading @@ -1079,6 +1090,23 @@ public class ShortcutService extends IShortcutService.Stub { file.failWrite(os); 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); getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); } } Loading Loading @@ -1117,26 +1145,25 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG || DEBUG_REBOOT) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Loading from " + path); Slog.d(TAG, "Loading from " + path); } } final AtomicFile file = new AtomicFile(path); final FileInputStream in; try (FileInputStream in = new AtomicFile(path).openRead()) { try { return loadUserInternal(userId, in, /* forBackup= */ false); in = file.openRead(); } catch (FileNotFoundException e) { } catch (FileNotFoundException e) { if (DEBUG || DEBUG_REBOOT) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Not found " + path); Slog.d(TAG, "Not found " + path); } } return null; } 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); } } try { Slog.e(TAG, "Failed to read file " + path, e); 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); } } return null; } } private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, Loading services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +152 −0 Original line number Original line Diff line number Diff line Loading @@ -118,10 +118,12 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.File; import java.io.FileOutputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.IOException; import java.io.InputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStream; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; Loading Loading @@ -4005,6 +4007,156 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields // 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() { public void testCleanupPackage() { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( assertTrue(mManager.setDynamicShortcuts(list( Loading Loading
services/core/java/com/android/server/pm/ShortcutService.java +43 −16 Original line number Original line Diff line number Diff line Loading @@ -119,10 +119,9 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.LocalServices; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemService; import com.android.server.security.FileIntegrity; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import libcore.io.IoUtils; import org.json.JSONArray; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONObject; Loading Loading @@ -207,6 +206,10 @@ public class ShortcutService extends IShortcutService.Stub { @VisibleForTesting @VisibleForTesting static final String FILENAME_USER_PACKAGES = "shortcuts.xml"; 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"; static final String DIRECTORY_BITMAPS = "bitmaps"; private static final String TAG_ROOT = "root"; private static final String TAG_ROOT = "root"; Loading Loading @@ -1055,6 +1058,11 @@ public class ShortcutService extends IShortcutService.Stub { return new File(injectUserDataPath(userId), FILENAME_USER_PACKAGES); 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") @GuardedBy("mLock") private void saveUserLocked(@UserIdInt int userId) { private void saveUserLocked(@UserIdInt int userId) { final File path = getUserFile(userId); final File path = getUserFile(userId); Loading @@ -1062,6 +1070,9 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, "Saving to " + path); Slog.d(TAG, "Saving to " + path); } } final File reservePath = getReserveCopyUserFile(userId); reservePath.delete(); path.getParentFile().mkdirs(); path.getParentFile().mkdirs(); final AtomicFile file = new AtomicFile(path); final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; FileOutputStream os = null; Loading @@ -1079,6 +1090,23 @@ public class ShortcutService extends IShortcutService.Stub { file.failWrite(os); 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); getUserShortcutsLocked(userId).logSharingShortcutStats(mMetricsLogger); } } Loading Loading @@ -1117,26 +1145,25 @@ public class ShortcutService extends IShortcutService.Stub { if (DEBUG || DEBUG_REBOOT) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Loading from " + path); Slog.d(TAG, "Loading from " + path); } } final AtomicFile file = new AtomicFile(path); final FileInputStream in; try (FileInputStream in = new AtomicFile(path).openRead()) { try { return loadUserInternal(userId, in, /* forBackup= */ false); in = file.openRead(); } catch (FileNotFoundException e) { } catch (FileNotFoundException e) { if (DEBUG || DEBUG_REBOOT) { if (DEBUG || DEBUG_REBOOT) { Slog.d(TAG, "Not found " + path); Slog.d(TAG, "Not found " + path); } } return null; } 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); } } try { Slog.e(TAG, "Failed to read file " + path, e); 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); } } return null; } } private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, private ShortcutUser loadUserInternal(@UserIdInt int userId, InputStream is, Loading
services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +152 −0 Original line number Original line Diff line number Diff line Loading @@ -118,10 +118,12 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.File; import java.io.FileOutputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.IOException; import java.io.InputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStream; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.ArrayList; import java.util.List; import java.util.List; Loading Loading @@ -4005,6 +4007,156 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // TODO Check all other fields // 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() { public void testCleanupPackage() { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertTrue(mManager.setDynamicShortcuts(list( assertTrue(mManager.setDynamicShortcuts(list( Loading