Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1403ee3a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Prune UsageStats data belonging to uninstalled packages."

parents 4d4e91db 2c0b9f5d
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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>
+9 −0
Original line number Diff line number Diff line
@@ -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);
}
+101 −11
Original line number Diff line number Diff line
@@ -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
@@ -93,6 +94,8 @@ public class UsageStatsDatabaseTest {
                for (File f : usageFiles) {
                    f.delete();
                }
            } else {
                intervalDir.delete();
            }
        }
    }
@@ -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
@@ -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 {
@@ -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);
    }
}
+19 −5
Original line number Diff line number Diff line
@@ -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);
@@ -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;
            }

@@ -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;
@@ -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;
            }

@@ -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;
@@ -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;
    }

    /**
+5 −2
Original line number Diff line number Diff line
@@ -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