Loading core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -5070,6 +5070,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.usage.UsageStatsIdleService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> Loading services/core/java/android/app/usage/UsageStatsManagerInternal.java +9 −0 Original line number Diff line number Diff line Loading @@ -281,4 +281,13 @@ public abstract class UsageStatsManagerInternal { return mUsageRemaining; } } /** * Called by {@link com.android.server.usage.UsageStatsIdleService} when the device is idle to * prune usage stats data for uninstalled packages. * * @param userId the user associated with the job * @return {@code true} if the pruning was successful, {@code false} otherwise */ public abstract boolean pruneUninstalledPackagesData(@UserIdInt int userId); } services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +101 −11 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest Loading Loading @@ -93,6 +94,8 @@ public class UsageStatsDatabaseTest { for (File f : usageFiles) { f.delete(); } } else { intervalDir.delete(); } } } Loading Loading @@ -587,6 +590,7 @@ public class UsageStatsDatabaseTest { db.readMappingsLocked(); db.init(1); db.putUsageStats(interval, mIntervalStats); db.writeMappingsLocked(); final String removedPackage = "fake.package.name0"; // invoke handler call directly from test to remove package Loading @@ -594,21 +598,21 @@ public class UsageStatsDatabaseTest { List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, mIntervalStatsVerifier); for (int i = 0; i < stats.size(); i++) { final IntervalStats stat = stats.get(i); assertEquals(1, stats.size(), "Only one interval stats object should exist for the given time range."); final IntervalStats stat = stats.get(0); if (stat.packageStats.containsKey(removedPackage)) { fail("Found removed package " + removedPackage + " in package stats."); return; } for (int j = 0; j < stat.events.size(); j++) { final Event event = stat.events.get(j); for (int i = 0; i < stat.events.size(); i++) { final Event event = stat.events.get(i); if (removedPackage.equals(event.mPackage)) { fail("Found an event from removed package " + removedPackage); return; } } } } @Test public void testPackageRetention() throws IOException { Loading @@ -617,4 +621,90 @@ public class UsageStatsDatabaseTest { verifyPackageNotRetained(UsageStatsManager.INTERVAL_MONTHLY); verifyPackageNotRetained(UsageStatsManager.INTERVAL_YEARLY); } private void verifyPackageDataIsRemoved(UsageStatsDatabase db, int interval, String removedPackage) { List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, mIntervalStatsVerifier); assertEquals(1, stats.size(), "Only one interval stats object should exist for the given time range."); final IntervalStats stat = stats.get(0); if (stat.packageStats.containsKey(removedPackage)) { fail("Found removed package " + removedPackage + " in package stats."); return; } for (int i = 0; i < stat.events.size(); i++) { final Event event = stat.events.get(i); if (removedPackage.equals(event.mPackage)) { fail("Found an event from removed package " + removedPackage); return; } } } private void verifyPackageDataIsNotRemoved(UsageStatsDatabase db, int interval, Set<String> installedPackages) { List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, mIntervalStatsVerifier); assertEquals(1, stats.size(), "Only one interval stats object should exist for the given time range."); final IntervalStats stat = stats.get(0); if (!stat.packageStats.containsAll(installedPackages)) { fail("Could not find some installed packages in package stats."); return; } // attempt to find an event from each installed package for (String installedPackage : installedPackages) { for (int i = 0; i < stat.events.size(); i++) { if (installedPackage.equals(stat.events.get(i).mPackage)) { break; } if (i == stat.events.size() - 1) { fail("Could not find any event for: " + installedPackage); return; } } } } @Test public void testPackageDataIsRemoved() throws IOException { UsageStatsDatabase db = new UsageStatsDatabase(mTestDir); db.readMappingsLocked(); db.init(1); // write stats to disk for each interval db.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats); db.putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, mIntervalStats); db.putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, mIntervalStats); db.putUsageStats(UsageStatsManager.INTERVAL_YEARLY, mIntervalStats); db.writeMappingsLocked(); final Set<String> installedPackages = mIntervalStats.packageStats.keySet(); final String removedPackage = installedPackages.iterator().next(); installedPackages.remove(removedPackage); // mimic a package uninstall db.onPackageRemoved(removedPackage, System.currentTimeMillis()); // mimic the idle prune job being triggered db.pruneUninstalledPackagesData(); // read data from disk into a new db instance UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir); newDB.readMappingsLocked(); newDB.init(mEndTime); // query data for each interval and ensure data for package doesn't exist verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, removedPackage); verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, removedPackage); verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, removedPackage); verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, removedPackage); // query data for each interval and ensure some data for installed packages exists verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, installedPackages); verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, installedPackages); verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, installedPackages); verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, installedPackages); } } services/usage/java/com/android/server/usage/IntervalStats.java +19 −5 Original line number Diff line number Diff line Loading @@ -448,8 +448,11 @@ public class IntervalStats { /** * Parses all of the tokens to strings in the obfuscated usage stats data. This includes * deobfuscating each of the package tokens and chooser actions and categories. * * @return {@code true} if any stats were omitted while deobfuscating, {@code false} otherwise. */ private void deobfuscateUsageStats(PackagesTokenData packagesTokenData) { private boolean deobfuscateUsageStats(PackagesTokenData packagesTokenData) { boolean dataOmitted = false; final int usageStatsSize = packageStatsObfuscated.size(); for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) { final int packageToken = packageStatsObfuscated.keyAt(statsIndex); Loading @@ -457,6 +460,7 @@ public class IntervalStats { usageStats.mPackageName = packagesTokenData.getPackageString(packageToken); if (usageStats.mPackageName == null) { Slog.e(TAG, "Unable to parse usage stats package " + packageToken); dataOmitted = true; continue; } Loading Loading @@ -489,14 +493,18 @@ public class IntervalStats { } packageStats.put(usageStats.mPackageName, usageStats); } return dataOmitted; } /** * Parses all of the tokens to strings in the obfuscated events data. This includes * deobfuscating the package token, along with any class, task root package/class tokens, and * shortcut or notification channel tokens. * * @return {@code true} if any events were omitted while deobfuscating, {@code false} otherwise. */ private void deobfuscateEvents(PackagesTokenData packagesTokenData) { private boolean deobfuscateEvents(PackagesTokenData packagesTokenData) { boolean dataOmitted = false; for (int i = this.events.size() - 1; i >= 0; i--) { final Event event = this.events.get(i); final int packageToken = event.mPackageToken; Loading @@ -504,6 +512,7 @@ public class IntervalStats { if (event.mPackage == null) { Slog.e(TAG, "Unable to parse event package " + packageToken); this.events.remove(i); dataOmitted = true; continue; } Loading Loading @@ -543,6 +552,7 @@ public class IntervalStats { Slog.e(TAG, "Unable to parse shortcut " + event.mShortcutIdToken + " for package " + packageToken); this.events.remove(i); dataOmitted = true; continue; } break; Loading @@ -554,21 +564,25 @@ public class IntervalStats { + event.mNotificationChannelIdToken + " for package " + packageToken); this.events.remove(i); dataOmitted = true; continue; } break; } } return dataOmitted; } /** * Parses the obfuscated tokenized data held in this interval stats object. * * @return {@code true} if any data was omitted while deobfuscating, {@code false} otherwise. * @hide */ public void deobfuscateData(PackagesTokenData packagesTokenData) { deobfuscateUsageStats(packagesTokenData); deobfuscateEvents(packagesTokenData); public boolean deobfuscateData(PackagesTokenData packagesTokenData) { final boolean statsOmitted = deobfuscateUsageStats(packagesTokenData); final boolean eventsOmitted = deobfuscateEvents(packagesTokenData); return statsOmitted || eventsOmitted; } /** Loading services/usage/java/com/android/server/usage/PackagesTokenData.java +5 −2 Original line number Diff line number Diff line Loading @@ -162,15 +162,18 @@ public final class PackagesTokenData { * * @param packageName the package to be removed * @param timeRemoved the time stamp of when the package was removed * @return the token mapped to the package removed or {@code PackagesTokenData.UNASSIGNED_TOKEN} * if not mapped */ public void removePackage(String packageName, long timeRemoved) { public int removePackage(String packageName, long timeRemoved) { removedPackagesMap.put(packageName, timeRemoved); if (!packagesToTokensMap.containsKey(packageName)) { return; return UNASSIGNED_TOKEN; } final int packageToken = packagesToTokensMap.get(packageName).get(packageName); packagesToTokensMap.remove(packageName); tokensToPackagesMap.delete(packageToken); return packageToken; } } Loading
core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -5070,6 +5070,10 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.usage.UsageStatsIdleService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> Loading
services/core/java/android/app/usage/UsageStatsManagerInternal.java +9 −0 Original line number Diff line number Diff line Loading @@ -281,4 +281,13 @@ public abstract class UsageStatsManagerInternal { return mUsageRemaining; } } /** * Called by {@link com.android.server.usage.UsageStatsIdleService} when the device is idle to * prune usage stats data for uninstalled packages. * * @param userId the user associated with the job * @return {@code true} if the pruning was successful, {@code false} otherwise */ public abstract boolean pruneUninstalledPackagesData(@UserIdInt int userId); }
services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +101 −11 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.List; import java.util.Locale; import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest Loading Loading @@ -93,6 +94,8 @@ public class UsageStatsDatabaseTest { for (File f : usageFiles) { f.delete(); } } else { intervalDir.delete(); } } } Loading Loading @@ -587,6 +590,7 @@ public class UsageStatsDatabaseTest { db.readMappingsLocked(); db.init(1); db.putUsageStats(interval, mIntervalStats); db.writeMappingsLocked(); final String removedPackage = "fake.package.name0"; // invoke handler call directly from test to remove package Loading @@ -594,21 +598,21 @@ public class UsageStatsDatabaseTest { List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, mIntervalStatsVerifier); for (int i = 0; i < stats.size(); i++) { final IntervalStats stat = stats.get(i); assertEquals(1, stats.size(), "Only one interval stats object should exist for the given time range."); final IntervalStats stat = stats.get(0); if (stat.packageStats.containsKey(removedPackage)) { fail("Found removed package " + removedPackage + " in package stats."); return; } for (int j = 0; j < stat.events.size(); j++) { final Event event = stat.events.get(j); for (int i = 0; i < stat.events.size(); i++) { final Event event = stat.events.get(i); if (removedPackage.equals(event.mPackage)) { fail("Found an event from removed package " + removedPackage); return; } } } } @Test public void testPackageRetention() throws IOException { Loading @@ -617,4 +621,90 @@ public class UsageStatsDatabaseTest { verifyPackageNotRetained(UsageStatsManager.INTERVAL_MONTHLY); verifyPackageNotRetained(UsageStatsManager.INTERVAL_YEARLY); } private void verifyPackageDataIsRemoved(UsageStatsDatabase db, int interval, String removedPackage) { List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, mIntervalStatsVerifier); assertEquals(1, stats.size(), "Only one interval stats object should exist for the given time range."); final IntervalStats stat = stats.get(0); if (stat.packageStats.containsKey(removedPackage)) { fail("Found removed package " + removedPackage + " in package stats."); return; } for (int i = 0; i < stat.events.size(); i++) { final Event event = stat.events.get(i); if (removedPackage.equals(event.mPackage)) { fail("Found an event from removed package " + removedPackage); return; } } } private void verifyPackageDataIsNotRemoved(UsageStatsDatabase db, int interval, Set<String> installedPackages) { List<IntervalStats> stats = db.queryUsageStats(interval, 0, mEndTime, mIntervalStatsVerifier); assertEquals(1, stats.size(), "Only one interval stats object should exist for the given time range."); final IntervalStats stat = stats.get(0); if (!stat.packageStats.containsAll(installedPackages)) { fail("Could not find some installed packages in package stats."); return; } // attempt to find an event from each installed package for (String installedPackage : installedPackages) { for (int i = 0; i < stat.events.size(); i++) { if (installedPackage.equals(stat.events.get(i).mPackage)) { break; } if (i == stat.events.size() - 1) { fail("Could not find any event for: " + installedPackage); return; } } } } @Test public void testPackageDataIsRemoved() throws IOException { UsageStatsDatabase db = new UsageStatsDatabase(mTestDir); db.readMappingsLocked(); db.init(1); // write stats to disk for each interval db.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats); db.putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, mIntervalStats); db.putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, mIntervalStats); db.putUsageStats(UsageStatsManager.INTERVAL_YEARLY, mIntervalStats); db.writeMappingsLocked(); final Set<String> installedPackages = mIntervalStats.packageStats.keySet(); final String removedPackage = installedPackages.iterator().next(); installedPackages.remove(removedPackage); // mimic a package uninstall db.onPackageRemoved(removedPackage, System.currentTimeMillis()); // mimic the idle prune job being triggered db.pruneUninstalledPackagesData(); // read data from disk into a new db instance UsageStatsDatabase newDB = new UsageStatsDatabase(mTestDir); newDB.readMappingsLocked(); newDB.init(mEndTime); // query data for each interval and ensure data for package doesn't exist verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, removedPackage); verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, removedPackage); verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, removedPackage); verifyPackageDataIsRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, removedPackage); // query data for each interval and ensure some data for installed packages exists verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_DAILY, installedPackages); verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_WEEKLY, installedPackages); verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_MONTHLY, installedPackages); verifyPackageDataIsNotRemoved(newDB, UsageStatsManager.INTERVAL_YEARLY, installedPackages); } }
services/usage/java/com/android/server/usage/IntervalStats.java +19 −5 Original line number Diff line number Diff line Loading @@ -448,8 +448,11 @@ public class IntervalStats { /** * Parses all of the tokens to strings in the obfuscated usage stats data. This includes * deobfuscating each of the package tokens and chooser actions and categories. * * @return {@code true} if any stats were omitted while deobfuscating, {@code false} otherwise. */ private void deobfuscateUsageStats(PackagesTokenData packagesTokenData) { private boolean deobfuscateUsageStats(PackagesTokenData packagesTokenData) { boolean dataOmitted = false; final int usageStatsSize = packageStatsObfuscated.size(); for (int statsIndex = 0; statsIndex < usageStatsSize; statsIndex++) { final int packageToken = packageStatsObfuscated.keyAt(statsIndex); Loading @@ -457,6 +460,7 @@ public class IntervalStats { usageStats.mPackageName = packagesTokenData.getPackageString(packageToken); if (usageStats.mPackageName == null) { Slog.e(TAG, "Unable to parse usage stats package " + packageToken); dataOmitted = true; continue; } Loading Loading @@ -489,14 +493,18 @@ public class IntervalStats { } packageStats.put(usageStats.mPackageName, usageStats); } return dataOmitted; } /** * Parses all of the tokens to strings in the obfuscated events data. This includes * deobfuscating the package token, along with any class, task root package/class tokens, and * shortcut or notification channel tokens. * * @return {@code true} if any events were omitted while deobfuscating, {@code false} otherwise. */ private void deobfuscateEvents(PackagesTokenData packagesTokenData) { private boolean deobfuscateEvents(PackagesTokenData packagesTokenData) { boolean dataOmitted = false; for (int i = this.events.size() - 1; i >= 0; i--) { final Event event = this.events.get(i); final int packageToken = event.mPackageToken; Loading @@ -504,6 +512,7 @@ public class IntervalStats { if (event.mPackage == null) { Slog.e(TAG, "Unable to parse event package " + packageToken); this.events.remove(i); dataOmitted = true; continue; } Loading Loading @@ -543,6 +552,7 @@ public class IntervalStats { Slog.e(TAG, "Unable to parse shortcut " + event.mShortcutIdToken + " for package " + packageToken); this.events.remove(i); dataOmitted = true; continue; } break; Loading @@ -554,21 +564,25 @@ public class IntervalStats { + event.mNotificationChannelIdToken + " for package " + packageToken); this.events.remove(i); dataOmitted = true; continue; } break; } } return dataOmitted; } /** * Parses the obfuscated tokenized data held in this interval stats object. * * @return {@code true} if any data was omitted while deobfuscating, {@code false} otherwise. * @hide */ public void deobfuscateData(PackagesTokenData packagesTokenData) { deobfuscateUsageStats(packagesTokenData); deobfuscateEvents(packagesTokenData); public boolean deobfuscateData(PackagesTokenData packagesTokenData) { final boolean statsOmitted = deobfuscateUsageStats(packagesTokenData); final boolean eventsOmitted = deobfuscateEvents(packagesTokenData); return statsOmitted || eventsOmitted; } /** Loading
services/usage/java/com/android/server/usage/PackagesTokenData.java +5 −2 Original line number Diff line number Diff line Loading @@ -162,15 +162,18 @@ public final class PackagesTokenData { * * @param packageName the package to be removed * @param timeRemoved the time stamp of when the package was removed * @return the token mapped to the package removed or {@code PackagesTokenData.UNASSIGNED_TOKEN} * if not mapped */ public void removePackage(String packageName, long timeRemoved) { public int removePackage(String packageName, long timeRemoved) { removedPackagesMap.put(packageName, timeRemoved); if (!packagesToTokensMap.containsKey(packageName)) { return; return UNASSIGNED_TOKEN; } final int packageToken = packagesToTokensMap.get(packageName).get(packageName); packagesToTokensMap.remove(packageName); tokensToPackagesMap.delete(packageToken); return packageToken; } }