Create package-level last time usage stats for app hibernation

Create a user-agnostic package-level last time used usage stats for app
hibernation. Usage is updated when USER_INTERACTION or
APP_COMPONENT_USED event reported. Also moved checkAndGetTimeLocked and
convertToSystemTimeLocked methods up to UsageStatsService class and
refactored usages.

Bug: 183142974
Test: atest CtsUsageStatsTestCases:UsageStatsTest
Test: atest UsageStatsServiceTest
Test: atest UserUsageStatsServiceTest

Change-Id: I93d8653bb92b98af8f6b57156eb1139fbd7e562a
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 92c21f3..86d9255 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1835,6 +1835,7 @@
   public final class UsageStatsManager {
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int getAppStandbyBucket(String);
     method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public java.util.Map<java.lang.String,java.lang.Integer> getAppStandbyBuckets();
+    method @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long getLastTimeAnyComponentUsed(@NonNull String);
     method public int getUsageSource();
     method @RequiresPermission(android.Manifest.permission.BIND_CARRIER_SERVICES) public void onCarrierPrivilegedAppsChanged();
     method @RequiresPermission(allOf={android.Manifest.permission.SUSPEND_APPS, android.Manifest.permission.OBSERVE_APP_USAGE}) public void registerAppUsageLimitObserver(int, @NonNull String[], @NonNull java.time.Duration, @NonNull java.time.Duration, @Nullable android.app.PendingIntent);
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index 30ea5c4..2a50e0d 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -67,4 +67,5 @@
     void reportUsageStop(in IBinder activity, String token, String callingPackage);
     int getUsageSource();
     void forceUsageSourceSettingRead();
+    long getLastTimeAnyComponentUsed(String packageName);
 }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 1db7e9d..067c212 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -1250,4 +1250,33 @@
         } catch (RemoteException re) {
         }
     }
+
+    /**
+     * Get the last time a package is used by any users including explicit user interaction and
+     * component usage, measured in milliseconds since the epoch and truncated to the boundary of
+     * last day before the exact time. For packages that are never used, the time will be the epoch.
+     * <p> Note that this usage stats is user-agnostic. </p>
+     * <p>
+     * Also note that component usage is only reported for component bindings (e.g. broadcast
+     * receiver, service, content provider) and only when such a binding would cause an app to leave
+     * the stopped state.
+     * See {@link UsageEvents.Event.USER_INTERACTION}, {@link UsageEvents.Event.APP_COMPONENT_USED}.
+     * </p>
+     *
+     * @param packageName The name of the package to be queried.
+     * @return last time the queried package is used since the epoch.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            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) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
index d786a5d..24c58f4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/usage/UserUsageStatsServiceTest.java
@@ -106,7 +106,9 @@
 
     @Test
     public void testReportEvent_packageUsedEventNotTracked() {
-        Event event = new Event(APP_COMPONENT_USED, SystemClock.elapsedRealtime());
+        // For APP_COMPONENT_USED event, the time stamp should have been converted to current time
+        // before reported here.
+        Event event = new Event(APP_COMPONENT_USED, System.currentTimeMillis());
         event.mPackage = TEST_PACKAGE_NAME;
         mService.reportEvent(event);
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 0510b9c..52f9fe5 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -75,6 +75,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Slog;
@@ -109,8 +110,10 @@
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.TimeUnit;
 
 /**
  * A service that collects, aggregates, and persists application usage data.
@@ -166,6 +169,11 @@
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
     int mUsageSource;
 
+    private long mRealTimeSnapshot;
+    private long mSystemTimeSnapshot;
+    // A map storing last time global usage of packages, measured in milliseconds since the epoch.
+    private final Map<String, Long> mLastTimeComponentUsedGlobal = new ArrayMap<>();
+
     /** Manages the standby state of apps. */
     AppStandbyInternal mAppStandby;
 
@@ -279,6 +287,9 @@
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
                 null, mHandler);
 
+        mRealTimeSnapshot = SystemClock.elapsedRealtime();
+        mSystemTimeSnapshot = System.currentTimeMillis();
+
         publishLocalService(UsageStatsManagerInternal.class, new LocalService());
         publishLocalService(AppStandbyInternal.class, mAppStandby);
         publishBinderServices();
@@ -804,6 +815,28 @@
     }
 
     /**
+     * Assuming the event's timestamp is measured in milliseconds since boot,
+     * convert it to a system wall time. System and real time snapshots are updated before
+     * conversion.
+     */
+    private void convertToSystemTimeLocked(Event event) {
+        final long actualSystemTime = System.currentTimeMillis();
+        if (ENABLE_TIME_CHANGE_CORRECTION) {
+            final long actualRealtime = SystemClock.elapsedRealtime();
+            final long expectedSystemTime =
+                    (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
+            final long diffSystemTime = actualSystemTime - expectedSystemTime;
+            if (Math.abs(diffSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS) {
+                // The time has changed.
+                Slog.i(TAG, "Time changed in by " + (diffSystemTime / 1000) + " seconds");
+                mRealTimeSnapshot = actualRealtime;
+                mSystemTimeSnapshot = actualSystemTime;
+            }
+        }
+        event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
+    }
+
+    /**
      * Called by the Binder stub.
      */
     void reportEvent(Event event, int userId) {
@@ -940,6 +973,12 @@
                         Slog.w(TAG, "Failed to note usage stop", iae);
                     }
                     break;
+                case Event.USER_INTERACTION:
+                    // Fall through
+                case Event.APP_COMPONENT_USED:
+                    convertToSystemTimeLocked(event);
+                    mLastTimeComponentUsedGlobal.put(event.mPackage, event.mTimeStamp);
+                    break;
             }
 
             final UserUsageStatsService service = getUserUsageStatsServiceLocked(userId);
@@ -2094,6 +2133,16 @@
         public void forceUsageSourceSettingRead() {
             readUsageSourceSetting();
         }
+
+        @Override
+        public long getLastTimeAnyComponentUsed(String packageName) {
+            synchronized (mLock) {
+                // Truncate the returned milliseconds to the boundary of the last day before exact
+                // time for privacy reasons.
+                return mLastTimeComponentUsedGlobal.getOrDefault(packageName, 0L)
+                        / TimeUnit.DAYS.toMillis(1) * TimeUnit.DAYS.toMillis(1);
+            }
+        }
     }
 
     void registerAppUsageObserver(int callingUid, int observerId, String[] packages,
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 22b4f4e..36d8c85 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -277,8 +277,11 @@
                     + eventToString(event.mEventType));
         }
 
-        checkAndGetTimeLocked();
-        convertToSystemTimeLocked(event);
+        if (event.mEventType != Event.USER_INTERACTION
+                && event.mEventType != Event.APP_COMPONENT_USED) {
+            checkAndGetTimeLocked();
+            convertToSystemTimeLocked(event);
+        }
 
         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
             // Need to rollover