content: /**
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   * use this file except in compliance with the License. You may obtain a copy
   * of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   * License for the specific language governing permissions and limitations
   * under the License.
   */
  
  package android.app.usage;
  
  import static android.app.usage.UsageEvents.Event.ACTIVITY_DESTROYED;
  import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
  import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
  import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
  import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
  import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
  import static android.app.usage.UsageEvents.Event.END_OF_DAY;
  import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
  import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
  import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
  import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
  
  import android.annotation.SystemApi;
  import android.annotation.UnsupportedAppUsage;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.util.ArrayMap;
  import android.util.SparseIntArray;
  
  /**
   * Contains usage statistics for an app package for a specific
   * time range.
   */
  public final class UsageStats implements Parcelable {
  
      /**
       * {@hide}
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      public String mPackageName;
  
      /**
       * {@hide}
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      public long mBeginTimeStamp;
  
      /**
       * {@hide}
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      public long mEndTimeStamp;
  
      /**
       * Last time an activity is at foreground (have focus), this is corresponding to
       * {@link android.app.usage.UsageEvents.Event#ACTIVITY_RESUMED} event.
       * {@hide}
       */
      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
      public long mLastTimeUsed;
  
      /**
       * Last time an activity is visible.
       * @hide
       */
      public long mLastTimeVisible;
  
      /**
       * Total time this package's activity is in foreground.
       * {@hide}
       */
      @UnsupportedAppUsage
      public long mTotalTimeInForeground;
  
      /**
       * Total time this package's activity is visible.
       * {@hide}
       */
      public long mTotalTimeVisible;
  
      /**
       * Last time foreground service is started.
       * {@hide}
       */
      public long mLastTimeForegroundServiceUsed;
  
      /**
       * Total time this package's foreground service is started.
       * {@hide}
       */
      public long mTotalTimeForegroundServiceUsed;
  
      /**
       * {@hide}
       */
      @UnsupportedAppUsage
      public int mLaunchCount;
  
      /**
       * {@hide}
       */
      public int mAppLaunchCount;
  
      /** Last activity ACTIVITY_RESUMED or ACTIVITY_PAUSED event.
       * {@hide}
       * @deprecated use {@link #mActivities} instead.
       */
      @UnsupportedAppUsage
      @Deprecated
      public int mLastEvent;
  
      /**
       * Key is instanceId of the activity (ActivityRecode appToken hashCode)..
       * Value is this activity's last event, one of ACTIVITY_RESUMED, ACTIVITY_PAUSED or
       * ACTIVITY_STOPPED.
       * {@hide}
       */
      public SparseIntArray mActivities = new SparseIntArray();
      /**
       * If a foreground service is started, it has one entry in this map.
       * When a foreground service is stopped, it is removed from this set.
       * Key is foreground service class name.
       * Value is the foreground service's last event, it is FOREGROUND_SERVICE_START.
       * {@hide}
       */
      public ArrayMap<String, Integer> mForegroundServices = new ArrayMap<>();
  
      /**
       * {@hide}
       */
      public ArrayMap<String, ArrayMap<String, Integer>> mChooserCounts = new ArrayMap<>();
  
      /**
       * {@hide}
       */
      public UsageStats() {
      }
  
      public UsageStats(UsageStats stats) {
          mPackageName = stats.mPackageName;
          mBeginTimeStamp = stats.mBeginTimeStamp;
          mEndTimeStamp = stats.mEndTimeStamp;
          mLastTimeUsed = stats.mLastTimeUsed;
          mLastTimeVisible = stats.mLastTimeVisible;
          mLastTimeForegroundServiceUsed = stats.mLastTimeForegroundServiceUsed;
          mTotalTimeInForeground = stats.mTotalTimeInForeground;
          mTotalTimeVisible = stats.mTotalTimeVisible;
          mTotalTimeForegroundServiceUsed = stats.mTotalTimeForegroundServiceUsed;
          mLaunchCount = stats.mLaunchCount;
          mAppLaunchCount = stats.mAppLaunchCount;
          mLastEvent = stats.mLastEvent;
          mActivities = stats.mActivities;
          mForegroundServices = stats.mForegroundServices;
          mChooserCounts = stats.mChooserCounts;
      }
  
      /**
       * {@hide}
       */
      public UsageStats getObfuscatedForInstantApp() {
          final UsageStats ret = new UsageStats(this);
  
          ret.mPackageName = UsageEvents.INSTANT_APP_PACKAGE_NAME;
  
          return ret;
      }
  
      public String getPackageName() {
          return mPackageName;
      }
  
      /**
       * Get the beginning of the time range this {@link android.app.usage.UsageStats} represents,
       * measured in milliseconds since the epoch.
       * <p/>
       * See {@link System#currentTimeMillis()}.
       */
      public long getFirstTimeStamp() {
          return mBeginTimeStamp;
      }
  
      /**
       * Get the end of the time range this {@link android.app.usage.UsageStats} represents,
       * measured in milliseconds since the epoch.
       * <p/>
       * See {@link System#currentTimeMillis()}.
       */
      public long getLastTimeStamp() {
          return mEndTimeStamp;
      }
  
      /**
       * Get the last time this package's activity was used, measured in milliseconds since the epoch.
       * <p/>
       * See {@link System#currentTimeMillis()}.
       */
      public long getLastTimeUsed() {
          return mLastTimeUsed;
      }
  
      /**
       * Get the last time this package's activity is visible in the UI, measured in milliseconds
       * since the epoch.
       */
      public long getLastTimeVisible() {
          return mLastTimeVisible;
      }
  
      /**
       * Get the total time this package spent in the foreground, measured in milliseconds.
       */
      public long getTotalTimeInForeground() {
          return mTotalTimeInForeground;
      }
  
      /**
       * Get the total time this package's activity is visible in the UI, measured in milliseconds.
       */
      public long getTotalTimeVisible() {
          return mTotalTimeVisible;
      }
  
      /**
       * Get the last time this package's foreground service was used, measured in milliseconds since
       * the epoch.
       * <p/>
       * See {@link System#currentTimeMillis()}.
       */
      public long getLastTimeForegroundServiceUsed() {
          return mLastTimeForegroundServiceUsed;
      }
  
      /**
       * Get the total time this package's foreground services are started, measured in milliseconds.
       */
      public long getTotalTimeForegroundServiceUsed() {
          return mTotalTimeForegroundServiceUsed;
      }
  
      /**
       * Returns the number of times the app was launched as an activity from outside of the app.
       * Excludes intra-app activity transitions.
       * @hide
       */
      @SystemApi
      public int getAppLaunchCount() {
          return mAppLaunchCount;
      }
  
      private void mergeEventMap(SparseIntArray left, SparseIntArray right) {
          final int size = right.size();
          for (int i = 0; i < size; i++) {
              final int instanceId = right.keyAt(i);
              final int event = right.valueAt(i);
              final int index = left.indexOfKey(instanceId);
              if (index >= 0) {
                  left.put(instanceId, Math.max(left.valueAt(index), event));
              } else {
                  left.put(instanceId, event);
              }
          }
      }
  
      private void mergeEventMap(ArrayMap<String, Integer> left, ArrayMap<String, Integer> right) {
          final int size = right.size();
          for (int i = 0; i < size; i++) {
              final String className = right.keyAt(i);
              final Integer event = right.valueAt(i);
              if (left.containsKey(className)) {
                  left.put(className, Math.max(left.get(className), event));
              } else {
                  left.put(className, event);
              }
          }
      }
  
      /**
       * Add the statistics from the right {@link UsageStats} to the left. The package name for
       * both {@link UsageStats} objects must be the same.
       * @param right The {@link UsageStats} object to merge into this one.
       * @throws java.lang.IllegalArgumentException if the package names of the two
       *         {@link UsageStats} objects are different.
       */
      public void add(UsageStats right) {
          if (!mPackageName.equals(right.mPackageName)) {
              throw new IllegalArgumentException("Can't merge UsageStats for package '" +
                      mPackageName + "' with UsageStats for package '" + right.mPackageName + "'.");
          }
  
          // We use the mBeginTimeStamp due to a bug where UsageStats files can overlap with
          // regards to their mEndTimeStamp.
          if (right.mBeginTimeStamp > mBeginTimeStamp) {
              // Even though incoming UsageStat begins after this one, its last time used fields
              // may somehow be empty or chronologically preceding the older UsageStat.
              mergeEventMap(mActivities, right.mActivities);
              mergeEventMap(mForegroundServices, right.mForegroundServices);
              mLastTimeUsed = Math.max(mLastTimeUsed, right.mLastTimeUsed);
              mLastTimeVisible = Math.max(mLastTimeVisible, right.mLastTimeVisible);
              mLastTimeForegroundServiceUsed = Math.max(mLastTimeForegroundServiceUsed,
                      right.mLastTimeForegroundServiceUsed);
          }
          mBeginTimeStamp = Math.min(mBeginTimeStamp, right.mBeginTimeStamp);
          mEndTimeStamp = Math.max(mEndTimeStamp, right.mEndTimeStamp);
          mTotalTimeInForeground += right.mTotalTimeInForeground;
          mTotalTimeVisible += right.mTotalTimeVisible;
          mTotalTimeForegroundServiceUsed += right.mTotalTimeForegroundServiceUsed;
          mLaunchCount += right.mLaunchCount;
          mAppLaunchCount += right.mAppLaunchCount;
          if (mChooserCounts == null) {
              mChooserCounts = right.mChooserCounts;
          } else if (right.mChooserCounts != null) {
              final int chooserCountsSize = right.mChooserCounts.size();
              for (int i = 0; i < chooserCountsSize; i++) {
                  String action = right.mChooserCounts.keyAt(i);
                  ArrayMap<String, Integer> counts = right.mChooserCounts.valueAt(i);
                  if (!mChooserCounts.containsKey(action) || mChooserCounts.get(action) == null) {
                      mChooserCounts.put(action, counts);
                      continue;
                  }
                  final int annotationSize = counts.size();
                  for (int j = 0; j < annotationSize; j++) {
                      String key = counts.keyAt(j);
                      int rightValue = counts.valueAt(j);
                      int leftValue = mChooserCounts.get(action).getOrDefault(key, 0);
                      mChooserCounts.get(action).put(key, leftValue + rightValue);
                  }
              }
          }
      }
  
      /**
       * Tell if any activity is in foreground.
       * @return
       */
      private boolean hasForegroundActivity() {
          final int size = mActivities.size();
          for (int i = 0; i < size; i++) {
              if (mActivities.valueAt(i) == ACTIVITY_RESUMED) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Tell if any activity is visible.
       * @return
       */
      private boolean hasVisibleActivity() {
          final int size = mActivities.size();
          for (int i = 0; i < size; i++) {
              final int type = mActivities.valueAt(i);
              if (type == ACTIVITY_RESUMED
                      || type == ACTIVITY_PAUSED) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * Tell if any foreground service is started.
       * @return
       */
      private boolean anyForegroundServiceStarted() {
          return !mForegroundServices.isEmpty();
      }
  
      /**
       * Increment total time in foreground and update last time in foreground.
       * @param timeStamp current timestamp.
       */
      private void incrementTimeUsed(long timeStamp) {
          if (timeStamp > mLastTimeUsed) {
              mTotalTimeInForeground += timeStamp - mLastTimeUsed;
              mLastTimeUsed = timeStamp;
          }
      }
  
      /**
       * Increment total time visible and update last time visible.
       * @param timeStamp current timestmap.
       */
      private void incrementTimeVisible(long timeStamp) {
          if (timeStamp > mLastTimeVisible) {
              mTotalTimeVisible += timeStamp - mLastTimeVisible;
              mLastTimeVisible = timeStamp;
          }
      }
  
      /**
       * Increment total time foreground service is used and update last time foreground service is
       * used.
       * @param timeStamp current timestamp.
       */
      private void incrementServiceTimeUsed(long timeStamp) {
          if (timeStamp > mLastTimeForegroundServiceUsed) {
              mTotalTimeForegroundServiceUsed +=
                      timeStamp - mLastTimeForegroundServiceUsed;
              mLastTimeForegroundServiceUsed = timeStamp;
          }
      }
  
      /**
       * Update by an event of an activity.
       * @param className className of the activity.
       * @param timeStamp timeStamp of the event.
       * @param eventType type of the event.
       * @param instanceId hashCode of the ActivityRecord's appToken.
       * @hide
       */
      private void updateActivity(String className, long timeStamp, int eventType, int instanceId) {
          if (eventType != ACTIVITY_RESUMED
                  && eventType != ACTIVITY_PAUSED
                  && eventType != ACTIVITY_STOPPED
                  && eventType != ACTIVITY_DESTROYED) {
              return;
          }
  
          // update usage.
          final int index = mActivities.indexOfKey(instanceId);
          if (index >= 0) {
              final int lastEvent = mActivities.valueAt(index);
              switch (lastEvent) {
                  case ACTIVITY_RESUMED:
                      incrementTimeUsed(timeStamp);
                      incrementTimeVisible(timeStamp);
                      break;
                  case ACTIVITY_PAUSED:
                      incrementTimeVisible(timeStamp);
                      break;
                  default:
                      break;
              }
          }
  
          // update current event.
          switch(eventType) {
              case ACTIVITY_RESUMED:
                  if (!hasVisibleActivity()) {
                      // this is the first visible activity.
                      mLastTimeUsed = timeStamp;
                      mLastTimeVisible = timeStamp;
                  } else if (!hasForegroundActivity()) {
                      // this is the first foreground activity.
                      mLastTimeUsed = timeStamp;
                  }
                  mActivities.put(instanceId, eventType);
                  break;
              case ACTIVITY_PAUSED:
                  if (!hasVisibleActivity()) {
                      // this is the first visible activity.
                      mLastTimeVisible = timeStamp;
                  }
                  mActivities.put(instanceId, eventType);
                  break;
              case ACTIVITY_STOPPED:
              case ACTIVITY_DESTROYED:
                  // remove activity from the map.
                  mActivities.delete(instanceId);
                  break;
              default:
                  break;
          }
      }
  
      /**
       * Update by an event of an foreground service.
       * @param className className of the foreground service.
       * @param timeStamp timeStamp of the event.
       * @param eventType type of the event.
       * @hide
       */
      private void updateForegroundService(String className, long timeStamp, int eventType) {
          if (eventType != FOREGROUND_SERVICE_STOP
                  && eventType != FOREGROUND_SERVICE_START) {
              return;
          }
          final Integer lastEvent = mForegroundServices.get(className);
          // update usage.
          if (lastEvent != null) {
              switch (lastEvent) {
                  case FOREGROUND_SERVICE_START:
                  case CONTINUING_FOREGROUND_SERVICE:
                      incrementServiceTimeUsed(timeStamp);
                      break;
                  default:
                      break;
              }
          }
  
          // update current event.
          switch (eventType) {
              case FOREGROUND_SERVICE_START:
                  if (!anyForegroundServiceStarted()) {
                      mLastTimeForegroundServiceUsed = timeStamp;
                  }
                  mForegroundServices.put(className, eventType);
                  break;
              case FOREGROUND_SERVICE_STOP:
                  mForegroundServices.remove(className);
                  break;
              default:
                  break;
          }
      }
  
      /**
       * Update the UsageStats by a activity or foreground service event.
       * @param className class name of a activity or foreground service, could be null to if this
       *                  is sent to all activities/services in this package.
       * @param timeStamp Epoch timestamp in milliseconds.
       * @param eventType event type as in {@link UsageEvents.Event}
       * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken.
       *                 if className is not an activity, instanceId is not used.
       * @hide
       */
      public void update(String className, long timeStamp, int eventType, int instanceId) {
          switch(eventType) {
              case ACTIVITY_RESUMED:
              case ACTIVITY_PAUSED:
              case ACTIVITY_STOPPED:
              case ACTIVITY_DESTROYED:
                  updateActivity(className, timeStamp, eventType, instanceId);
                  break;
              case END_OF_DAY:
                  // END_OF_DAY updates all activities.
                  if (hasForegroundActivity()) {
                      incrementTimeUsed(timeStamp);
                  }
                  if (hasVisibleActivity()) {
                      incrementTimeVisible(timeStamp);
                  }
                  break;
              case FOREGROUND_SERVICE_START:
              case FOREGROUND_SERVICE_STOP:
                  updateForegroundService(className, timeStamp, eventType);
                  break;
              case ROLLOVER_FOREGROUND_SERVICE:
                  // ROLLOVER_FOREGROUND_SERVICE updates all foreground services.
                  if (anyForegroundServiceStarted()) {
                      incrementServiceTimeUsed(timeStamp);
                  }
                  break;
              case CONTINUING_FOREGROUND_SERVICE:
                  mLastTimeForegroundServiceUsed = timeStamp;
                  mForegroundServices.put(className, eventType);
                  break;
              case DEVICE_SHUTDOWN:
              case FLUSH_TO_DISK:
                  // update usage of all active activities/services.
                  if (hasForegroundActivity()) {
                      incrementTimeUsed(timeStamp);
                  }
                  if (hasVisibleActivity()) {
                      incrementTimeVisible(timeStamp);
                  }
                  if (anyForegroundServiceStarted()) {
                      incrementServiceTimeUsed(timeStamp);
                  }
                  break;
              default:
                  break;
          }
          mEndTimeStamp = timeStamp;
  
          if (eventType == ACTIVITY_RESUMED) {
              mLaunchCount += 1;
          }
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeString(mPackageName);
          dest.writeLong(mBeginTimeStamp);
          dest.writeLong(mEndTimeStamp);
          dest.writeLong(mLastTimeUsed);
          dest.writeLong(mLastTimeVisible);
          dest.writeLong(mLastTimeForegroundServiceUsed);
          dest.writeLong(mTotalTimeInForeground);
          dest.writeLong(mTotalTimeVisible);
          dest.writeLong(mTotalTimeForegroundServiceUsed);
          dest.writeInt(mLaunchCount);
          dest.writeInt(mAppLaunchCount);
          dest.writeInt(mLastEvent);
          Bundle allCounts = new Bundle();
          if (mChooserCounts != null) {
              final int chooserCountSize = mChooserCounts.size();
              for (int i = 0; i < chooserCountSize; i++) {
                  String action = mChooserCounts.keyAt(i);
                  ArrayMap<String, Integer> counts = mChooserCounts.valueAt(i);
                  Bundle currentCounts = new Bundle();
                  final int annotationSize = counts.size();
                  for (int j = 0; j < annotationSize; j++) {
                      currentCounts.putInt(counts.keyAt(j), counts.valueAt(j));
                  }
                  allCounts.putBundle(action, currentCounts);
              }
          }
          dest.writeBundle(allCounts);
  
          writeSparseIntArray(dest, mActivities);
          dest.writeBundle(eventMapToBundle(mForegroundServices));
      }
  
      private void writeSparseIntArray(Parcel dest, SparseIntArray arr) {
          final int size = arr.size();
          dest.writeInt(size);
          for (int i = 0; i < size; i++) {
              dest.writeInt(arr.keyAt(i));
              dest.writeInt(arr.valueAt(i));
          }
      }
  
      private Bundle eventMapToBundle(ArrayMap<String, Integer> eventMap) {
          final Bundle bundle = new Bundle();
          final int size = eventMap.size();
          for (int i = 0; i < size; i++) {
              bundle.putInt(eventMap.keyAt(i), eventMap.valueAt(i));
          }
          return bundle;
      }
  
      public static final @android.annotation.NonNull Creator<UsageStats> CREATOR = new Creator<UsageStats>() {
          @Override
          public UsageStats createFromParcel(Parcel in) {
              UsageStats stats = new UsageStats();
              stats.mPackageName = in.readString();
              stats.mBeginTimeStamp = in.readLong();
              stats.mEndTimeStamp = in.readLong();
              stats.mLastTimeUsed = in.readLong();
              stats.mLastTimeVisible = in.readLong();
              stats.mLastTimeForegroundServiceUsed = in.readLong();
              stats.mTotalTimeInForeground = in.readLong();
              stats.mTotalTimeVisible = in.readLong();
              stats.mTotalTimeForegroundServiceUsed = in.readLong();
              stats.mLaunchCount = in.readInt();
              stats.mAppLaunchCount = in.readInt();
              stats.mLastEvent = in.readInt();
              Bundle allCounts = in.readBundle();
              if (allCounts != null) {
                  stats.mChooserCounts = new ArrayMap<>();
                  for (String action : allCounts.keySet()) {
                      if (!stats.mChooserCounts.containsKey(action)) {
                          ArrayMap<String, Integer> newCounts = new ArrayMap<>();
                          stats.mChooserCounts.put(action, newCounts);
                      }
                      Bundle currentCounts = allCounts.getBundle(action);
                      if (currentCounts != null) {
                          for (String key : currentCounts.keySet()) {
                              int value = currentCounts.getInt(key);
                              if (value > 0) {
                                  stats.mChooserCounts.get(action).put(key, value);
                              }
                          }
                      }
                  }
              }
              readSparseIntArray(in, stats.mActivities);
              readBundleToEventMap(in.readBundle(), stats.mForegroundServices);
              return stats;
          }
  
          private void readSparseIntArray(Parcel in, SparseIntArray arr) {
              final int size = in.readInt();
              for (int i = 0; i < size; i++) {
                  final int key = in.readInt();
                  final int value = in.readInt();
                  arr.put(key, value);
              }
          }
  
          private void readBundleToEventMap(Bundle bundle, ArrayMap<String, Integer> eventMap) {
              if (bundle != null) {
                  for (String className : bundle.keySet()) {
                      final int event = bundle.getInt(className);
                      eventMap.put(className, event);
                  }
              }
          }
  
          @Override
          public UsageStats[] newArray(int size) {
              return new UsageStats[size];
          }
      };
  }
  