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(); + } +}