Merge "Limit broadcast rate for low priority DropBox entries"
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index b92e713..b7cccc6 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -69,7 +69,8 @@
     /**
      * Broadcast Action: This is broadcast when a new entry is added in the dropbox.
      * You must hold the {@link android.Manifest.permission#READ_LOGS} permission
-     * in order to receive this broadcast.
+     * in order to receive this broadcast. This broadcast can be rate limited for low priority
+     * entries
      *
      * <p class="note">This is a protected intent that can only be sent
      * by the system.
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5f3b328..05b5389 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3978,4 +3978,19 @@
 
     <!-- Whether or not to enable automatic heap dumps for the system server on debuggable builds. -->
     <bool name="config_debugEnableAutomaticSystemServerHeapDumps">false</bool>
+
+    <!-- See DropBoxManagerService.
+         The minimum period in milliseconds between broadcasts for entries with low priority
+         dropbox tags. -->
+    <integer name="config_dropboxLowPriorityBroadcastRateLimitPeriod">2000</integer>
+
+    <!-- See DropBoxManagerService.
+         An array of dropbox entry tags to marked as low priority. Low priority broadcasts will be
+         rated limited to a period defined by config_dropboxLowPriorityBroadcastRateLimitPeriod
+         (high frequency broadcasts for the tag will be dropped) -->
+    <string-array name="config_dropboxLowPriorityTags" translatable="false">
+        <item>keymaster</item>
+        <item>system_server_strictmode</item>
+    </string-array>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0ce6851f..7b15675 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3730,4 +3730,7 @@
   <java-symbol type="dimen" name="resolver_icon_size"/>
   <java-symbol type="dimen" name="resolver_badge_size"/>
 
+  <!-- For DropBox -->
+  <java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
+  <java-symbol type="array" name="config_dropboxLowPriorityTags" />
 </resources>
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f0f8adbb..33b846f 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
@@ -32,6 +33,9 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.StatFs;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -39,8 +43,11 @@
 import android.text.TextUtils;
 import android.text.format.Time;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IDropBoxManagerService;
 import com.android.internal.util.DumpUtils;
@@ -76,9 +83,6 @@
     private static final int DEFAULT_RESERVE_PERCENT = 10;
     private static final int QUOTA_RESCAN_MILLIS = 5000;
 
-    // mHandler 'what' value.
-    private static final int MSG_SEND_BROADCAST = 1;
-
     private static final boolean PROFILE_DUMP = false;
 
     // TODO: This implementation currently uses one file per entry, which is
@@ -95,6 +99,9 @@
     private FileList mAllFiles = null;
     private ArrayMap<String, FileList> mFilesByTag = null;
 
+    private long mLowPriorityRateLimitPeriod = 0;
+    private ArraySet<String> mLowPriorityTags = null;
+
     // Various bits of disk information
 
     private StatFs mStatFs = null;
@@ -105,7 +112,7 @@
     private volatile boolean mBooted = false;
 
     // Provide a way to perform sendBroadcast asynchronously to avoid deadlocks.
-    private final Handler mHandler;
+    private final DropBoxManagerBroadcastHandler mHandler;
 
     private int mMaxFiles = -1; // -1 means uninitialized.
 
@@ -152,8 +159,142 @@
         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             DropBoxManagerService.this.dump(fd, pw, args);
         }
+
+        @Override
+        public void onShellCommand(FileDescriptor in, FileDescriptor out,
+                                   FileDescriptor err, String[] args, ShellCallback callback,
+                                   ResultReceiver resultReceiver) {
+            (new ShellCmd()).exec(this, in, out, err, args, callback, resultReceiver);
+        }
     };
 
+    private class ShellCmd extends ShellCommand {
+        @Override
+        public int onCommand(String cmd) {
+            if (cmd == null) {
+                return handleDefaultCommands(cmd);
+            }
+            final PrintWriter pw = getOutPrintWriter();
+            try {
+                switch (cmd) {
+                    case "set-rate-limit":
+                        final long period = Long.parseLong(getNextArgRequired());
+                        DropBoxManagerService.this.setLowPriorityRateLimit(period);
+                        break;
+                    case "add-low-priority":
+                        final String addedTag = getNextArgRequired();
+                        DropBoxManagerService.this.addLowPriorityTag(addedTag);
+                        break;
+                    case "remove-low-priority":
+                        final String removeTag = getNextArgRequired();
+                        DropBoxManagerService.this.removeLowPriorityTag(removeTag);
+                        break;
+                    case "restore-defaults":
+                        DropBoxManagerService.this.restoreDefaults();
+                        break;
+                    default:
+                        return handleDefaultCommands(cmd);
+                }
+            } catch (Exception e) {
+                pw.println(e);
+            }
+            return 0;
+        }
+
+        @Override
+        public void onHelp() {
+            PrintWriter pw = getOutPrintWriter();
+            pw.println("Dropbox manager service commands:");
+            pw.println("  help");
+            pw.println("    Print this help text.");
+            pw.println("  set-rate-limit PERIOD");
+            pw.println("    Sets low priority broadcast rate limit period to PERIOD ms");
+            pw.println("  add-low-priority TAG");
+            pw.println("    Add TAG to dropbox low priority list");
+            pw.println("  remove-low-priority TAG");
+            pw.println("    Remove TAG from dropbox low priority list");
+            pw.println("  restore-defaults");
+            pw.println("    restore dropbox settings to defaults");
+        }
+    }
+
+    private class DropBoxManagerBroadcastHandler extends Handler {
+        private final Object mLock = new Object();
+
+        static final int MSG_SEND_BROADCAST = 1;
+        static final int MSG_SEND_DEFERRED_BROADCAST = 2;
+
+        @GuardedBy("mLock")
+        private final ArrayMap<String, Intent> mDeferredMap = new ArrayMap();
+
+        DropBoxManagerBroadcastHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SEND_BROADCAST:
+                    prepareAndSendBroadcast((Intent) msg.obj);
+                    break;
+                case MSG_SEND_DEFERRED_BROADCAST:
+                    Intent deferredIntent;
+                    synchronized (mLock) {
+                        deferredIntent = mDeferredMap.remove((String) msg.obj);
+                    }
+                    if (deferredIntent != null) {
+                        prepareAndSendBroadcast(deferredIntent);
+                    }
+                    break;
+            }
+        }
+
+        private void prepareAndSendBroadcast(Intent intent) {
+            if (!DropBoxManagerService.this.mBooted) {
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            }
+            getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM,
+                    android.Manifest.permission.READ_LOGS);
+        }
+
+        private Intent createIntent(String tag, long time) {
+            final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
+            dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
+            dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
+            return dropboxIntent;
+        }
+
+        /**
+         * Schedule a dropbox broadcast to be sent asynchronously.
+         */
+        public void sendBroadcast(String tag, long time) {
+            sendMessage(obtainMessage(MSG_SEND_BROADCAST, createIntent(tag, time)));
+        }
+
+        /**
+         * Possibly schedule a delayed dropbox broadcast. The broadcast will only be scheduled if
+         * no broadcast is currently scheduled. Otherwise updated the scheduled broadcast with the
+         * new intent information, effectively dropping the previous broadcast.
+         */
+        public void maybeDeferBroadcast(String tag, long time) {
+            synchronized (mLock) {
+                final Intent intent = mDeferredMap.get(tag);
+                if (intent == null) {
+                    // Schedule new delayed broadcast.
+                    mDeferredMap.put(tag, createIntent(tag, time));
+                    sendMessageDelayed(obtainMessage(MSG_SEND_DEFERRED_BROADCAST, tag),
+                            mLowPriorityRateLimitPeriod);
+                } else {
+                    // Broadcast is already scheduled. Update intent with new data.
+                    intent.putExtra(DropBoxManager.EXTRA_TIME, time);
+                    final int dropped = intent.getIntExtra(DropBoxManager.EXTRA_DROPPED_COUNT, 0);
+                    intent.putExtra(DropBoxManager.EXTRA_DROPPED_COUNT, dropped + 1);
+                    return;
+                }
+            }
+        }
+    }
+
     /**
      * Creates an instance of managed drop box storage using the default dropbox
      * directory.
@@ -176,15 +317,7 @@
         super(context);
         mDropBoxDir = path;
         mContentResolver = getContext().getContentResolver();
-        mHandler = new Handler(looper) {
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == MSG_SEND_BROADCAST) {
-                    getContext().sendBroadcastAsUser((Intent)msg.obj, UserHandle.SYSTEM,
-                            android.Manifest.permission.READ_LOGS);
-                }
-            }
-        };
+        mHandler = new DropBoxManagerBroadcastHandler(looper);
     }
 
     @Override
@@ -211,6 +344,8 @@
                             mReceiver.onReceive(getContext(), (Intent) null);
                         }
                     });
+
+                getLowPriorityResourceConfigs();
                 break;
 
             case PHASE_BOOT_COMPLETED:
@@ -298,17 +433,16 @@
             long time = createEntry(temp, tag, flags);
             temp = null;
 
-            final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
-            dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
-            dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
-            if (!mBooted) {
-                dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-            }
             // Call sendBroadcast after returning from this call to avoid deadlock. In particular
             // the caller may be holding the WindowManagerService lock but sendBroadcast requires a
             // lock in ActivityManagerService. ActivityManagerService has been caught holding that
             // very lock while waiting for the WindowManagerService lock.
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));
+            if (mLowPriorityTags != null && mLowPriorityTags.contains(tag)) {
+                // Rate limit low priority Dropbox entries
+                mHandler.maybeDeferBroadcast(tag, time);
+            } else {
+                mHandler.sendBroadcast(tag, time);
+            }
         } catch (IOException e) {
             Slog.e(TAG, "Can't write: " + tag, e);
         } finally {
@@ -382,6 +516,22 @@
         return null;
     }
 
+    private synchronized void setLowPriorityRateLimit(long period) {
+        mLowPriorityRateLimitPeriod = period;
+    }
+
+    private synchronized void addLowPriorityTag(String tag) {
+        mLowPriorityTags.add(tag);
+    }
+
+    private synchronized void removeLowPriorityTag(String tag) {
+        mLowPriorityTags.remove(tag);
+    }
+
+    private synchronized void restoreDefaults() {
+        getLowPriorityResourceConfigs();
+    }
+
     public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, pw)) return;
 
@@ -421,6 +571,10 @@
         out.append("Drop box contents: ").append(mAllFiles.contents.size()).append(" entries\n");
         out.append("Max entries: ").append(mMaxFiles).append("\n");
 
+        out.append("Low priority rate limit period: ");
+        out.append(mLowPriorityRateLimitPeriod).append(" ms\n");
+        out.append("Low priority tags: ").append(mLowPriorityTags).append("\n");
+
         if (!searchArgs.isEmpty()) {
             out.append("Searching for:");
             for (String a : searchArgs) out.append(" ").append(a);
@@ -936,4 +1090,21 @@
 
         return mCachedQuotaBlocks * mBlockSize;
     }
+
+    private void getLowPriorityResourceConfigs() {
+        mLowPriorityRateLimitPeriod = Resources.getSystem().getInteger(
+                R.integer.config_dropboxLowPriorityBroadcastRateLimitPeriod);
+
+        final String[] lowPrioritytags = Resources.getSystem().getStringArray(
+                R.array.config_dropboxLowPriorityTags);
+        final int size = lowPrioritytags.length;
+        if (size == 0) {
+            mLowPriorityTags = null;
+            return;
+        }
+        mLowPriorityTags = new ArraySet(size);
+        for (int i = 0; i < size; i++) {
+            mLowPriorityTags.add(lowPrioritytags[i]);
+        }
+    }
 }