diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 6c245ce80b4..75061a5dac5 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -243,7 +243,7 @@ public class SettingsSliceProvider extends SliceProvider { .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 @@ public class SettingsSliceProvider extends SliceProvider { 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 6bafc00f9fc..6eb154e9558 100644 --- a/src/com/android/settings/slices/SliceBackgroundWorker.java +++ b/src/com/android/settings/slices/SliceBackgroundWorker.java @@ -20,6 +20,12 @@ import android.annotation.MainThread; 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 @@ public abstract class SliceBackgroundWorker implements Closeable { private static final String TAG = "SliceBackgroundWorker"; + private static final long SLICE_UPDATE_THROTTLE_INTERVAL = 300L; + private static final Map LIVE_WORKERS = new ArrayMap<>(); private final Context mContext; @@ -164,6 +172,75 @@ public abstract class SliceBackgroundWorker implements Closeable { * 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 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 4da5c094f13..e34737b7d85 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.ContentResolver; 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.RuntimeEnvironment; 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 624bbd8101f..be86b6e3984 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.MediaRoute2ProviderService; 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 @@ import java.util.List; @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 0aec9529b3c..423c7acf098 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.MediaSessionManager; 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.ArrayList; 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 00000000000..7bf8358a850 --- /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 d40331c3332..33195438fe0 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.net.wifi.WifiManager; 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.Arrays; 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;