Adding History of Location Requests to Dumpsys

Bug: 141716920
Test: m -j ROBOTEST_FILTER=LocationRequestStatisticsTest RunFrameworksServicesRoboTests
Test: On device verified history
Change-Id: Ia862e64415ef6132422ea3b2f1a22197e9b44c44
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
index b7ccb26..45c8334 100644
--- a/services/core/java/com/android/server/location/LocationRequestStatistics.java
+++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java
@@ -1,8 +1,29 @@
+/*
+ * Copyright 2020 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 com.android.server.location;
 
 import android.os.SystemClock;
 import android.util.Log;
+import android.util.TimeUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -17,6 +38,8 @@
     public final HashMap<PackageProviderKey, PackageStatistics> statistics
             = new HashMap<PackageProviderKey, PackageStatistics>();
 
+    public final RequestSummaryLimitedHistory history = new RequestSummaryLimitedHistory();
+
     /**
      * Signals that a package has started requesting locations.
      *
@@ -34,6 +57,7 @@
         }
         stats.startRequesting(intervalMs);
         stats.updateForeground(isForeground);
+        history.addRequest(packageName, providerName, intervalMs);
     }
 
     /**
@@ -48,6 +72,7 @@
         if (stats != null) {
             stats.stopRequesting();
         }
+        history.removeRequest(packageName, providerName);
     }
 
     /**
@@ -77,7 +102,7 @@
          */
         public final String providerName;
 
-        public PackageProviderKey(String packageName, String providerName) {
+        PackageProviderKey(String packageName, String providerName) {
             this.packageName = packageName;
             this.providerName = providerName;
         }
@@ -100,6 +125,104 @@
     }
 
     /**
+     * A data structure to hold past requests
+     */
+    public static class RequestSummaryLimitedHistory {
+        @VisibleForTesting
+        static final int MAX_SIZE = 100;
+
+        final ArrayList<RequestSummary> mList = new ArrayList<>(MAX_SIZE);
+
+        /**
+         * Append an added location request to the history
+         */
+        @VisibleForTesting
+        void addRequest(String packageName, String providerName, long intervalMs) {
+            addRequestSummary(new RequestSummary(packageName, providerName, intervalMs));
+        }
+
+        /**
+         * Append a removed location request to the history
+         */
+        @VisibleForTesting
+        void removeRequest(String packageName, String providerName) {
+            addRequestSummary(new RequestSummary(
+                    packageName, providerName, RequestSummary.REQUEST_ENDED_INTERVAL));
+        }
+
+        private void addRequestSummary(RequestSummary summary) {
+            while (mList.size() >= MAX_SIZE) {
+                mList.remove(0);
+            }
+            mList.add(summary);
+        }
+
+        /**
+         * Dump history to a printwriter (for dumpsys location)
+         */
+        public void dump(IndentingPrintWriter ipw) {
+            long systemElapsedOffsetMillis = System.currentTimeMillis()
+                    - SystemClock.elapsedRealtime();
+
+            ipw.println("Last Several Location Requests:");
+            ipw.increaseIndent();
+
+            for (RequestSummary requestSummary : mList) {
+                requestSummary.dump(ipw, systemElapsedOffsetMillis);
+            }
+
+            ipw.decreaseIndent();
+        }
+    }
+
+    /**
+     * A data structure to hold a single request
+     */
+    static class RequestSummary {
+        /**
+         * Name of package requesting location.
+         */
+        private final String mPackageName;
+        /**
+         * Name of provider being requested (e.g. "gps").
+         */
+        private final String mProviderName;
+        /**
+         * Interval Requested, or REQUEST_ENDED_INTERVAL indicating request has ended
+         */
+        private final long mIntervalMillis;
+        /**
+         * Elapsed time of request
+         */
+        private final long mElapsedRealtimeMillis;
+
+        /**
+         * Placeholder for requested ending (other values indicate request started/changed)
+         */
+        static final long REQUEST_ENDED_INTERVAL = -1;
+
+        RequestSummary(String packageName, String providerName, long intervalMillis) {
+            this.mPackageName = packageName;
+            this.mProviderName = providerName;
+            this.mIntervalMillis = intervalMillis;
+            this.mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+        }
+
+        void dump(IndentingPrintWriter ipw, long systemElapsedOffsetMillis) {
+            StringBuilder s = new StringBuilder();
+            long systemTimeMillis = systemElapsedOffsetMillis + mElapsedRealtimeMillis;
+            s.append("At ").append(TimeUtils.formatForLogging(systemTimeMillis)).append(": ")
+                    .append(mIntervalMillis == REQUEST_ENDED_INTERVAL ? "- " : "+ ")
+                    .append(String.format("%7s", mProviderName)).append(" request from ")
+                    .append(mPackageName);
+            if (mIntervalMillis != REQUEST_ENDED_INTERVAL) {
+                s.append(" at interval ").append(mIntervalMillis / 1000).append(" seconds");
+            }
+            ipw.println(s);
+        }
+    }
+
+    /**
      * Usage statistics for a package/provider pair.
      */
     public static class PackageStatistics {