Fix the overlapping problem of the burst of slice updates
Implement a throttle in SliceBackgroundWorker to control slice updates. Test: robotest Fixes: 152366832 Change-Id: I8b65d1b57973e036b932172627aca506f4fae3a4
This commit is contained in:
@@ -243,7 +243,7 @@ public class SettingsSliceProvider extends SliceProvider {
|
|||||||
.createWifiCallingPreferenceSlice(sliceUri);
|
.createWifiCallingPreferenceSlice(sliceUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
|
final SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
|
||||||
if (cachedSliceData == null) {
|
if (cachedSliceData == null) {
|
||||||
loadSliceInBackground(sliceUri);
|
loadSliceInBackground(sliceUri);
|
||||||
return getSliceStub(sliceUri);
|
return getSliceStub(sliceUri);
|
||||||
@@ -466,14 +466,14 @@ public class SettingsSliceProvider extends SliceProvider {
|
|||||||
final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(
|
final SliceBackgroundWorker worker = SliceBackgroundWorker.getInstance(
|
||||||
getContext(), sliceable, uri);
|
getContext(), sliceable, uri);
|
||||||
mPinnedWorkers.put(uri, worker);
|
mPinnedWorkers.put(uri, worker);
|
||||||
worker.onSlicePinned();
|
worker.pin();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopBackgroundWorker(Uri uri) {
|
private void stopBackgroundWorker(Uri uri) {
|
||||||
final SliceBackgroundWorker worker = mPinnedWorkers.get(uri);
|
final SliceBackgroundWorker worker = mPinnedWorkers.get(uri);
|
||||||
if (worker != null) {
|
if (worker != null) {
|
||||||
Log.d(TAG, "Stopping background worker for: " + uri);
|
Log.d(TAG, "Stopping background worker for: " + uri);
|
||||||
worker.onSliceUnpinned();
|
worker.unpin();
|
||||||
mPinnedWorkers.remove(uri);
|
mPinnedWorkers.remove(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,12 @@ import android.annotation.MainThread;
|
|||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
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.ArrayMap;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
@@ -47,6 +53,8 @@ public abstract class SliceBackgroundWorker<E> implements Closeable {
|
|||||||
|
|
||||||
private static final String TAG = "SliceBackgroundWorker";
|
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 static final Map<Uri, SliceBackgroundWorker> LIVE_WORKERS = new ArrayMap<>();
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
@@ -164,6 +172,75 @@ public abstract class SliceBackgroundWorker<E> implements Closeable {
|
|||||||
* Notify that data was updated and attempt to sync changes to the Slice.
|
* Notify that data was updated and attempt to sync changes to the Slice.
|
||||||
*/
|
*/
|
||||||
protected final void notifySliceChange() {
|
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());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,7 @@ import android.content.ContentResolver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.android.settings.slices.ShadowSliceBackgroundWorker;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -35,7 +36,7 @@ import org.robolectric.RuntimeEnvironment;
|
|||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(shadows = {ShadowBluetoothAdapter.class})
|
@Config(shadows = {ShadowBluetoothAdapter.class, ShadowSliceBackgroundWorker.class})
|
||||||
public class BluetoothUpdateWorkerTest {
|
public class BluetoothUpdateWorkerTest {
|
||||||
|
|
||||||
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
||||||
|
@@ -34,6 +34,7 @@ import android.media.MediaRoute2ProviderService;
|
|||||||
import android.media.RoutingSessionInfo;
|
import android.media.RoutingSessionInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.android.settings.slices.ShadowSliceBackgroundWorker;
|
||||||
import com.android.settings.testutils.shadow.ShadowAudioManager;
|
import com.android.settings.testutils.shadow.ShadowAudioManager;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||||
@@ -57,7 +58,7 @@ import java.util.List;
|
|||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class,
|
@Config(shadows = {ShadowAudioManager.class, ShadowBluetoothAdapter.class,
|
||||||
ShadowBluetoothUtils.class})
|
ShadowBluetoothUtils.class, ShadowSliceBackgroundWorker.class})
|
||||||
public class MediaDeviceUpdateWorkerTest {
|
public class MediaDeviceUpdateWorkerTest {
|
||||||
|
|
||||||
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
||||||
|
@@ -39,6 +39,7 @@ import android.media.session.MediaSessionManager;
|
|||||||
import android.media.session.PlaybackState;
|
import android.media.session.PlaybackState;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.android.settings.slices.ShadowSliceBackgroundWorker;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
|
||||||
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
|
||||||
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
import com.android.settingslib.bluetooth.BluetoothEventManager;
|
||||||
@@ -59,7 +60,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class})
|
@Config(shadows = {ShadowBluetoothAdapter.class, ShadowBluetoothUtils.class,
|
||||||
|
ShadowSliceBackgroundWorker.class})
|
||||||
public class MediaOutputIndicatorWorkerTest {
|
public class MediaOutputIndicatorWorkerTest {
|
||||||
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
private static final Uri URI = Uri.parse("content://com.android.settings.slices/test");
|
||||||
private static final String TEST_PACKAGE_NAME = "com.android.test";
|
private static final String TEST_PACKAGE_NAME = "com.android.test";
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
@@ -43,6 +43,7 @@ import android.net.wifi.WifiManager;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
|
|
||||||
|
import com.android.settings.slices.ShadowSliceBackgroundWorker;
|
||||||
import com.android.settings.testutils.shadow.ShadowWifiManager;
|
import com.android.settings.testutils.shadow.ShadowWifiManager;
|
||||||
import com.android.settingslib.wifi.AccessPoint;
|
import com.android.settingslib.wifi.AccessPoint;
|
||||||
import com.android.settingslib.wifi.WifiTracker;
|
import com.android.settingslib.wifi.WifiTracker;
|
||||||
@@ -63,10 +64,8 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@Config(shadows = {
|
@Config(shadows = {ShadowSliceBackgroundWorker.class, ShadowWifiManager.class,
|
||||||
ShadowWifiManager.class,
|
WifiScanWorkerTest.ShadowWifiTracker.class})
|
||||||
WifiScanWorkerTest.ShadowWifiTracker.class,
|
|
||||||
})
|
|
||||||
public class WifiScanWorkerTest {
|
public class WifiScanWorkerTest {
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
Reference in New Issue
Block a user