Add a filter to take out unnecessary homepage data.

After getting the full list from CardContentProvider, at render side
(Settings side) we should perform final filtering to filter out those
data that are not applicable at that moment. Apart from that, we should
also perform slice URI check in this stage to take out invalid slices.

Bug: 116063073
Test: robotests
Change-Id: Idfa94ec6176a9d9cb3379543e0a23858941af742
This commit is contained in:
Emily Chuang
2018-10-01 19:21:23 +08:00
parent bf421af90f
commit 9f25efc4d8
3 changed files with 179 additions and 104 deletions

View File

@@ -16,13 +16,21 @@
package com.android.settings.homepage;
import static android.app.slice.Slice.HINT_ERROR;
import static androidx.slice.widget.SliceLiveData.SUPPORTED_SPECS;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.slice.Slice;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
@@ -30,6 +38,7 @@ import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class CardContentLoader extends AsyncLoaderCompat<List<ContextualCard>> {
private static final String TAG = "CardContentLoader";
@@ -58,8 +67,7 @@ public class CardContentLoader extends AsyncLoaderCompat<List<ContextualCard>> {
try (Cursor cursor = getContextualCardsFromProvider()) {
if (cursor.getCount() == 0) {
result.addAll(createStaticCards());
return result;
}
} else {
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
final ContextualCard card = new ContextualCard(cursor);
if (card.isCustomCard()) {
@@ -69,7 +77,8 @@ public class CardContentLoader extends AsyncLoaderCompat<List<ContextualCard>> {
}
}
}
return result;
}
return filter(result);
}
@VisibleForTesting
@@ -94,15 +103,15 @@ public class CardContentLoader extends AsyncLoaderCompat<List<ContextualCard>> {
.build());
//TODO(b/115971399): Will change following values of SliceUri and Name
// after landing these slice cards.
add(new ContextualCard.Builder()
.setSliceUri("content://com.android.settings.slices/intent/battery_card")
.setName(packageName + "/" + "battery_card")
.setPackageName(packageName)
.setRankingScore(rankingScore)
.setAppVersion(appVersionCode)
.setCardType(ContextualCard.CardType.SLICE)
.setIsHalfWidth(true)
.build());
// add(new ContextualCard.Builder()
// .setSliceUri("content://com.android.settings.slices/battery_card")
// .setName(packageName + "/" + "battery_card")
// .setPackageName(packageName)
// .setRankingScore(rankingScore)
// .setAppVersion(appVersionCode)
// .setCardType(ContextualCard.CardType.SLICE)
// .setIsHalfWidth(true)
// .build());
add(new ContextualCard.Builder()
.setSliceUri(DeviceInfoSlice.DEVICE_INFO_CARD_URI.toString())
.setName(packageName + "/" + DeviceInfoSlice.PATH_DEVICE_INFO_CARD)
@@ -116,6 +125,41 @@ public class CardContentLoader extends AsyncLoaderCompat<List<ContextualCard>> {
return result;
}
@VisibleForTesting
List<ContextualCard> filter(List<ContextualCard> candidates) {
return candidates.stream().filter(card -> isCardEligibleToDisplay(card)).collect(
Collectors.toList());
}
@VisibleForTesting
boolean isCardEligibleToDisplay(ContextualCard card) {
if (card.isCustomCard()) {
return true;
}
final Uri uri = card.getSliceUri();
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
return false;
}
//check if the uri has a provider associated with.
final ContentProviderClient provider =
mContext.getContentResolver().acquireContentProviderClient(uri);
if (provider == null) {
return false;
}
//release contentProviderClient to prevent from memory leak.
provider.release();
final Slice slice = Slice.bindSlice(mContext, uri, SUPPORTED_SPECS);
if (slice == null || slice.hasHint(HINT_ERROR)) {
return false;
}
return true;
}
private long getAppVersionCode() {
try {
return mContext.getPackageManager().getPackageInfo(mContext.getPackageName(),

View File

@@ -18,128 +18,92 @@ package com.android.settings.homepage;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import com.android.settings.homepage.deviceinfo.DataUsageSlice;
import com.android.settings.homepage.deviceinfo.DeviceInfoSlice;
import com.android.settings.slices.SettingsSliceProvider;
import com.android.settings.testutils.SettingsRobolectricTestRunner;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowContentResolver;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@RunWith(SettingsRobolectricTestRunner.class)
public class CardContentLoaderTest {
private static final String[] QUERY_PROJECTION = {
CardDatabaseHelper.CardColumns.NAME,
CardDatabaseHelper.CardColumns.TYPE,
CardDatabaseHelper.CardColumns.SCORE,
CardDatabaseHelper.CardColumns.SLICE_URI,
CardDatabaseHelper.CardColumns.CATEGORY,
CardDatabaseHelper.CardColumns.LOCALIZED_TO_LOCALE,
CardDatabaseHelper.CardColumns.PACKAGE_NAME,
CardDatabaseHelper.CardColumns.APP_VERSION,
CardDatabaseHelper.CardColumns.TITLE_RES_NAME,
CardDatabaseHelper.CardColumns.TITLE_TEXT,
CardDatabaseHelper.CardColumns.SUMMARY_RES_NAME,
CardDatabaseHelper.CardColumns.SUMMARY_TEXT,
CardDatabaseHelper.CardColumns.ICON_RES_NAME,
CardDatabaseHelper.CardColumns.ICON_RES_ID,
CardDatabaseHelper.CardColumns.CARD_ACTION,
CardDatabaseHelper.CardColumns.EXPIRE_TIME_MS,
CardDatabaseHelper.CardColumns.SUPPORT_HALF_WIDTH
};
private Context mContext;
private CardContentLoader mCardContentLoader;
private SettingsSliceProvider mProvider;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mCardContentLoader = spy(new CardContentLoader(mContext));
mCardContentLoader = new CardContentLoader(mContext);
mProvider = new SettingsSliceProvider();
ShadowContentResolver.registerProviderInternal(SettingsSliceProvider.SLICE_AUTHORITY,
mProvider);
}
@Test
public void loadInBackground_hasDataInDb_shouldReturnData() {
final Cursor cursor = generateTwoRowContextualCards();
doReturn(cursor).when(mCardContentLoader).getContextualCardsFromProvider();
public void createStaticCards_shouldReturnTwoCards() {
final List<ContextualCard> defaultData = mCardContentLoader.createStaticCards();
final List<ContextualCard> contextualCards = mCardContentLoader.loadInBackground();
assertThat(contextualCards.size()).isEqualTo(cursor.getCount());
assertThat(defaultData).hasSize(2);
}
@Test
public void loadInBackground_hasNoData_shouldReturnThreeDefaultData() {
final Cursor cursor = generateEmptyContextualCards();
doReturn(cursor).when(mCardContentLoader).getContextualCardsFromProvider();
public void createStaticCards_shouldContainDataUsageAndDeviceInfo() {
final Uri dataUsage = DataUsageSlice.DATA_USAGE_CARD_URI;
final Uri deviceInfo = DeviceInfoSlice.DEVICE_INFO_CARD_URI;
final List<Uri> expectedUris = Arrays.asList(dataUsage, deviceInfo);
final List<ContextualCard> contextualCards = mCardContentLoader.loadInBackground();
final List<Uri> actualCardUris = mCardContentLoader.createStaticCards().stream().map(
ContextualCard::getSliceUri).collect(Collectors.toList());
assertThat(contextualCards.size()).isEqualTo(mCardContentLoader.createStaticCards().size());
assertThat(actualCardUris).containsExactlyElementsIn(expectedUris);
}
private MatrixCursor generateEmptyContextualCards() {
final MatrixCursor result = new MatrixCursor(QUERY_PROJECTION);
return result;
@Test
public void isCardEligibleToDisplay_customCard_returnTrue() {
final ContextualCard customCard = new ContextualCard.Builder()
.setName("custom_card")
.setCardType(ContextualCard.CardType.DEFAULT)
.setTitleText("custom_title")
.setSummaryText("custom_summary")
.build();
assertThat(mCardContentLoader.isCardEligibleToDisplay(customCard)).isTrue();
}
private MatrixCursor generateTwoRowContextualCards() {
final MatrixCursor result = generateEmptyContextualCards();
result.addRow(generateFirstFakeData());
result.addRow(generateSecondFakeData());
return result;
@Test
public void isCardEligibleToDisplay_invalidScheme_returnFalse() {
final String sliceUri = "contet://com.android.settings.slices/action/flashlight";
assertThat(
mCardContentLoader.isCardEligibleToDisplay(getContextualCard(sliceUri))).isFalse();
}
private Object[] generateFirstFakeData() {
final Object[] ref = new Object[]{
"auto_rotate", /* NAME */
ContextualCard.CardType.SLICE, /* TYPE */
0.5, /* SCORE */
"content://com.android.settings.slices/action/auto_rotate", /* SLICE_URI */
2, /* CATEGORY */
"", /* LOCALIZED_TO_LOCALE */
"com.android.settings", /* PACKAGE_NAME */
1l, /* APP_VERSION */
"", /* TITLE_RES_NAME */
"", /* TITLE_TEXT */
"", /* SUMMARY_RES_NAME */
"", /* SUMMARY_TEXT */
"", /* ICON_RES_NAME */
0, /* ICON_RES_ID */
0, /* CARD_ACTION */
-1, /* EXPIRE_TIME_MS */
0 /* SUPPORT_HALF_WIDTH */
};
return ref;
@Test
public void isCardEligibleToDisplay_noProvider_returnFalse() {
final String sliceUri = "content://com.android.settings.test.slices/action/flashlight";
assertThat(
mCardContentLoader.isCardEligibleToDisplay(getContextualCard(sliceUri))).isFalse();
}
private Object[] generateSecondFakeData() {
final Object[] ref = new Object[]{
"toggle_airplane", /* NAME */
ContextualCard.CardType.SLICE, /* TYPE */
0.5, /* SCORE */
"content://com.android.settings.slices/action/toggle_airplane", /* SLICE_URI */
2, /* CATEGORY */
"", /* LOCALIZED_TO_LOCALE */
"com.android.settings", /* PACKAGE_NAME */
1l, /* APP_VERSION */
"", /* TITLE_RES_NAME */
"", /* TITLE_TEXT */
"", /* SUMMARY_RES_NAME */
"", /* SUMMARY_TEXT */
"", /* ICON_RES_NAME */
0, /* ICON_RES_ID */
0, /* CARD_ACTION */
-1, /* EXPIRE_TIME_MS */
0 /* SUPPORT_HALF_WIDTH */
};
return ref;
private ContextualCard getContextualCard(String sliceUri) {
return new ContextualCard.Builder()
.setName("test_card")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(sliceUri)
.build();
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2018 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.homepage;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class CardContentLoaderTest {
private Context mContext;
private CardContentLoader mCardContentLoader;
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
mCardContentLoader = new CardContentLoader(mContext);
}
@Test
public void filter_twoInvalidCards_shouldReturnOneCard() {
final String sliceUri1 = "content://com.android.settings.slices/action/flashlight"; //valid
final String sliceUri2 = "content://com.android.settings.test.slices/action/flashlight";
final String sliceUri3 = "cotent://com.android.settings.slices/action/flashlight";
final List<ContextualCard> cards = new ArrayList<>();
cards.add(getContextualCard(sliceUri1));
cards.add(getContextualCard(sliceUri2));
cards.add(getContextualCard(sliceUri3));
final List<ContextualCard> result = mCardContentLoader.filter(cards);
assertThat(result).hasSize(1);
}
private ContextualCard getContextualCard(String sliceUri) {
return new ContextualCard.Builder()
.setName("test_card")
.setCardType(ContextualCard.CardType.SLICE)
.setSliceUri(sliceUri)
.build();
}
}