Merge "Remove data from DiscreteRegistry for uninstalled packages." into sc-dev
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java
index 232005b..f2e422d 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java
@@ -44,6 +44,7 @@
 import android.os.Process;
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.TypedXmlPullParser;
 import android.util.TypedXmlSerializer;
@@ -83,7 +84,7 @@
  */
 
 final class DiscreteRegistry {
-    static final String TIMELINE_FILE_SUFFIX = "tl";
+    static final String DISCRETE_HISTORY_FILE_SUFFIX = "tl";
     private static final String TAG = DiscreteRegistry.class.getSimpleName();
 
     private static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis";
@@ -194,17 +195,6 @@
         }
     }
 
-    private void createDiscreteAccessDir() {
-        if (!mDiscreteAccessDir.exists()) {
-            if (!mDiscreteAccessDir.mkdirs()) {
-                Slog.e(TAG, "Failed to create DiscreteRegistry directory");
-            }
-            FileUtils.setPermissions(mDiscreteAccessDir.getPath(),
-                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
-        }
-    }
-
-    /* can be called only after HistoricalRegistry.isPersistenceInitialized() check */
     void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag,
             @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime,
             long accessDuration) {
@@ -220,80 +210,34 @@
     void writeAndClearAccessHistory() {
         synchronized (mOnDiskLock) {
             if (mDiscreteAccessDir == null) {
-                Slog.e(TAG, "State not saved - persistence not initialized.");
+                Slog.d(TAG, "State not saved - persistence not initialized.");
                 return;
             }
-            final File[] files = mDiscreteAccessDir.listFiles();
-            if (files != null && files.length > 0) {
-                for (File f : files) {
-                    final String fileName = f.getName();
-                    if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) {
-                        continue;
-                    }
-                    try {
-                        long timestamp = Long.valueOf(fileName.substring(0,
-                                fileName.length() - TIMELINE_FILE_SUFFIX.length()));
-                        if (Instant.now().minus(sDiscreteHistoryCutoff,
-                                ChronoUnit.MILLIS).toEpochMilli() > timestamp) {
-                            f.delete();
-                            Slog.e(TAG, "Deleting file " + fileName);
-
-                        }
-                    } catch (Throwable t) {
-                        Slog.e(TAG, "Error while cleaning timeline files: " + t.getMessage() + " "
-                                + t.getStackTrace());
-                    }
-                }
-            }
             DiscreteOps discreteOps;
             synchronized (mInMemoryLock) {
                 discreteOps = mDiscreteOps;
                 mDiscreteOps = new DiscreteOps();
                 mCachedOps = null;
             }
-            if (discreteOps.isEmpty()) {
-                return;
-            }
-            long currentTimeStamp = Instant.now().toEpochMilli();
-            try {
-                final File file = new File(mDiscreteAccessDir,
-                        currentTimeStamp + TIMELINE_FILE_SUFFIX);
-                discreteOps.writeToFile(file);
-            } catch (Throwable t) {
-                Slog.e(TAG,
-                        "Error writing timeline state: " + t.getMessage() + " "
-                                + Arrays.toString(t.getStackTrace()));
+            deleteOldDiscreteHistoryFilesLocked();
+            if (!discreteOps.isEmpty()) {
+                persistDiscreteOpsLocked(discreteOps);
             }
         }
     }
 
-    void getHistoricalDiscreteOps(AppOpsManager.HistoricalOps result, long beginTimeMillis,
-            long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
+    void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
+            long beginTimeMillis, long endTimeMillis,
+            @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
             @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
             @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
-        DiscreteOps discreteOps = getAndCacheDiscreteOps();
+        DiscreteOps discreteOps = getAllDiscreteOps();
         discreteOps.filter(beginTimeMillis, endTimeMillis, filter, uidFilter, packageNameFilter,
                 opNamesFilter, attributionTagFilter, flagsFilter);
         discreteOps.applyToHistoricalOps(result);
         return;
     }
 
-    private DiscreteOps getAndCacheDiscreteOps() {
-        DiscreteOps discreteOps = new DiscreteOps();
-
-        synchronized (mOnDiskLock) {
-            synchronized (mInMemoryLock) {
-                discreteOps.merge(mDiscreteOps);
-            }
-            if (mCachedOps == null) {
-                mCachedOps = new DiscreteOps();
-                readDiscreteOpsFromDisk(mCachedOps);
-            }
-            discreteOps.merge(mCachedOps);
-        }
-        return discreteOps;
-    }
-
     private void readDiscreteOpsFromDisk(DiscreteOps discreteOps) {
         synchronized (mOnDiskLock) {
             long beginTimeMillis = Instant.now().minus(sDiscreteHistoryCutoff,
@@ -303,11 +247,11 @@
             if (files != null && files.length > 0) {
                 for (File f : files) {
                     final String fileName = f.getName();
-                    if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) {
+                    if (!fileName.endsWith(DISCRETE_HISTORY_FILE_SUFFIX)) {
                         continue;
                     }
                     long timestamp = Long.valueOf(fileName.substring(0,
-                            fileName.length() - TIMELINE_FILE_SUFFIX.length()));
+                            fileName.length() - DISCRETE_HISTORY_FILE_SUFFIX.length()));
                     if (timestamp < beginTimeMillis) {
                         continue;
                     }
@@ -322,8 +266,19 @@
             synchronized (mInMemoryLock) {
                 mDiscreteOps = new DiscreteOps();
             }
-            FileUtils.deleteContentsAndDir(mDiscreteAccessDir);
-            createDiscreteAccessDir();
+            clearOnDiskHistoryLocked();
+        }
+    }
+
+    void clearHistory(int uid, String packageName) {
+        synchronized (mOnDiskLock) {
+            DiscreteOps discreteOps;
+            synchronized (mInMemoryLock) {
+                discreteOps = getAllDiscreteOps();
+                clearHistory();
+            }
+            discreteOps.clearHistory(uid, packageName);
+            persistDiscreteOpsLocked(discreteOps);
         }
     }
 
@@ -332,7 +287,7 @@
             @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
             @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
             int nDiscreteOps) {
-        DiscreteOps discreteOps = getAndCacheDiscreteOps();
+        DiscreteOps discreteOps = getAllDiscreteOps();
         String[] opNamesFilter = dumpOp == OP_NONE ? null
                 : new String[]{AppOpsManager.opToPublicName(dumpOp)};
         discreteOps.filter(0, Instant.now().toEpochMilli(), filter, uidFilter, packageNameFilter,
@@ -340,17 +295,26 @@
         discreteOps.dump(pw, sdf, date, prefix, nDiscreteOps);
     }
 
-    public static boolean isDiscreteOp(int op, int uid, @AppOpsManager.OpFlags int flags) {
-        if (!ArrayUtils.contains(sDiscreteOps, op)) {
-            return false;
+    private void clearOnDiskHistoryLocked() {
+        mCachedOps = null;
+        FileUtils.deleteContentsAndDir(mDiscreteAccessDir);
+        createDiscreteAccessDir();
+    }
+
+    private DiscreteOps getAllDiscreteOps() {
+        DiscreteOps discreteOps = new DiscreteOps();
+
+        synchronized (mOnDiskLock) {
+            synchronized (mInMemoryLock) {
+                discreteOps.merge(mDiscreteOps);
+            }
+            if (mCachedOps == null) {
+                mCachedOps = new DiscreteOps();
+                readDiscreteOpsFromDisk(mCachedOps);
+            }
+            discreteOps.merge(mCachedOps);
+            return discreteOps;
         }
-        if (uid < Process.FIRST_APPLICATION_UID) {
-            return false;
-        }
-        if ((flags & (sDiscreteFlags)) == 0) {
-            return false;
-        }
-        return true;
     }
 
     private final class DiscreteOps {
@@ -399,6 +363,15 @@
             }
         }
 
+        private void clearHistory(int uid, String packageName) {
+            if (mUids.containsKey(uid)) {
+                mUids.get(uid).clearPackage(packageName);
+                if (mUids.get(uid).isEmpty()) {
+                    mUids.remove(uid);
+                }
+            }
+        }
+
         private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) {
             int nUids = mUids.size();
             for (int i = 0; i < nUids; i++) {
@@ -406,8 +379,7 @@
             }
         }
 
-        private void writeToFile(File f) throws Exception {
-            FileOutputStream stream = new FileOutputStream(f);
+        private void writeToStream(FileOutputStream stream) throws Exception {
             TypedXmlSerializer out = Xml.resolveSerializer(stream);
 
             out.startDocument(null, true);
@@ -423,7 +395,6 @@
             }
             out.endTag(null, TAG_HISTORY);
             out.endDocument();
-            stream.close();
         }
 
         private void dump(@NonNull PrintWriter pw, @NonNull SimpleDateFormat sdf,
@@ -475,6 +446,60 @@
         }
     }
 
+    private void createDiscreteAccessDir() {
+        if (!mDiscreteAccessDir.exists()) {
+            if (!mDiscreteAccessDir.mkdirs()) {
+                Slog.e(TAG, "Failed to create DiscreteRegistry directory");
+            }
+            FileUtils.setPermissions(mDiscreteAccessDir.getPath(),
+                    FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
+        }
+    }
+
+    private void persistDiscreteOpsLocked(DiscreteOps discreteOps) {
+        long currentTimeStamp = Instant.now().toEpochMilli();
+        final AtomicFile file = new AtomicFile(new File(mDiscreteAccessDir,
+                currentTimeStamp + DISCRETE_HISTORY_FILE_SUFFIX));
+        FileOutputStream stream = null;
+        try {
+            stream = file.startWrite();
+            discreteOps.writeToStream(stream);
+            file.finishWrite(stream);
+        } catch (Throwable t) {
+            Slog.e(TAG,
+                    "Error writing timeline state: " + t.getMessage() + " "
+                            + Arrays.toString(t.getStackTrace()));
+            if (stream != null) {
+                file.failWrite(stream);
+            }
+        }
+    }
+
+    private void deleteOldDiscreteHistoryFilesLocked() {
+        final File[] files = mDiscreteAccessDir.listFiles();
+        if (files != null && files.length > 0) {
+            for (File f : files) {
+                final String fileName = f.getName();
+                if (!fileName.endsWith(DISCRETE_HISTORY_FILE_SUFFIX)) {
+                    continue;
+                }
+                try {
+                    long timestamp = Long.valueOf(fileName.substring(0,
+                            fileName.length() - DISCRETE_HISTORY_FILE_SUFFIX.length()));
+                    if (Instant.now().minus(sDiscreteHistoryCutoff,
+                            ChronoUnit.MILLIS).toEpochMilli() > timestamp) {
+                        f.delete();
+                        Slog.e(TAG, "Deleting file " + fileName);
+
+                    }
+                } catch (Throwable t) {
+                    Slog.e(TAG, "Error while cleaning timeline files: " + t.getMessage() + " "
+                            + t.getStackTrace());
+                }
+            }
+        }
+    }
+
     private void createDiscreteAccessDirLocked() {
         if (!mDiscreteAccessDir.exists()) {
             if (!mDiscreteAccessDir.mkdirs()) {
@@ -524,6 +549,10 @@
             }
         }
 
+        private void clearPackage(String packageName) {
+            mPackages.remove(packageName);
+        }
+
         void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag,
                 @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
                 long accessTime, long accessDuration) {
@@ -934,5 +963,25 @@
         }
         return result;
     }
+
+    private static boolean isDiscreteOp(int op, int uid, @AppOpsManager.OpFlags int flags) {
+        if (!ArrayUtils.contains(sDiscreteOps, op)) {
+            return false;
+        }
+        if (!isDiscreteUid(uid)) {
+            return false;
+        }
+        if ((flags & (sDiscreteFlags)) == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isDiscreteUid(int uid) {
+        if (uid < Process.FIRST_APPLICATION_UID) {
+            return false;
+        }
+        return true;
+    }
 }
 
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 9ff5492..fcf82fa 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -394,8 +394,8 @@
         }
 
         if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
-            mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis,
-                    filter, uid, packageName, opNames, attributionTag,
+            mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis,
+                    endTimeMillis, filter, uid, packageName, opNames, attributionTag,
                     flags);
         }
 
@@ -428,8 +428,8 @@
                 inMemoryAdjEndTimeMillis);
 
         if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
-            mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis,
-                    filter, uid, packageName, opNames, attributionTag, flags);
+            mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis,
+                    endTimeMillis, filter, uid, packageName, opNames, attributionTag, flags);
         }
 
         if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
@@ -645,6 +645,7 @@
                 mPersistence.clearHistoryDLocked(uid, packageName);
             }
         }
+        mDiscreteRegistry.clearHistory(uid, packageName);
     }
 
     void writeAndClearDiscreteHistory() {