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

Commit bf5667be authored by Zhen Zhang's avatar Zhen Zhang Committed by Android (Google) Code Review
Browse files

Merge "Persist user-agnostic lastTimeAnyComponentUsed in disk" into sc-dev

parents 5d2a09ad 131f53fd
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -1272,7 +1272,6 @@ public final class UsageStatsManager {
            android.Manifest.permission.INTERACT_ACROSS_USERS,
            android.Manifest.permission.PACKAGE_USAGE_STATS})
    public long getLastTimeAnyComponentUsed(@NonNull String packageName) {
        // TODO(b/183462940): This usage data is not persisted to disk yet.
        try {
            return mService.getLastTimeAnyComponentUsed(packageName);
        } catch (RemoteException re) {
+9 −0
Original line number Diff line number Diff line
@@ -40,6 +40,13 @@ message IntervalStatsObfuscatedProto {
    optional bool active = 5;
  }


  // Stores the information of last time a package is used by any users
  message PackageUsage {
    optional string package_name = 1;
    optional int64 time_ms = 2;
  }

  // The following fields contain supplemental data used to build IntervalStats.
  optional int64 end_time_ms = 1;
  optional int32 major_version = 2;
@@ -57,6 +64,8 @@ message IntervalStatsObfuscatedProto {
  repeated EventObfuscatedProto event_log = 22;
  // The following field is only used to persist the reported events before a user unlock
  repeated PendingEventProto pending_events = 23;
  // The following field is only used to persist the user-agnostic package usage before shut down
  repeated PackageUsage package_usage = 24;
}

/**
+72 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.app.usage.ConfigurationStats;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStats;
import android.content.res.Configuration;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -30,6 +31,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
@@ -812,4 +814,74 @@ final class UsageStatsProtoV2 {
        }
        proto.flush();
    }

    private static Pair<String, Long> parseGlobalComponentUsage(ProtoInputStream proto)
            throws IOException {
        String packageName = "";
        long time = 0;
        while (true) {
            switch (proto.nextField()) {
                case (int) IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME:
                    packageName = proto.readString(
                            IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME);
                    break;
                case (int) IntervalStatsObfuscatedProto.PackageUsage.TIME_MS:
                    time = proto.readLong(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS);
                    break;
                case ProtoInputStream.NO_MORE_FIELDS:
                    return new Pair<>(packageName, time);
            }
        }
    }

    /**
     * Populates the map of latest package usage from the input stream given.
     *
     * @param in the input stream from which to read the package usage.
     * @param lastTimeComponentUsedGlobal the map of package's global component usage to populate.
     */
    static void readGlobalComponentUsage(InputStream in,
            Map<String, Long> lastTimeComponentUsedGlobal) throws IOException {
        final ProtoInputStream proto = new ProtoInputStream(in);
        while (true) {
            switch (proto.nextField()) {
                case (int) IntervalStatsObfuscatedProto.PACKAGE_USAGE:
                    try {
                        final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE);
                        final Pair<String, Long> usage = parseGlobalComponentUsage(proto);
                        proto.end(token);
                        if (!usage.first.isEmpty() && usage.second > 0) {
                            lastTimeComponentUsedGlobal.put(usage.first, usage.second);
                        }
                    } catch (IOException e) {
                        Slog.e(TAG, "Unable to parse some package usage from proto.", e);
                    }
                    break;
                case ProtoInputStream.NO_MORE_FIELDS:
                    return;
            }
        }
    }

    /**
     * Writes the user-agnostic last time package usage to a ProtoBuf file.
     *
     * @param out the output stream to which to write the package usage
     * @param lastTimeComponentUsedGlobal the map storing the global component usage of packages
     */
    static void writeGlobalComponentUsage(OutputStream out,
            Map<String, Long> lastTimeComponentUsedGlobal) {
        final ProtoOutputStream proto = new ProtoOutputStream(out);
        final Map.Entry<String, Long>[] entries =
                (Map.Entry<String, Long>[]) lastTimeComponentUsedGlobal.entrySet().toArray();
        final int size = entries.length;
        for (int i = 0; i < size; ++i) {
            if (entries[i].getValue() <= 0) continue;
            final long token = proto.start(IntervalStatsObfuscatedProto.PACKAGE_USAGE);
            proto.write(IntervalStatsObfuscatedProto.PackageUsage.PACKAGE_NAME,
                    entries[i].getKey());
            proto.write(IntervalStatsObfuscatedProto.PackageUsage.TIME_MS, entries[i].getValue());
            proto.end(token);
        }
    }
}
+68 −1
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -142,6 +143,10 @@ public class UsageStatsService extends SystemService implements
    // For migration purposes, indicates whether to keep the legacy usage stats directory or not
    private static final boolean KEEP_LEGACY_DIR = false;

    private static final File COMMON_USAGE_STATS_DE_DIR =
            new File(Environment.getDataSystemDeDirectory(), "usagestats");
    private static final String GLOBAL_COMPONENT_USAGE_FILE_NAME = "globalcomponentusage";

    private static final char TOKEN_DELIMITER = '/';

    // Handler message types.
@@ -152,6 +157,7 @@ public class UsageStatsService extends SystemService implements
    static final int MSG_REPORT_EVENT_TO_ALL_USERID = 4;
    static final int MSG_UNLOCKED_USER = 5;
    static final int MSG_PACKAGE_REMOVED = 6;
    static final int MSG_ON_START = 7;

    private final Object mLock = new Object();
    Handler mHandler;
@@ -293,6 +299,8 @@ public class UsageStatsService extends SystemService implements
        publishLocalService(UsageStatsManagerInternal.class, new LocalService());
        publishLocalService(AppStandbyInternal.class, mAppStandby);
        publishBinderServices();

        mHandler.obtainMessage(MSG_ON_START).sendToTarget();
    }

    @VisibleForTesting
@@ -716,6 +724,7 @@ public class UsageStatsService extends SystemService implements
            // orderly shutdown, the last event is DEVICE_SHUTDOWN.
            reportEventToAllUserId(event);
            flushToDiskLocked();
            persistGlobalComponentUsageLocked();
        }

        mAppStandby.flushToDisk();
@@ -794,6 +803,60 @@ public class UsageStatsService extends SystemService implements
        }
    }

    private void loadGlobalComponentUsageLocked() {
        final File[] packageUsageFile = COMMON_USAGE_STATS_DE_DIR.listFiles(
                (dir, name) -> TextUtils.equals(name, GLOBAL_COMPONENT_USAGE_FILE_NAME));
        if (packageUsageFile == null || packageUsageFile.length == 0) {
            return;
        }

        final AtomicFile af = new AtomicFile(packageUsageFile[0]);
        final Map<String, Long> tmpUsage = new ArrayMap<>();
        try {
            try (FileInputStream in = af.openRead()) {
                UsageStatsProtoV2.readGlobalComponentUsage(in, tmpUsage);
            }
            // only add to in memory map if the read was successful
            final Map.Entry<String, Long>[] entries =
                    (Map.Entry<String, Long>[]) tmpUsage.entrySet().toArray();
            final int size = entries.length;
            for (int i = 0; i < size; ++i) {
                // In memory data is usually the most up-to-date, so skip the packages which already
                // have usage data.
                mLastTimeComponentUsedGlobal.putIfAbsent(
                        entries[i].getKey(), entries[i].getValue());
            }
        } catch (Exception e) {
            // Most likely trying to read a corrupted file - log the failure
            Slog.e(TAG, "Could not read " + packageUsageFile[0]);
        }
    }

    private void persistGlobalComponentUsageLocked() {
        if (mLastTimeComponentUsedGlobal.isEmpty()) {
            return;
        }

        if (!COMMON_USAGE_STATS_DE_DIR.mkdirs() && !COMMON_USAGE_STATS_DE_DIR.exists()) {
            throw new IllegalStateException("Common usage stats DE directory does not exist: "
                    + COMMON_USAGE_STATS_DE_DIR.getAbsolutePath());
        }
        final File lastTimePackageFile = new File(COMMON_USAGE_STATS_DE_DIR,
                GLOBAL_COMPONENT_USAGE_FILE_NAME);
        final AtomicFile af = new AtomicFile(lastTimePackageFile);
        FileOutputStream fos = null;
        try {
            fos = af.startWrite();
            UsageStatsProtoV2.writeGlobalComponentUsage(fos, mLastTimeComponentUsedGlobal);
            af.finishWrite(fos);
            fos = null;
        } catch (Exception e) {
            Slog.e(TAG, "Failed to write " + lastTimePackageFile.getAbsolutePath());
        } finally {
            af.failWrite(fos); // when fos is null (successful write), this will no-op
        }
    }

    private void reportEventOrAddToQueue(int userId, Event event) {
        if (mUserUnlockedStates.contains(userId)) {
            mHandler.obtainMessage(MSG_REPORT_EVENT, userId, 0, event).sendToTarget();
@@ -1483,7 +1546,11 @@ public class UsageStatsService extends SystemService implements
                    }
                    break;
                }

                case MSG_ON_START:
                    synchronized (mLock) {
                        loadGlobalComponentUsageLocked();
                    }
                    break;
                default:
                    super.handleMessage(msg);
                    break;