diff --git a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java index 13bbc3382ebfd..4b0ab59a931c6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java +++ b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java @@ -94,16 +94,27 @@ public class SuggestionParser { private static final long MILLIS_IN_DAY = 24 * 60 * 60 * 1000; + // Default dismiss control for smart suggestions. + private static final String DEFAULT_SMART_DISMISS_CONTROL = "0,10"; + private final Context mContext; private final List mSuggestionList; private final ArrayMap, Tile> mAddCache = new ArrayMap<>(); private final SharedPreferences mSharedPrefs; + private final String mSmartDismissControl; - public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) { + + public SuggestionParser( + Context context, SharedPreferences sharedPrefs, int orderXml, String smartDismissControl) { mContext = context; mSuggestionList = (List) new SuggestionOrderInflater(mContext) .parse(orderXml); mSharedPrefs = sharedPrefs; + mSmartDismissControl = smartDismissControl; + } + + public SuggestionParser(Context context, SharedPreferences sharedPrefs, int orderXml) { + this(context, sharedPrefs, orderXml, DEFAULT_SMART_DISMISS_CONTROL); } @VisibleForTesting @@ -111,26 +122,35 @@ public class SuggestionParser { mContext = context; mSuggestionList = new ArrayList(); mSharedPrefs = sharedPrefs; + mSmartDismissControl = DEFAULT_SMART_DISMISS_CONTROL; Log.wtf(TAG, "Only use this constructor for testing"); } public List getSuggestions() { + return getSuggestions(false); + } + + public List getSuggestions(boolean isSmartSuggestionEnabled) { List suggestions = new ArrayList<>(); final int N = mSuggestionList.size(); for (int i = 0; i < N; i++) { - readSuggestions(mSuggestionList.get(i), suggestions); + readSuggestions(mSuggestionList.get(i), suggestions, isSmartSuggestionEnabled); } return suggestions; } + public boolean dismissSuggestion(Tile suggestion) { + return dismissSuggestion(suggestion, false); + } + /** * Dismisses a suggestion, returns true if the suggestion has no more dismisses left and should * be disabled. */ - public boolean dismissSuggestion(Tile suggestion) { + public boolean dismissSuggestion(Tile suggestion, boolean isSmartSuggestionEnabled) { String keyBase = suggestion.intent.getComponent().flattenToShortString(); int index = mSharedPrefs.getInt(keyBase + DISMISS_INDEX, 0); - String dismissControl = suggestion.metaData.getString(META_DATA_DISMISS_CONTROL); + String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled); if (dismissControl == null || parseDismissString(dismissControl).length == index) { return true; } @@ -141,20 +161,23 @@ public class SuggestionParser { } @VisibleForTesting - public void filterSuggestions(List suggestions, int countBefore) { + public void filterSuggestions( + List suggestions, int countBefore, boolean isSmartSuggestionEnabled) { for (int i = countBefore; i < suggestions.size(); i++) { if (!isAvailable(suggestions.get(i)) || !isSupported(suggestions.get(i)) || !satisifesRequiredUserType(suggestions.get(i)) || !satisfiesRequiredAccount(suggestions.get(i)) || !satisfiesConnectivity(suggestions.get(i)) || - isDismissed(suggestions.get(i))) { + isDismissed(suggestions.get(i), isSmartSuggestionEnabled)) { suggestions.remove(i--); } } } - private void readSuggestions(SuggestionCategory category, List suggestions) { + @VisibleForTesting + void readSuggestions( + SuggestionCategory category, List suggestions, boolean isSmartSuggestionEnabled) { int countBefore = suggestions.size(); Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(category.category); @@ -163,7 +186,7 @@ public class SuggestionParser { } TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent, mAddCache, null, suggestions, true, false); - filterSuggestions(suggestions, countBefore); + filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled); if (!category.multiple && suggestions.size() > (countBefore + 1)) { // If there are too many, remove them all and only re-add the one with the highest // priority. @@ -288,12 +311,11 @@ public class SuggestionParser { Settings.Secure.putInt(mContext.getContentResolver(), name, 1); } - private boolean isDismissed(Tile suggestion) { - Object dismissObj = suggestion.metaData.get(META_DATA_DISMISS_CONTROL); - if (dismissObj == null) { + private boolean isDismissed(Tile suggestion, boolean isSmartSuggestionEnabled) { + String dismissControl = getDismissControl(suggestion, isSmartSuggestionEnabled); + if (dismissControl == null) { return false; } - String dismissControl = String.valueOf(dismissObj); String keyBase = suggestion.intent.getComponent().flattenToShortString(); if (!mSharedPrefs.contains(keyBase + SETUP_TIME)) { mSharedPrefs.edit() @@ -333,7 +355,16 @@ public class SuggestionParser { return dismisses; } - private static class SuggestionCategory { + private String getDismissControl(Tile suggestion, boolean isSmartSuggestionEnabled) { + if (isSmartSuggestionEnabled) { + return mSmartDismissControl; + } else { + return suggestion.metaData.getString(META_DATA_DISMISS_CONTROL); + } + } + + @VisibleForTesting + static class SuggestionCategory { public String category; public String pkg; public boolean multiple; diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk index 7a89884ae747e..596d614ba355e 100644 --- a/packages/SettingsLib/tests/robotests/Android.mk +++ b/packages/SettingsLib/tests/robotests/Android.mk @@ -43,6 +43,12 @@ LOCAL_AAPT_FLAGS := --auto-add-overlay \ LOCAL_SRC_FILES := \ $(call all-java-files-under, src) +LOCAL_JAR_EXCLUDE_FILES := none + +LOCAL_RESOURCE_DIR := \ + $(LOCAL_PATH)/res + + include frameworks/base/packages/SettingsLib/common.mk include $(BUILD_PACKAGE) diff --git a/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml new file mode 100644 index 0000000000000..1eeafba1bf8f5 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/res/xml/suggestion_ordering.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingLibRobolectricTestRunner.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingLibRobolectricTestRunner.java new file mode 100644 index 0000000000000..11c925eb11b88 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SettingLibRobolectricTestRunner.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 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.settingslib; + +import org.junit.runners.model.InitializationError; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.manifest.AndroidManifest; +import org.robolectric.res.Fs; +import org.robolectric.res.ResourcePath; + +import java.util.List; + +public class SettingLibRobolectricTestRunner extends RobolectricTestRunner { + + public SettingLibRobolectricTestRunner(Class testClass) throws InitializationError { + super(testClass); + } + + @Override + protected AndroidManifest getAppManifest(Config config) { + // Using the manifest file's relative path, we can figure out the application directory. + final String appRoot = "frameworks/base/packages/SettingsLib"; + final String manifestPath = appRoot + "/AndroidManifest.xml"; + final String resDir = appRoot + "/tests/robotests/res"; + final String assetsDir = appRoot + config.assetDir(); + + final AndroidManifest manifest = new AndroidManifest(Fs.fileFromPath(manifestPath), + Fs.fileFromPath(resDir), Fs.fileFromPath(assetsDir)); + + manifest.setPackageName("com.android.settingslib"); + return manifest; + } + +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java new file mode 100644 index 0000000000000..0032cf083d8d1 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/SuggestionParserTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017 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.settingslib; + +import android.util.Log; +import android.content.Context; +import android.content.ComponentName; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.preference.PreferenceManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import com.android.settingslib.drawer.Tile; +import com.android.settingslib.drawer.TileUtilsTest; + +import java.util.ArrayList; +import java.util.List; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import org.robolectric.RuntimeEnvironment; + +@RunWith(SettingLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SuggestionParserTest { + + @Mock + private PackageManager mPackageManager; + private Context mContext; + private SuggestionParser mSuggestionParser; + private SuggestionParser.SuggestionCategory mSuggestioCategory; + private List mSuggestionsBeforeDismiss; + private List mSuggestionsAfterDismiss; + private SharedPreferences mPrefs; + private Tile mSuggestion; + private List mInfo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext); + mSuggestion = new Tile(); + mSuggestion.intent = new Intent("action"); + mSuggestion.intent.setComponent(new ComponentName("pkg", "cls")); + mSuggestion.metaData = new Bundle(); + mSuggestionParser = new SuggestionParser( + mContext, mPrefs, R.xml.suggestion_ordering, "0,0"); + mSuggestioCategory = new SuggestionParser.SuggestionCategory(); + mSuggestioCategory.category = "category1"; + mSuggestioCategory.multiple = true; + mInfo = new ArrayList<>(); + ResolveInfo info1 = TileUtilsTest.newInfo(true, "category1"); + info1.activityInfo.packageName = "pkg"; + ResolveInfo info2 = TileUtilsTest.newInfo(true, "category1"); + info2.activityInfo.packageName = "pkg2"; + mInfo.add(info1); + mInfo.add(info2); + when(mPackageManager.queryIntentActivitiesAsUser( + any(Intent.class), anyInt(), anyInt())).thenReturn(mInfo); + } + + @Test + public void testDismissSuggestion_withoutSmartSuggestion() { + assertThat(mSuggestionParser.dismissSuggestion(mSuggestion, false)).isTrue(); + } + + @Test + public void testDismissSuggestion_withSmartSuggestion() { + assertThat(mSuggestionParser.dismissSuggestion(mSuggestion, true)).isFalse(); + } + + @Test + public void testGetSuggestions_withoutSmartSuggestions() { + readAndDismissSuggestion(false); + mSuggestionParser.readSuggestions(mSuggestioCategory, mSuggestionsAfterDismiss, false); + assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2); + assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(1); + assertThat(mSuggestionsBeforeDismiss.get(1)).isEqualTo(mSuggestionsAfterDismiss.get(0)); + } + + @Test + public void testGetSuggestions_withSmartSuggestions() { + readAndDismissSuggestion(true); + assertThat(mSuggestionsBeforeDismiss.size()).isEqualTo(2); + assertThat(mSuggestionsAfterDismiss.size()).isEqualTo(2); + assertThat(mSuggestionsBeforeDismiss).isEqualTo(mSuggestionsAfterDismiss); + } + + private void readAndDismissSuggestion(boolean isSmartSuggestionEnabled) { + mSuggestionsBeforeDismiss = new ArrayList(); + mSuggestionsAfterDismiss = new ArrayList(); + mSuggestionParser.readSuggestions( + mSuggestioCategory, mSuggestionsBeforeDismiss, isSmartSuggestionEnabled); + if (mSuggestionParser.dismissSuggestion( + mSuggestionsBeforeDismiss.get(0), isSmartSuggestionEnabled)) { + mInfo.remove(0); + } + mSuggestionParser.readSuggestions( + mSuggestioCategory, mSuggestionsAfterDismiss, isSmartSuggestionEnabled); + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index 021a96cedd6de..16839017e03ed 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -40,6 +40,7 @@ import android.util.Pair; import com.android.settingslib.SuggestionParser; import com.android.settingslib.TestConfig; +import com.android.settingslib.drawer.TileUtilsTest; import static org.mockito.Mockito.atLeastOnce; import org.junit.Before; @@ -179,7 +180,7 @@ public class TileUtilsTest { assertThat(outTiles.size()).isEqualTo(1); SuggestionParser parser = new SuggestionParser(mContext, null); - parser.filterSuggestions(outTiles, 0); + parser.filterSuggestions(outTiles, 0, false); assertThat(outTiles.size()).isEqualTo(0); } @@ -303,16 +304,16 @@ public class TileUtilsTest { assertThat(outTiles.get(0).summary).isEqualTo("dynamic-summary"); } - private ResolveInfo newInfo(boolean systemApp, String category) { + public static ResolveInfo newInfo(boolean systemApp, String category) { return newInfo(systemApp, category, null); } - private ResolveInfo newInfo(boolean systemApp, String category, String keyHint) { + private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint) { return newInfo(systemApp, category, keyHint, null, null); } - private ResolveInfo newInfo(boolean systemApp, String category, String keyHint, String iconUri, - String summaryUri) { + private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint, + String iconUri, String summaryUri) { ResolveInfo info = new ResolveInfo(); info.system = systemApp; info.activityInfo = new ActivityInfo();