Merge "Fix the overlapping problem of the burst of slice updates" into rvc-dev
diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java
index 6c245ce..75061a5 100644
--- a/src/com/android/settings/slices/SettingsSliceProvider.java
+++ b/src/com/android/settings/slices/SettingsSliceProvider.java
@@ -243,7 +243,7 @@
                         .createWifiCallingPreferenceSlice(sliceUri);
             }
 
-            SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
+            final SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
             if (cachedSliceData == null) {
                 loadSliceInBackground(sliceUri);
                 return getSliceStub(sliceUri);
@@ -466,14 +466,14 @@
         final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(
                 getContext(), sliceable, uri);
         mPinnedWorkers.put(uri, worker);
-        worker.onSlicePinned();
+        worker.pin();
     }
 
     private void stopBackgroundWorker(Uri uri) {
         final SliceBackgroundWorker worker = mPinnedWorkers.get(uri);
         if (worker != null) {
             Log.d(TAG, "Stopping background worker for: " + uri);
-            worker.onSliceUnpinned();
+            worker.unpin();
             mPinnedWorkers.remove(uri);
         }
     }
diff --git a/src/com/android/settings/slices/SliceBackgroundWorker.java b/src/com/android/settings/slices/SliceBackgroundWorker.java
index 6bafc00..6eb154e 100644
--- a/src/com/android/settings/slices/SliceBackgroundWorker.java
+++ b/src/com/android/settings/slices/SliceBackgroundWorker.java
@@ -20,6 +20,12 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -47,6 +53,8 @@
 
     private static final String TAG = "SliceBackgroundWorker";
 
+    private static final long SLICE_UPDATE_THROTTLE_INTERVAL = 300L;
+
     private static final Map<Uri, SliceBackgroundWorker> LIVE_WORKERS = new ArrayMap<>();
 
     private final Context mContext;
@@ -164,6 +172,75 @@
      * Notify that data was updated and attempt to sync changes to the Slice.
      */
     protected final void notifySliceChange() {
-        mContext.getContentResolver().notifyChange(mUri, null);
+        NotifySliceChangeHandler.getInstance().updateSlice(this);
     }
+
+    void pin() {
+        onSlicePinned();
+    }
+
+    void unpin() {
+        onSliceUnpinned();
+        NotifySliceChangeHandler.getInstance().cancelSliceUpdate(this);
+    }
+
+    private static class NotifySliceChangeHandler extends Handler {
+
+        private static final int MSG_UPDATE_SLICE = 1000;
+
+        private static NotifySliceChangeHandler sHandler;
+
+        private final Map<Uri, Long> mLastUpdateTimeLookup = new ArrayMap<>();
+
+        private static NotifySliceChangeHandler getInstance() {
+            if (sHandler == null) {
+                final HandlerThread workerThread = new HandlerThread("NotifySliceChangeHandler",
+                        Process.THREAD_PRIORITY_BACKGROUND);
+                workerThread.start();
+                sHandler = new NotifySliceChangeHandler(workerThread.getLooper());
+            }
+            return sHandler;
+        }
+
+        private NotifySliceChangeHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what != MSG_UPDATE_SLICE) {
+                return;
+            }
+
+            final SliceBackgroundWorker worker = (SliceBackgroundWorker) msg.obj;
+            final Uri uri = worker.getUri();
+            final Context context = worker.getContext();
+            mLastUpdateTimeLookup.put(uri, SystemClock.uptimeMillis());
+            context.getContentResolver().notifyChange(uri, null);
+        }
+
+        private void updateSlice(SliceBackgroundWorker worker) {
+            if (hasMessages(MSG_UPDATE_SLICE, worker)) {
+                return;
+            }
+
+            final Message message = obtainMessage(MSG_UPDATE_SLICE, worker);
+            final long lastUpdateTime = mLastUpdateTimeLookup.getOrDefault(worker.getUri(), 0L);
+            if (lastUpdateTime == 0L) {
+                // Postpone the first update triggering by onSlicePinned() to avoid being too close
+                // to the first Slice bind.
+                sendMessageDelayed(message, SLICE_UPDATE_THROTTLE_INTERVAL);
+            } else if (SystemClock.uptimeMillis() - lastUpdateTime
+                    > SLICE_UPDATE_THROTTLE_INTERVAL) {
+                sendMessage(message);
+            } else {
+                sendMessageAtTime(message, lastUpdateTime + SLICE_UPDATE_THROTTLE_INTERVAL);
+            }
+        }
+
+        private void cancelSliceUpdate(SliceBackgroundWorker worker) {
+            removeMessages(MSG_UPDATE_SLICE, worker);
+            mLastUpdateTimeLookup.remove(worker.getUri());
+        }
+    };
 }
diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
index 4da5c09..e34737b 100644
--- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorkerTest.java
@@ -25,6 +25,7 @@
 import android.content.Context;
 import android.net.Uri;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 
 import org.junit.Before;
@@ -35,7 +36,7 @@
 import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class})
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowSliceBackgroundWorker.class})
 public class BluetoothUpdateWorkerTest {
 
     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
diff --git a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
index 624bbd8..be86b6e 100644
--- a/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaDeviceUpdateWorkerTest.java
@@ -34,6 +34,7 @@
 import android.media.RoutingSessionInfo;
 import android.net.Uri;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowAudioManager;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
@@ -57,7 +58,7 @@
 
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class,
-        ShadowBluetoothUtils.class})
+        ShadowBluetoothUtils.class, ShadowSliceBackgroundWorker.class})
 public class MediaDeviceUpdateWorkerTest {
 
     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
index 0aec952..423c7ac 100644
--- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java
@@ -39,6 +39,7 @@
 import android.media.session.PlaybackState;
 import android.net.Uri;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
 import com.android.settingslib.bluetooth.BluetoothEventManager;
@@ -59,7 +60,8 @@
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class})
+@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class,
+        ShadowSliceBackgroundWorker.class})
 public class MediaOutputIndicatorWorkerTest {
     private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
     private static final String TEST_PACKAGE_NAME = "com.android.test";
diff --git a/tests/robotests/src/com/android/settings/slices/ShadowSliceBackgroundWorker.java b/tests/robotests/src/com/android/settings/slices/ShadowSliceBackgroundWorker.java
new file mode 100644
index 0000000..7bf8358
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/slices/ShadowSliceBackgroundWorker.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 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.settings.slices;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+
+@Implements(SliceBackgroundWorker.class)
+public class ShadowSliceBackgroundWorker {
+
+    @RealObject
+    private SliceBackgroundWorker mRealWorker;
+
+    @Implementation
+    protected final void notifySliceChange() {
+        mRealWorker.getContext().getContentResolver().notifyChange(mRealWorker.getUri(), null);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
index d40331c..3319543 100644
--- a/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
+++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiScanWorkerTest.java
@@ -43,6 +43,7 @@
 import android.os.Bundle;
 import android.os.UserHandle;
 
+import com.android.settings.slices.ShadowSliceBackgroundWorker;
 import com.android.settings.testutils.shadow.ShadowWifiManager;
 import com.android.settingslib.wifi.AccessPoint;
 import com.android.settingslib.wifi.WifiTracker;
@@ -63,10 +64,8 @@
 import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {
-        ShadowWifiManager.class,
-        WifiScanWorkerTest.ShadowWifiTracker.class,
-})
+@Config(shadows = {ShadowSliceBackgroundWorker.class, ShadowWifiManager.class,
+        WifiScanWorkerTest.ShadowWifiTracker.class})
 public class WifiScanWorkerTest {
 
     private Context mContext;