Fix settings slice caching
Previously if there were multiple clients it would trigger an infinite loop as the cache gits dropped after the first bind, and the second client would trigger another load. Instead now cache as long as slices are pinned since thats the intended behavior of caching. Test: make RunSettingsRoboTests Change-Id: I7d29fab87573120b34cd55e1696c4c5b70fc891c Fixes: 78471335
This commit is contained in:
@@ -29,6 +29,7 @@ import android.provider.SettingsSlicesContract;
|
|||||||
import android.support.annotation.VisibleForTesting;
|
import android.support.annotation.VisibleForTesting;
|
||||||
import android.support.v4.graphics.drawable.IconCompat;
|
import android.support.v4.graphics.drawable.IconCompat;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.ArrayMap;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ import com.android.settingslib.utils.ThreadUtils;
|
|||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
@@ -111,12 +113,14 @@ public class SettingsSliceProvider extends SliceProvider {
|
|||||||
SlicesDatabaseAccessor mSlicesDatabaseAccessor;
|
SlicesDatabaseAccessor mSlicesDatabaseAccessor;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
Map<Uri, SliceData> mSliceWeakDataCache;
|
||||||
Map<Uri, SliceData> mSliceDataCache;
|
Map<Uri, SliceData> mSliceDataCache;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateSliceProvider() {
|
public boolean onCreateSliceProvider() {
|
||||||
mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext());
|
mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext());
|
||||||
mSliceDataCache = new WeakHashMap<>();
|
mSliceDataCache = new ArrayMap<>();
|
||||||
|
mSliceWeakDataCache = new WeakHashMap<>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +135,17 @@ public class SettingsSliceProvider extends SliceProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSlicePinned(Uri sliceUri) {
|
||||||
|
// Start warming the slice, we expect someone will want it soon.
|
||||||
|
loadSliceInBackground(sliceUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSliceUnpinned(Uri sliceUri) {
|
||||||
|
mSliceDataCache.remove(sliceUri);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Slice onBindSlice(Uri sliceUri) {
|
public Slice onBindSlice(Uri sliceUri) {
|
||||||
String path = sliceUri.getPath();
|
String path = sliceUri.getPath();
|
||||||
@@ -141,14 +156,16 @@ public class SettingsSliceProvider extends SliceProvider {
|
|||||||
return createWifiSlice(sliceUri);
|
return createWifiSlice(sliceUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
SliceData cachedSliceData = mSliceDataCache.get(sliceUri);
|
SliceData cachedSliceData = mSliceWeakDataCache.get(sliceUri);
|
||||||
if (cachedSliceData == null) {
|
if (cachedSliceData == null) {
|
||||||
loadSliceInBackground(sliceUri);
|
loadSliceInBackground(sliceUri);
|
||||||
return getSliceStub(sliceUri);
|
return getSliceStub(sliceUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the SliceData from the cache after it has been used to prevent a memory-leak.
|
// Remove the SliceData from the cache after it has been used to prevent a memory-leak.
|
||||||
mSliceDataCache.remove(sliceUri);
|
if (!mSliceDataCache.containsKey(sliceUri)) {
|
||||||
|
mSliceWeakDataCache.remove(sliceUri);
|
||||||
|
}
|
||||||
return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData);
|
return SliceBuilderUtils.buildSlice(getContext(), cachedSliceData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +253,12 @@ public class SettingsSliceProvider extends SliceProvider {
|
|||||||
long startBuildTime = System.currentTimeMillis();
|
long startBuildTime = System.currentTimeMillis();
|
||||||
|
|
||||||
final SliceData sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri);
|
final SliceData sliceData = mSlicesDatabaseAccessor.getSliceDataFromUri(uri);
|
||||||
mSliceDataCache.put(uri, sliceData);
|
List<Uri> pinnedSlices = getContext().getSystemService(
|
||||||
|
SliceManager.class).getPinnedSlices();
|
||||||
|
if (pinnedSlices.contains(uri)) {
|
||||||
|
mSliceDataCache.put(uri, sliceData);
|
||||||
|
}
|
||||||
|
mSliceWeakDataCache.put(uri, sliceData);
|
||||||
getContext().getContentResolver().notifyChange(uri, null /* content observer */);
|
getContext().getContentResolver().notifyChange(uri, null /* content observer */);
|
||||||
|
|
||||||
Log.d(TAG, "Built slice (" + uri + ") in: " +
|
Log.d(TAG, "Built slice (" + uri + ") in: " +
|
||||||
|
@@ -21,9 +21,12 @@ import static android.content.ContentResolver.SCHEME_CONTENT;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.spy;
|
import static org.mockito.Mockito.spy;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.app.slice.SliceManager;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -43,7 +46,9 @@ import org.robolectric.RuntimeEnvironment;
|
|||||||
|
|
||||||
import androidx.slice.Slice;
|
import androidx.slice.Slice;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,17 +70,22 @@ public class SettingsSliceProviderTest {
|
|||||||
private Context mContext;
|
private Context mContext;
|
||||||
private SettingsSliceProvider mProvider;
|
private SettingsSliceProvider mProvider;
|
||||||
private SQLiteDatabase mDb;
|
private SQLiteDatabase mDb;
|
||||||
|
private SliceManager mManager;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
mContext = spy(RuntimeEnvironment.application);
|
mContext = spy(RuntimeEnvironment.application);
|
||||||
mProvider = spy(new SettingsSliceProvider());
|
mProvider = spy(new SettingsSliceProvider());
|
||||||
|
mProvider.mSliceWeakDataCache = new HashMap<>();
|
||||||
mProvider.mSliceDataCache = new HashMap<>();
|
mProvider.mSliceDataCache = new HashMap<>();
|
||||||
mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext);
|
mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext);
|
||||||
when(mProvider.getContext()).thenReturn(mContext);
|
when(mProvider.getContext()).thenReturn(mContext);
|
||||||
|
|
||||||
mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase();
|
||||||
SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
|
SlicesDatabaseHelper.getInstance(mContext).setIndexedState();
|
||||||
|
mManager = mock(SliceManager.class);
|
||||||
|
when(mContext.getSystemService(SliceManager.class)).thenReturn(mManager);
|
||||||
|
when(mManager.getPinnedSlices()).thenReturn(Collections.emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -98,6 +108,30 @@ public class SettingsSliceProviderTest {
|
|||||||
insertSpecialCase(KEY);
|
insertSpecialCase(KEY);
|
||||||
Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
|
Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
|
||||||
|
|
||||||
|
mProvider.loadSlice(uri);
|
||||||
|
SliceData data = mProvider.mSliceWeakDataCache.get(uri);
|
||||||
|
|
||||||
|
assertThat(data.getKey()).isEqualTo(KEY);
|
||||||
|
assertThat(data.getTitle()).isEqualTo(TITLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadSlice_doesntCacheWithoutPin() {
|
||||||
|
insertSpecialCase(KEY);
|
||||||
|
Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
|
||||||
|
|
||||||
|
mProvider.loadSlice(uri);
|
||||||
|
SliceData data = mProvider.mSliceDataCache.get(uri);
|
||||||
|
|
||||||
|
assertThat(data).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadSlice_cachesWithPin() {
|
||||||
|
insertSpecialCase(KEY);
|
||||||
|
Uri uri = SliceBuilderUtils.getUri(INTENT_PATH, false);
|
||||||
|
when(mManager.getPinnedSlices()).thenReturn(Arrays.asList(uri));
|
||||||
|
|
||||||
mProvider.loadSlice(uri);
|
mProvider.loadSlice(uri);
|
||||||
SliceData data = mProvider.mSliceDataCache.get(uri);
|
SliceData data = mProvider.mSliceDataCache.get(uri);
|
||||||
|
|
||||||
@@ -108,11 +142,23 @@ public class SettingsSliceProviderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testLoadSlice_cachedEntryRemovedOnBuild() {
|
public void testLoadSlice_cachedEntryRemovedOnBuild() {
|
||||||
SliceData data = getDummyData();
|
SliceData data = getDummyData();
|
||||||
mProvider.mSliceDataCache.put(data.getUri(), data);
|
mProvider.mSliceWeakDataCache.put(data.getUri(), data);
|
||||||
mProvider.onBindSlice(data.getUri());
|
mProvider.onBindSlice(data.getUri());
|
||||||
insertSpecialCase(data.getKey());
|
insertSpecialCase(data.getKey());
|
||||||
|
|
||||||
SliceData cachedData = mProvider.mSliceDataCache.get(data.getUri());
|
SliceData cachedData = mProvider.mSliceWeakDataCache.get(data.getUri());
|
||||||
|
|
||||||
|
assertThat(cachedData).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadSlice_cachedEntryRemovedOnUnpin() {
|
||||||
|
SliceData data = getDummyData();
|
||||||
|
mProvider.mSliceDataCache.put(data.getUri(), data);
|
||||||
|
mProvider.onSliceUnpinned(data.getUri());
|
||||||
|
insertSpecialCase(data.getKey());
|
||||||
|
|
||||||
|
SliceData cachedData = mProvider.mSliceWeakDataCache.get(data.getUri());
|
||||||
|
|
||||||
assertThat(cachedData).isNull();
|
assertThat(cachedData).isNull();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user