From 9f25efc4d820ed007b22455b97bd7cb8bc7246ba Mon Sep 17 00:00:00 2001 From: Emily Chuang Date: Mon, 1 Oct 2018 19:21:23 +0800 Subject: [PATCH] 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 --- .../settings/homepage/CardContentLoader.java | 80 ++++++++--- .../homepage/CardContentLoaderTest.java | 136 +++++++----------- .../homepage/CardContentLoaderTest.java | 67 +++++++++ 3 files changed, 179 insertions(+), 104 deletions(-) create mode 100644 tests/unit/src/com/android/settings/homepage/CardContentLoaderTest.java diff --git a/src/com/android/settings/homepage/CardContentLoader.java b/src/com/android/settings/homepage/CardContentLoader.java index 9980503b216..4fb868a4fb2 100644 --- a/src/com/android/settings/homepage/CardContentLoader.java +++ b/src/com/android/settings/homepage/CardContentLoader.java @@ -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> { private static final String TAG = "CardContentLoader"; @@ -58,18 +67,18 @@ public class CardContentLoader extends AsyncLoaderCompat> { try (Cursor cursor = getContextualCardsFromProvider()) { if (cursor.getCount() == 0) { result.addAll(createStaticCards()); - return result; - } - for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { - final ContextualCard card = new ContextualCard(cursor); - if (card.isCustomCard()) { - //TODO(b/114688391): Load and generate custom card,then add into list - } else { - result.add(card); + } else { + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + final ContextualCard card = new ContextualCard(cursor); + if (card.isCustomCard()) { + //TODO(b/114688391): Load and generate custom card,then add into list + } else { + result.add(card); + } } } } - return result; + return filter(result); } @VisibleForTesting @@ -94,15 +103,15 @@ public class CardContentLoader extends AsyncLoaderCompat> { .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> { return result; } + @VisibleForTesting + List filter(List 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(), diff --git a/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java b/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java index 20ad067ae15..865989d3765 100644 --- a/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java +++ b/tests/robotests/src/com/android/settings/homepage/CardContentLoaderTest.java @@ -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 defaultData = mCardContentLoader.createStaticCards(); - final List 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 expectedUris = Arrays.asList(dataUsage, deviceInfo); - final List contextualCards = mCardContentLoader.loadInBackground(); + final List 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(); } } diff --git a/tests/unit/src/com/android/settings/homepage/CardContentLoaderTest.java b/tests/unit/src/com/android/settings/homepage/CardContentLoaderTest.java new file mode 100644 index 00000000000..1f762bbf0ec --- /dev/null +++ b/tests/unit/src/com/android/settings/homepage/CardContentLoaderTest.java @@ -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 cards = new ArrayList<>(); + cards.add(getContextualCard(sliceUri1)); + cards.add(getContextualCard(sliceUri2)); + cards.add(getContextualCard(sliceUri3)); + + final List 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(); + } +}